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!
Related
I am having some problems and wondering if someone could help.
I have always used storyboards so I am rusty at programmatically creating UIViews.
Basically, I am loading data from an API and displaying it in a UITableView. The problem is, on one part of my app, the loading of the data takes at least 10 seconds as it loads 10 different things from the API. (This must use separate calls). I use a sleep of 1 second so as not to make too many calls to the API per second (Hence why it takes so long to load). This is integral to my app.
I have implemented a count which increments after each call. Now all I am hoping to do, is create a UIView with a label and progress bar to say that it is loading and how much. This must be displayed over the tableview.
How do I go about doing this?
I do it this way:
1 - Create a UIView subclass, with outlets to all of the view's sub elements.
2 - Create a xib file. Change the class of the top view to the name of your UIView subclass. Add all the elements you want to the view and then hook up the outlets from the topmost view to the elements. File's Owner remains blank and unused.
3 - In the view controller that I want to use the view in I add:
NSArray *nibContents = [[NSBundle mainBundle] loadNibNamed:#"MyCustomView"
owner:self
options:nil];
MyCustomView *myCustomView = [nibContents objectAtIndex:0];
Then you can add the custom view to your table view. I would probably add a property in the view controller and assign the custom view to it so I could access the custom view using self.myCustomView.
I have a custom view I made with an IB file since it's quite complex: (RotatorView.xib/.h/.m).
I want to add the RotatorView as a subview to a view controller. I can do this programmatically using:
NSArray *array = [[NSBundle mainBundle] loadNibNamed:#"RotatorView"
owner:self options:nil];
But I'd rather not do that. It would be better for my design if I could add the RotatorView to the canvas of a View Controller in Interface Builder. That way I could use IB to manage the properties of each instance of RotatorView instead of setting properties programmatically in each class. Is there a way that I can add my custom view with XIB to a parent view controller? Do I have to build an IB plugin to do this?
You can add a UIView control to the canvas in IB and change its class name to match your custom view class name.
Although this will save you from calling loadNibNamed method programmatically, but you'll not be able to set the custom properties directly from IB.
The only solution for doing this is to build an IB plugin, but unfortunately it seems you can't make Cocoa Touch IB plugins because you can't make Cocoa Touch frameworks.
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.
I have been using UIViewControllers and initWithNibName with much success, basically using them as a convenient way to design the view with Interface Builder. Unfortunately I have built a hierarchy of views before noticing this line in the UIViewController documentation:
Note: You should not use view
controllers to manage views that fill
only a part of their window
My question is this: Having a very simple NIB that only has a UIView in addition to the default First Responder and Owning Object, what is the simplest way to load the UIView into my code?
I have not been able to get loadNibNamed:owner:options: to work at this point, but suspect the answer will involve it somehow.
Yes, just call
[[NSBundle mainBundle] loadNibNamed:#"viewNib" owner:self options:nil];
You normally do this from the view controller you have set as File's Owner in the NIB. That way, you can declare an outlet for the view in the view controller which will automatically get connected when you load the NIB file. You don't even have to work with the return value of the method in this case.
I have a VC controlling a screen of content that has 2 modes; a normal mode and an edit mode.
Can I create a single VC with 2 views, each from separate nibs?
In many situations on the iphone, you have a VC which controls an associated view. Then on
a button press or other event, a new VC is loaded and its view becomes the top level view etc.
But in this situation, I have 2 modes that I want to use the same
VC for, because they are closely related. So I want a VC which can swap in/out 2 views.
As per here:
How to load a UIView using a nib file created with Interface Builder
I have found that I can load a VC with an associated view from a nib and then later on load
a different view from another nib and make that new view the active view.
NSArray *nibObjects = [[NSBundle mainBundle] loadNibNamed:#"EditMode" owner:self options:nil];
UIView *theEditView = [nibObjects objectAtIndex:0];
self.editView = theEditView;
[self.view addSubview:theEditView];
The secondary nib has outlets wired up to the VC like the primary nib. When the new nib is loaded,
the outlets are all connected up fine and everything works nicely. Unfortunately when this edit
view is then removed, there doesn't seem to be any elegant way of getting the outlets hooked up
again to the (normal mode) view from the original nib. Nib loading and outlet setting seems a
once only thing.
So, if you want to have a VC that swaps in/out 2 views without creating a new VC, what are the options?
1) You can do everything in code, but I want to use nibs because it makes creating the UI simpler.
2) You have 1 nib for your VC and just hide/show elements using the hidden property of UIView and its subclasses.
3) You load a new nib as described above. This is fine for the new nib, but how do you sort the outlets when you go back to the original nib.
4) Give up and accept having a 1:1 between VCs and nibs. There is a nib for normal mode, a nib for edit mode and each mode has a VC that subclasses a common superclass.
In the end, I went with 4) and it works, but requires a fair amount of extra work, because I have a model class that I instantiate in normal mode and then have to pass to the edit mode VC because both modes need access to the model. I'm also using NSTimer and have to start and stop the timer in each mode. It is because of all this shared functionality that I wanted a single VC with 2 nibs in the first place.
I would just add another view to your original nib, and wire it up to another IBOutlet called 'editView'.
#interface TestViewController : UIViewController {
IBOutlet UIView *editView;
}
#end
Then you can can do a [self.view addSubview:theEditView]; whenever you need to show it.