View-based iOS application template - iphone

I've read Apple's "Your first iOS application" guide and everything there seems crystal clear to me. However, when I try to understand how View-based iOS application template provided in XCode works, I run into some interesting conundrums.
I understand that the application gets the main nib file name (usually, the MainWindow.xib) form the *-Info.plist file. What I do not understand is, how does the XCode know which nib file is associated with the controller that is created with this View-based application template by default. In the guide, you start with the Window-based application, and you "have to write" something like:
MyViewController *aViewController = [[MyViewController alloc]
initWithNibName:#"MyViewController" bundle:[NSBundle mainBundle]];
[self setMyViewController:aViewController];
which makes perfect sense. However, it turns out that in the View-based iOS application template there is no such thing, and that this nib specification was not actually needed in the first place, as long as you created your UIViewController subclass with option "With XIB for user interface" checked. My question is, how does XCode know which nib is associated with this controller, i.e. is it storing this connection in some of the files, or maybe by some sort of convention (same name for controller and nib file, perhaps)? Moreover, where does that 'Loaded from "MyViewBasedAppController"' subtitle come from in Interface builder's view of controller within MainWindow.xib? It's definitely not there when I add the controller by hand, so I'm curious to what magic does XCode do behind my back, when I think I'm just selecting a simple code template.

If you look in the target info (double click on the target to bring that up), 'Properties' tab you'll see the name of the main Nib file. The words 'Nib' and 'Xib' are interchangeable for these purposes; Xib is just a newer alternative for Nib.
It'll be 'MainWindow' fresh from the template. If you open MainWindow.xib you'll see that in there is an object called '[project name] App Delegate', and if you show the inspector and check under the 'i' tab you'll see the type of class that is named at the top. If you check the connections tab (the right facing arrow), you'll see that the File Owner (which is the UIApplication itself) has its 'delegate' property attached.
You'll also see that it has an outlet called 'viewController'. That's attached to another object in the xib called '[project name] View Controller'. Check the type on that and you'll see it's the type of view controller that Xcode has added to your project. Looking at its attributes (the first tab in the inspector, with the slider graphic), you'll also see that a separate nib file is specified as containing its main details.
For argument's sake, suppose I called my project 'NibTest' and made no changes.
At runtime, the device loads Info.plist. In there it sees that the delegate is of type NibTestAppDelegate. So it'll instantiate an instance of the class NibTestAppDelegate and set the UIApplication's delegate property to it.
It'll then see from MainWindow.nib that NibTestAppDelegate has a member named viewController of type NibTestViewController. So it'll create an instance of that and set the viewController property on the NibTestAppDelegate instance it just created to it.
In doing that it'll open the other xib and continue doing the same sort of steps.
Objective-C has a fully reflective runtime, so you can instantiate objects by their class name at runtime. This is one of the differences between Objective-C and C++, for example.
Xcode isn't generating any hidden code or relying on any hidden naming conventions. The whole thing is figured out at runtime by the OS.
EDIT: for example, in place of your example:
MyViewController *aViewController = [[MyViewController alloc]
initWithNibName:#"MyViewController" bundle:[NSBundle mainBundle]];
You could actually do:
MyViewController *aViewController = [[NSClassFromString(#"MyViewController") alloc]
initWithNibName:#"MyViewController" bundle:[NSBundle mainBundle]];
They'll operate identically as long as MyViewController exists in the program or in the wider runtime.
You could alternatively pass any other string object you like to NSClassFromString. Even ask the user for it if you want (though it'd be a really bad idea for security reasons).

Related

What are the possibilities of getting NULL IBOutlets while loading a view.. IOS

I have been working on IOS application. Before I had single project in which all source code was there application loaded properly in that setup, now I had split that into multiple projects. After that I am facing now a problem... in ViewDidLoad, IBOutlet for buttons all are coming nil values, view also loading black colored. I am not able to guess what was the problem. Any idea about what could cause this...
I have loaded my view like this...
main =[[main_page_controller alloc] init];
if (main != NULL)
[root.navigationController pushViewController:main animated:YES];
I am not sure which part of the code do I need to post here, to make the question more understandable... Please share your suggestions..
Edit: I ran my old project and then tried with my new set up application launching successfully. I removed the application from device, and loaded using new set up only, problem again shows up. So what was there in old set up? What am I missing in new... ????
Refer to the documentation
To initialize your view controller object using a nib, you use the initWithNibName:bundle: method to specify the nib file used by the view controller. Then, when the view controller needs to load its views, it automatically creates and configures the views using the information stored in the nib file.
When initialising a view controller, and you're using a .xib file for the view, you need to call initWithNibName:bundle:. This means it'll use the xib file to create the view within loadView. At the moment, you're just using init, that will create a blank UIViewController object.
So in this case, your code would be (assuming the .xib is called "MainViewControllerView.xib" within the main bundle):
main =[[main_page_controller alloc] initWithNibName:#"MainViewControllerView" bundle:nil];
if (main) {
[root.navigationController pushViewController:main animated:YES];
}
Also sanity check your .xib file to see if all the IBOutlets are connected to what you want.
First check all your connection in xib(nib) file, if its already connected then just disconnect them , clean project (cmd+k) and then connect connection again.
take a look on this image for connection

Two UIViewController, one XIB File

Let's say, I have three UIViewController
UserFormViewContoller
NewUserFormViewController : UserFormViewController
UpdateUserFormViewController : UserFormViewController
So, NewUserFormViewController and UpdateUserFormViewController view controller inherit from it's parent to share the basic functionality. The different will be their method, create and update.
The views also have a lot of things in common, almost everything. The different view components between NewUserFormViewController and UpdateUserFormViewController is a button to perform save task (create or update)
Is it possible to have two UIViewController sharing one XIB file? Let's say, UserFormViewController.xib and then I do
[[NewUserFormViewController alloc] initWithNibName#"UserFormViewController" bundle:nil];
[[UpdateUserFormViewController alloc] initWithNibName#"UserFormViewController" bundle:nil];
The other question but important is, when I edit xib file with Interface Builder, what owner's reference outlets and IBActions is it talking about, NewUserFormViewController or UpdateUserFormViewController?
(IBActions and Outlets showing when we right click at the Placeholders -> File's Owner)
If that's so, I will just use one XIB file and programmatically add other specific view component (It would be great to have only one XIB file so that I can make some changes at a place but effective on both)
The "file owner" is just a convention so that XCode can show you the correct IBOutlets and IBActions in its inspectors. If you create a generic (in OO terms: abstract) UserFormViewController (.h, .m, .xib), wire it in IB; then subclass it in two NewUserFormViewController and UpdateUserFormViewController, they'll inherit their outlets and actions from their parent class without any problem.
#Armaan I met the same problem by simply call subclass' alloc init method. I fixed this problem by using initWithNibName: and supply xib file name of parent class.

'NSInternalInconsistencyException', reason: '-[UIViewController _loadViewFromNibNamed:bundle:] loaded the GameView nib but the view outlet was not set

This is not the same situation as the multitude of other similar questions here.
* Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[UIViewController _loadViewFromNibNamed:bundle:] loaded the GameView nib but the view outlet was not set.'
You might be thinking "do as it says, connect the File's Owner to the View in IB!". But the thing is, I don't even HAVE a GameView.xib in my project or even in the project directory.
I do have a "GameViewController.m" and matching "GameViewController.xib" in my project. Using that GameViewController is what brings up this error, but I don't understand where it gets the idea to try and load "GameView.xib". Shouldn't it use "GameViewController.xib" instead?
If I grep my project directory, I do see it referenced from "UserInterfaceState.xcuserstate".
<string>file://localhost/Users/bemmu/Dropbox/b2/iphone/ValleyStory/ValleyStory/GameView.xib</string>
This mentioned file does not exist. I might have had a file with that name before and renamed/deleted it, but it's not being referenced to from anywhere that I can see in IB.
Did I manage to confuse xcode?
My solution was a little different.
Click on the xib in interface builder
Select File's Owner on the left
Open the File's Owner's connections inspector
If the view property isn't yet wired, control-drag it to the view icon (under the file's owner and first responder icons).
Check any nib files you're using (like MainWindow.xib). If you are loading GameViewController from a nib, check the file it's loading from (under the info tab in the inspector). Make sure it's set to "GameViewController" and not "GameView".
I had this issue as well, but had to solve it a different way. Basically, I have a view controller name MainViewController, which has a xib named MainViewController.xib. This nib has it's view property set to the File Owner which was MainViewController.
I also made a MainView.xib that contained a view that was going to be programmatically added to the view defined in MainViewController.xib and it's view. It basically encapsulated an internal view that would be in the MainViewController.xib's view, and also had it's File Owner set to MainViewController.
So basically, I wanted MainViewController.xib to load as the nib for the MainViewController object, and inside MainViewController, at some later point, I would add the internal view specified by MainView.xib.
A couple issues arose:
1.) I found in the Apple docs that when loading a view controller via storyboard or nib:
"If the view controller class name ends with the word “Controller”, as
in MyViewController, it looks for a nib file whose name matches the
class name without the word “Controller”, as in MyView.nib.
It looks for a nib file whose name matches the name of the view
controller class. For example, if the class name is MyViewController,
it looks for a MyViewController.nib file."
Therefore, you cannot have a nib called MainView.xib if you also have a nib called MainViewController and want MainViewController.xib to be the primary nib for MainViewController.
2.) Even if you delete MainView.xib or rename it to something else (MainInternalView.xib in this case), you MUST delete / clean your iOS simulator as the old nib file (MainView.xib) will still remain in the application. It doesn't overwrite the whole application package when you rebuild / rerun your application.
If you don't want to reset your content settings (perhaps you have some data you want to preserve), then right-click on your application in your iOS Simulator folder, Show Package Contents, find MainView.nib, and delete it. Xcode will NOT do this automatically for you when you rebuild, so we need to manually remove the old nib.
Overall, don't make nibs named MainViewController and MainView, i.e. nibs with the same prefix. Call MainView.xib something else, like MainInternalView.xib.
I recently solved this issue. Make sure you back up your project before following the steps given here (just in case). These steps solved my issue
Quit Xcode
Navigate to UserInterfaceState.xcuserstate located at .xcodeproj/project.xcworkspace/xcuserdata/<username>.xcuserdata and delete the file.
Reopen Xcode. Xcode will create a new UserInterfaceState.xcuserstate which will be clean.
In my case this error was produced by dumb mistake - I delete _view view
In my case, I was not using a xib at all. I needed remove the .m file from Build Phases > Compile Sources and added it back.
Given you referenced it previously it sounds like xcode hasn't ackowledged it no longer exists. From the Product menu select "Clean" and then "Build" hopefully this will get past the old reference for you.
Face the same Problem, had to change the view's name in code:
MyViewController *controller = [[MyViewController alloc] initWithNibName:#"WrongViewName" bundle:nil];
To
MyViewController *controller = [[MyViewController alloc] initWithNibName:#"RightViewName" bundle:nil];
I had multiple views, and by accident (I don't know how this happenned) but my background view didn't have a file owner, so for anyone else who has this problem in the future, make sure all your views have a file owner.
I was gettint the same error then check the classname from interface builder and see that I typed the view controller class name at the custom class attribute.
UIViewController searches for a nib with the same name as the controller when passed nil to initWithNibNamed:bundle: Check that the file name that you pass to the initializer is correct and exists!
For example:(e.g. [[CCVisitorsController alloc] initWithNibName:nil bundle:nil] then UIViewController tries to load nib with name CCVisitorsController as default.
If that file does not exist then the error you mentioned is thrown.
I had this problem because I was doing something bad in
(id) initWithCoder:(NSCoder *) coder
which the NIB loads.

Is it possible to create different .xib files using a single class iphone sdk

In my application,I need to load different .xib in different tableView cells depending upon the category of data which I'm getting from parser. I wanted to ask that is it possible to create different .xibs belonging to same class as it'll reduce the load as I have almost 13 categories so 13 .xib files.
Thanks in advance.
#"I wanted to ask that is it possible to create different .xibs belonging to same class as it'll reduce the load as I have almost 13 categories so 13 .xib files."
The xib files are not a burden on memory unless they are loaded, in which case, the file's owner object is created. So keeping this in mind, it doesnt matter how many nibs you have for your class, for an object of each viewController class, the corresponding xib is loaded. So ultimately you have to put in a check condition as stated by RaYell, it would be better to introduce that check where you spawn the viewController object instead checking the condition for loading appropriate xib.
Dont bother about creating 13 viewControllers, you will find it easier to make changes in your project later if there are changes in requirements. You will appreciate this approach.
If you create only one UIViewController sub-class and load one of 13 xib's based on some condition, say, there comes a requirement that you add a button / label / textField in the 13th xib ONLY and need its reference in your viewController class. How would you achieve it, you maintain an IBOutlet in the common viewController class and introduce the if-else check to see if it is the 13th category. The code becomes untidy with lots of if else conditions.
If you mean that you'd want to have several NIBs for the same view controller then it's most certainly possible. In fact that's how application localization is done. You can then load the specific NIB when you initialize your controller.
NSString *nibName = #"DefaultNibName";
if (someCondition) {
nibName = #"SomeOtherNib";
}
YourViewController *controller = [[YourViewController alloc]
initWithNibName:nibName bundle:nil];
[self.navigationController pushViewController:controller animated:YES];
[controller release];
But how will you make connections(outlets) which will be different in different .xib files?
will you keep lot of outlets and actions in a single controller? If so, then think accidenlty you try to access the outlet which is suppose to be of some other nib. Then what will happen?
If you try to do so then you view controller will look like a garbage. So please dont try to use only one controller for loading more than one .xib files.

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)