Making Your Classes Talk to Each Other, Part 1

This is the first part of a three-part tutorial for beginning iOS programmers on how to make classes, especially view controllers, communicate with each other. See also Part 2 and Part 3.

One question that I see a lot on developer forums is: “How do I use a variable from one class in another?” Or related: “How does my class call a method from another class?” Or: “How do I send my data from view controller A to view controller B?”

Obviously these are newbie questions, and a typical answer is: go read a book on programming. While I certainly recommend that beginners get a good book on Objective-C and a book on developing for iOS, I may be able to offer some tips on how to structure your programs.

There are several approaches you can use and I’ll highlight the most common ones. I will also point out bad practices that you should avoid in your own apps.

I always put a lot of effort into keeping my code as clean as possible. I will often rewrite large sections of my app when they have become too messy. If you let the source become too complex, then fixing bugs or adding new features will be a nightmare. Knowing a handful of best practices for structuring your code will help you to stay sane and it will also make your apps better.

Hopefully my explanations will be clear enough to understand, even if you’re just getting started developing for the iPhone or iPad. Let me know in the comments how you’re getting along!

Model-View-Controller

Much of UIKit is built around the idea of Model-View-Controller, or MVC for short. You will get the most out of working with UIKit if you split up your code into three different parts:

The models. These classes contain your data and any operations on the data. For example, if you are writing a cookbook app, the model would consist of the recipes. You should be able to take this code and port it to a completely different platform (such as Mac OS X or Android) without too many modifications (although you may need to switch programming languages). Your model code will be dependent on Objective-C and Foundation, but not on UIKit.

The views. These form the visual part of the app: images, buttons, labels, text fields, and so on. A view can draw itself and respond to user input, but it typically does not handle any application logic. Most views can be re-used in many different applications because they are not tied to a specific data model. To show a list of recipes, we could use a standard UITableView. To show a single recipe in more detail, we would also use standard controls: a label for the title of the recipe, an image view for a photo, and a text view for the cooking instructions.

The controllers. Simply put, each screen in your application will have its own controller. The main list of recipes sits in a UITableViewController, which is a subclass of UIViewController that works with a dedicated table view. If you tap on a row in that list, the details screen for that recipe should open. That screen is also a subclass of UIViewController. The controller is what connects your data model to your views. The view controller has a view that shows the contents of the screen, which it loads from your data model.

Conceptually, this is how it fits together:

Model View ControllerThe trick is splitting up our app’s functionality into such models, views and controllers, and making all these different classes talk to each other. That’s exactly what this tutorial is about.

All this talk of recipes makes me hungry. Let’s do something about that!

Smoothies!

To illustrate these concepts, I have made a simple but complete app, named Smoothies. As you can guess from the name, it’s a smoothie recipe app. In its finished state it looks like this:

The final Smoothies app

In this article series we will build the app step-by-step. We will begin with the most basic of features and with every new version we add features and refactor the code to keep it clean and well-organized. By the end of this article you should have a good idea of the different approaches to organizing your classes and what are the pros and cons of each approach. But let’s begin with the very basics.

Version 1: Building the skeleton

This first version of the app will look like this:

Figure 1.1. The very basic first version of our smoothie recipes app.

Figure 1.1. The very basic first version of our smoothie recipes app.

It’s just an empty table view with a title bar on top. The table view sits in a UITableViewController, which in turn belongs to a UINavigationController. This is a standard design for many iPhone apps. I’m sure you will have seen it before.

You can view and download the source code for Version 1 from github.

I used Xcode’s “View-based Application” template to create a new project. Then I reorganized the files a bit because I don’t like where the standard template puts them.

The template makes an application delegate, SmoothiesAppDelegate, and a view controller, SmoothiesViewController. I cleaned up the app delegate a bit and changed SmoothiesViewController from a basic UIViewController into a UITableViewController. We want to display a list of smoothies and a table view is ideal for that.

