Saturday, August 27, 2011

Changing the root controller - part 4!



Well, I've delayed the inevitable as long as I can. Today, we'll create the view needed which will eventually become the root controller, opening the question display when a button is pushed.

However, first, as recommended by apple, let's create the controller and its interface, or header file. We'll follow along the same steps discussed in the "HelloWord" tutorial we saw yesterday.

I doesn't help in this case - the file was already created when they created the project. Well, hmm...let's google. I know it sounds elementary, but I wonder is there's a way we can get it to create the nib for us.

Let's check out this link:

http://developer.apple.com/library/ios/#documentation/iphone/conceptual/iPhone101/Articles/03_AddingViewController.html

Adding a View Controller

In this application you’ll need two classes.

Xcode’s application template provided an application delegate class and an instance is created in the nib file.


An instance of the app delegate is created in the nib? So I still don't understand how the kickoff loop works.

You need to implement a view controller class and create an instance of it.


I believe this is what we need to do, since we're replacing the old view controller, no?

Adding a View Controller Class

View controller objects play a central role in most iOS applications. As the name implies, they’re responsible for managing a view, but on iOS they also help with navigation and memory management. You’re not going to use the latter features here, but it’s important to be aware of them for future development. UIKit provides a special class—UIViewController—that encapsulates most of the default behavior you want from a view controller. You have to create a subclass to customize the behavior for your application.


Ah, I see. Let's check question controller; Yes, it subclasses UIViewController. Then again so does the AnswerController. Good. It looks like this may be something pretty standard for anything with a view.

Let's continue.

To add a custom view controller class . . .

In Xcode, in the project organizer select either the project (HelloWorld at the top of the Groups and Files list) or the HelloWorld group folder.

The new files will be added to the current selection.

Choose File > New File and in the New File window.

Select the Cocoa Touch Classes group, then select UIViewController subclass.



Great! This is exactly what I was looking for.


Click Next.

Using the combo box, choose to create a subclass of UIViewController.

Using the checkboxes, make sure that Targeted for iPad is not selected, and that With XIB for user interface is selected.

Click Next.


Make sure the Add to targets check box is selected.

Give the file a new name such as MyViewController.

By convention, class names begin with a capital letter.

Click Save.

Make sure that the files were added to your project.



Good, although I was hoping the nib file would somehow get created. Actually - it was - just at the wrong level, along thing the .m and .h. files. Let's move them to where they belong.


Adding a View Controller Property

You want to make sure that the view controller lasts for the lifetime of the application, so it makes sense to add it as a property of the application delegate (which will also last for the lifetime of the application). (To understand why, consult Memory Management Programming Guide.)

Perform the following tasks in the header file for the application delegate class (HelloWorldAppDelegate.h).
bullet


In my case it's QuizAppDelegate, but ok.

To add a view controller property . . .

Add a forward declaration for the MyViewController class.

Before the interface declaration for HelloWorldAppDelegate, add:

@class MyViewController;

The property will be an instance of the MyViewController class. The compiler will generate an error, though, if you declare the variable but you don’t tell it about the MyViewController class.


Ok, so it's as if we declared a member variable in Java - but it's not defined anywhere. That would be compiler error.

You could import the header file, but typically in Cocoa you instead provide a forward declaration—a promise to the compiler that MyViewController will be defined somewhere else and that it needn’t waste time checking for it now.


It's saying - don't worry, let it go. We'll define it for you later.

(
Doing this also avoids circularities if two classes need to refer to each other and would otherwise include each other’s header files.)


If A needs B's header and vice versa, that's a problem. This avoids it by not parsing through the declaration of B.

Later, you will import the header file itself in the implementation file.


Ah. Use in the implementation, where it's really needed. Not in the above complicated scenarios.

Add a declaration for the view controller property. After the closing brace but before @end, add:
@property (nonatomic, retain) MyViewController *myViewController;


Hmmm. I have a couple of problems. First, there is a little tiny red mark next to my property name in the synthesize. And also, some kind of warning about needing a method.

At this point, I'm going to vary from the script an just start substituting (actually adding alongside) the code from QuestionController for MyViewController

That means, under the interface section in the header, adding a declaration for the new class:

interface QuizAppDelegate : NSObject {
UIWindow *window;
QuestionController *questionViewController;
UINavigationController *navController;
Question *currentQuestion;
AppState *appState;
MyViewController *myViewController;
}


This above isn't mentioned in the tutorial - I dont' know if I'm doing something different the requires this, or what.

Continuing:

Properties are described in the “Declared Properties” chapter in The Objective-C Programming Language. Basically, though, this declaration specifies that an instance of HelloWorldAppDelegate has a property that you can access using the getter and setter methods myViewController and setMyViewController: respectively, and that the instance retains the property (retaining is discussed in more detail later).

