Mar 272012
 

Finally, I have managed to viagra farmaco generico work on one of my favorite iOS subjects: NIB files. It’s in the list of my favorites because of their complexity, at least from a C# developer’s point of view. Whenever I can, online viagra experment I create my UI in NIBs. Let’s face it. It is always better to write less code, so why not? Read below for some of my (hopefully) useful findings.


How a controller loads its UI

Let’s start with some basic stuff. When we add a new “iPhone View Controller” in a MonoTouch project, generic viagra MonoDevelop declares a class for us like this (I named it “MainController”):

public partial class MainController : UIViewController
{
    public MainController() : base(“MainController”, null)
    {
    }
    //..

It also adds some more useful stuff, but we don’t care about them here. What we care about is the constructor above. The default constructor of our class calls the UIViewController(string nibName, NSBundle mainBundle) constructor. This constructor is exposed to buy real viagra the Objective-C runtime as the “initWithNibName:bundle:” instance method, through the MonoTouch ExportAttribute. What this method basically does is to search inside our app’s main bundle for the NIB file with the name we pass to it. To allow the runtime to generic viagra money order find the file, we must not include any paths or a file extension. Also, when passing null to the NSBundle parameter, we tell the runtime to search in the main bundle of our app. So, problems with generic viagra simply put: we create an instance and the interface is loaded from the file system automatically, when we do the following:

MainController mController = new MainController();

… almost. The NIB file is actually loaded when the view of our controller is first accessed. This can be checked by entering some code in the affiliate generic viagra ViewDidLoad method. You will see that the initialization of the class does not trigger the ViewDidLoad method. But if, for example, we do the following:

mController.View.Hidden = false;

the ViewDidLoad method will be called. That’s the basic stuff. Let’s move on to buy viagra online alternative viagra a bit more complex situations.

Complex situations

What if I want to create new instances of MainController multiple times in the life time of my app? That will mean that every time the class is instantiated, the same NIB file will have to be loaded also. But why should that happen? Shouldn’t I be able to create multiple instances of the same class, is buying viagra online illegal without having to load its interface from the file system every single time? Well, we can do that. Meet the UINib class. This class reads a NIB file from the file system once and instantiates its user interface when we want it to. It does all we want with only two methods. Here’s how it works:

// Load a NIB file
UINib mNib = UINib.FromName(“MainController”, discount prescriptions cialis tadalafil NSBundle.MainBundle);

The above line loads a NIB into memory. This NIB is now ready for us to use. It accepts the same parameters as the UIViewController constructor we saw earlier. One small difference is that we cannot pass null to it in MonoTouch, although its default implementation in Objective-C accepts nil. No harm done. Anyway, now that we have loaded our NIB into memory, let’s get some UI from it for our controller. But, we have to change something first. Since the code created by MonoDevelop automatically adds the call to the base constructor, we have to remove that. We do not want the initWithNibName:bundle: method to be called. Let’s comment it out:

public MainController() //: base(“MainController”, null)
{
}

So, to create our instance and provide it with a UI:

// Instantiate the class
MainController mController = new MainController();

// “Attach” the UI
myNib.InstantiateWithOwneroptions(mController, new NSDictionary());

Ready! Our controller is created and it has a UI!. There is an automation we loose with this though. The ViewDidLoad method will not get called. So, let’s just call it ourselves:

mController.ViewDidLoad();

All other methods that should get called automatically, are called (eg. ViewWillAppear, ViewDidAppear, ViewDidUnload etc.).
And, we are done with the controller UI. In theory, it should be more efficient than loading the same UI from the file system each time we want to present it. Of course, we should not forget a basic rule: when NIBs are loaded, all of their contents are loaded into memory, so the more UI objects a NIB file contains, the slower it will get. So performance always comes along with good design practices.

More info on the InstantiateWithOwneroptions method and some more NIB stuff

Let’s take a look at the method signature. No need for its native version, the C# version will do just fine:

NSObject[] InstantiateWithOwneroptions(NSObject ownerOrNil, NSDictionary optionsOrNil);

As you can see, the method not only instantiates a NIB for us, but it returns some objects also. We’ll talk about these in a moment, let’s talk about the parameters first. The “ownerOrNil” parameter is the owner of the NIB contents. Basically, it is the instance that the NIB’s “File’s Owner” will be represented by. In our case above, we pass the “mController” object to it. The File’s Owner in a NIB file, is the interface that exposes the contents of the file to us. Along with the First Responder we see in a NIB, these are Placeholder objects. We can forget about the parameter name’s “-OrNil” suffix. We must not pass null to it because we’ll get an exception. That is like saying “I want to get into a room without opening the door”.

The second parameter, “optionsOrNil” is for instructing the runtime how to handle external objects (or, Proxy objects). For example, if we have a view controller in a NIB file, but the UI for that view controller is loaded from another NIB file, we must pass this information or that NIB file will not be loaded. Consider the following scenario: in a NIB file, we have a UINavigationController with its root view controller connected to a controller form another NIB file (this root view controller in the first NIB file is the Proxy object). It is the “optionsOrNill” parameter we will use to load everything correctly:

NSDictionary proxyObj = NSDictionary.FromObjectAndKey(myRootViewController, (NSString)“MyRootViewController”);
NSDictionary options = NSDictionary.FromObjectAndKey(proxyObj, (NSString)”UINibExternalObjects);
myNib.InstantiateWithOwneroptions(myNavController, options);

Simply put, we need one NSDictionary to hold the owner(s) of the Proxy object(s) (we can include multiple Proxy objects) and one NSDictionary that will contain the first NSDictionary, with the key “UINibExternalObjects”.

How about the return value? This is where it gets a bit more interesting. The return value contains the Top-Level objects of the NIB. Top-Level objects are all the objects in a NIB that do not have a parent object and are not Placeholder objects. So, if we have a NIB that contains a UIViewController as File’s Owner (Placeholder object), the method will return one item in the NSObject[]: the view controller’s View. Anything on the same hierarchy level of that View, is a Top-Level object. If that View has subviews, these views are not Top-Level objects. I believe the following screenshot makes it clear:

Enough with the theory

Ok, so why do we need all this stuff? My opinion is that the most important role of the above information is that it will allow us to use NIB files more efficiently. Efficiency in this case, comes from the ability to cache the NIBs that will be used multiple times in the life time of our app. For this reason, I have created JTNibManager, a small MonoTouch library that will make all this stuff easier. You can get it from GitHub here (this is my first repo over there, so please excuse me for any strange stuff/files you may encounter), along with a sample project that shows how to use it. Let’s get to the bottom of it:

class NibManager : NSObject

This is the class that does all the NIB caching/loading for us. All that we need is enclosed in two methods. It stores all NIBs in a Dictionary<string, UINib> and provides us with the ability to instantiate them whenever we want. These are the methods:

void LoadController<TController>(TController owner, string nibName, NSBundle bundle)
    where TController : UIViewController;

The above method loads a NIB for us (if it is not already loaded) with “nibName” and instantiates its contents to the UIViewController instance we pass to it as “owner”.

TUIObject LoadUIObject<TUIObject>(NSObject owner, string nibName, NSBundle bundle)
    where TUIObject : NSObject;

This method loads the first Top-Level object from a NIB.
Examples to follow, for now, we’ll take a look at the other class the library contains.

[Register(“NibManagedController”)]
abstract class NibManagedController : UIViewController, INibManaged

This is just a helper abstract class. We can derive from it and the NIB associated with a controller will be automatically cached.

Using JTNibManager

The following examples are from the included NibManagerTest project. The first thing we need is to create a Singleton for the NibManager, so it will be accessible throughout all the objects. I usually do this in my AppDelegate:

using JTNibManager;
//..

// NibManager Singleton
// (in the example project, the declaration is “public static NibManager NibManager”)

public static NibManager NibM
{
    get;
    private set;
}

I instantiate it in the FinishedLaunching method, before everything else:

AppDelegate.NibM = new NibManager();

Then, I just add a new “iPhone View Controller” (here I named it AutoLoadedController) to my project and made the following change to make use of  NibManager:

using JTNibManager;
//..

// Change the base class from UIViewController to NibManagedController
public partial class AutoLoadedController : NibManagedController
{
    // Change the base constructor call to that of  NibManagedController’s
    public AutoLoadedController() : base(“AutoLoadedController”, AppDelegate.NibM, NSBundle.MainBundle)
    {
    }