I also made some changes to the nib files. I removed SmoothiesViewController.xib because we no longer need it now that we’re using a table view controller. In MainWindow.xib I added a navigation controller and put the SmoothiesViewController inside that. The navigation controller provides the title bar at the top of the screen, and we’ll use it later to present a details screen when you tap the name of a recipe.

Figure 1.2. The MainWindow.xib in Interface Builder.

Figure 1.2. The MainWindow.xib in Interface Builder.

The application delegate has a navigationController outlet, which I connected to our UINavigationController (see under “Referencing Outlets” in the right side bar in figure 1.2). When the app starts up, the delegate adds this controller’s view to the window and makes it visible. Because we set the SmoothiesViewController as the root of the navigation controller, it shows up in the window as well.

This should be familiar to you if you’ve done any iOS programming before. Having an app delegate and a view controller are the basics of any app.

Our class structure currently looks like this:

Figure 1.3. We have three classes that are connected together.

Figure 1.3. We have three classes that are connected together.

Notice that nowhere in our code do we use the UINavigationController class directly. It is instantiated for us when MainWindow.xib is loaded during app startup, and we only need it to hold our SmoothiesViewController.

Take a quick peek at the source and build and run the project. It doesn’t do much yet, but we’re off to a good start!

Version 2: Adding a Recipe class

Let’s put some recipes on the screen. First, we need to decide exactly what constitutes a recipe. To keep things simple we’ll just give each recipe three attributes: a name, the preparation instructions, and an image. We’ll show the name and image in the table view on the app’s main screen. To see the entire recipe in detail you’ll have to tap on it, but that is for the next version.

Figure 2.1. What the app will look like at the end of this chapter.

Figure 2.1. What the app will look like at the end of this chapter.

Since a recipe is a separate entity in our app, it makes sense to give it its own class. It shouldn’t surprise you that I named it Recipe. In this case it was easy to name our class, but it isn’t always so. Finding good names for your classes is an important part of writing clear and maintainable code, so don’t skimp on it!

Recipe is a so-called data model class. Its purpose is to describe what a recipe is, nothing more. In a little while we will also need write some logic to display the recipes on the screen, but that logic will not be part of the Recipe class.

This the Model-View-Controller separation that I talked about earlier. It makes sense to organize your classes by the principle that anything that can be considered pure data goes into its own set of classes. Any calculations on that data (often called the “business logic” or “domain logic”) also goes into these classes. In our case, the topic of our app is recipes, so it makes sense to consider recipes to be part of our app’s data model.

The data model classes are separate from the controllers and views. Recall that the views are used to display the items from the data model on the screen. Their function isn’t to store data or perform calculations on it; views just know how to draw stuff. Examples of views are labels, sliders, buttons, and so on. The task of the controller is to connect the two together.

For the moment, we won’t do much with recipes except show them in a table, so the Recipe class will be very simple.

I used Xcode’s New File assistant to create the Recipe class, which extends from NSObject. The assistant created two files, Recipe.h and Recipe.m, and this is what they look like after I added my data model code to them.

Here is Recipe.h, the interface of the Recipe class:

@interface Recipe : NSObject
{
}
 
@property (nonatomic, copy) NSString* name;
@property (nonatomic, copy) NSString* instructions;
@property (nonatomic, retain) UIImage* image;
 
@end

It’s very simple. I subclassed NSObject and added properties for the recipe’s name, instructions and image.

Copy vs retain

Notice that the name and instructions properties are declared with “copy” semantics. This is a good idea for properties of type NSString, NSArray, NSSet, and a few others.

Let me briefly explain. NSString has a subclass NSMutableString, which means you can use an NSMutableString instance anywhere you can use an NSString. The following code is perfectly valid, but it produces different rules if the name property is declared with retain or copy:

NSMutableString* str = [NSMutableString stringWithString:@"Raspberry Smoothie"];
 
Recipe* recipe = [[[Recipe alloc] init] autorelease];
recipe.name = str;
 
NSLog(@"Recipe name is now: %@", recipe.name);
// This outputs "Raspberry Smoothie"
 
[str appendString:@" with Chocolate"];
 
