2

I've been working on a class derived from UITabBarController. At the most basic level, what I'm trying to do is add a BackgroundColor property, and other public properties, that I can instantiate in my AppDelegate when I'm constructing my TabBarController.

The problem I've run into is, I can either have all public properties end up being null when ViewDidLoad is called, or, I can add a constructor and a [Register] attribute, and end up having properties not null, but the ViewControllers property (which contains all tabs) becomes inexplicably null (even when you set it in ViewDidLoad).

Obviously, I need both things to be true and I'm missing something specific.

Here is the version of the code that always results in a null BackgroundColor, even if I set it explicitly in AppDelegate:

public class TabBarController : UITabBarController
{
    public UIColor BackgroundColor { get; set; }

    public override ViewDidLoad()
    {
        base.ViewDidLoad();

        if(BackgroundColor != null) // Always null, even when set explicitly
        {
            var frame = new RectangleF(0.0f, 0.0f, this.View.Bounds.Size.Width, 46);
            UIView myTabView = new UIView(frame);
            myTabView.BackgroundColor = BackgroundColor;
            myTabView.Alpha = 0.5f;
            this.TabBar.InsertSubview(myTabView, 0);
        }

        // Add tabs here, which show up correctly (on default background color)
        ViewControllers = new UIViewController[] { one, two, three, etc };
    }
}

Here is the edited code that shows the correct background color (the property is not null), but refuses to allow ViewControllers property be anything but null, even when it is just set in ViewDidLoad:

[Register("TabBarController")]
public class TabBarController : UITabBarController
{
    public UIColor BackgroundColor { get; set; }

    // Added a binding constructor
    [Export("init")]
    public TabBarController() : base(NSObjectFlag.Empty)
    {

    }

    public override ViewDidLoad()
    {
        base.ViewDidLoad();

        if(BackgroundColor != null) // Hey, this works now!
        {
            var frame = new RectangleF(0.0f, 0.0f, this.View.Bounds.Size.Width, 46);
            UIView myTabView = new UIView(frame);
            myTabView.BackgroundColor = BackgroundColor;
            myTabView.Alpha = 0.5f;
            this.TabBar.InsertSubview(myTabView, 0);
        }

        // Tabs disappear, ViewControllers is always null
        ViewControllers = new UIViewController[] { one, two, three, etc };
        if(ViewControllers == null)
        {
            Console.WriteLine("Not bro");
        }
    }
}

This obviously deters my ability to write some custom controls if I have to explicitly add all elements without being able to access public properties at runtime. Does anyone know where I'm going wrong?

poupou
  • 43,413
  • 6
  • 77
  • 174
Daniel Crenna
  • 3,326
  • 1
  • 24
  • 34
  • that "public override ViewDidLoad()" will not compile. please provide something complete enough to be useful to people that wish to help you :-) – poupou Aug 22 '11 at 12:07

1 Answers1

1

There are things that must occurs before ViewDidLoad is called. They can be done in the constructor. However the following ctor is bad:

 public TabBarController() : base(NSObjectFlag.Empty)

because it won't allow UITabController default ctor to execute - and its job is to send a message to the 'init' selector.

I think what you want looks a bit like:

public class TabBarController : UITabBarController
{
    UIViewController one = new UIViewController ();
    UIViewController two = new UIViewController ();
    UIViewController three = new UIViewController ();

    private UIView myTabView;

    public UIColor BackgroundColor { 
        get { return myTabView.BackgroundColor; }
        set { myTabView.BackgroundColor = value; }
    }       

    public TabBarController()
    {
        var frame = new RectangleF(0.0f, 0.0f, this.View.Bounds.Size.Width, 46);
        myTabView = new UIView(frame);
        myTabView.Alpha = 0.5f;
        this.TabBar.InsertSubview(myTabView, 0);

        // Add tabs here, which show up correctly (on default background color)
        ViewControllers = new UIViewController[] { one, two, three };
    }
}

    public override bool FinishedLaunching (UIApplication app, NSDictionary options)
    {
        TabBarController controller = new TabBarController ();
        // change background (to cyan) works before adding subview
        controller.BackgroundColor = UIColor.Cyan;
        window.AddSubview (controller.View);
        // change background (to blue) works after adding subview
        controller.BackgroundColor = UIColor.Blue;
... 

EDIT: Removed no-op background setting in .ctor. Added FinishedLaunching sample code.

poupou
  • 43,413
  • 6
  • 77
  • 174
  • There a few problems with this, namely that I want to use a property and your call to change the BackgroundColor in the ctor is basically a no-op, but using a ctor based approach and removing the other ctors has the effect of ignoring the BackgroundColor as in my first example, even when retaining myTabView, but thanks for the effort. – Daniel Crenna Aug 23 '11 at 03:18
  • Yes it's a no-op (and you can remove it, it's a left over from my previous attempt) but it does allow you to select the background color (it's not ignored here) after the controller was created without breaking the initialization. I'll edit the sample – poupou Aug 23 '11 at 12:05
  • Sample edited. Anyway the main point is that some things can only be done at certain times. Adding some Console.WriteLine lines will show you when things gets called (e.g. when 'init' is not called the order will be different, which is the main difference between your two samples). – poupou Aug 23 '11 at 12:13
  • After looking at your edited sample, I was able to connect the dots and get this to work, thanks! I'm still perplexed about how this is mean to fit together. For example, can you explain why simply adding a "if(BackgroundColor != null) { }" scope around the first four lines of code in the ctor causes a NullReferenceException? It seems almost as if hidden backing fields are throwing exceptions when a property is simply accessed... – Daniel Crenna Aug 26 '11 at 23:13
  • IIRC adding "if(BackgroundColor != null) { }" removed (not caused) the NRE. The problem was that ViewDidLoad was called from the .ctor (so before the property was being set). Try adding Console.WriteLine(Environment.StackTrace); in a few place. – poupou Aug 27 '11 at 14:46
  • I can actually reproduce the NRE by adding that around the first four ctor lines as I described. I find it odd that a) viewDidLoad will actually run *before* your ctor is called, and that trying to access the property BackgroundColor, rather than a private field, will cause NRE. I think it's a precedence problem where there's some kind of IL weaving going on where at runtime the property is looking for a backing field that isn't initialized until after the constructor is called, i.e. ViewDidAppear. – Daniel Crenna Aug 28 '11 at 04:29
  • There is a [bug](https://bugzilla.xamarin.com/show_bug.cgi?id=2700) that relates to this. `ViewDidLoad` is called by the base parameterless `UITabBarController` constructor (which is automatically called before the inheriting constructor), meaning any constructor-set properties in an inheriting class will not have happened yet. Since I didn't really need my custom `UITabBarController` to control the things I was modifying in my `ViewDidLoad` override, I was able to build up everything in a static factory method built around the core `UITabBarController`. – patridge Oct 18 '12 at 15:40