I was trying to use IB in a slightly different way that I am use to and I can't get it working extending the normal approach I use, when dealing with IB.
Instead of making a new UIViewController and have the view XIB generated for me and everything linked together by Xcode, I would like to just build a small (320x40px) View XIB and link it to my already existing ViewController.
I start out by making a new file in Xcode, select "view XIB".
I then open IB and add some labels etc. to the view and I set "Files Owner" to be my existing ViewController.
In my existing ViewController I set the IBOutlets for the labels etc. I put in my view.
I go back to IB and hook up the UILabels to my outlets in "Files Owner".
I would now think that I have a reference to the labels inside the XIB, in my viewController.
This is not really the approach I would like, I see no need for my viewController to have a reference to labels inside my view.
How I usually do in code:
My ViewController controls a bunch of UIViews made entirely in code and who instantiate them by:
UIView *customView = [[CustomView alloc] initWithFrame:aFrame];
[customView setTag:MY_CUSTOM_VIEW];
[customView setDelegate:self];
[self.view addSubView:customView];
[customView release];
After this I would access the labels, buttons etc. from my controller by using the [(UILabel*)[[self.view viewWithTag:MY_CUSTOM_VIEW] myLabel] setText#"Hello, World"];
have my UIViewController implement what ever methods the customView protocol required.
How to get that functionality with IB
Should I first build a MyCustomView class extending the UIView class, have it hold all my IBOutlets, set MyCustomClass as files owner and then instantiate that as shown above?
Is it OK to have a view act as viewController for the IB view and how would I relay actions to my "real" viewController?
What I would like to achieve is to deal with instantiating and laying out several UIViews in my UIViewControllers code, but have the freedom of designing some of these UIViews in IB.
All the info I can find is regarding the standard "build a UIViewController with a XIB for the view" or "How to build libraries of IB components".
I hope it makes sense and thanks for any help given:)
You can create whatever view structure you want in Interface Builder and then instantiate it using the UINib class. Once you create an UINib object it loads the contents from the nib and keeps them. Then, whenever you send it the instantiateWithOwner:options: message, it will instantiate the objects contained in the xib and return an array with the top level views. You can then add these views to your view hierarchy and handle them just like any other view you created programmatically.
If you keep the UINib object (as a property for example), you can instantiate the contents again and again, which allows your xib to be used like a template.
update: For a pre-iOS 4 workaround see my recent question and answer.
Related
I have an WizardSequenceViewController with an IBOutlet WizardView *_wizardView. In many WizardSequenceViewController.xib file I have the view outlet connected to the File's Owner - WizardSequenceViewController. I have a subview of that view defined with the class attribute set to WizardView. I have connected that WizardView to the outlet in the File's Owner. Finally, in my WizardView.xib I have a UILabel that I have placed in the file to test if the view is being rendered. When I select the WizardSequenceViewController from my tab bar, I see the superview view but not the subview _wizardView. When I set a breakpoint in my -(id)initWithCoder method in my WizardView.m file I see it stop there, so I know that it is calling that initializer (and thus it should be using the xib to load that file). I have tried many iterations and variations to get this thing to work but I can't and I am going crazy. Does anybody have any ideas?
From Apple doc "View Controller Basics, About Custom View Controllers":
The one-to-one correspondence between a view controller and the views in its view hierarchy is the key design consideration. You should not use multiple custom view controllers to manage different portions of the same view hierarchy. Similarly, you should not use a single custom view controller object to manage multiple screens worth of content.
Note: If you want to divide a view hierarchy into multiple subareas and manage each one separately, use generic controller objects (custom objects descending from NSObject) instead of view controller objects to manage each subarea. Then use a single view controller object to manage the generic controller objects.
Maybe you can't do a view-and-subview outlet setup in a view controller. And I'm not sure assigning the subview outlet to a separate NSObject subclass would work either, because how would you present it? Could you write your subview programmatically, using initWithFrame and addSubview, instead of making it an outlet? Or, if you really want to set it up graphically, could you assign it to a separate view controller as owner? Then the top view controller will call presentModal on the sub view controller. Or, if all you need is a UILabel as a subview, just add the label to the main view?
Even I faced a similar issue. But got it resolved by following steps given in the following link. Hope it helps.
http://blog.yangmeyer.de/blog/2012/07/09/an-update-on-nested-nib-loading
I have a root view controller which loads a custom UIView subclass I have created and adds it as a subview.
Inside this custom UIView subclass I code/generate a UIButton in the awakeFromNib method.
Is there a simple way to access the File Owner without creating a delegate if the UIButton's action method is inside the root view controller?
E.g
[myButton addTarget:[self.file_owner ?] action:#selector(methodInFileOwner:) ....
Using Interface Builder it's still easy to assign a UIView my custom UIView subclass and just drag a UIButton's selector reference to the file owner. Voila!
How is this done through code though? Do I have to create a delegate and use
[myButton addTarget:[self.delegate] ...
?
File's Owner is an Interface Builder concept. It doesn't exist on the programming side, basically, because it's not needed. In interface Builder, File's Owner is the class that instantiates the nib file. Often, it just refers to the class of the nib file you're currently working with. Since you're working with a view controller, the File's Owner is your view controller subclass, and it allows you to make connections to instance variables and methods of that class.
On the programming side, in this case, the equivalent of File's Owner would just be self. And, you access an instance variable, using properties, as self.instanceVariable.
On to your question. If you want the selector method to be in the view controller, that makes perfect sense. But then, the view controller can create the button, set its target/action, and add it as a subview to the custom view. You could do this in -viewDidLoad, which is called after the nib file is loaded and is the standard place where you would make any programmatic additions to the view controller. So, you could do it as follows:
- (void)viewDidLoad {
self.myButton = [[[UIButton alloc] initWithFrame:CGRectMake(x, y, width, height)] autorelease];
self.myButton.buttonType = ...;
[myButton addTarget:self action:#selector(actionMethod)...];
self.myCustomView = [[[MyCustomViewClass alloc] initWithFrame:...] autorelease];
[self.myCustomView addSubview:self.myButton.view];
[super viewDidLoad];
}
The above code is just an example. You can initialize your objects in different ways. In this case, the button would now be an instance variable of the view controller. But, you could just as easily leave it in the custom view and just refer to it as: self.myCustomView.myButton
I hope this is helpful.
Correction: The above code should be in viewWillAppear rather than viewDidLoad. When viewDidLoad is called, the geometry (i.e. the view's bounds) has not yet been set. So, in order to set the frame of any object, it must be done in viewWillAppear.
The target should be an object of the root view controller class. In your UIView subclass you will need a reference to your root view controller class.
If a nested widget is hidden from the controller, then that essentially means that the custom view should manage all aspects of that nested widget. Here are some options (and probably not a complete list of them either):
You could have the custom subview handle UIControl events and propagate them into the button. Your custom subview would implement the methods of UIControl and essentially hand them down to the button.
You could also use a delegate like you mentioned.
Or you could restructure it so that the widget hierarchy is flattened, but their display is nested.
If you plan on making a custom component that you reuse in multiple places, then the first and second options are probably better since they are more flexible. If this is not the case, the third option is probably best since there is actual interaction between the button and the controller.
The Delegates and DataSources section of the Cocoa Fundamentals Guide gives an example of what the code looks like to create delegates.
In my iPhone app, I made a custom UIView that I use in several different view controllers to display some information.
I'm familiar with making customer table cells for use in a table but I've never made a custom view again.
My question is what is the best way to load this view? I've tried simply placing the view in the view controllers I want it to appear in using IB, but that doesn't seem to be enough. It seems to me that it would make sense that if you set the Class Identity for the view that it should load up that view when the view controller is created. Or maybe it doesn't because it doesn't know what nib to get it from (it's in its own nib, of course).
Since it's not a view controller, I can't just use initWithNibName. I've tried using NSBundle loadNibNamed like I do with table cells but it seems like an awfully large amount of work. The code looks like this:
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"MyCustomView" owner:nil options:nil];
for (id currentObject in topLevelObjects) {
if ([currentObject isKindOfClass:[MyCustomView class]]) {
self.customView = currentObject;
break;
}
}
[self.view addSubview:self.customView];
There must be an easier way which doesn't involve the loop. Also, creating the view this way makes me lose all the info from IB on where I actually placed the view. It's created at 0,0 which is not where I want it. Yes, I know I can set a new frame, but again, there must be an easier way. Is there a way I can do this all through IB? Or at least with less work programmatically?
I also make it that way. I ran into the problem that I want to subclass UIView and using nib at the same time. I don't think there's better way to do so, because nib file can contains many objects and you have to specify which one to use. However, do it this way you can put IBOutlet in your MyCustomView and bind it in IB so the view can manage itself e.g. overriding layoutSubviews.
Try this: create a new UIViewController subclass (called ParentViewController for the sake of answer) that each of your controllers that need to display this information will then also subclass. This controller should not have a nib associated with it.
In ParentViewController, create an IBOutlet for a UIView that will hold a reference to a view containing your custom nib. (Called customView, again just because)
Now, open your nib (for the commonly used customView) in Interface Builder and select the File's Owner. Now in the Identity tab, choose the ParentViewController created above for the class. Now here comes the trick. In Connections, remove any connection to "view" (if there is any) and create a connection from the customView outlet to the topmost UIView of the nib that defines your customView.
Now, when you do a loadNibNamed in the code for a subclass of ParentViewController, it should automagically load the UIView from your nib and put a reference to it in the outlet for you to use. No loop required.
You could also add outlets for any other interface components in your nib and do the same thing.
Good luck!
I'm having a problem setting a View Controller nib's default View Outlet in Interface Builder. Here's my setup:
I have a TabBar based application where I load a Navigation Controller as a modal view...
MyNavCtrlrSubClass *menu = [[MyNavCtrlrSubClass alloc]initWithNibName:#"MenuController" bundle:nil];
[tabBarController presentModalViewController:menu animated:anim];
MenuController itself is structured as follows:
MenuController.xib
File's Owner (MyNavCtrlrSubClass : UIViewController)
Navigation Controller (UINavigationController)
Navigation Bar (UINavigationBar)
Root View Controller (Nib Name is set to load AnotherViewController.nib)
Navigation Item -> (UINavigationItem)
This all works fine, except that when MyNavCtrlrSubClass is loaded, I get the following error:
Loaded the "MenuController" nib but the view outlet was not set
It's clear why this is happening - File's Owner doesn't have an outlet connection for its view. The question is what should I set as its view, and does something have to be set in the first place? The Navigation Bar is the only candidate in MenuController.xib, but doing this will just size the UINavigationBar itself to the fullscreen mode, so to speak.
I'm obviously missing something in IB, but what? MyNavCtrlrSubClass.m has no code itself, except an IBOutlet for the UINavigationController. Am I making a mistake trying to set this up entirely in IB? The idea is to keep the modal Navigation Controller in one nib, and all the views it loads in separate nibs, but since MenuController is just a container for the navigation and contains no views itself, I'm obviously designing it wrong. :)
If you're wondering why I'm not designing it some other way, it's because I'm trying to obey my (possibly mistaken) perception of how IB asks you to build an ideal hierarchy.
Any help would be greatly appreciated.
I think you might not be understanding how the File's Owner is meant to be used in a NIB file. I've written up a response describing the file's owner under another question.
When you invoke this line:
[[MyNavCtrlrSubClass alloc] initWithNibName:#"MenuController" bundle:nil]
You create an instance of MyNavCtrlrSubClass, and tell it to load #"MenuController". Then in the MenuController.xib file, there is a second unrelated UINavigationController with things inside of it. When MenuController.nib is loaded at runtime, that second navigation controller will be created.
A second problem is that telling a UINavigationController to load a NIB file isn't really sensible because navigation controllers create and manage their own views. You really want to create the root view controller, and tell that view controller to load a NIB file.
Try this:
MyRootViewController *rootController = [[[MyRootViewController alloc] initWithNibName:#"AnotherViewController" bundle:nil] autorelease];
MyNavCtrlrSubClass *menu = [[MyNavCtrlrSubClass alloc] initWithRootViewController:rootController];
Where your XIB File looks like this:
File's Owner (Class set to MyRootViewController, view outlet connected to subsequent UIView)
UIView
Subview A
Subview B
After you're confortable with how all of this is working, you might also consider instantiating the navigation controller and root view controller in one XIB file like you were starting to do in the code you posted.
The crux of this question is stated by Travis himself: "How would you load Navigation Controller nib, designing as much as possible in Interface Builder?" And also from the example, it looks like this means the UINavigationController and associated UIViewControllers.
With this interpretation, the answer is you cannot fully configure a UINavigationController and it's UIViewControllers in a single XIB. Yes it is intuitive to want to do this so you are not crazy.
When I say you cannot do this, I mean the most commonly used framework methods do not have a way to handle this. There is no [UINavigationController alloc] initWithMegaNibName. Yes you could stuff almost anything in a single XIB and write code to hydrate objects in special ways, but I don't think that's what you're looking for.
You could use two or more XIBs as Jon suggested, but then things are less self contained and so you have many folks who find it simpler to do part, or all, of the controllers in code.
Unfortunately there is not a 1:1 correspondance between Interface Builder capability and code like there is on other dev platforms. I generally prefer to be able let designers participate as much as possible in creating assets, but most of them I know do not code objective-c.
In IB, in the MenuController nib add a UIView and set that view as the outlet. You will need to set that view for a UIViewController. Here is a quick link to an Apple page showing a basic setup. Not sure if it will help you at your current stage though.
I have a relatively simple app. I have a fullscreen UIView subclass (musicGridView) that the user interacts with, and a UIScrollView subclass (scrollView) that loads this class in, as well as a ViewController and mainWindow.xib.
Right now, I have a viewController instance loaded in the nib, which is of type myViewController. The nib also has instance of myUIView, which the myViewController is pointed to.
In AppDelegate, I do this:
[window addSubview:scrollView];
[scrollView addSubview: viewController.view];
scrollView.musicGridView = (MusicGridView*) viewController.view;
which I think is wrong. Ideally, the appDelegate doesn't have an instance of scrollView.
In scrollView's AwakeFromNib class, I do the initialization on the scrollView.
The way I think it should go, is load the [window addSubview:viewController.view] in appDelegate, and then point the viewController to an instance of scrollView instead of musicGridView. Then, in scrollView awakeFromNib add a subview of musicGridView.
Eventually, I want to create another view owned by scrollView that is actually a side TabBar (which isn't possible with the given API's) that the user can scroll to the left to reach.
So I guess amongst other MVC stuff, the question is, should the viewController point to the scrollView, which contains all other content UIView subclasses, including musicGridView?
It sounds like you are not using Interface Builder to design your UI. Since this is a new project, I would suggest doing that. You should not have to write any code like this at all. You will however need to describe your outlets and actions.
The two most important things you need to know when dealing with IB are the IBOutlet and IBAction keywords.
Sample class header:
#interface MyClass : NSObject {
IBOutlet UIScrollView* myScrollView;
}
- (IBAction) doWork;
#end
Now you can wire these two things up using Interface Builder by dragging sources to destinations with your mouse. YouTube has lots of tutorial videos on IB if you need a better description of how to do this.
I'm someone who doesn't use Interface Builder for UI design; in my experience, you need to go either all-IB, or all-programmatic. To solve your particular problem, I think you need your musicGridView to be an instance or extension of UIScrollView.
This can be done in your custom viewController's loadView method - simply initialize it to a UIScrollView (and add things to it), instead of a plain old UIView.
However, as described above, this approach isn't compatible with an IB-centric approach, as is confirmed in the UIViewController Class Reference
All subclasses of UIViewController
should implement the loadView method
to set the view property unless you
load your view from a nib file.