NSLog(@"Recipe name is now: %@", recipe.name);
// This should still output "Raspberry Smoothie"
// and not "Raspberry Smoothie with Chocolate"

In this example, we assign the NSMutableString object to recipe’s name parameter. No problem here, but then we change that mutable string object. If name was declared as retain instead of copy, then the Recipe‘s name would be changed out from under it. Since you usually do not want that to happen, specify copy for any property whose type also has a mutable subclass. If you’re not very familiar with the various classes in the Foundation framework yet, then just remember to do this for NSString and NSArray.

By the way, if you haven’t seen NSLog() before, this function writes text to the console window. It’s very handy for debugging and for figuring out what is going on in your app.

The implementation of Recipe

On to the implementation inside Recipe.m:

#import "Recipe.h"
 
@implementation Recipe
 
@synthesize name, instructions, image;
 
- (void)dealloc
{
	[name release];
	[instructions release];
	[image release];
	[super dealloc];
}
 
- (NSString*)description
{
	return [NSString stringWithFormat:@"%@ %@", [super description], self.name];
}
 
@end

There is not much special in the .m file, except maybe for the description method. This method is defined by NSObject and we override it here with our own version. The purpose of description is to help with debugging. When we now do:

NSLog(@"Recipe name is now: %@", recipe);

the console will show something like this:

Recipe name is now:  Raspberry Smoothie

Note that we did not call [recipe description]. NSLog() will automatically call description for us, so that saves some typing. The bit between the brackets is the name of the class and its memory pointer. This is done by NSObject, thanks to our call to [super description].

Our own version of description added the name of the smoothie to the output. If our app now crashes on something that has to do with a recipe, this makes it a little easier for us to determine which particular Recipe object is the culprit.

Filling up the table

A UITableView needs a data source and a delegate to provide it with the entries it needs to display and other information. If you recall, our SmoothiesViewController is actually a subclass of UITableViewController, and a table view controller automatically serves as both the delegate and data source for the table. That’s very handy! Now we simply have to add a few methods to our SmoothiesViewController to make this all work.

Let’s create a list of recipes first. Add the following to SmoothiesViewController.h:

// The list of Recipe objects
@property (nonatomic, retain) NSMutableArray* recipes;

And to SmoothiesViewController.m:

@synthesize recipes;
 
- (void)dealloc
{
	[recipes release];
	[super dealloc];
}

All we did here was add a property recipes of type NSMutableArray. This list will hold our Recipe objects. You always have to do this sort of housekeeping when you want to add a property: declare the @property in your .h, add a @synthesize in your .m, and release it in your dealloc if the property is of type “copy” or “retain”.

Now we can create the recipes list and fill it up with some tasty recipes. In SmoothiesViewController.m add:

- (id)initWithCoder:(NSCoder*)aDecoder
{
	if ((self = [super initWithCoder:aDecoder]))
	{
		// Create the recipe list object
		recipes = [[NSMutableArray arrayWithCapacity:10] retain];
 
		// And add a few recipes to it
		Recipe* recipe = [[Recipe alloc] init];
		recipe.name = @"Banana Shake";
		recipe.instructions = @"...";
		recipe.image = [UIImage imageNamed:@"Banana Shake.png"];
		[recipes addObject:recipe];
		[recipe release];
 
		... we add more recipes here ...
	}
	return self;
}

The initWithCoder method is called when the view controller is loaded from the nib. We use it to allocate and initialize the recipes array, and then we create several new Recipe objects and add them to that array.

We’re not done yet. We still have to tell the table view to display the entries from this array. Add the following methods to SmoothiesViewController.m:

- (NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section
{
	return self.recipes.count;
}
 
- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
	static NSString* CellIdentifier = @"RecipeCellIdentifier";
 
	UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
	if (cell == nil)
		cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
 
	Recipe* recipe = [self.recipes objectAtIndex:indexPath.row];
	cell.textLabel.text = recipe.name;
	cell.imageView.image = recipe.image;
	return cell;
}

