Much I've read talks about the advantages of setting your UI up in IB. And how when the nib is "awoken" all the objects in it "come to life".
I'm experimenting with placing about 10 UIView objects into a nib that is owned by a ViewController. The UIView objects are of type MyView and are wired back to their respective properties in the ViewController. MyView is a subclass of UIView with a couple additional properties like a UIImage *image & NSNumber *value.
The nib clearly loads as I can see some other UIImageView elements that are also in the nib. But when I try to set a property in one of the 10 MyView objects I'm unable to. And in the debugger my properties for these objects are all still 0x0.
I was also under the impression that the initWithCoder method in my MyView class would fire as each of these 10 objects awoke from sleep so I could set some properties at runtime?!?
Anyone know what's going on... I'm happy to abandon IB for this but thought I'd give it a try...
viewDidLoad is the first method called when all of the objects in your view have been created. initWithCoder is called before any of them are.
viewWillAppear is called after viewDidLoad, and called before anything is displayed - it gets called each time, where viewDidLoad only gets called when your view objects are created.
UIViewControllers load their views when first accessing the self.view property. Try to see if the viewDidLoad method of your controller is called.
Also, I remember reading somewhere (could be Apple documentation) that having multiple independent views loaded from a nib is not recommended, mostly because of memory usage.
I suspect it might be that you have assigned the view to the files owner.
Open IB. In the window that has.
File's Owner
First Responser
View
Right click on the "view" object.
If it only has one option
"new referencing outlet" then it means you havent wired the view into the files owner.
Simply grab that little o beside this and drag it upto the files owner.
Once over files owner a new menu should popup and you can should be able to select "view"
Save this and see if is now working.
Lastly check that the "File's Owner" has been set to your class, you can simply select the File's Owner and inspect this. In one of the drop downs you will be able to select the class.
IB is cool. Dont give up... Saves upto 70% of you setup code (as stated at WWDC by Apple) so stick with it.
John.
Related
Can anyone summarize the relationship between the following items?
Content View
View Controller
Nib
the view, subclass of UIView
Application delegate
I got very confused about these. Coz some people say the "content view" contains the "nib" while other people say "content view" and "nib" are not containing each other.
Many thanks!
Oh man… that's not so easy. But I'll try.
Application is being launched from main().
Application delegate receives callbacks from Application during runloop. For example, when app finished launching or something else.
Usually application contains single instance of UIWindow, that is the root of all view hierarchy.
UIWindow can have UIViews, they can have UIViews by themselves. So, there's a hierarchy of UIViews (a tree)
Each view has controller, that gets user input and other events and controls UIView (for example, tells it to redraw itself because of user tap). Controller can be standard or custom, written by developer.
Content View is a normal view. Usually within a table cell. UITableViewCell instance has a property that is called contentView. It's a normal view and it can be any UIView subclass.
NIB is another story. You can create whole view hierarchy by yourself. But there is an alternative way: use Interface Builder. After creating views/subviews in the interface builder — you can save this hierarchy with all its properties as a single (serialized) file. And load it at once during application run.
NIB has three main objects. File Owner is an object, that you'll get when you send some message like
+ (BOOL)loadNibNamed:(NSString *)aNibName owner:(id)owner
Here owner will be filled with all properties of File Owner from the NIB.
First responder - first receives input. You can simply forget about it for now.
View — is the main view. Usually it is linked to a view property of File Owner.
It's a veeery short overview of all these things. You really have to read documentation to understand it better.
This should be a pretty easy fix, but I haven't figured out from reading the Apple documentation or other SO questions how to do this simple switch from creating my Interface programmatically to using Interface Builder.
I am basing my code around this framework:
http://www.pushplay.net/blog_detail.php?id=27
The only difference is that, where each View is programmatically created (View01.m, View02.m) in ViewDidLoad, I instead want to import from a nib (while still using this framework) for each view (each view has a unique IB design).
Thanks for the help.
Think of IB as an Object Creator and not a code generator. That really helps. What IB does is actually create instances of objects as they are dragged on to the desktop/view/XIB window. It then allows you to start creating various connections (with a control drag on the mouse) from one object to another object. You then instantiate the entire XIB by unarchiving it from your bundle. This is highly automatic and reading up on UIViewController should move you along a bit. Look at:
initWithNibName:bundle:
You basically have two types of connections:
Outlet: This is how you teach one object about the existence of another object. For example, you might have a controller object that needs access to a button. You create an outlet (either in XCode Text Edit in the controller.h file/property area or in IB by adding an outlet) in your controller and then control-click-drag from the outlet to the button.
Actions: This is how you trigger an event on one object to call a method on another object. Actions will have a prototype of:
- (IBAction) someMethod:(id) sender;
I think the ":(id) sender" is optional if your method does not need a link to the object causing the event.
Within IB, you can arrange objects and set various attributes like size, color, position, target/actions, user interactions, Files Owner...
That brings me to files owner. Big concept here. It tends to be the Controller that loads the NIB (OK: I have a custom window controller I have used for over 15 years but Apple has a really good one UIViewController that does all sorts of goodness.) and acts as a proxy in IB. It is not actually instantiated in IB but it will be when you alloc and request it to load the NIB (XIB files are XML files that are turned into NIB files by the compile process)
As I move more and more of my UI construction from XCode to IB there is one remaining hurdle I need to clear.
Currently, I create my custom viewControllers in XCode with custom initializers. They typically have a bunch of ivars that are set during initialization via passed params. I would really like to move as much of this as I can to IB.
Could someone carefully explain to me - or, better, point me to code - how to replicate the XCode approach of passing params via custom initializer in IB - presumably via IBOutlet.
Thanks in advance.
Cheers,
Doug
UPDATE:
A typical scenerio is my AppDelegate will go out to the network, grab some data - NSMutableData, and then pass a reference to a root viewController - typically a TableViewController - that is pushed on the viewController stack of an navigationController.
When a cell is selected an secondViewController is alloc/init'ed and a subset of the data is passed to it. The secondViewController goes to the network for a bit more data, builds a view hierarchy, hands bits of the retrieved data to each subview and then each subview is messaged with setNeedsDisplay to process the data.
It is this hierarchical data passing that I want to hand off to IB is possible.
You can still have a custom initializer. However, inside this initialized you'll call -[initWithNibName:#"yourNibName" bundle:bundle]. You will connect your UIKit related instance variables (UILabel, UIButton, etc.) in Interface builder and therefore won't have to instantiate those objects in your initializer. Other instance variables (strings, dictionaries, custom objects, etc.) you'll instantiate and set in your initializer.
Also, keep in mind that manipulation of your UIKit related variables such as setting label text, or setting the position of a UIView, should be done in the viewDidLoad method since these objects may not have been fully created at the time initializer is executing.
Let me know if you need more information.
Lets say we have MyViewController which extends UIViewController and MyViewControllerParameter which is one of our custom objects we pass into MyViewController on initialization. MyViewController has an IBOutlets of type MyViewControllerParameter defined in it. We want to put MyViewController into another containing view, ParentView.xib. ParentView.xib is owned by ParentViewController.m and created elsewhere. We double click ParentView.xib in XCode to launch Interface Builder. We then drag an "object" from the Library View (I believe you open the library view with Cmd+Shift+L if its not open by default) onto the Document window. (This is the window activated by Cmd+0.)
An "Object" is an arbitrary widget found in the library (identified by a solid gold cube icon) that can represent anything in your project. After we've placed the new object into the document window, we type Cmd+4 to open the Indentity Inspector Window. In the top text field we can type the name of our view controller parameter, "MyViewControllerParameter". Next we find the ViewController widget in the library and drop it into the document window. We open the Attribute inspector (Cmd+1) and set the nib name to "MyView.xib". This should cause CocoaTouch to load MyViewController using the definitions in "MyView.xib".
Open the identity inspector and set the class attribute to MyViewController. Last we right click or control click (or two finger tap if we've correctly configured the trackpad on our MacbookPro) MyViewController in the document window. That will open a window showing all of the IBOutlets in MyViewController. Drag a line from the little open circle next to the MyViewControllerParameter outlet and drop it on the MyViewControllerParameter object sitting in the document window. Connecting these dots is how you set parameters between objects using interface builder.
As a newbie, IB and all the possible connections is bewildering to me. Most tutorials I've found are what I'd call the reincarnation of spaghetti code, in which the entanglement is all the connections created by dragging. Of course, I want to use IB for layout of views (sizing & placing visual elements), that's what IB is great for. But a controller is not a view, so it's less confusing if all my controllers are solely code and don't appear anywhere in IB. I suspect this will minimize the spaghetti. It also encourages the one-xib-per-view admonishment. To that end, and here's the question, where can I find example projects that adhere to this strategy?
I don't have a set of sample projects, but I will give you some information about how things work and when you should create controller's in a XIB file or in code.
If your controllers are created dynamically by way of a user's action, you typically won't instantiate them in a XIB file. Instead, you'll instantiate them in code like harms mentions above. Once you do that, you'll still need a mechanism to connect this controller that was created in code, to the user interface elements that you've created in IB.
The mechanism that IB provides for solving this is the File's Owner. Mastering the File's Owner is essential to "getting" Interface Builder.
The file's owner is not an object that is "in" the XIB file, it's an object that's represented in the XIB file. It's a placeholder for an object that will already exist when the XIB file is loaded. When NIB files are loaded at run time, they're loaded with the NSBundle method -[NSBundle loadNibNamed:ownwer:options:]. The owner parameter is used to resolve the file's owner placeholder object in the XIB/NIB file. When the file is loaded at runtime, all of the connections made against the file's owner will resolve against the object you passed in as the owner parameter to the NSBundle method. On the iPhone, you typically don't load NIB file yourself. Instead UIViewController does it for you. The default implementation of UIViewController's loadView method might look something like this:
- (void)loadView {
[[self nibBundle] loadNibNamed:[self nibName] owner:self options:nil];
}
So, by connecting the elements in your XIB file to the file's owner, you'll be connecting them to your view controller.
You will have some controllers that are statically in your application - they'll be alive forever. A navigation or tab controller along with their root items are typically alive for for the entire life of their application. When that's the case, I'd set those view controllers up in the MainWindow.xib file. Most of the other controllers would be created dynamically, and programatically in response the the user doing things.
Good question. The pattern I try to stick with is making the controller in code, adding IBOutlets and IBActions for the things in the XIB/NIB that interact with that code, and a thing in the controller's constructor which loads the XIB/NIB with "self" as the owner, and in IB I connect stuff to the "File's Owner" placeholder, whose identity I will have specified to be the my controller's class.
In the Memory Management Programming Guide for Cocoa Apple talks about Top-Level Objects. They say, that I need an Outlet for each of them.
If there are any top-level objects you
do not store in outlets, however, you
must retain either the array returned
by the loadNibNamed:owner:options:
method or the objects inside the array
to prevent those objects from being
released prematurely.
So what exactly do they mean with "top-level object"? I would say they talk about the root view and window. What else? And is this hint just for cases in which I would want to load their nib manually? Or does it apply for any nib and any case?
Top-level objects are objects that appear in the main nib window in Interface Builder, other than File’s Owner and Application.
Yes, this is about the cases where you load the Nib manually, else you wouldn't have called loadNibNamed:owner:options:.
Objects that appear in the window with File's Owner, First Responder, View are top level objects. Anything you add to the view will be a subview - all subviews are retained by their direct superview, so they don't need to be retained elsewhere. The view itself is retained by the view controller, so that doesn't need to be retained.
If you add non-view objects, or views that you don't put into your main view right away (such as tool bar buttons that aren't currently showing) you need to retain them, or they will get released. My preferred method for doing this is with IBOutlets using #property, like this:
#property(nonatomic, retain) IBOutlet UIBarButtonItem * myButton;
This causes -setMyButton: to be called on file's owner (if this code is in file's owner) and the object connected through IB is sent to be stored in this property.