Designing a view controller with multiple xibs - iphone

Is it a best practice to use more than one xib in a view controller for a responsive design?
I'm currently implementing an app that relies heavily on a remote API for its data. One particular view is troubling since the layout depends on the data returned from that API. It is either a product that has a full review and pricing, or a product that we only have limited information on.
The design is responsive to the data. Depending on how much information that is returned, the view has a layout that is much different from the other. Also, there's a quite a few elements that need to be repositioned depending on text length, etc. I had originally implemented the design nearly all in code, but that became quickly unmaintainable so I offloaded almost all of it to the xib and only use code to reposition elements.
Using more than one xib might be a good solution. When the view loads, the controller performs the API request, then loads the new xib if need be. I'm not sure of what downsides there may be to this approach.

I'm implementing now the client-server app. Layout of the most of views is also depending on data from API. In situation like yours I've created several xib files (one for detailed product, one for limited etc.) and depending on server response I'm choosing xib to load. Code sample:
// load proper view from nib
NSArray *nibViews;
if(product.details) {
nibViews = [[NSBundle mainBundle] loadNibNamed:#"DetailedView" owner:self options:nil];
}
else {
nibViews = [[NSBundle mainBundle] loadNibNamed:#"NormalView" owner:self options:nil];
}
// create and initialize product view object
ProductView *productView = [nibViews objectAtIndex:0];
// configure a view here..
Yo could put all versions of view into one xib. In that case you only change index in nibViews objectAtIndex:

Related

iPhone - Slow scroll on TableView

i've problems with the scroll of my Table View. I have a custom cell that i load with this code :
if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:#"CustomCellQuartieri" owner:self options:NULL];
cell=customCell;
}
This is the code. customCell is an UITableViewCell object with a own xib. The controller of the xib is my view controller where the table is placed in. I load 2 label and one imageView from the internet. What is the problem? And how can i make my custom cell reusable?
Thanks
To make your custom cell reusable, set the identifier propertie in Interface Builder.
Who do you load the data from the internet (Async)?
Since you are using a custom UITableViewCell you have to set the identifier string property through Interface Builder, and then dequeue the cell in the usual way, using that same string as the key. This way your app will not create a new cell for each row of the table, but will reuse the already present cells, reducing build and presentation times.
If this won't fix the issue, you should look at the internet connection, to understand why data loading is so slow. If you own the server that serves the data, you would try to speed it up, otherwise you should look for a different or more efficient way for loading data remotely. Some code example would be great.
Edit
As stated in the comments, the slowness could be related to the loading time of remote images. You could try to build a local dictionary, of something similar, in which you'll save the images you already loaded associating them to their URL as the key, while you'll read remotely those you still don't own. This will work like a local cache to improve loading time for remote data.
The problem was that images have to be loaded asynchronously. Search SDWebImages on Google.

Single View controller with multiple nibs?

I am trying to construct a view controller that can be 'skinned' -- that is, have multiple appearances or personalities but that uses a single controller. Each view will have the same buttons, etc, but I would like to be able to load each nib file (skin) into the same view controller. I can create multiple nib files, but I don't see how to connect the buttons, and actions. Can I specify the same 'file's owner' for multiple nib files? (HOW?).
Can this be done?
This is totally possible. Just create new nib files and in Interface Builder set the file owner to the class. You can then hook up your outlets and actions just like before. From your code just specify the correct nib file in the initWithNibName: method.
If the only changes are cosmetic, you might be better off just making those changes in code, but your proposed method will work just fine.
you can do it much easier if you literally copy and paste the view inside the nib file into the same nib file, so that you have 2 separate views inside 1 nib file.
then you can swap between the views as you load the nib like so:
NSArray *temp = [[NSBundle mainBundle] loadNibNamed:#"Widget" owner:self options:nil];
Widget *w = [temp objectAtIndex:0]]; // or 1 or 2 etc to get the different views
this will copy all your button connections etc, so you can just fiddle around with the copy without having to setup everything again

How do I get a view in Interface Builder to load a custom view in another nib?

I am making a custom widget that I would like to use in multiple nibs. So I make a new view nib Screen3, add some buttons, and now want my UIAwesomeSauce widget.
If I just add a view and then change the Class Identity, it doesn't get the subelements from the UIAwesomeSauce nib. Same thing if I go to the Library and switch to Classes. It seems only a UIViewController has the field for "Load from nib", which would be beautiful.
I know I can load the UIAwesomeSauce nib from code, get the top level objects, and place it by hand. But the point of IB is so you don't have to place things in code. Even better would be if I could get UIAwesomeSauce to show up in the Library list itself.
SOLVED - by Nimrod - READ ON FOR EXPLANATION AND CODE
Anyway, dood, that is great. I can make my own widget classes now for goofy Awesome stuff. Make the FileOwner of your UI nib your class, and in it just have a normal UIView with all your stuff. (The single view in the widget's nib can't be the class itself, or you get recursive in initWithCoder.) Then in the nib you want to use it, make a vanilla UIView and change its class. You won't be able to actually see the widget inside that square, but deal.
Self is now a blank view, and tMyActualSelf is the single view that you did the work in in the other nib. Yay!
- (id)initWithCoder:(NSCoder*)coder
{
if ((self = [super initWithCoder:coder]))
{
UIView *tMyActualSelf = nil;
// Initialization code
NSArray *tNibItems = [[NSBundle mainBundle] loadNibNamed:#"UIAwesomeSauce" owner:self options:nil];
for (id object in tNibItems)
{
if ([object isKindOfClass:[UIView class]])
tMyActualSelf = (UIView *)[object retain];
}
if( tMyActualSelf )
{
tMyActualSelf.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
[self addSubview:tMyActualSelf];
}
}
return self;
}
I feel your pain when it comes to the UIViewController being the only thing you can specify a nib load with, but I think it's because there has to be a File's Owner that's a controller, or something like that, though I'm not sure why a UIView can't be a File's Owner.
I think you can put things in the library under Custom Objects but I think you'd be limited to putting a UIView in there with the class just set to a custom subclass of UIView. I actually looked into trying to extend interface builder and it looks like you can do it for OS X stuff, but not for iPhone stuff. It involves compiling code for the new widget in interface builder to tell it how to draw the widget just within IB. I think there's actually an OS X template for this.
I think what I'd probably do is create a main view in a nib file that contains all the subviews, then in initWithCoder: in the UIAwesomeSauce thing open the nib file, load the main view, and just place it inside self so that you have an "unnecessary" view between the UIAwesomeSauce view and the subviews (or sub subviews as the case would be.) I'm not sure how inefficient that is, but in messing with some stuff it looks like some views like UIWebView seem to have one of these seemingly unnecssary intermediate views (IIRC).
If you come up with a better solution please tell me because I'd like to know too.
EDIT
Now that I look at it, I think the UIView CAN be the file's owner, you just can't set it up in IB. Basically you'd call the nib load something like this in the UIView subclass:
[bundle loadNibNamed:#"UISpecialSauce" owner:self options:...]
I think if you create outlets in UISpecialSauce or whatever then they should be assigned correctly because of the owner:self.
Theres a cocoapod that might actually help with that:
https://github.com/mobilejazz/NibWrapper
It provides a wrapper view that loads a child view from another NIB. You can specify the name of that NIB inside interface builder via a runtime attribute.

Is there a pre-iOS 4 replacement or workaround for UINib?

My application features a tableview which contains some rather complex tableviewcells. Therefore, these cells have been designed in Interface Builder and are instanciated later as needed using UINib which allows just that - load the content from a nib and instanciate it as needed.
But UINib is only available for iOS 4.0 and above.
Before I go ahead and abandon all the 3.x users, is there a way to (easily) recreate what I'm doing using pre-iOS4 classes and methods?
Thanks alot!!
You can load views from a nib using the following:
NSArray* nibContents = [[NSBundle mainBundle] loadNibNamed:#"MyNibFilename" owner:self options:nil];
UITableViewCell* cell = (UITableViewCell*)[nibContents objectAtIndex:0];
Note that the index you supply to objectAtIndex: is determined by the order of the views in the nib. If the only thing in the nib is your custom table view cell, then 0 is likely the correct index.
EDIT:
It is a documented UIKit addition to NSBundle.

iPhone Having Two Views associated with ONE view controller

Blarg!
I'm trying to develop a game with zelda-like qualities. (i.e. When the PC hits the edge of the screen, the screen changes to the next view, and the PC's position restarts at the appropriate edge.)
The problem I'm having is that I don't want to have multiple View Controllers for each level-segment, because all of the data/functionality exists in the original "LevelView" controller. How do I retain the code from this "LevelView" controller, while only switching NIB files? (i.e. The only classes that I want to be there are the AppDelegate, "LevelView", "ItemView", etc.) I don't want to have to re-create a view controller for each NIB file in the game.
I appreciate any help you can offer! Thank you very much! :D
Create a NIB file that has just the new view in it. Change the class of the File's Owner in the NIB file to the class of the LevelView controller. When you want to load a new NIB file, call the following code from the LevelView controller:
NSArray *topLevelObjects = [[[NSBundle mainBundle] loadNibNamed:#"Put your nib file name here" owner:self options:nil];
UIView *newLevelView = [[topLevelObjects objectAtIndex:0] retain];
What is this doing? When you call loadNibNamed, it returns to you an array of the top-level objects in the NIB file. The "File's Owner" and "First Responder" proxy objects don't count as top-level objects. So, you'll get an array with just the top-level view in the NIB file. You pass self as the file's owner so that any outlet connections you make between the subviews of the top-level view and the "File's Owner" in interface builder get connected.
If you connect the top-level view to an outlet in your view controller, you don't need to do anything with the array loadNibNamed returned. Just ignore it and it'll get released automatically (it gets returned to you with a retain count of 0).
Now, I think doing things this way is a bad idea. I think there are better ways to design your app. For example, it probably makes more sense to create a data file (a property list or XML file for example) that describes the levels than to put all of the levels into NIB files. But, if you really want to do this, the stuff above should get you started.
Use NSUserDefaults and get it from there.