    //.. view controller implementation, etc.

The NibManagedController’s constructor implementation:

this.nibManager = nibManager;
// The following method just calls
// this.Manager.LoadController<NibManagedController>(this, nibName, bundle);
this.LoadFromNib(nibName, bundle);
this.ViewDidLoad();

This is where the NIB is loaded and cached. To help keep our code clean, I have included the call to ViewDidLoad in there.

The above shows how to use the library for controllers. How about single UI objects? Like views or table cells? No worries, that can also be done. For this blog post, I am skipping on the “how to create a custom view/custom cell in a NIB” stuff. Inside the Views directory of the NibManagerTest project you will find a custom view with its XIB. Here’s how that view is loaded:

// Loads a view from a XIB
// “this” is just a view controller that the custom view will be used in.
CustomView customView = AppDelegate.NibManager.LoadUIObject<CustomView>(this, “CustomView”, NSBundle.MainBundle);

Similarly, to load a custom table cell from a NIB:

// Inside GetCell
//
// The usual stuff
UITableViewCell cell = tableView.DequeueReusableCell(“someID”);
if (null == cell)
{
    // Note which object is set as owner...
    cell = AppDelegate.NibM.LoadUIObject<UITableViewCell>(tableView,
                “someID”, NSBundle.MainBundle);
}
// Populate cell data and  return it

//...

The above solution is way better than the one I wrote in my previous article “Loading custom UITableViewCells from Nib files without a controller in MonoTouch” and is based on Alex York’s solution. The difference here is that this uses the JTNibManager library, so the NIB files of the cells are cached into memory and we do not have to load them each time. So if, for example, you have a UITableView whose rows are presented through multiple instances of only one custom cell, there is no need to load the NIB file each time.

How about Outlets and Actions?

Didn’t forget about them. The JTNibManager library will work as it should, however some considerations are worth mentioning. Here’s a list of them:

