By Kevin McNeish on Thu, 08/29/2013
Do you have an idea for an app but lack the programming knowledge to begin building it? In this weekly blog series, I will take you, the non-programmer, step by step through the process of creating apps for the iPhone, iPod touch, and iPad. Join me each week on this adventure, and you will experience how much fun turning your ideas into reality can be! This is Part 21 of the series. If you are just getting started, check out the beginning of the series here.
Now that you have a basic understanding of displaying lists of data under your belt, it's time to talk about passing information between view controllers. This is an extremely important topic because this is something you will need to do many times over in your iOS apps. In this post I'll outline the main steps and teach you best practices. In order to follow along, you can get the most up to date version of the iAppsReview project we will be working with in this post from this link. For the best learning experience, I encourage you to work through the steps on your own. If you run into trouble, you can get the completed project from this link.
Why do you need to pass information from one view controller to another? Figure 1 provides a great example of this.
|Figure 1 - You need to pass the currently selected app category between view controllers.|
At run time when a user taps on the App Category table view row on the left, the Write Review view controller needs to pass the currently selected category to the App Category view controller. The App Category view controller needs to know this information so it can display a check mark in the row of the currently selected category. Conversely, when the user selects a different category in the App Category scene at run time, the App Category view controller needs to pass the newly selected category back to the Write Review view controller so the currently selected category can be displayed in its table view.
In the context of this discussion, the source view controller is the controller from which data is being passed and the destination view controller is the controller that is receiving the data.
Passing Data to a View Controller
Let's talk about passing data to a view controller first. This usually requires three steps:
- Create a property on the destination view controller to hold the data being passed by the source view controller.
- Configure the segue between the source and destination view controllers.
- In the source view controller, implement the prepareForSegue: method and add code that stores the data to be passed to the destination view controller's property.
In this example, it makes sense to pass an app category ID between the Write Review scene and the App Category scene.
Step 1: Create a Property on the Destination View Controller
Our first step is an easy one. We need to add an appCategoryID property to the destination view controller so the source view controller has a place to store the ID of the currently selected category.
- Go to the Project Navigator and select the AppCategoryViewController.h file.
- Add the property declaration shown in Figure 2.
Step 2: Configure the Segue Between the Source and Destination Controllers
This second step is also an easy one. You just need to specify an identifier for the segue between the source and destination view controllers.
- In the Project Navigator, select the MainStoryboard.storyboard file.
- Click on the segue between the Write Review and App Category scenes to select it.
- Go to the Attributes Inspector (the third button from the right in the Inspector toolbar) and set the segue's Identifier attribute to AppCategorySegue. This provides an identifier for the segue that we can use from within our source view controller code file. Be very careful with the spelling and upper and lower case because you need to use the exact same name when you reference it from the code file.
|Figure 3 - Set the segue's Identifier to AppCategorySegue.|
Enhancing the Write Review Scene
Before we perform the third step of implementing the prepareForSegue: method in the source view controller, there is a bit of housekeeping we need to do in the Write Review scene's view controller.
For starters, we should add a new instance variable to the scene to hold a reference to the currently selected app category ID.
- In the Project Navigator, select the WriteReviewViewController.m file.
- At the top of the file, add the import statements shown in Figure 4. These will allow us to reference AppCategoryEntity objects and the AppCategoryViewController.
|Figure 4 - Add import statements to WriteReviewViewController.h.|
- Add the instance variable shown in Figure 5. This provides a place for us to store the current app category ID.
|Figure 5 - Add new instance variables.|
- In the WriteReviewViewController.m file scroll to the isReadyToShare method. Since we don't want the user to be able to save a review unless an app category has been selected, we need to add some code to the view controller's isReadyToShare method. You may remember we created this method in a previous post. The Share and Post buttons are enabled or disabled based on whether this method returns YES or NO.
- If the user hasn't selected an app category, the value of this variable will be zero. So add the check for a zero appCategoryID tas shown in Figure 6.
|Figure 6 - Check if the user has selected an app category.|
- Now let's give the user a visual cue that they need to select an app category. In the Project Navigator, select the MainStoryboard.storyboard file. In the Write Review scene, double-click the right side of the App Category table view row to put the detail label into edit mode. Then enter the text Select as shown in Figure 7 and press return.
|Figure 7 - Set the text of the detail label to Select.|
Step 3: Implement the prepareForSegue: Method
Now you're ready to implement the prepareForSegue: method in the source view controller, which in this case, is WriteReviewViewController.
The prepareForSegue: method is called automatically on your source view controller whenever a segue is triggered. It allows you to perform custom actions before the segue to the destination controller is executed. In this case, the segue is triggered when the user taps the App Category table view row in the Write Review scene.
- In the Project Navigator, select the WriteReviewViewController.m file.
- Add the method shown in Figure 8 directly below the viewDidLoad method.
|Figure 8 - The prepareForSegue: method code|
As you can see, the prepareForSegue: method is passed a reference to the segue that was triggered. The code in this method performs the following steps, which are very typical for methods of this type:
- The first line of code checks the segue's identifier property to make sure the segue we are interested in was the one that was triggered (you can have multiple segues for a view controller).
- The second line of code gets a reference to the destination controller from the segue's destinationViewController property.
- The third line of code stores the current app category ID on the destination view controller's appCategoryID property.
After this method executes, the segue to the destination controller is automatically executed.
Enhancing the App Category View Controller
Now that the Write Review view controller is passing the currently selected app category ID to the App Category view controller, let's add some code to the App Category view controller that places a check mark next to the currently selected category in the table view.
- In the Project Navigator, select the AppCategoryViewController.m file.
- Add the oldIndexPath instance variable shown in Figure 9. This variable allows us to keep track of the item that was last selected (you will see why we need this in just a bit).
|Figure 9 - Create an oldIndexPath instance variable.|
- Now add the code shown in Figure 10 to the tableView:cellForRowAtIndexPath: method.
|Figure 10 - Code that sets a check mark on the currently selected app category|
Remember that the tableView:cellForRowAtIndexPath: method gets called once for each cell in a table view. The code you just added checks if the ID of the AppCategoryEntity associated with the current cell is the same as the currently selected category ID. If it is, a check mark is added to the cell and the index path of the cell is stored in the oldIndexPath instance variable for later use. If it isn't the same, the accessoryType of the cell is set to "None." You need this else statement so that the previously selected category is unchecked.
Returning Data From a View Controller
In order to complete the functionality for selecting an app category, we now need to pass back the currently selected category from the App Category scene to the Write Review scene, so it can display the newly selected category in the detail label of the App Category table view (Figure 11).
The best way to pass back this information is to create a method on the WriteReviewViewController that can be called by the AppCategoryViewController. However, before creating this method, we need to be a little forward thinking. As shown in Figure 11, there are two different scenes in the app that segue to the App Category scene—the Write Review scene and the Online Reviews scene.
|Figure 11 - The Write Review and Online Reviews scenes both segue to the App Category scene.|
This means we need to come up with a scheme for returning the currently selected category that works well for both the Write Review and Online Reviews scene. This is where an Objective-C protocol can come to the rescue!
Outside the realm of software, the word protocol is defined as:
The established code of behavior in any group, organization, or situation.
This definition isn't far off the mark when it comes to protocols in Objective-C. Protocols are used to define a standard set of behavior. In our situation, we need the Write Review and Online Reviews view controllers to possess a standard callback method that can be used by the App Category view controller to pass back the currently selected AppCategoryEntity. To do this, we can have the App Category view controller declare a protocol that can be adopted by the Write Review and Online Reviews view controllers.
Figure 11 provides an overview of the five main steps that you need to perform whenever you need to pass information back from one view controller to another. In this context, the Write Review and Online Reviews view controllers are referred to as the presenting view controller, and the App Category view controller is referred to as the presented view controller.
|Figure 12 - Creating a callback method for passing data back from a view controller|
Here are the details for these steps:
- Declare a protocol in the presented view controller that specifies a method that can be implemented by the presenting view controller. The presented view controller will use this method at run time to pass data back to the presenting view controller.
- Declare a delegate property on the presented view controller that will hold a reference to the presenting view controller. The presented view controller will use this reference to the presenting view controller when it calls the protocol method on that view controller.
- Add code to the presented view controller that calls the protocol method, passing data to the presenting view controller.
- Adopt the protocol of the presenting view controller and implement the method with code that does something with the data that is passed back to it.
- In the presenting view controller's prepareForSegue: method, have the presenting view controller store a reference to itself in the presented view controller's delegate property.
Now you're ready to implement these steps to refresh the App Category table view row in the Write Review scene.
Step 1: Declare a Protocol in the Presented View Controller
- In the Project Navigator, select the AppCategoryViewController.h file.
- Add the import statement and AppCategoryDelegate protocol declaration shown in Figure 13.
|Figure 13 - The AppCategoryDelegate protocol declaration|
This protocol declares a single method, updateAppCategory: that accepts a reference to an AppCategoryEntity object. This is the method that the Write Review and Online Reviews view controllers must implement so that the App Category view controller can pass back the currently selected AppCategoryEntity.
Step 2: Declare a Delegate Property on the Presented View Controller
Further down in the AppCategoryViewController.h file, declare the delegate property shown in Figure 14.
|Figure 14 - Add a delegate property to the AppCategoryViewController.h file.|
This property will be used to hold a reference to the presenting view controller.
Step 3: Call the Protocol Method on the Delegate
Now we need to add code to the App Category view controller that calls the protocol method on the delegate (the presenting view controller) when the user selects a new app category.
- In the Project Navigator, select the AppCategoryViewController.m file.
- Scroll to the bottom of the file and you will see the tableView:didSelectRowAtIndexPath: method. This method is automatically called when the user taps a row in the table view at run time.
- Remove all the existing code and comments in this method and replace it with the code shown in Figure 15.
|Figure 15 - Add code to the tableView:didSelectRowAtIndexPath method.|
Here is the breakdown of what the code in this method does:
- The first line of code deselects the current selected row. If you don't do this, the row remains highlighted in blue after the user taps it.
- The cell that was previously checked is unchecked. The value in the oldIndexPath variable is used to determine the previously selected cell.
- A check mark is added to the currently selected cell.
- The currently selected cell's indexPath is saved for later use.
- An updateAppCategory: message is sent to the delegate object (the presenting view controller), passing a reference to the AppCategoryEntity object associated with the currently selected cell.
Step 4: Adopt the Protocol in the Presenting View Controller
Now that you have set up the presented view controller, it's time to implement the protocol in the presenting view controller.
- In the Project Navigator, select the WriteReviewViewController.h file.
- Add the import statement at the top of the file and adopt the AppCategoryDelegate protocol as shown in Figure 16. As you can see, a single class can adopt many different protocols.
|Figure 16 - Adopting the AppCategoryDelegate protocol|
- Now we need to implement the updateAppCategory: method that is declared in the AppCategoryDelegate protocol. But before we do this, we need a way to get a reference to the App Category table view cell in the Write Review scene. The most bulletproof way to do this is to create an outlet for the cell.
To do this, first select the MainStoryboard.storyboard file in the Project Navigator, and then display the Assistant Editor by clicking the center button in the Editor button group in the toolbar at the top of the Xcode window.
- Click on the status bar at the top of the Write Review scene to select the view controller. This should automatically display the WriteReviewViewController.h file in the Assistant Editor. If it doesn't, click the Manual button in the jump bar at the top of the Assistant Editor and select Automatic > WriteReviewViewController.h from the popup menu.
- Hold the Control key down, click on the App Category cell and drag your mouse pointer down to the end of the property list in the Assistant Editor. When the Insert Outlet or Outlet Collection popup appears (Figure 17) let go of the Control key and mouse button.
|Figure 17 - Create an outlet for the table view cell.|
- In the Create Connection popup, set the Name to appCategoryCell, and then click the Connect button. This adds a new outlet property to the header file.
|Figure 18 - Name the outlet appCategoryCell.|
- You can close the Assistant Editor now by clicking the button on the left in the Editor button group at the top of the Xcode window.
- Now you're ready to implement the protocol method in the presenting view controller. To do this, first select the WriteReviewViewController.m file in the Project Navigator. Afterwards, add the new updateAppCategory method shown in Figure 19 just below the prepareForSegue: method.
|Figure 19 - Add the updateAppCategory protocol method to the WriteReviewViewController.m file.|
Here's the breakdown on this code:
- This method accepts an AppCategoryEntity object that is passed from the presented view controller.
- It takes the value in the entity's categoryID property and stores it in the appCategoryID instance variable.
- It also gets the name of the category from the name property of the entity and updates the detail text label of the App Category table view cell.
- The last line of code tells the table view to reload its data, which refreshes the display of the table view cell to reflect the new category.
Step 5: Store a Reference to the Presenting View Controller in the Delegate Property
Now you're ready for the final step. We need to store a reference to the presenting view controller (the Write Review view controller) in the presented view controller's delegate property. A great place to do this is in the prepareForSegue: method.
- In the prepareForSegue: method of the WriteReviewViewController.m file, add the code shown in Figure 20.
|Figure 20 - Store a reference to the presenting view controller in the delegate property of the presented view controller.|
- While we're here, we should also change the postReview: method to save the selected app category. Scroll down to the postReview: method and change the second line of code as shown in Figure 20.
|Figure 20 - Store the selected app category ID on the ReviewEntity object.|
Testing the New Code
Now you're ready to take the code for a test drive!
- Click Xcode's Run button. When the app appears in the Simulator, select the Write a Review option. You should see the App Category row's detail text is set to Select (Figure 21).
|Figure 21 - The App Category row's detail text is set to Select.|
- Click the App Category row to navigate to the App Category scene. There should be no categories with a check mark next to them. Click one of the categories and you should see a check mark next to it. If you click on another row, the check mark should disappear from the previous row and appear on the newly selected row as shown in Figure 22. This is caused by the code that is executing in the view controller's tableView:didSelectRowAtIndexPath: method.
|Figure 22 - A check mark should appear in the selected row.|
- Now click the back button (labeled Write Review) in the upper-left corner of the view to navigate back to the Write Review scene. You should see the category that you last selected in the App Category detail label as shown in Figure 23!
|Figure 23 - The newly selected category displayed in the App Category row|
We covered a lot of territory here, but I wanted to present all of this information in a single post so you could see the entire process of passing information to and from view controllers. In the process you learned a pratical application for Objective-C protocols as well as how to display a check mark in a table view row. I recommend going through these steps a few times to make sure you understand the important iOS concepts outlined here.