Wednesday 3 October 2012

Beginning UICollectionView In iOS 6: Part 1


When Apple first launched the iPad in 2010, you might have been particularly impressed by the Photos app bundled with the device. It had a unique and stylish way of displaying photos via a multitude of layouts. You could view your photos in a nice grid view:
Or you could view your photo albums at the top level as stacks:
You could even transition between the two layouts with a cool pinch gesture. “Wow, I want that in my app!”, you may have thought.
Well, implementing a grid view and other alternative layouts like this was possible to get working on your own, but also quite tricky! It required a lot of code and was difficult to get working exactly right. Couldn’t there be an easier way?
Good news – now there is in iOS 6! Apple has introduced a new class called UICollectionView that makes adding your own custom layouts and layout transitions (like those in the Photos app) incredibly simple to build.
You’re by no means limited to stacks and grids, because UICollectionView is extremely customizable. You can use it to make circle layouts, cover-flow style layouts, Pulse news style layouts – almost anything you can dream up!
The bottom line is you have an exremely powerful new way to present ordered data to users, and you should start learning about it! It’s just as important (and helpful) of a class as UITableView is. The good news is if you’re familiar with UITableView, you’ll have no problem picking it up – using it is very similar to the table view data source and delegate pattern.
In this tutorial, you’ll get hands-on experience with UICollectionView by creating your own grid-based photo browsing app. By the time you are done this tutorial, you will know the basics of using UICollectionView and will be ready to start using this amazing technology in your apps!

Anatomy of a UICollectionViewController

Let’s go right to an example of one of these babies in action. The UICollectionViewController family contains several key components, as you can see below:
Take a look at these components one-by-one:
  1. UICollectionView – the main view in which the content is displayed, similar to a UITableView. Note that it doesn’t necessarily have to take up the entire space inside the view controller – in the screenshot above, there’s some space above the collection view where the user can search for a term.
  2. UICollectionViewCell – similartoaUITableViewCellinUITableView. Thesecells make up the content of the view and are added as subviews to the UICollectionView. Cells can either be created programmatically, inside Interface Builder, or via a combination of the two methods.
  3. Supplementary Views – if you have extra information you need to display that shouldn’t be in the cells but still somewhere within the UICollectionView, you should use supplementary views. These are commonly used for headers or footers of sections.
  4. Decoration View – if you want to add some extra views to enhance the visual appearance of the UICollectionView (but don’t really contain useful data), you should use decoration views. Background images or other visual embellishments are good examples of decoration views.
  5. In addition to the above visual components, UICollectionView also has non-visual components that help with laying out content:
  6. UICollectionViewLayout – UICollectionView does not know anything about how to set up cells on screen. Instead, its UICollectionViewLayout class handles this task. It uses a set of delegate methods to position every single cell in the UICollectionView. Layouts can be swapped out during runtime and the UICollectionView can even automatically animate switching from one layout to another!
  7. UICollectionViewFlowLayout – You can subclass UICollectionViewLayout to create your own custom layouts (as you’ll learn about in the next tutorial), but Apple has graciously provided developers with a basic “flow-based” layout called UICollectionViewLayout. It lays elements out one after another based on their size, quite like a grid view. You can use this layout class out of the box, or subclass it to get some interesting behavior and visual effects.
You will learn more about these elements in-depth throughout this tutorial and the next. But for now, it’s time for you to get your hands into the mix with a project!

Introducing FlickrSearch