  1. Make sure that the object you pass as an owner to NibManager’s methods, already contain the same outlets that exist in the NIB. Outlets in MonoTouch are exposed through the [Outlet] attribute and can either be found in the auto-generated files “SomeController.designer.cs”, or we can create them ourselves:
  2. // When the XIB file is double-clicked in MonoDevelop,
    // it will automatically create an outlet named “myOutlet”
    // for us in the corresponding .h file.
    // We will still have to connect it to the UI object ourselves (Ctrl-drag).
    [Outlet(“myOutlet”)]
    public UILabel SomeLabel { get; set; }

    The same applies for actions, which are marked in a class with the [Action] attribute.

  3. When creating outlets in the classes with the [Outlet] attribute, make sure to provide full access to them so that the runtime can set it when it loads the UI. The following will raise an “NSInternalInconsistency” exception upon creation:
    [Outlet(“myOutlet”)]
    public UILabel SomeLabel { get; private set; } // Runtime exception: the set accessor is  private
  4. When creating standalone UI objects in NIBs (like a custom UITableViewCell), there is no need to connect the cell to the File’s Owner through an outlet. This is because the UINib.InstantiateWithOwneroptions returns the Top-Level objects, so a standalone cell in a NIB is a Top-Level object. Hence, the NibManager.LoadUIObject<TUIObject> method returns the first Top-Level object it gets from the InstantiateWithOwneroptions call. If you load a custom table cell from a NIB and you get an exception regarding “setValueForUndefinedKey”, it most likely means that the runtime did not find an outlet where one was expected.
  5. The “standard” rules apply regarding cleaning: release any outlets in the ViewDidUnload override.

Note that the above considerations are the same for using NIB files normally, without JTNibManager or similar.

Closing

I hope you will find the above useful. There’s more to NIB loading/caching than what is mentioned here. In fact, there is something even more useful than just caching the NIBs. Let me give you a hint: selectable skins…

Update #1 (March 28th, 2012): Fixed formatting.

To understand everything better, I suggest reading Apple’s Resource Programming Guide on NIB files:
Resource Programming Guide on NIB files

JTNibManager on GitHub

Post to Twitter Post to Facebook Post to Yahoo Buzz Post to Delicious Post to Digg Post to Google Buzz Send Gmail Post to LinkedIn Post to MySpace Post to StumbleUpon Post to Technorati

 Leave a Reply

(required)

(required)

* Copy This Password *

* Type Or Paste Password Here *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>