To make sure you’re on track, confirm that your HelloWorldAppDelegate class interface file (HelloWorldAppDelegate.h) looks like this (comments are not shown):

#import

@class MyViewController;

@interface HelloWorldAppDelegate : NSObject {

}

@property (nonatomic, retain) IBOutlet UIWindow *window;

@property (nonatomic, retain) MyViewController *myViewController;




Ok - I have the same property declaration, and I have the forward class declaration.

The only thing different is I also have this:

@interface QuizAppDelegate : NSObject {
UIWindow *window;
QuestionController *questionViewController;
UINavigationController *navController;
Question *currentQuestion;
AppState *appState;
MyViewController *myViewController;
}

Hmmm...maybe I should rename this app so I can refer to a copy of my old app. I have a backup, but if I want to experiment with the backup, I should give this or that a new name. But, I dunno - I'm so unfamiliar with IOS, and I had trouble with a rename earlier.

Ok, let's continue - we can always backup the backup and muck around with that.



You can now create an instance of the view controller.
Creating the View Controller Instance

Now that you’ve added the view controller property to the application delegate, you need to actually create an instance of the view controller and set it as the value for the property. To make code-completion work for your new class in Xcode, you also need to import the relevant header file for the view controller.

Perform the following tasks in the implementation file for the application delegate class (HelloWorldAppDelegate.m).
bullet

To import the view controller’s header file . . .

At the top of file, add:

#import "MyViewController.h"


Ok - that's added. It somehow will help code completion work.

Let's keep going.

To add a view controller instance . . .

Add the following code as the first statements in the implementation of the application:didFinishLaunchingWithOptions: method:

MyViewController *aViewController = [[MyViewController alloc]

initWithNibName:@"MyViewController" bundle:nil];

[self setMyViewController:aViewController];

[aViewController release];


And keep going:


Setting Up the View

View controllers are responsible for configuring and managing each “screenful” you see in an application. Each screenful should be managed by a view controller. To codify this principle, a window has a root view controller—the view controller responsible for configuring the view first displayed when the window appears. To get your view controller’s content into the window, you set your view controller to be the window’s root view controller.
bullet



This is good info. The AppDelegate has a window. It also has a root view controller - the view controller responsible for configuring the view displayed when the first window appears. If you want *your* view controller's "screenful" to be seen first, you need to set it to be the windows root view controller.

Continuing:

To set your view controller as the window’s root view controller . . .

Add the following line of code just before the call to makeKeyAndVisible.

self.window.rootViewController = self.myViewController;

Instead of using the dot syntax, you could also use square brackets:

[[self window] setRootViewController:[self myViewController]];

Both lines of code would compile to the same thing; again you can choose which you feel is more readable. A further important point about this line, though, is that it uses the accessor method to retrieve myViewController.


Ok, now, this is a bit of a problem. We are right at the heart of the thorniest part of replacing the root controller.

Here's what the old code with the new code added in below it looks like:


// old code

navController.viewControllers = [NSArray arrayWithObject:questionViewController];

[window addSubview:navController.view];

[self.window makeKeyAndVisible];


// new code

MyViewController *aViewController = [[MyViewController alloc]

initWithNibName:@"MyViewController" bundle:nil];

[self setMyViewController:aViewController];

[aViewController release];

self.window.rootViewController = self.myViewController;

[self.window makeKeyAndVisible];



What a mess! I was thinking I could sort of just inherit the navigation logic once I added the StartController and delegated to that. But I'm really thinking that we have to keep the nav controller at this level, in the app delegate. There was some code in QuestionController that pulled in the nav from app delegate to bring up the answer activity. It's in the main nib.

So, in this case, I think the key must be how to properly negotiate this new view into the nav controller. I don't think we should be setting the root controller of this window to anything else. Btw, is the current code setting the current rootViewController anywhere, I mean, before the logic we just added?

I guess not. But, they all may be magically hooked together in the interface builder.

Right. If I look at "MainWindow.xlb", underneath "Objects", it has an app delegate, a "QuestionController", "Window" - and Navigation Controller.

Ok - what will happen if I just replace it in code?

Sigabort. So, it is probably somehow geting connected in the nib.

Ok, we can add a ViewController object to the objects under the Nav. Then, using property inspector, match it up with the added view. Also, we need to make it an IBOutput. Ok, now we can drag line, and we have the connection! Nice. Run it...yes!

Just in time for dinner.

Here's a look at the new display:



In the next post, we'll tackle have this new view call the QuestionView.

No comments:

Post a Comment