In the rest of this tutorial, you are going to create a cool photo browsing app called FlickrSearch. It will allow you to search for a term on the popular photo sharing site Flickr, and it will download and display any matching photos on a beautiful corkboard-themed grid view:
Before you begin, make sure you download the assets that will be used throughout this tutorial. You won’t get very far without them!
Ready to get started? Fire up Xcode and go to File\New\Project… and select the iOS\Application\Single View Application template.
This template will provide you with a simple UIViewController and storyboard to start out with, and nothing more. It’s a good “almost from scratch” point to start from.
Click Next to fill out the information about the application. Set the Product Name to FlickrSearch, the device type to iPad, and make sure that the Use Storyboards and Use Automatic Reference Countingboxes are checked. Click Next to select the project location, and then click Create.
Compile and run, and you’ll see that it’s just a vanilla application with a single blank
view.
Next you should import the assets – drag the images from the unzipped Assets mentioned earlier into your project in Xcode, making sure that the box Copy items into destination group’s folder (if needed) is checked. Click Finish.

Pinning Up The Corkboard

You’ll begin by creating a basic but stylish design to get your application looking smart. Once the initial design is in place, you’ll add your UICollectionView.
Before you crack open the storyboard, declare some IBOutlets and IBActions so your class can interact with the interface items. Open ViewController.m and update the @interface declaration at the top to look like the code below:
@interface ViewController () <UITextFieldDelegate>
@property(nonatomic, weak) IBOutlet UIToolbar *toolbar; 
@property(nonatomic, weak) IBOutlet UIBarButtonItem *shareButton; 
@property(nonatomic, weak) IBOutlet UITextField *textField;
- (IBAction)shareButtonTapped:(id)sender; 
@end
Also add an empty placeholder for shareButtonTapped: to the end of the file (you’ll fill this in later on):
-(IBAction)shareButtonTapped:(id)sender { 
    // TODO
}
You are declaring these outlets and actions in the implementation file because they only need to be visible to the ViewController class. Now it’s time to hook them up.
Open MainStoryBoard.storyboard. Drag a Toolbar object from the Object Library (third tab on the lower half of the right sidebar) onto the main view and change the text of the button in the upper left to Share by double clicking it (or by changing the title property in the Attributes Inspector).
Next control-drag from the Share button (either in the interface view or the left sidebar) to the view controller object in the left sidebar. Select the shareButtonTapped: method from the list that appears to connect the button to that method.
Next, add a search label and search box. Drag an image view object onto your main view and set its image property to search_text.png. Currently the image looks terrible, but you can fix it. Set the mode property to center and position the search just under the toolbar. Alternatively, you can also use the Editor\Size to Fit Content menu option to resize the image view to fit its contents exactly.
Note: If you’re wondering why you used an image view instead of a label, that’s a good question. In this particular instance, you want to have a specific look for your text. So you use an image that has the look you want for the app. Note this method has a major disadvantage in that it makes localization more difficult, but is the easiest way for a certain look if you’re sure you just want one language.
For the search box, drag a text field object onto your view and align it to the right of the search label. Make sure you set its border style to none (the icon in the inspector with the dotted lines around it), as you will later give it custom styling in the code.
After you’ve added the text field, control-drag from the text field to the view controller object in the left sidebar and select “delegate” from the popup menu. This way, the ViewController class will be set as the delegate for this text field, which you will need so you can implement code to dismiss the keyboard upon return.
Finally, add a line under the search box to separate the search area from the results area. To do this, drag another image view object onto your view directly under the search box and label. Set the image property to divider_bar.png, size the image to fit, and adjust its position so that it’s centered (or size it to fit the content). Your interface should now look something like this:
The last step is to hook up the IBOutlets. Click on View Controller in the left sidebar and then select the Connections Inspector (last tab on the upper half of the right sidebar). Drag from each of the IBOutlets you created (shareButton, textField, and toolbar) to their respective interface elements.
Now comes the fun part. You are going to stylize the view to make it look less bland in the storyboard. Open up ViewController.m and add the following to the end of viewDidLoad:
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"bg_cork.png"]];
 