This should be old-hat for anyone who has used a UITableView before. In numberOfRowsInSection:, we simply return the number of recipes in our list. In cellForRowAtIndexPath: we first obtain a UITableViewCell and then set the cell’s text label and image with the properties from the Recipe at the specified row.

You can run the app now and you’ll see a list of recipe names, each of which has a small thumbnail image to the left. There is one more tweak I’d like to make. The standard height of table view rows is 44 points, which is a little small for our thumbnails. So we’ll set it to 66 points instead.

Add the following snippet to SmoothiesViewController.m:

- (void)viewDidLoad
{
	[super viewDidLoad];
	self.tableView.rowHeight = 66;
}

As its name implies, this method is called after the view controller’s view is loaded. We want to set the rowHeight property of the table view, and this is the ideal place to do it. We cannot do this in initWithCoder because that is too early; by then the table view is not created yet and self.tableView is still nil.

The class structure

To recap, we added a Recipe class and modified the SmoothiesViewController class to hold an array of those Recipe objects. The class structure is now as follows:

Figure 2.2. We added a list of Recipe objects to the SmoothiesViewController.

Figure 2.2. We added a list of Recipe objects to the SmoothiesViewController.

I have encircled the classes that form our data model. It consists not just of the Recipe objects, but also of the NSMutableArray that contains them.

We also wrote code to display these Recipe objects on the screen. For every Recipe, we made a UITableViewCell and set its text label and image to the data from the recipe. The UITableViewCell serves as the “view” for the recipe. The SmoothiesViewController ties both of them together in the table view’s data source methods. There you have Model-View-Controller in action!

This concludes version 2 of the app. You can get the full source code from github. Next, we’re going to add the Recipe Details screen, which shows up when you tap on a recipe.

Version 3: Recipe details

So far we’ve just shown the names of the recipes and a thumbnail image. I would really like to go make a smoothie now, so it’s time to add some functionality to the app that lets us see the actual recipes. We’re going to add the Recipe Details screen that shows up when you tap a smoothie in the table view. It will show the title of the smoothie, a photo (a bigger version of the thumbnail), and the recipe instructions.

This is what it will look like:

Figure 3.1. That looks delicious!

Figure 3.1. That looks delicious!

A new screen means a new view controller. Basically, every screen in your app uses its own UIViewController subclass (such as UITableViewController or a subclass of your own). Sometimes you can re-use the same view controller class for different screens if those screens are similar, and we’ll see an example of that later in this tutorial.

For the Recipe Details screen we will make our own subclass, RecipeDetailsViewController, that extends directly from UIViewController. It will be loaded from a nib file, so we can make the UI in Interface Builder.

I used Xcode’s New File wizard and chose “UIViewController subclass” and checked “With XIB for user interface”. I opened the nib in Interface Builder and added an image view for the photo and a text view for the instructions. We’re going to display the recipe’s name in the navigation bar, so we don’t need to add a label for that.

Then I connected these new subviews to outlets in the source file. In Xcode 4 that is really easy to do. Hold down Ctrl and drag from a subview into the .h file. This will automatically insert the @property statement for you, as well as @synthesize and release statements in the .m file.

Figure 3.2. The new view controller in Interface Builder.

Figure 3.2. The new view controller in Interface Builder.

Let’s connect this new screen to our table view. Add the following code to SmoothiesViewController.m:

