Understanding Model-View-Controller on iOS - iOS UICollectionView: The Complete Guide, Second Edition (2014)

iOS UICollectionView: The Complete Guide, Second Edition (2014)

Chapter 1. Understanding Model-View-Controller on iOS

Before you dive into UICollectionView, you should get familiar with some of the conventions and terms used in this book. The book starts with the basics of the iOS application lifecycle and then discusses the Model-View-Controller (MVC) paradigm. Even if you’re an experienced iOS developer already familiar with these topics, I encourage you to read this chapter to make sure that you’re on the same page (or screen, so to speak) that I am while you’re reading the rest of this book.

Basics of the Application Lifecycle

The iOS application lifecycle differs a little from typical native applications on other platforms (although recent changes to OS X show Apple is interested in making the iOS lifecycle the norm). Developers no longer have hard-and-fast rules for when their applications are terminated, suspended, and so on. Let’s start with a simple scenario to describe a typical application lifecycle.

The user has just turned on his phone, and no applications are running except for those that belong to the operating system. Your application is not running. After the user taps your app’s icon, Springboard—the part of the OS that operates the Home screen of iOS—launches your app. Your app, and the shared libraries it needs to execute, is loaded into memory while Springboard animates your Default.png on the screen. Eventually, your app begins execution, and your application delegate receives the appropriate notification. When your application is running and in the foreground, it is in the active state.

On iOS, users tend to only use any given application for a few seconds before returning their phones to their pockets. After the user has put away your app by pressing the Home button on her iPhone or iPad, your application enters the background state. Typically, apps have 10 seconds to complete any database saves or other long-running tasks (though applications can request additional time from the OS). When all the background processing is complete, the application finally becomes suspended. While suspended, applications remain in memory but may not execute code. The state of your application is persisted. If the user opens your application while it is suspended, it begins execution exactly where it left off. If memory becomes low, the OS can kill your app while it is in the suspended state. The user can also manually terminate your app from the multitasking tray. Once terminated, applications return to their initial state of not running.

But wait, it gets more complicated! If the user receives a calendar alert, opens the multitasking tray, or gets a phone call, your application can be put into the inactive state. Your application is still running, but it is no longer the foremost thing the user interacts with. For example, games pause themselves. As an application developer, you need to be aware of this and use it as an indication that the user might leave your application soon.

The user can open your application without tapping its icon on the Home screen. If your application receives local or push notifications, or if it is registered for custom URL scheme handling, the user can open it in any number of ways.

The application lifecycle is important to understand for all iOS developers who want to make enriched, immersive experiences. These types of applications are exactly what UICollectionView is great for, so no comprehensive discussion of UICollectionView would be complete without a summary of the application lifecycle.

If your app enters the inactive state, stop updating your interface. It would be disconcerting for a user to see your collection-view contents move about while he’s deciding whether to view the details of an appointment that has popped up over your application. Likewise, don’t update your app’s interface while the application is in the background. The state of the user interface should remain fixed between the switch from active to background and back to active.

How to Use MVC

MVC is not a difficult concept, but there are two main reasons for emphasizing its importance in iOS:

Image MVC is used by CocoaTouch (and Cocoa on OS X). If you adhere to the same paradigm as the frameworks used for writing all iOS applications, your code will flow well and not clash with the built-in classes, including UICollectionView.

Image MVC is generally a good framework, and using it will help you make well-written, maintainable apps.

Now that you know why MVC is important, it’s time to look at what MVC is. Figure 1.1 shows the basics of MVC; strong relationships are represented with solid lines, and weak relationships are represented by dashed ones. Strong and weak relationships indicate to the compiler how to manage memory and are important to avoid memory leaks, which would eventually lead to the app being terminated.

Image

Figure 1.1 Basics of MVC

At the heart of MVC is the controller object. The controller is a view controller—as in UIViewController—and it controls the view. It maintains a strong relationship to this view, which is what is presented to the user on the screen. The controller also maintains a strong relationship to the model. The model represents data that is represented in the view.

If your view ever has a reference to your model, or vice versa, you’re doing it wrong. This book uses MVC and you should, too.

Most of the code in any given application resides in the controller; controllers mediate the interactions between views and models, which is why the code in controllers is often referred to as glue code.

What sort of interactions does a controller mediate? Well, if the view contains a button, the view controller is notified when the user taps that button. User interactions usually trigger actions to modify, create, or delete models belonging to the controller. The controller receives the user interaction from the view, updates the model, and then updates the view to reflect the changes made to the model.

Sometimes, the model changes without user interaction. For example, consider a view that displays a large JPEG, which is being downloaded. When the download completes, the controller should be notified so that it can update the view. On iOS, you have a few different choices for how to notify the controller. My favorite is Key-Value Observation (KVO). Controllers can register themselves as observers on model objects so that they are notified whenever the model’s properties are changed. Other ways for models to interact with controllers on iOS includeNSNotificationCenter, delegation, and NSFetchedResultsController. I would avoid NSNotificationCenter for model-controller interaction in favor of NSFetchedResultsController or KVO. Although this book doesn’t discuss Core Data,UICollectionView works very well with NSFetchedResultsController in a similar way to UITableViewController.

This last example demonstrates a gaping hole in MVC: Where does the network code go? As a responsible iOS developer, you should keep the view controller to only mediating the interactions between the view and the model. If that’s the case, it shouldn’t be used to house the network access code. As discussed in Chapter 6, “Adding Interactivity to UICollectionView,” the network code should be placed outside of the typical MVC pyramid. Network access should not involve the view whatsoever, but it can sometimes involve the model.

Well, that’s mostly true. In fact, a common paradigm for fetching details about a model from an application programming interface (API) involves Grand Central Dispatch blocks. A block lets developers treat anonymous functions as first-class Objective-C objects. These blocks can be invoked later. Controllers can start a network request and pass the network-fetching object a callback block that updates the view. Technically, the network code has an indirect reference to the view, but you ignore it lest you find yourself falling down a rabbit hole of pedantry.

If you are experienced in iOS development, all of this should sound familiar. UICollectionView and UICollectionViewController don’t exist in silos; they are used within applications with models and with the rest of CocoaTouch. It would be irresponsible to present them in any other context than that of MVC.

MVC and UICollectionView

Now that you’ve read about the MVC paradigm, look at its application in the context of writing UICollectionView code.

The view component of MVC with UICollectionView is unsurprisingly the UICollectionView itself; the controller is either a subclass of UICollectionView-Controller or a subclass of UIViewController that conforms to the UICollectionViewDataSourceand UICollectionViewDelegate protocols; the model can be anything.

Like with UITableView, your controller can either subclass UIViewController and conform to the two protocols for the collection view data source and delegate or it can subclass UICollectionViewController itself. If you look in the header file ofUICollectionViewController, you see that it’s very sparse. The controller inherits from UIViewController—conforming to UICollectionViewDataSource and UICollectionViewDelegate—and has a convenience initializer to programmatically create an instance of it using a collection view with a specific layout. It contains a property to access the collection view and another property to specify whether the selection in a collection view becomes cleared when it (re)appears.

When using a UICollectionViewController subclass, the view property of UIViewController points to the same object as the collectionView property of UICollectionViewController. The view is the collection view. If you plan to use onlyUICollectionView to display data to your user, I strongly recommend subclassing this prebuilt controller. In my experience, you run into fewer “gotchas” using these special controllers from Apple.

In some circumstances, subclassing UIViewController is preferable. For example, if your view contains a collection view, but also contains other views, it’s easier to have the collection view as a subview of the controller’s view. The distinction is minor, but important.

Figures 1.2 and 1.3 demonstrate the differences in the two approaches to using collection views. UICollectionViewController is much simpler; it should be the approach you take first. If you find you can’t solve your problem with it, switch to using the second approach. It’s usually easy to switch from using the first method to the second.

Image

Figure 1.2 Example of MVC using UICollectionViewController

Image

Figure 1.3 Example of MVC using UICollectionView’s protocols

This book uses the first approach unless there is a good reason not to. Even though the view property of UICollectionViewController is the same as its collectionView property, the code used in this book carefully distinguishes between the two.

Now that you’ve seen how collection views fit within the MVC paradigm of iOS apps, look at the following simple example. Don’t worry; you experiment a lot with collection views in Chapter 2, “Displaying Content Using UICollectionView.”

In the following example, you create a simple iPhone app that displays a bunch of cells with random colors. To get started, create a new application with the Single View template. Make sure that Use Storyboards is unchecked; this book focuses on collection views, and I don’t want to have to diverge to discuss the peculiarities of storyboards. Delete everything in the view controller header file and replace it with the code in Listing 1.1.

Listing 1.1 Basic UICollectionViewController Header File


@interface AFViewController : UICollectionViewController

@end


Replace AFViewController with the name of your view controller. My initials are AF, so I prefix my class names with them to avoid namespace collisions.

Next, head over to your .xib file and delete the view. Drag a collection view onto the blank canvas and connect the collection view’s delegate and dataSource outlets to the File’s Owner, the view controller. It should look like Figure 1.4 when you’re done.

Image

Figure 1.4 Basic UICollectionView setup using a .xib

Now comes the fun part: the code! UICollectionViewDataSource has two required methods. One returns the number of items in a section, and another configures a cell for a given index path.

If you’re not familiar with these terms, don’t worry. Chapter 2 explains everything in great detail. This quick example just gets your feet wet.

Following MVC, you need a model. Use a basic array that you’ll populate with a bunch of randomly generated colors. The top of your implementation file should look something like Listing 1.2.

Listing 1.2 Setting Up the Model


static NSString *kCellIdentifier = @"Cell Identifier";

@implementation AFViewController
{
NSArray *colorArray;
}

- (void)viewDidLoad
{
[super viewDidLoad];

[self.collectionView registerClass:[UICollectionViewCell class]
forCellWithReuseIdentifier:kCellIdentifier];

const NSInteger numberOfColors = 100;

NSMutableArray *tempArray = [NSMutableArray
arrayWithCapacity:numberOfColors];

for (NSInteger i = 0; i < numberOfColors; i++)
{
CGFloat redValue = (arc4random() % 255) / 255.0f;
CGFloat blueValue = (arc4random() % 255) / 255.0f;
CGFloat greenValue = (arc4random() % 255) / 255.0f;

[tempArray addObject:[UIColor colorWithRed:redValue green:greenValue
blue:blueValue alpha:1.0f]];
}

colorArray = [NSArray arrayWithArray:tempArray];
}


Notice the copy of the array; we’re doing so to avoid a mutable instance as our color array, which would be unnecessarily slower.

The kCellIdentifier string is used to register a plain UICollectionViewCell as the cell for the collection view to use, so don’t pay much attention to it. The part that involves the model is the instance variable called colorArray. In viewDidLoad, you use a for loop to populate this array with random colors.

Now that you have the model set up, you need to configure your view to represent it. For this, use the two UICollectionViewDataSource methods mentioned earlier (see Listing 1.3).

Listing 1.3 Configuring the View


-(NSInteger)collectionView:(UICollectionView *)collectionView
numberOfItemsInSection:(NSInteger)section
{
return colorArray.count;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [collectionView
dequeueReusableCellWithReuseIdentifier:kCellIdentifier forIndexPath:indexPath];
//Discussed in Chapter 2 - pay no attention

cell.backgroundColor = colorArray[indexPath.item];

return cell;
}


The first method—collectionView:numberOfItemsInSection:—lets the collection view know how many cells it’s going to display. You rely on the model to let the controller know what number to return. Next is collectionView:cellForItemAtIndexPath:, which returns a cell that you are responsible for configuring in a way that represents your model. To do this, you grab the model at the given index and use that color as the background color for the cell. If you run the app, you get something like what you see in Figure 1.5. Because the colors are randomly generated, of course, your app will look different.

Image

Figure 1.5 First run of the basic app

Note that we’re not using this collection view within a UINavigationController, so the status bar is transparent. In production code on iOS 7+, you’ll usually encapsulate your collection view within a navigation controller, whose navigation bar is extended behind the status bar.

So, this simple example demonstrates how a model can represent a view and how you can configure a view to represent that model without either being aware of the other. This example demonstrates the platonic ideal of what you should strive for: clear separation between model, view, and controller.