UIImage *navBarImage = [[UIImage imageNamed:@"navbar.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(27, 27, 27, 27)];
[self.toolbar setBackgroundImage:navBarImage forToolbarPosition:UIToolbarPositionAny
barMetrics:UIBarMetricsDefault];
 
UIImage *shareButtonImage = [[UIImage imageNamed:@"button.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(8, 8, 8, 8)];
[self.shareButton setBackgroundImage:shareButtonImage forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
 
UIImage *textFieldImage = [[UIImage imageNamed:@"search_field.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(10, 10, 10, 10)];
[self.textField setBackground:textFieldImage];

This sets the background of the entire view to a repeatable corkboard image (using the handy UIColor:colorWithPattenImage method), and sets the background image of the toolbar, share button, and text field to an image.
Build and run your app to see what the initial user interface will look like. You should see something like this:
Not bad – this is a great place to start! It looks like a bulletin board where you might want to tack all sorts of cool images. In the rest of this tutorial, you will use UICollectionView to bring this design to life!

Fetching Flickr Photos

You first task for this section is to say the section title ten times fast. OK, just kidding.
Flickr is a wonderful image sharing service that has a publicly accessible and dead- simple API for developers to use. With the API you can search for photos, add photos, comment on photos, and much more.
To use the Flickr API, you need an API key. If you are doing a real project, I recommend you sign up for one here: http://www.flickr.com/services/api/keys/apply/.
However, for test projects like this, Flickr has a sample key they rotate out every so often that you can use without having to sign up. Simply perform any search at: http://www.flickr.com/services/api/explore/?method=flickr.photos.search and copy the API key out of the URL at the bottom – it follows the “&api_key=” all the way to the next “&”. Paste it somewhere in a text editor for later use.
For example, if the URL is:
http://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=6593783 efea8e7f6dfc6b70bc03d2afb&format=rest&api_sig=f24f4e98063a9b8ecc8b522b238 d5e2f
Then the API key is: 6593783efea8e7f6dfc6b70bc03d2afb
Note: If you use the sample API key, note that it is changed periodically. So if you’re doing this tutorial over the course of several days, you might find that you have to get a new API key every so often. For this reason it might be easier to get an API key of your own from Flickr if you think you’re going to spend several days on this project.
Since this tutorial is about UICollectionView and not the Flickr API, I have created a set of classes for you that abstracts the Flickr search code. You can download them here.
Drag the four files into your project, making sure that the box Copy items into destination group’s folder (if needed) is checked, and click Finish.
The two classes you imported are:
  • Flickr: Provides a simple block-based API to perform a search and return an array of FlickrPhotos.
  • FlickrPhoto: Data about a photo retrieved from Flickr – its thumbnail, image, and metadata information such as its ID.
Feel free to take a look at the code – it’s pretty simple and might inspire you to make use of Flickr in your own projects!
When you’re ready to go, move on to the next section – it’s time to do a little prep work before hooking into Flickr.

Preparing Data Structures

You’re going to design this project so that after each time you perform a search, it displays a new “section” in the collection view with the results (rather than simply replacing the previous section). In other words, if you search for “ninjas” and then “pirates”, there will be a section of ninjas and a section of pirates in the table view. Talk about a recipe for disaster!
To accomplish this, you’re going to need to create a data structure so you can keep the data for each section separate. If you’re thinking an NSMutableDictionary would be a good fit for this, you are correct. The keys of the dictionary will be the search erms, and the values will be arrays of FlickrPhoto objects representing images that match the given search term.
Begin by building the array and dictionary to hold the search terms and results, and by creating the Flickr object that will do the searching. Open up ViewController.m and import the following classes:
#import "Flickr.h" 
#import "FlickrPhoto.h"
Next, add a few properites to the @interface declaration:
@property(nonatomic, strong) NSMutableDictionary *searchResults; 
@property(nonatomic, strong) NSMutableArray *searches; 
@property(nonatomic, strong) Flickr *flickr;
Then initialize these properties by adding the following to the end of viewDidLoad:
self.searches = [@[] mutableCopy]; 
self.searchResults = [@{} mutableCopy]; 
self.flickr = [[Flickr alloc] init];
searches is an array that will keep track of all the searches made in the app, and searchResults will associate each search term to a set of results.
Next up, you’ll learn how to populate these properties based on the user’s input.

Getting Good Results

Before you can search Flickr, you need to enter an API key. Open up Flickr.m and replace the value of kFlickrAPIKey with the API key you obtained earlier. It should look something like this:
#define kFlickrAPIKey @"ca67930cac5beb26a884237fd9772402"
You are now ready to get your Flickr search on! Switch to ViewController.m and add the following code to the end of the file (but above @end):
#pragma mark - UITextFieldDelegate methods
- (BOOL) textFieldShouldReturn:(UITextField *)textField {
    // 1
    [self.flickr searchFlickrForTerm:textField.text completionBlock:^(NSString *searchTerm, NSArray *results, NSError *error) {
    if(results && [results count] > 0) {
        // 2
        if(![self.searches containsObject:searchTerm]) {
            NSLog(@"Found %d photos matching %@", [results count],searchTerm);
            [self.searches insertObject:searchTerm atIndex:0];
            self.searchResults[searchTerm] = results; }
            // 3
            dispatch_async(dispatch_get_main_queue(), ^{
            // Placeholder: reload collectionview data
            }); 
        } else { // 1
        NSLog(@"Error searching Flickr: %@", error.localizedDescription);
    } }];
    [textField resignFirstResponder];
    return YES; 
}
When the user hits the enter key on the keyboard, this method will be called (because earlier you set the view controller up as the delegate of the text field). Here is an explanation of the code:
  1. Uses the handy Flickr wrapper class I provided to search Flickr for photos that match the given search term asynchronously. When the search completes, the completion block will be called with a reference to the searched term, the result set of FlickrPhoto objects, and an error (if there was one).
  2. Checks to see if you have searched for this term before. If not, the term gets added to the front of the searches array and the results get stashed in the searchResults dictionary, with the key being the search term.
  3. At this stage, you have new data and need to refresh the UI. Here the collection view needs to be reloaded to reflect the new data. However, you haven’t yet implemented a collection view, so this is just a placeholder comment for now.
  4. Finally, logs any errors to the console. Obviously, in a production application you would want to display these errors to the user.
Go ahead and run your app. Perform a search in the text box, and you should see a log message in the console indicating the number of search results, similar to this:
2012-07-10 21:44:16.505 Flickr Search[11950:14f07] Found 18 photos matching 1337 h4x
2012-07-10 21:44:32.069 Flickr Search[11950:14f0b] Found 20 photos matching cat pix
Note that the results are limited to 20 by the Flickr class to keep load times down.
Now that you’ve got a list of photos to display, it’s finally time to tryUICollectionView and display them on the screen!

Preparing for the UICollectionView

As you probably already know, when you use a UITableView you have to set a data source and a delegate in order to provide the data to display and handle events (like row selection).
Similarly, when you use a UICollectionView you have to set a data source and a delegate as well. Their roles are the following:
  • The data source (UICollectionViewDataSource) returns information about the number of items in the collection view and their views.
  • The delegate (UICollectionViewDelegate) is notified when events happen such as cells being selected, highlighted, or removed.
And new to UICollectionView, you have a third protocol you must implement – a protocol specific to the layout manager you are using for the collection view. In this tutorial you will be using the premade UICollectionViewFlowLayout layout manager, so you must implement the UICollectionViewDelegateFlowLayout protocol. It allows you to tweak the behaviour of the layout, configuring things like the cell spacing, scroll direction, and more.
In this section, you’re going to implement the required UICollectionViewDataSource, UICollectionViewDelegate, and UICollectionViewDelegateFlowLayout methods on your view controller, so you are all set up to work with your collection view.
To start, indicate that the view controller implements the UICollectionViewDelegate and UICollectionViewDataSource protocols by adding them to the @interface declaration at the top of ViewController.m. The @interface line should look like this:
@interface ViewController () <UITextFieldDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
Note: You might wonder why UICollectionViewDelegateFlowLayout is listed, byt UICollectionViewDelegate is not. This is because UICollectionViewDelegateFlowLayout is actually a sub-protocol of UICollectionViewDelegate, so there is no need to list both.
Next it’s time to implement those protocols!

UICollectionViewDataSource

Let’s start with the data source. Add the following code to the end of ViewController.m:
#pragma mark - UICollectionView Datasource
// 1
- (NSInteger)collectionView:(UICollectionView *)view numberOfItemsInSection:(NSInteger)section {
    NSString *searchTerm = self.searches[section];
    return [self.searchResults[searchTerm] count]; 
}
// 2
- (NSInteger)numberOfSectionsInCollectionView: (UICollectionView *)collectionView {
    return [self.searches count]; 
}
// 3
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [cv dequeueReusableCellWithReuseIdentifier:@"FlickrCell " forIndexPath:indexPath];
    cell.backgroundColor = [UIColor whiteColor];
    return cell; 
}
// 4
/*- (UICollectionReusableView *)collectionView:
(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
    return [[UICollectionReusableView alloc] init];
}*/
OK, but what do these methods do? Useful things, I promise. Read on:
  1. collectionView:numberOfItemsInSection: returns the number of cells to be displayed for a given section. Remember in this app, each search term (and its list of photo results) is in its own section. So this first finds the search term in the searches array, then looks up the photo results in the search term => results dictionary.
  2. numberOfSectionsInCollectionView: returns the total number of sections, as you may have guessed from the name. It’s a simple matter of returning the total number of searches.
  3. collectionView:cellForItemAtIndexPath: is responsible for returning the cell at a given index path. Similaly to table view cells, collection view cells are put into a reuse queue and dequeued using a reuse identifier. You’ll see in a moment how to register a specific cell class for a given reuse identifier. Unlike UITableViewCell, UICollectionViewCell doesn’t have a default cell style. So the layout of the cell has be specified by you. For now, this just returns an empty UICollectionViewCell.
  4. collectionView:viewForSupplementaryElementOfKind:atIndexPath is very simple, even though it has a crazy signature. It is responsible for returning a view for either the header or footer for each section of the UICollectionView. The variable “kind” is an NSString that determines which view (header or footer) the class is asking for. This commented it out for the time being, as implementing it will cause issues in the near term. But rest assured, you will implement it later in the tutorial!

UICollectionViewDelegate

Now that the UICollectionViewDataSource is implemented, you can turn your attention to UICollectionViewDelegate. Add the following code to the end of ViewController.m:
#pragma mark - UICollectionViewDelegate
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath 
{
    // TODO: Select Item
}
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath {
    // TODO: Deselect item
}
For now, you’re going to leave these methods as stubs. As their signatures indicate, these methods fire when you tap on a cell to select or deselect it. Note that collectionView:didDeselectItemAtIndexPath: is only called if the UICollectionView allows multiple selection – you’ll see this for yourself later on.

UICollectionViewFlowLayoutDelegate

As I mentioned early in the section, every UICollectionView has an associated layout. You’ll use the pre-made UICollectionViewFlowLayout for this project, since it’s nice and easy to use and gives you the grid-view style you’re looking for in this project.
Still in ViewController.m, add the following code to the end of the file:
#pragma mark – UICollectionViewDelegateFlowLayout
 
// 1
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
    NSString *searchTerm = self.searches[indexPath.section]; FlickrPhoto *photo =
    self.searchResults[searchTerm][indexPath.row];
    // 2
    CGSize retval = photo.thumbnail.size.width > 0 ? photo.thumbnail.size : CGSizeMake(100, 100);
    retval.height += 35; retval.width += 35; return retval;
}
 
// 3
- (UIEdgeInsets)collectionView:
(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
    return UIEdgeInsetsMake(50, 20, 50, 20); 
}
There are more delegate methods you can implement than this, but these are all you’ll need for this project.
  1. collectionView:layout:sizeForItemAtIndexPath is responsible for telling the layout the size of a given cell. To do this, you must first determine which FlickrPhoto you are looking at, since each photo could have different dimensions.
  2. Here the ternary operator is used to determine which size should be returned. The reason this is even an issue is because you will be loading the Flickr photos asynchronously. This means that sometimes a photo might be nil or have a 0 width/height. In that case, a blank photo of size 100×100 will be displayed. Finally, a height of 35px is added so that the photos have a nice border around them.
  3. collectionView:layout:insetForSectionAtIndex: returns the spacing between the cells, headers, and footers.
With this infrastructure in place, you are now ready to add the UICollectionView and all associated subviews.

UICollectionView & friends

One of the great things about UICollectionView is like table views, Apple has made it incredibly easy to set up collection views visually in the Storyboard editor. You can drag and drop UICollectionViews into your view controller, and design the layout for your UICollectionViewCells right from within the Storyboard editor! Let’s see how it works.

Adding a UICollectionView

Before you add the collection view to your storyboard, set up an IBOutlet so you can reference it. In ViewController.m, add the following to the @interface section:
@property(nonatomic, weak) IBOutlet UICollectionView *collectionView;
Now open MainStoryboard.storyboard and drag a collection view object (note: not a collection view controller) from the Object Library into your view. Position it just under the line image and size it so it fills all the space below:
I’ve set the background color of the collection view to blue so you can see its placement, but you should set the background color to transparent/clear. Otherwise, you won’t be able to see the background corkboard that gives the app its defining look.
Next, you need to set up the delegate and dataSource properties of the collection view. To do this, control-drag from the collection view to the view controller object in the scene inspector and select dataSource. Do this again, this time selecting delegate.
Finally, click on the view controller object in the scene inspector and switch to the Connections Inspector (you can select the last tab on the upper right sidebar or you can click View\Utilities\Show Connections Inspector). Drag from the collectionView property to your collection view inside of the storyboard to make the connection.
Now that this connection is made, I’m sure you’re itching to see some data displayed.  Fortunately, there are only two steps left. The first is to tell the UICollectionView what class it’s supposed to use to create cells.
Add the following line at the end of viewDidLoad in ViewController.m:
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"MY_CELL"];
Now, whenever the collection view needs to create a cell, it uses the default UICollectionViewCell class. You will be writing your own custom cells later, but this is just to get you up and running quickly.
The last step is to reload the collection view when new results have been found. Head back totextFieldShouldReturn: in ViewController.m and replace the comment “// Placeholder: reload collectionview data” with the following code:
[self.collectionView reloadData];
Build and run, and then start searching. As you search, you should see the view populating with white boxes. When you perform multiple searches, the boxes should have a gap in between them where the header view will go.
Congratulations – your collection view is now showing placeholder results for each row!
While it’s no doubt pleasing to see that you got results back, the app still doesn’t show the actual images produced by the search. Time to implement the code that fetches those images and displays them in custom UICollectionViewCells.

Creating custom UICollectionViewCells

By default, UICollectionViewCells don’t allow for much customization beyond changing the background color. You will almost always want to create your own UICollectionViewCell subclass.
Go to File\New\File…, select the iOS\Cocoa Touch\Objective-C class template, and click Next. Name the class FlickrPhotoCell, set the subclass to UICollectionViewCell and click Next. Finally, select the location to save the file and click Create.
The FlickrPhotoCell will have only a single subview, which will be a UIImageView displaying the fetched image from Flickr. Before you create the user interface, let’s set up the class for the cell. Replace the contents of FlickrPhotoCell.h with:
@class FlickrPhoto;
@interface FlickrPhotoCell : UICollectionViewCell
@property (nonatomic, strong) IBOutlet UIImageView *imageView; 
@property (nonatomic, strong) FlickrPhoto *photo;
@end
You made the UIImageView outlet public because the other classes might need to modify the displayed image after the photo has been asynchronously loaded. You’ve also added a reference to the photo that you’re displaying, since you’ll need that information later. Now you are ready to build the view.
When you added the UICollectionView to your main view, Interface Builder automatically created a UICollectionViewCell for you. Select MainStoryboard.xib to open it up in Interface Builder. Expand the list under the “Collection View” heading to reveal the cell. There are two preliminary steps that must be taken in order to use this cell, setting the cell’s class and setting its identifier.
Click on the “Collection View Cell” heading and open the Identity Inspector. Type FlickrPhotoCell inside of the Class box to denote that this cell with be of the type FlickrPhotoCell.
Now, open the Attributes Inspector and type in FlickrCell into the Identifier box. This is the reuse identifier that you will use in the cellForItemAtIndexPath method.
Next, drag and resize the cell to roughly 300×300 pixels to give you some room to work. The actual size here doesn’t matter, as the view will be dynamically resized in the delegate.
Drag an image view onto the main view. Resize it to fit within the cell view. Make sure you snap to the blue guides on all sides to ensure that the layout behaves correctly. There is only one user constraint you must add to get this to work correctly. With the image view selected, click the user constraints icon and select “Bottom Space To Superview”.
Now, open the Attributes Inspector and change the mode to Aspect Fit so that the Flickr photos size appropriately.
Select the Flickr Photo Cell in the left sidebar and open up the Connections Inspector. Drag from the imageView outlet to your UIImageView to make the connection.
The last step in customizing the view is to add the pushpin at the top. Drag another image view on to your view, this time centering it at the very top of the cell. Also, change the mode to center in the Attributes Inspector and the image to pushpin.png. The final view should look similar to this:
With that done, you surely want to populate this view with content.
Start by telling the UICollectionView to use your FlickrPhotoCell class instead of the default UICollectionView class. Open ViewController.m and add the following import after the other imports:
#import "FlickrPhotoCell.h"
Now, replace collectionView:cellForItemAtIndexPath: with this:
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    FlickrPhotoCell *cell = [cv dequeueReusableCellWithReuseIdentifier:@"FlickrCell" forIndexPath:indexPath];
    NSString *searchTerm = self.searches[indexPath.section]; 
    cell.photo = self.searchResults[searchTerm]        
    [indexPath.row];
    return cell; 
}
The first thing you’ll notice is this code dequeues a FlickrPhotoCell instead of a UICollectionViewCell. It knows to use the one you created inside of the Storyboard based on the FlickrCell identifier. Next, it determines which photo you’re referencing and sets the photo property accordingly.
Build and run the app, perform a search, and observe the results.
OK, the interface is starting to get closer to what you’re after. At least it’s now using your custom UICollectionViewCell. But why isn’t it showing your photos?
It’s because when you set the photo property of the cell, you aren’t updating the image of the UIImageView. To fix this, override the setter for the photo property in FlickrPhotoCell. First, add the following import to the top of FlickrPhotoCell.m:
#import "FlickrPhoto.h"
Then add the following to the end of the file (but before @end):
-(void) setPhoto:(FlickrPhoto *)photo { 
 
    if(_photo != photo) {
        _photo = photo; 
    }
    self.imageView.image = _photo.thumbnail; 
}
Run the app again and perform a search. This time, you should see your photos appear in each cell!
Yes! Success! Notice that each photo fits perfectly inside its cell, with the cell echoing the photo’s dimensions. The credit is due to the work you did inside of sizeForItemAtIndexPath to tell the cell size to be the size of the photo plus 35 pixels, as well as the Auto Layout settings you modified.
Note: If your view doesn’t look like this or the photos are acting weird, it likely means your Auto Layout settings aren’t correct. If you get stuck, try comparing your settings to the solution for this project.
The sizeForItemAtIndexPath code ensures that the cell is 35 pixels wider and taller than the image, and the Auto Layout rules ensure that the image view resizes and centers within the new cell frame.


Happy iCoding

1 comment: