0

In the app I'm working on there's a need for custom UITableView section headers and footers. For this I'd like to create a custom control that works with binding and our logic.

For that I've created a XIB and added a backing class that looks like the following:

public partial class HeaderFooterView : MvxTableViewHeaderFooterView
{
    public static readonly NSString Key = new NSString("HeaderFooterView");
    public static readonly UINib Nib = UINib.FromName("HeaderFooterView", NSBundle.MainBundle);

    public HeaderFooterView(IntPtr handle) : base(handle)
    {
    }

    public override void AwakeFromNib()
    {
        base.AwakeFromNib();

        //var binding = this.CreateBindingSet<HeaderFooterView, TableViewSection>();


        //binding.Apply();
    }
}

The MvxTableViewHeaderFooterView is actually a pretty simple class, combining the stock UITableViewHeaderFooterView with IMvxBindable. Nothing fancy.

However for some reason, even though I register it properly within the TableViewSource constructor:

tableView.RegisterNibForHeaderFooterViewReuse(HeaderFooterView.Nib, HeaderFooterView.Key);

And do the proper way of returning the Header itself only:

    public override UIView GetViewForHeader(UITableView tableView, nint section)
    {
        return tableView.DequeueReusableHeaderFooterView(HeaderFooterView.Key);
    }

The app dies with the following error:

2017-07-12 16:56:40.517 MyAppiOS[3833:58706] *** Assertion failure in -[UITableView _dequeueReusableViewOfType:withIdentifier:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3600.7.47/UITableView.m:6696
2017-07-12 16:56:40.528 MyAppiOS[3833:58706] WARNING: GoogleAnalytics 3.17 void GAIUncaughtExceptionHandler(NSException *) (GAIUncaughtExceptionHandler.m:48): Uncaught exception: invalid nib registered for identifier (HeaderFooterView) - nib must contain exactly one top level object which must be a UITableViewHeaderFooterView instance

My NIB actually contains a single root object, the root view itself, that is set to the HeaderFooterView class (which derives from MvxTableViewHeaderFooterView which in turn derives from UITableViewHeaderFooterView). Yet it claims there's no UITableViewHeaderFooterView instance.

Why isn't it working as it's supposed to?

fonix232
  • 2,132
  • 6
  • 39
  • 69
  • I believe you have to assign an estimated height against the footer/header otherwise it won't render it correctly. – JoeTomks Jul 12 '17 at 16:31
  • My problem is not that it isn't rendering, it's that the app crashes when I try to call `DequeueReusableHeaderFooterView`. – fonix232 Jul 13 '17 at 07:17

3 Answers3

0

It's because return tableView.DequeueReusableHeaderFooterView(HeaderFooterView.Key); can return null if there are no HeaderFooterViews to reuse. In that case you have to create your own:

var view = tableView.DequeueReusableHeaderFooterView(HeaderFooterView.Key);
if (view == null){
    //instantiate the nib here and set view
}

return view;
pnavk
  • 4,552
  • 2
  • 19
  • 43
  • Except the error you see above (`Uncaught exception: invalid nib registered for identifier (HeaderFooterView) - nib must contain exactly one top level object which must be a UITableViewHeaderFooterView instance`) happens during the `DequeueReusableHeaderFooterView` call. I won't even get as far as to check if the view is null, because the app crashes on that line. – fonix232 Jul 13 '17 at 07:16
0

I would suggest structuring the backing class as follows:

public partial class HeaderFooterView : MvxTableViewHeaderFooterView
{
    public static readonly NSString Key = new NSString("HeaderFooterView");
    public static readonly UINib Nib = UINib.FromName("HeaderFooterView", NSBundle.MainBundle);

    static HeaderFooterView()
    {
        //Adding this alone should allow your tableview to properly instantiate the view.
        Nib = UINib.FromName("HeaderFooterView", NSBundle.MainBundle);
    }

    public static HeaderFooterView Create()
    {
        // However you can add this static method and create and return the view yourself.
        var arr = NSBundle.MainBundle.LoadNib(nameof(HeaderFooterView ), null, null);
        var v = Runtime.GetNSObject<HeaderFooterView >(arr.ValueAt(0));
        return v;
    }

    public HeaderFooterView(IntPtr handle) : base(handle)
    {
        // Note: this .ctor should not contain any initialization logic.
    }

    public override void AwakeFromNib()
    {
        base.AwakeFromNib();
    }
}

Adding the static constructor by itself should be enough to allow your table view to properly instantiate the Nib. However if you still end up having problems like that you can use the static method 'Create' to instantiate the nib yourself as so:

public override UIView GetViewForHeader(UITableView tableView, nint section)
{
    HeaderFooterView theView = HeaderFooterView.Create()
    return theView;
}

Try those suggestions, one or both should work for you.

JoeTomks
  • 3,243
  • 1
  • 18
  • 42
  • The static constructor wouldn't be needed since the Nib field is already statically initialized. Also I believe your Create() method won't work, since it returns a different type. I'll try this method though. – fonix232 Jul 13 '17 at 08:25
  • @fonix232 sorry that was a copy and paste error. I've changed the return type in my answer. – JoeTomks Jul 13 '17 at 08:45
0

Okay, found the issue.

While my initial XIB was correct, for some reason the root object's type was erased, and Interface Builder refused to accept mine.

However using VS2017 for Mac, I was able to set the proper root view class, and now everything works fine.

fonix232
  • 2,132
  • 6
  • 39
  • 69