- (void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath
{
	RecipeDetailsViewController* controller = [[RecipeDetailsViewController alloc] init];
	[self.navigationController pushViewController:controller animated:YES];
	[controller release];
}

This is a method from the UITableViewDelegate protocol. It is called when the user taps one of the rows in the table. We create a new instance of the RecipeDetailsViewController (this will automatically load the nib file) and then push it on the navigation stack. Try it out. You can now tap on the name of a smoothie and in response the Recipe Details screen will appear. Because we pushed this screen onto the navigation controller, you can close it again by pressing the back button in the top left.

Displaying the recipe

At this point, the Recipe Details screen doesn’t do much yet. We still have to tell it which Recipe object to display. So how do we get the recipe to show up in this screen? First, we add a @property to the interface of the view controller in RecipeDetailsViewController.h:

@class Recipe;
 
@interface RecipeDetailsViewController : UIViewController
{
}
 
@property (nonatomic, retain) IBOutlet UIImageView* imageView;
@property (nonatomic, retain) IBOutlet UITextView* instructionsTextView;
 
// This is the new property
@property (nonatomic, retain) Recipe* recipe;
 
@end

Then we change the table view delegate method to:

// in SmoothiesViewController.m:
- (void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath
{
	RecipeDetailsViewController* controller = [[RecipeDetailsViewController alloc] init];
 
	// Pass the Recipe object to the controller
	Recipe* recipe = [self.recipes objectAtIndex:indexPath.row];
	controller.recipe = recipe;
 
	[self.navigationController pushViewController:controller animated:YES];
	[controller release];
}

After we have instantiated the controller, we look up the Recipe object in our data model and then pass it on to the new controller. This gives the controller everything it needs to know about the recipe.

We’re not done yet. In the implementation of RecipeDetailsViewController, we add the viewDidLoad method:

// in RecipeDetailsViewController.m:
- (void)viewDidLoad
{
	[super viewDidLoad];
	self.title = self.recipe.name;
	self.imageView.image = self.recipe.image;
	self.instructionsTextView.text = self.recipe.instructions;
}

Here, we take the Recipe‘s attributes and use them to configure our subviews. We set self.title to the recipe’s name; the navigation bar will use this as its own title. We set the recipe’s image on the image view and the instructions on the text view.

This approach works because we assigned the recipe to the view controller before we did pushViewController. If we had done it in the order below, it would not have worked because viewDidLoad would have been called while the recipe property was still nil:

// This is the wrong order!
[self.navigationController pushViewController:controller animated:YES];
controller.recipe = recipe;

Classes are black boxes

Maybe you’re wondering why we’re giving a Recipe object to the RecipeDetailsViewController when we could have done it as follows:

- (void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath
{
	RecipeDetailsViewController* controller = [[RecipeDetailsViewController alloc] init];
 
	Recipe* recipe = [self.recipes objectAtIndex:indexPath.row];
	controller.title = recipe.name;
	controller.imageView.image = recipe.image;
	controller.instructionsTextView.text = recipe.instructions;
 
	[self.navigationController pushViewController:controller animated:YES];
	[controller release];
}

After all, imageView and instructionsTextView are properties on the view controller, so we can access them from outside the class. I advise against doing it like this. It exposes the internal details of RecipeDetailsViewController and it is better to keep those hidden.

This is one of the main principles of object-oriented programming. Each class performs a certain job but from the point of view of someone using that class, it acts as a “black box”. You know what goes into it and you can see what comes out of it, but you have no idea what it does on the inside. And that’s great, because that is one less thing to worry about (as long as you are guaranteed that the class does its job properly and is fast enough).

Hiding away the details of how a class works helps to reduce the complexity of your program. That makes it easier to understand how all the classes in your program work together, without getting lost in unnecessary details. It also helps to reduce “side effects”, where a change in the implementation of one class causes an unwanted change (usually a bug!) somewhere else in the code. So in short: make your classes as independent from each other as possible!

In our app, the subviews of RecipeDetailsViewController are its own business. The SmoothiesViewController shouldn’t have to know whether the name of the recipe goes into the Recipe Details screen’s title bar or in a UILabel, for example. It should just say to the RecipeDetailsViewController, “Here is the name of the recipe. Do with it what you want. I trust you will put it in the right place.”

It is then the responsibility of RecipeDetailsViewController to set the content of its subviews. The SmoothiesViewController doesn’t need to know how RecipeDetailsViewController does its thing; it just gives it a Recipe object and that’s it.

Likewise, the RecipeDetailsViewController does not have to be concerned with the list of all the recipes, it just needs to know about a single one. When you tap on another smoothie, we create a new RecipeDetailsViewController and assign it the Recipe object for that particular smoothie. This “separation of concerns” helps to keep the design of your apps simple and clean.

That’s it. In this chapter you’ve seen how you can pass data from one view controller to another, and we’ll be doing this again and again in next chapters.

Our class structure now looks like this:

Figure 3.3. We added a view controller for the Recipe Details screen.

Figure 3.3. We added a view controller for the Recipe Details screen.

Download the source code for this version

If you’re ready for more, head on over to part 2.

Comments

  1. John Topley says:

    An excellent tutorial – thank you!

  2. valshebnik says:

    hey Matthijs, don’t you think there’s a language-level problems in objC regarding the encapsulation? in particular case, i’d prefer to assign some object to class’ property (like Receipt) and do not let anyone assigning separate values to class’ outlets (like name, image. description etc). how can you achieve such a limitation? is there some way of hiding the outlets keeping them as properties?

  3. Matthijs says:

    @valshebnik: You can use class extensions for this, which allow you to add private properties to the class that only you can use in the @implementation block. You can read about them here: http://www.friday.com/bbum/2009/09/11/class-extensions-explained/

  4. valshebnik says:

    Matthijs thank you for the response. i’m aware of extensions. but i’m afraid the IBOutlets can not be implemented that way or they will not appear in InterfaceBuilder.

  5. Matthijs says:

    @valshebnik Correct, but an outlet is obviously something that needs to be public, otherwise Interface Builder cannot see it. I don’t think this creates an encapsulation issue. It has never bothered me anyway. :-)

  6. Dun says:

    Thank you for the tutorial. It is well explained. I had done some of the things differently but it is similar. I have a question with regard to the recipe detail view. I have an object that I am constructing from the table array which populates my details view. I have about 7 properties such as icon, title, author, description, date_created, file_size. Only 5 out of 7 are showing in the details view. Do you know what could be the issue? why does my object that is populated from indexPath.row only return a few of my properties. Any help greatly appreciated. Thanks.

  7. Dun says:

    Found a solution. I just had to redesign my detailview.xib. For some reason it was not recognizing my IBOutlets from the fileOwner. Now I have to figure out how to display screenshots of multiple images in similar way as an asp.net repeater control.

  8. Andrew says:

    Hey man, thanks for these tutorials. Very helpful. One question, though. I think of a data class as holding all the data for the program, and thus it would seem to me that the recipe names and information should be contained within that class specifically. I image a data model containing your recipe database, for example, and then you could take this class and put anywhere: an online recipe database, a Mac OS recipe model, etc, with little to not changes. Putting the actual recipe-specific data in the controller seems against the MVC logic to me, isn’t it?

  9. Matthijs says:

    @Andrew Thanks! But I’m not sure I follow. You say: “Putting the actual recipe-specific data in the controller seems against the MVC logic to me”, but can you point at the source where I do that?

  10. Andrew says:

    I’m referring to the view controller where you put the recipe-specific information: // And add a few recipes to it
    Recipe* recipe = [[Recipe alloc] init];
    recipe.name = @”Banana Shake”;
    recipe.instructions = @”…”;
    recipe.image = [UIImage imageNamed:@"Banana Shake.png"];
    [recipes addObject:recipe];
    [recipe release];

    However, I saw in your other post that you moved this to a separate Data Model, which made the sense I was after. These are great tutorials.

  11. dingwenjie says:

    Thank you for the tutorial!!!
    I was curious, so tried this method,it doesn’t work,why the controller’s property can not be changed but the title??:
    - (void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath
    {
    RecipeDetailsViewController* controller = [[RecipeDetailsViewController alloc] init];

    Recipe* recipe = [self.recipes objectAtIndex:indexPath.row];
    controller.title = recipe.name;
    controller.imageView.image = recipe.image;
    controller.instructionsTextView.text = recipe.instructions;

    [self.navigationController pushViewController:controller animated:YES];
    [controller release];
    }

  12. Matthijs says:

    @dingwenjie: I’m not sure I understand. What exactly isn’t working?

  13. dingwenjie says:

    Thank you for your reply.
    Sorry I am a Chinese, and English is rather poor, I fear I can not express what I mean well, so I used source code to illustrate my question right.

    - (void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath
    {
    // Create the Recipe Details screen
    RecipeDetailsViewController* controller = [[RecipeDetailsViewController alloc] init];

    // Tell the Recipe Details screen which Recipe it should display
    Recipe* recipe = [self.recipes objectAtIndex:indexPath.row];

    //controller.recipe = recipe;
    controller.title = recipe.name;
    controller.imageView.image = recipe.image;
    controller.instructionsTextView.text = recipe.instructions;
    NSLog(@”rcipe.name is :%@”,recipe.name);
    NSLog(@”recipe.instructions is:%@\n\n”,recipe.instructions);

    NSLog(@”controller.title is:%@”,controller.title);
    NSLog(@”controller.instructionsTextView.text is:%@”,controller.instructionsTextView.text);
    // Show the Recipe Details screen
    [self.navigationController pushViewController:controller animated:YES];
    [controller release];
    }

    i output I output the value of these properties on console,the result is:

    2011-08-23 08:48:39.250 Smoothies[309:207] rcipe.name is :Green Smoothie
    2011-08-23 08:48:39.252 Smoothies[309:207] recipe.instructions is:2 bananas
    half a glass of orange juice
    handful of fresh spinach

    2011-08-23 08:48:39.256 Smoothies[309:207] controller.title is:Green Smoothie
    2011-08-23 08:48:39.257 Smoothies[309:207] controller.instructionsTextView.text is:(null)

    i can’t understand that the controller.instructionTextView.text is still null,(in fact the controller.imageView.image is null too,only the controller.title is set up correctly)

  14. Matthijs says:

    @dingwenjie: Ah, I understand what you mean now. The reason controller.instructionTextView.text is nil is that controller.instructionTextView itself is nil at that point. The view has not been loaded yet and the outlets have not been connected to the properties. It is too soon to put anything into instructionTextView. You will have to put the value into a temporary property and then give it to instructionTextView in viewDidLoad.

  15. dingwenjie says:

    Thank you very much!
    the perfect interpretation of your answer to my question,thanks again.

  16. Martin says:

    Thank you for the excellent tutorial. I have many iOS books and this is as good, if not better, then they all are.
    I have one query, what does it mean to say the smoothiesViewController is “inside” the navigation controller.
    I know I can see it “inside” within the .xib, but what does it mean from a programmatical point of view. If the .xib didn’t exist and everything was being defined and created in code how would it look?

  17. Matthijs says:

    @Martin The navigation controller acts as a container for your content view controllers. In code you would do something like this, probably in the app delegate:

    UINavigationController *navController = 
       [[UINavigationController alloc]
          initWithRootViewController:smoothiesViewController];
    
    self.window.rootViewController = navController;
    
  18. Mike Nibeck says:

    Excellent! I love how you walk through the solution, making improvements as you go. Much nicer then just showing the final product.

    Quick question. Could you add some more information regarding the exact changes you made to the nib files? You stated

    “I also made some changes to the nib files. I removed SmoothiesViewController.xib because we no longer need it now that we’re using a table view controller. In MainWindow.xib I added a navigation controller and put the SmoothiesViewController inside that. The navigation controller provides the title bar at the top of the screen, and we’ll use it later to present a details screen when you tap the name of a recipe.”

    I had trouble replicating this on my own. Also, in light on the new templates in XCode 4.2, it becomes even harder to follow.

    Again, thanks!

  19. Matthijs says:

    @Mike Nibeck: The new templates in Xcode 4.2 no longer include a MainWindow.xib file. You can still make one yourself from the New File panel, under User Interface choose Application. Then in Interface Builder you simply drag the Navigation Controller into that nib file. Replace its root view controller with a Table View Controller, and change the Class of that table view controller to SmoothiesViewController (in the Identity Inspector).

Leave a Reply

Your email address is required but will not be visible to others.

 *
 *