iPhone Having Two Views associated with ONE view controller - iphone

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.

Related

Put a UIView into a UITableView Header

I have a UITableView which is in my xib file. And I created a property like this for the controller:
#property (nonatomic, retain) IBOutlet UITableView *myTableView;
Now, I want to have a table view header (not a header for each section). So, because I want to have custom styling, I created a new xib file with a view (and I connected to my controller which has the myTableView implemented).
Then I can write in viewDidLoad in my controller:
[self.myTableView setTableHeaderView:self.myTableHeaderView];
where myTableViewHeader is a UIView property in the controller.
Unfortunately, the UITableView won't display this UIView, so my question is, how can I put a UIView to a UITableView into the header?
Thank you in advance & Best Regards.
A couple of things to check:
myTableHeaderView must also be an IBOutlet or created in code in your Class somewhere
self.myTableHeaderView must not be nil when you're trying to add it as the table header view. If it is nil then you didn't hook up your outlets correctly
if you've designed the table view header in IB in its own .xib file, then you must somewhere call this (viewDidLoad is a good place):
[[NSBundle mainBundle] loadNibNamed:#"MyTableViewHeader" owner:self options:nil];
The File's Owner of the MyTableViewHeader.xib must be your TableViewController subclass, and you must hook up the myTableHeaderView object to File's Owner's corresponding outlet.
EDIT: in answer to "what does [[NSBundle mainBundle] loadNibNamed:#"MyTableViewHeader" owner:self options:nil];" do?
This one little line contains the magic that will open your eyes to how XIB files and Objective-C objects work together in Cocoa (touch), elevating your iOS programming kung-fu to entirely new levels. There are two classes of Cocoa programmers, those who understand what it does and use and benefit from it, and those who don't yet know what they're missing, and instead stumble through the wilderness trying to create XIB files for their objects and never quite getting it to work.
With that massive build-up here's the details:
A XIB (NIB) file is a collection of archived Objective-C objects (and reference to objects not actually within the XIB, like "File's Owner", a so-called "proxy object") and connections between these objects. When a XIB file is loaded, these archived objects are brought to life in exactly the state they were saved into the XIB, and then the connections between those live objects (and "proxy objects") are made according to the connections recorded in the XIB file.
For example, in your standard UIViewController subclass .xib file, you have File's Owner set to your MyViewController class. Inside the .xib is a UIView object, which usually itself contains other UIKit objects. The "view" outlet of the UIViewController class is set to point to the UIView object in the .xib. When this .xib file is loaded, the UIView object is unarchived and becomes a living UIView object, with all of the properties and settings recorded in the .xib. That's the "unarchiving part".
And then, because of the connection in the .xib from File's Owner (MyViewController class) to the UIView object, the pointer to this new UIView object is stored in the "view" field of your MyViewController instance. Any other connections also present in the .xib file, like UILabels, UIButton actions, etc., are also set up, to any other "IBOutlet" fields in MyViewController.
This all seems like magic and happens with the
(id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)nibBundle
method when you init a new UIViewController subclass.
Now the good part: you get to do this sort of associating nib files with objects yourself! You can use the loadNibNamed:owner:options method to associate any .xib file with matching set of nil IBOutlets in any object that you want!!!
All of a sudden, creating entirely custom table view cells, table headers, footers, whatever, is a breeze, you can easily write modular reusable UIView components, etc., all laid out in Interface Builder.
The object whose nil IBOutlets you want to fill in with objects loaded from a .xib file is the "owner" object. Usually (but I'm not sure this is absolutely required, any class with the identically typed and named IBOutlets set to File's Owner may work), this is the class that will be specified as "File's Owner" in the xib.
OK, now you've got your existing owner object with nil IBOutlets (the IBOutlets must be nil or they won't be changed, that's the "rule" of loading XIBs into an owner object. It's ok that some IBOutlets are not nil, they just won't be changed when you load the XIB file, and usually that's what you want), and you've got your .xib file with objects that you want to load into the owner object. You call:
[[NSBundle mainBundle] loadNibNamed:#"MyXIBFileToLoad" owner:theOwner options:nil];
.. and voila! Now any nil IBOutlets in "theOwner" that are connected to objects in the MyXIBFileToLoad.xib have been set to the new objects loaded from the XIB file.
(also, this method returns an array of all objects unarchived from the xib. If you don't care about setting any owner's outlets, you can just search this list for your objects by class and by tag).
So that's the story, now go crazy with new ways to associate Objective-C classes with objects stored in XIB files!

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

Multiple XIBs same File's Owner

I am trying to create a two view, single controller application as follows: I have two XIB's. Each with the same File's Owner.
As a test, I have placed a UILabel on each XIB. I have connected the File Owner to the UILabel in each XIB. The outlet property is the same.
When I instantiate the nib using loadNibNamed I also set the 'owner' to the instance of File's Owner, e.g.:
nib=[[NSBundle mainBundle] loadNibNamed:#"ONE" owner:OWNER options:nil];
nib=[[NSBundle mainBundle] loadNibNamed:#"TWO" owner:OWNER options:nil];
Now, in OWNER, if I call
[myLabel setText:#"Hello World"];
I see the label update only in nib TWO.
If I create additional UILabels that are unique to each NIB then I can properly update and view them. It seems that I can only have one connection from the property on File's Owner to each NIB.
Any ideas?
What you want is an IBOutletCollection. That allows you to assign a property to more than one nib element, and talk about the entire group all at once.
an IBOutlet can only point to one object. You will need two of every IBOutlet you want to use.
This is very old and not much viewed, but I can't help but notice why this doesn't work. You're passing in the same instance of the owner. Make two instances of the file's owner and you can have two different label values. There's no reason you couldn't assign all of your outlets in your whole project to one Object class in fact, although you probably wouldn't want to do this. Another thing to think about is whether you shouldn't be using inheritance here, by making a superclass, connecting all of the common outlets to that class and then a subclass with unique outlets. Indeed, since you will either have to distinguish your nibs by calling them by nib identifier or by a the class associated with them I think it's better practice to associate separate classes and use inheritance to cover the overlap between them.

How can I Create a Nib with an associated View Class that can be used by Multiple ViewControllers

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.

Can you swap the NIB file for a UIViewController that's already on-screen?

For instance:
Create a new UIVC using initWithNibName, using "nib-v1"
Display it, e.g. using [(UINavigationController) nav pushViewController: myVC]
Change the NIB that myVC is using to "nib-v2"
So far as I can see, this is the "correct" approach to app-design for a lot of apps, when paging through information where you need two slightly different UI screens for the info being displayed.
For instance, most of your pages are text, but some of them have an image too (think of an RSS reader, where some RSS entries have text + image, some are text only).
I've dealt with this previously by having one NIB file with a second, invisible, named UIView instance that I layered over the top of the first one, and switched on/off depending on on context, using the "hidden" flag.
But this is clearly wrong, and wastes memory.
However, I cannot see an obvious way to "reload" the view from the NIB file. I'm guessing I want to somehow reproduce the magic that initWithNibName does?
I suspect this is possible, but I'm sure that if you do it "the wrong way" then the app will simply crash horribly.
You can always perform
[[NSBundle mainBundle] loadNibNamed:#"FileName" owner:viewController options:nil]];
but this will undoubtedly really mess things up, if you're not sure what you're doing, especially if view is connected in both of those nibs
You should redesign your view controller hierarchy to swap between two different controllers that load from two different nib files.
Alternately, you can have the controller manage swapping views that it loads from different files that are unrelated to it's nibName. In that case, you can load them in the above manner. And you will want to have their outlets (to, for example, subviewOne and subviewTwo) connected in different nibs.
You should check out the UINib class to see if it does what you want. It will allow you to load a nib file and keep it in memory.
But just to clarify: Do you want to modify the nib file itself? Or do you want to modify the contents of the nib file when it has been loaded into memory?
Off the top of my head, the first would be quite difficult (you can't modify the original file, since it is part of application bundle...maybe you copy it to Documents folder and write your own coder/decoder?) The second is easier, but I am not sure what the reason would be? Why not just modify the viewController/view after it has been loaded (in awakeFromNib, for example) and, if you want those changes to persist, save those changes to file afterwards.
In short, I don't know exactly what you would like to do, but the chances seem high to me there might be a better way to do it.
I agree with Rob, but if you really want to mess with swapping nib's (which is bad as it can easily lead to dangling pointers and the like), you could maybe load the view from the new nib with NSBundle's - (NSArray *)loadNibNamed:(NSString *)name owner:(id)owner options:(NSDictionary *)options method and do the view swapping yourself.
You should rather use different view controllers for different types of content. If they only differ slightly, you can still consider creating one base class and subclassing the different variations.
You shouldn't change what NIB file a UIViewController uses. Attaching the NIB to the UIViewController should be a one-time event. The hidden views are fine; they're certainly not clearly wrong. You can also programmatically add view elements after loading. If you're doing a lot of that, you can skip the NIB entirely and programmatically build the view in -loadView. Any of these are fine, but don't switch the NIB after initialization. I don't even recommend having multiple NIBs that you choose between for a given UIViewController class; it's just too confusing. Generally each NIB should map to a dedicated UIViewController class with a very similar (or identical) name.
In a related note, I recommend moving the name of the NIB into the UIViewController, as described in an earlier posting.
I came looking for an answer to the same problem, and ended up solving it like this:
UIViewController* ctrler = [[UIViewController alloc] initWithNibName:#"NewControllerNIB" bundle:nil];
// Replace previous controller with the new one
UINavigationController* nav = self.navigationController;
[nav popViewControllerAnimated:NO];
[nav pushViewController:ctrler animated:NO];
I'm not sure if it's possible the current controller gets deallocated before the call to push the new controller is executed, but so far it seems to work (until I just redesign the app with a better approach)