I'd like to use IB to create a toolbar with assorted toolbar items, but create and use this toolbar programatically - adding it to a view at my discretion, and not while initializing the view controller.
I'm pretty sure it can be done - but haven't been able to find a good reference. Any help?
Read about NSBundle's loadNibNamed:owner:options: method:
NSArray *objects = [[NSBundle mainBundle] loadNibNamed:#"Toolbar" owner:self options:nil];
This will return all the objects in nib which you can get using simple array methods. Also note that if nib file references any outlets to other objects in nib - they must be defined in your controller (or any other given nib owner) as well otherwise you'll get KVC exceptions.
Related
I have a UIViewController class MyClass that initially had no XIB, and was initialized programmatically. Now, I don't want to load it from an XIB, but I do want to make a small 50x50 UIView (settings overlay view), and I want to add it MyClass, so instead of programatically declaring the new settings overlay view, I thought I would create an XIB file, set the file's owner to MyClass, declare in MyClass.h an IBOutlet UIView *settingsOverlay, and link it in the XIB.
Then in the viewDidLoad method, I do [self.view addSubview:settingsOverlay], but for some annoying reason it doesn't work. It just doesn't appear. I tried creating a simple UIImageView programmatically and adding it to the subView, and it works just fine, but when done through the XIB, it doesn't work. Can anyone point out what could possible be wrong? Please let me know what other details I might need to include.
If you are trying to add a view using xib then you need to use loadNibNamed method.
[[NSBundle mainBundle] loadNibNamed:#"settingsOverlay" owner:self options:nil];
You can refer developer link for more info - http://developer.apple.com/library/IOs/#documentation/UIKit/Reference/NSBundle_UIKitAdditions/Introduction/Introduction.html
Creating an XIB for the settings overlay view and setting it's owner to MyClass does not implicitly cause that XIB to be loaded as a result of manually instantiating MyClass. You would have to manually load the settings overlay view XIB in MyClass viewDidLoad and add it as a subview. Loading it and passing owner as self will cause it to be bound to the IBOutlet you created, but you still have to add it as a subview.
The code in viewDidLoad would look like this:
[[NSBundle mainBundle] loadNibNamed:#"OverlayView" owner:self];
[self.view addSubview:self.overlayView];
You need to load the MyClass xib before settingsOverlay will be set. Try adding this line before your addSubview call.
[[NSBundle mainBundle] loadNibNamed:#"MyClass" owner:self options:nil];
I'm in the midst of creating a framework to be used internally, and I'm running into issues making a flexible view controller with a custom view.
The goal is to have an implementation similar to UITableViewController:
If the view controller is initialized with a nib (or if a nib of the same name is found), it uses the outlets defined therein to define it's view. It is somewhat up to the developer to ensure the right subclass of UIView is used.
If the view controller is initialized without a nib (or if no nib can be found), it creates it's own view, using the correct UIView subclass.
As a first stab, I overrode -loadView to create the custom view. This is what you would do if you weren't concerned with nibs, and it works great. However, if I try to load a nib (using, for example, -initWithNibNamed:bundle:) the -loadView method executes and the nib is ignored.
I've also tried using -nibName to determine if a nib name was passed, and this PARTIALLY works, but fails if nil was passed (which is still valid, and should absolutely still work if there is a nib of the same name as the view controller).
Keep in mind that I'm building a framework that will be used by other developers. "Flexible" and "Foolproof" are the keywords.
Any help at all would be more than I have now. Thanks much.
Edit: Solved
As was alluded to by #bunnyhero's suggestion below, checking for the availability of the nib during -loadView and attempting to recreate it's default implementation turned out to do the trick. I've settled on something like this:
- (void)loadView {
NSString *nib = self.nibName;
NSBundle *bundle = self.nibBundle;
if(!nib) nib = NSStringFromClass([self class]);
if(!bundle) bundle = [NSBundle mainBundle];
NSString *path = [bundle pathForResource:nib ofType:#"nib"];
if(path) {
[bundle loadNibNamed:nib owner:self options:nil];
return;
}
// Create custom view programmatically here.
}
Thanks #bunnyhero
What if you combine your nibName nil check with a manual check for a nib with the same name as the view controller?
Maybe something like this (untested code off the top of my head):
if ([self nibName] != nil || [[self nibBundle] pathForResource:NSStringFromClass([self class]) ofType:#"xib"] != nil)
{
// nib file exists...
}
I admit this is kind of crude and indirect :)
I have a View Controller inside my MainWindow.xib file that loads the nib "Cover" when loaded. Cover (and the rest of my pages) is simply a nib file containing it's Owner, First Responder and a View. There is also an associated class declaration.
In MainViewController.m I have:
- (void)viewDidLoad {
[[NSBundle mainBundle] loadNibNamed:#"Cover" owner:self options:nil];
[super viewDidLoad];
}
This successfully loads the Cover of my app. On a button press I'd like to have a function switch Cover with Page1. I tried:
-(IBAction)funcGoToPage:(id)sender{
[[NSBundle mainBundle] loadNibNamed:#"Page1" owner:self options:nil];
}
This function is also in MainViewController. I know the function is being called but It doesn't seem to do anything. Is the new nib showing up underneath the current nib? Do I have to release the current nib?
Depending on how you want users to navigate, you either need to add the view of the nib as a subview to your current view, or push a new viewController on to the stack using a navigationController. Here is a tutorial on using a navigationController. Or, if you just want to exchange them, here is a link to a SO answer for just that.
I have a nib call "Hello.xib", and I have a HelloView that is inherit from the UIView, and I want to do the layout in the Hello.xib, and I want to allocate them to the HelloView.m / HelloView.h, how can I do so? Thank you.
You normally do so on the outside. In HelloView you should have a UIViewController derived class. Then when initializing it on the outside you would call:
hello = [[HelloViewController alloc] initWithNibName:#"Hello" bundle:nil];
The bundle:nil make Cocoa use the default bundle.
In the interface builder's inspector for your Hello.xib's view, set the class of the view (in identity tab) to HelloView. I hope thats what you are looking for.
Just today I wrote a demonstration code, that also uses instantiation of custom view by loading a nib
the controller has a member DetailContactHeaderView *headerView
in the nib, I have a DetailContactHeaderView and the Files' owner type is my Controller
the files' owner property headerView and the View get Connected
in this controller I have this code
[[NSBundle mainBundle] loadNibNamed:#"DetailContactHeader" owner:self options:nil];
See my MyContacts for an implementation.
FYI:
DetailContactHeaderView
DetailContactHeader.xib
DetailContactViewController
especially -tableView:viewForHeaderInSection:
I'm opening a new question to followup on my last one (superview and parentviewcontroller nil after adding a subview). Basically I get that using subviews is a good idea, but that I shouldn't have a ViewController controlling a subview that lives inside another ViewController. Basically I'd like to do the following...
I have two ViewControllers which share a common subview. I've created that subview as a nib called SearchDate.xib. The file owner is a corresponding class SearchDateView.m/h. That class has an outlet for the only element inside the UIView in the nib which is a label. The SearchDateView class also has a function for changing the value of the label in the SearchDateView.xib. I'd like both of my ViewControllers to load this nib but apparently I have no idea how to properly load the nib. No matter what I do at best nothing displays and at worst an exception is thrown. The apple docs talk about dragging in other instances of classes in IB right into your main view but that seems not to be working out. I have a SearchDateView outlet in my ViewController and I tried doing this on the controller's load view:
searchDateView = [[[NSBundle mainBundle] loadNibNamed:#"SearchDateView" owner:self options:nil] objectAtIndex:0];
[[self view] addSubview:searchDateView];
But I get this exception:
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<MainViewController 0x431fac0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key searchDateLabel.'
I know I'm doing something totally wrong but what is the right way to have a nib, associated view class which updates some of the objects in that nib, and reuse that nib in different controllers?
EDIT: Given the comment perhaps this wasn't clear enough. I don't want to use the same nib file for an ENTIRE view - rather a subview. So for instance controller A has a map and also a SearchDateView, and controller B has a table view and a SearchDateView. So I'm wondering how to load a subview into multiple controllers...
There should be no problem using the same NIB file for multiple controllers when each one is initialized using initWithNibName:bundle:. You don't normally load the controller's own nib file from within the controller though.
If you load a nib file using loadNibNamed::: then you get a NSArray with the objects defined therein, so you can't use it as a view directly. One way to get a view is to search through the array using for() or something to find the object you want, but if you set owner:self then it should connect to outlets connected to File's Owner in self as File's Owner will be self. But you can discard the return value in this case; you don't need the returned array. That may be your main problem (clobbering the outlet with the array) if you have that outlet connected.
It is possible to use loadNibNamed to load a specific view object (assuming you pick it out of the returned array), then display it somehow, but it's usually easier to use initWithNibName on the controller (in which case File's Owner will be the controller).
Oh, and you can also set the nib file for a controller in Interface Builder. There shouldn't be a problem with using the same nib for multiple controllers since essentially you'd just be telling Interface Builder to set up the nib file to do something like initWithNibNamed. Click the controller object and check the inspector window.
Update
I'd probably do this in order to use only one view in multiple controllers:
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"FooView"
owner:self options:nil];
for (id obj in nib)
if ([obj isKindOfClass:[FooView class]])
myNewView = (FooView *)obj;
(Stolen from Chapter 8: Cells 2 in the sample code from Beginning iPhone 3 Development.)
You could do this with outlets in IB, in which case you could leave out the for loop, but you'll probably need a superclass for both controllers declaring the outlet, and something to tell Interface Builder that File's Owner is an instance of that superclass so it knows about the outlet. Probably not worth the trouble.
I have a SearchDateView outlet in my ViewController and I tried doing this on the controller's load view:
searchDateView = [[[NSBundle mainBundle] loadNibNamed:#"SearchDateView" owner:self options:nil] objectAtIndex:0];
[[self view] addSubview:searchDateView];
But I get this exception:
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<MainViewController 0x431fac0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key searchDateLabel.'
In the absence of Cocoa Bindings (which is only available in Cocoa, not Cocoa Touch), I don't think these two are related.
In Xcode, add a symbolic breakpoint on objc_exception_throw, then run your app in the debugger. When it breaks, look in the call stack. You'll be able to find where the problem really originated.