Beginning UICollectionView In iOS 6: Part 2/2

Brandon Trebitowski Brandon Trebitowski

This post is also available in: Chinese (Simplified), Spanish, Korean

Learn how to use UICollectionView in iOS 6!

Note from Ray: This is the fourth iOS 6 tutorial in the iOS 6 Feast! This tutorial comes from our new book iOS 6 By Tutorials. Brandon Trebitowski wrote this chapter – a friend of mine and one of the newest members of the Tutorial Team. Enjoy!

This is a blog post by iOS Tutorial Team member Brandon Trebitowski, a software developer and author who regularly blogs at brandontreb.com.

In the first part of this tutorial, you saw how to use a UICollectionView to display a grid of photos.

In this second and final part of the tutorial, you will continue the journey and learn how to interact with a collection view as well as customize it a bit further with headers.

Adding a header

Now let’s make this app even cooler. It would be nice if we could add a nice header before each set of search results, to give the user a bit more context about the photo set.

You will create this header using a new class called UICollectionReusableView. Think of this class as kind of like a collection view cell, but used for other things like headers or footers.

This view can be built inside of your storyboard and connected to its own class. Start off by adding a new file via File\New\File…, select the iOS\Cocoa Touch\Objective-C class template and click Next. Name the class FlickrPhotoHeaderView and make it a subclass of UICollectionReusableView. Click Next and then Create to save the file.

There are two outlets that you must set up before beginning. Open FlickrPhotoHeaderView.m and add the following code below the #import line:

@interface FlickrPhotoHeaderView ()
@property(weak) IBOutlet UIImageView *backgroundImageView; 
@property(weak) IBOutlet UILabel *searchLabel;
@end

This sets up a class extension where you define two IBOutlets. The UILabel will display the search text for a given group of items and the image view will be the background. The image view needs to be wired up via an outlet, since it will need to be dynamically resized to fit the UILabel.

Next, open up MainStoryboard.storyboard and click on the collection view inside of the Scene Inspector on the left (you might need to drill down a couple of levels from the main view first). Open up the Attributes Inspector and check the Section Header box under Accessories:

If you look at the scene inspector on the left, a UICollectionReusableView has automatically been added under the Collection View. Click on the UICollectionReusableView to select it, and you can begin adding the subviews. To give you a little more space to work with, click the white handle at the bottom of the view and drag it down, making the view 90 pixels tall. (Or, you can set the size for the view explicitly via the Size Inspector.)

Drag an Image View from the Object Library onto your UICollectionReusableView and make sure that it’s centered. The dimensions for the image view are not important at this point (but do make it at least 400 points or so wide), just make sure you align it with the center of the view using the guides. Alternatively, you can center the object easily by using Editor\Align from the menu and selecting horizontal and vertical centering, one after the other. Also, set the mode of the image view to center.

Next, drag a label directly on top of the image view, center it using the guide and make it as wide as the image view. Change its font size to System 32.0, set its alignment to center, and set its text color to some shade of blue. When you’re done, the view should look something like this:

The last step here is to tell the UICollectionReusableView that it’s a subclass of FlickrPhotoHeaderView and hook up the outlets you added earlier.

Click on the Collection Reusable View in the scene inspector and open the Identity Inspector. Set the class to FlickrPhotoHeaderView. Then open the Attributes Inspector and set the Identifier to FlickrPhotoHeaderView. This is the identifier that will be used when dequeuing this view.

Also, go to the Attributes inspector and set the Reuse Identifier to FlickrPhotoHeaderView. This is how you will identify the header view in code. Next, open the Outlet Inspector and drag from each of the outlets to their respective interface elements (backgroundImageView and searchLabel).

If you build and run the app, you still won’t see a header (even if it is just a blank one with the word “Label”). That’s because you commented out collectionView:viewForSupplementaryElementOfKind:atIndexPath: early on.

So let’s fix that. Open ViewController.m and add the following import statement:

#import "FlickrPhotoHeaderView.h"

Next, replace the commented out collectionView:viewForSupplementaryElementOfKind:atIndexPath: with the following code (and make sure to remove the comment delimiters!):

- (UICollectionReusableView *)collectionView: (UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
    FlickrPhotoHeaderView *headerView = [collectionView dequeueReusableSupplementaryViewOfKind:
UICollectionElementKindSectionHeader withReuseIdentifier:@"FlickrPhotoHeaderView" forIndexPath:indexPath];
    NSString *searchTerm = self.searches[indexPath.section]; [headerView setSearchText:searchTerm];
    return headerView;
}

In the above code, you dequeue the header view for each section and set the search text for that cell. This tells the collection view which header to display for each section. The setSearchText method is obviously one that you haven’t written yet, so you will see an error. Time to implement it!

Open FlickrPhotoHeaderView.h and add the following code before @end:

-(void)setSearchText:(NSString *)text;

Then switch to FlickrPhotoHeaderView.m and add the following code:

-(void)setSearchText:(NSString *)text { 
    self.searchLabel.text = text;
    UIImage *shareButtonImage = [[UIImage imageNamed:@"header_bg.png"] resizableImageWithCapInsets:
    UIEdgeInsetsMake(68, 68, 68, 68)];
    self.backgroundImageView.image = shareButtonImage;
    self.backgroundImageView.center = self.center; 
}

setSearchText builds a new UIImage to span the background image, sets the label text, and then centers the text on the label.

This is a good spot to do a build and run. You will see that your UI is mostly complete.

Interacting With Cells

The final section of this tutorial will show you some ways to interact with collection view cells via touching and tapping. You’ll take two different approaches. The first will bring up a modal view displaying the image in a larger window. The second will demonstrate how to support multiple-selection in order to share the images via email.

Single selection

Your first task is to create the modal view controller that will be displayed when the user touches a cell.

Go to File\New\File…, select the iOS\Cocoa Touch\Objective-C class template and click Next. On the following screen, name this class FlickrPhotoViewController, make it a subclass of UIViewController, and check Targeted for iPad. Make sure to leave With xib for user interface unchecked, as you are going to layout the view inside the storyboard. Click Next and then Create to create the class.

Open FlickrPhotoViewController.h and replace its contents with this code:

@class FlickrPhoto;
@interface FlickrPhotoViewController : UIViewController @property(nonatomic, strong) 
FlickrPhoto *flickrPhoto; 
@end

This adds a public property for the FlickrPhoto object that will be displayed in the modal popup.

Now, open FlickrPhotoViewController.m and add these imports to the top of the file:

#import "Flickr.h" 
#import "FlickrPhoto.h"

Add this code inside the @interface section at the top:

@property (weak) IBOutlet UIImageView *imageView; 
-(IBAction)done:(id) sender;

The outlet is for the image that you’ll be displaying, and the action that is used when the user touches the Done button on the view to close the view.

Also add a placeholder for the done: method to the end of the file:

- (IBAction)done:(id)sender {
    // TODO
}

Now open MainStoryboard.storyboard. Drag a view controller object from the Object Library onto your main window. Select the new view controller, switch to the Identity Inspector, and change the class name to FlickrPhotoViewController.

Next, control-drag from your main view controller object to the new Flickr Photo View Controller and release. A context menu should pop up allowing you to create a segue. Select modal from this menu to create the segue.

The next step is to configure the segue. Click on the segue and open the Attributes Inspector. Set the Identifier to ShowFlickrPhoto and the presentation to Form Sheet. Immediately, you should see your Flickr Photo View Controller shrink down to the size of a form sheet.

Now, drag a toolbar and an image view on to the Flickr Photo View Controller’s main view. Change the text of the toolbar button to “Done” and control-drag from the button to the Flickr Photo View Controller object in the Scene Inspector. Select done: from the popup.

Next, control-drag from the Flickr Photo View Controller object to the image view you just put down. Select imageView from the popup to hook up the outlet.

Open ViewController.m and add the following property to the @interface section:

@property (nonatomic) BOOL sharing;

You’ll set this boolean to true when the user is making a multi-selection to share images (which you’ll implement next), but the normal setting will be false (which means tapping an image will bring up the modal detail view).

Place the following code in collectionView:didSelectItemAtIndexPath: (this is the callback you get for collection views when a row is tapped):

if (!self.sharing) {
    NSString *searchTerm = self.searches[indexPath.section]; 
    FlickrPhoto *photo = self.searchResults[searchTerm][indexPath.row]; 
    [self performSegueWithIdentifier:@"ShowFlickrPhoto"
sender:photo]; 
    [self.collectionView
deselectItemAtIndexPath:indexPath animated:YES]; 
} else {
    // Todo: Multi-Selection
}

If the user is not in sharing mode (for now they are not), you fetch the photo they tapped and perform the ShowFlickrPhoto segue. Notice that you are passing the photo as the sender. This allows you to determine which photo to display when the modal view is shown. Finally, the cell gets deselected so that it won’t remain highlighted.

There is one more method you must implement in this class to make this presentation work correctly. Before a segue is performed, prepareForSegue:sender is called on the object performing the segue.

Make sure you import FlickrPhotoViewController at the top of ViewController.m:

#import "FlickrPhotoViewController.h"

Next, add the following code to the end of the file:

#pragma mark - Segue
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if ([segue.identifier isEqualToString:@"ShowFlickrPhoto"]) { 
        FlickrPhotoViewController *flickrPhotoViewController = segue.destinationViewController;         
        flickrPhotoViewController.flickrPhoto = sender;
    } 
}

This method simply takes the sender of the segue (in this case, the photo tapped) and sets it as the flickrPhoto property of the destination view controller (an instance of FlickrPhotoViewController in this case). Now everything is hooked up.

Build and run, perform a search, and tap on a photo. You should see the modal view pop up with an empty image view.

Why a blank view? Why doesn’t your image display? It’s because you don’t have any code in FlickrPhotoViewController to handle setting the image from the photo on to the image view.

To fix this, open up FlickrPhotoViewController.m and add the following code:

-(void)viewDidAppear:(BOOL)animated { 
    // 1
    if(self.flickrPhoto.largeImage) {
        self.imageView.image = self.flickrPhoto.largeImage; 
    } else {
        // 2
        self.imageView.image = self.flickrPhoto.thumbnail;
        // 3
        [Flickr loadImageForPhoto:self.flickrPhoto thumbnail:NO completionBlock:^(UIImage *photoImage, NSError *error) {
            if(!error) { // 4
                dispatch_async(dispatch_get_main_queue(), ^{ self.imageView.image =
                    self.flickrPhoto.largeImage;
                }); 
            }
        }]; 
    }
}


Let’s go over this section by section.

  1. If the large photo has already been fetched, simply set imageView to display that image.
  2. If the large photo has not been fetched, display a stretched version of the thumbnail (Facebook’s app uses this technique).
  3. Tell Flickr to load the larger size for that photo.
  4. If there wasn’t an error, update the image view image in the main thread, since the photo now has a valid large photo.

Do another build and run. Perform a search and touch on a result. You should see the image presented as a modal view. It will initially appear blurry and then sharpen soon after as the larger image replaces the scaled thumbnail.

Note: If you have trouble where the thumbnail appears small before the full image loads, try setting the Content Hugging Priority and Content Compression Resistance Priority for the UIImageView to a very small amount (like 1).

Cool! Of course, if you try tapping the Done button to dismiss the image and view another image, you’ll discover that the Done button does nothing. Doh, we forgot to implement the done: method.

Add the following code to FlickrPhotoViewController.m, replacing the existing empty implementation of done:

-(void)done:(id)sender { 
    [self.presentingViewController dismissViewControllerAnimated:YES completion:^{}];
}

Now, when the user taps the Done button, the view will dismiss.

Multiple selection

Your final task for this tutorial is to let the user select multiple photos and share them with a friend. The process for multi-selection on a UICollectionView is very similar to that of a UITableView. The only trick is to tell the collection view to allow multiple selection.

The process for selection works in the following way:

  1. The user taps the Share button to tell the UICollectionView to allow multi- selection and set the sharing property to YES.
  2. The user taps multiple photos that they want to share, adding them to an array.
  3. The user taps the Done button (previously called Share), which brings up the mail composer interface.
  4. Some HTML displaying the images is injected into the body of the email.
  5. When the user sends the email or taps Cancel, the photos are deselected and the collection view goes back to single selection mode.

Start by creating the array that will hold the selected photos.

Open ViewController.m and add the following property declaration inside the @interface section:

@property(nonatomic, strong) NSMutableArray *selectedPhotos;

Now add the following line at the end of viewDidLoad:

self.selectedPhotos = [@[] mutableCopy];

Now that the array has been set up, it’s time to add some content to it. Replace the “Todo: Multi-Selection” comment in collectionView:didSelectItemAtIndexPath: with the following code:

NSString *searchTerm = self.searches[indexPath.section]; 
FlickrPhoto *photo = self.searchResults[searchTerm][indexPath.row]; 
[self.selectedPhotos addObject:photo];

This code simply determines which photo has been selected and adds it to the selectedPhotos array.

Next, replace the comment in collectionView:didDeselectItemAtIndexPath: with the following code:

if (self.sharing) {
    NSString *searchTerm = self.searches[indexPath.section]; 
    FlickrPhoto *photo = self.searchResults[searchTerm][indexPath.row];
    [self.selectedPhotos removeObject:photo]; 
}

This allows the user to deselect photos that they may have selected by accident.

Now, implement shareButtonTapped: by replacing the existing empty method with the following:

-(IBAction)shareButtonTapped:(id)sender {
    UIBarButtonItem *shareButton = (UIBarButtonItem *)sender; 
    // 1
    if (!self.sharing) {
        self.sharing = YES;
        [shareButton setStyle:UIBarButtonItemStyleDone]; 
        [shareButton setTitle:@"Done"];
        [self.collectionView setAllowsMultipleSelection:YES];
    } else { 
        // 2
        self.sharing = NO;
        [shareButton setStyle:UIBarButtonItemStyleBordered]; 
        [shareButton setTitle:@"Share"];     
        [self.collectionView setAllowsMultipleSelection:NO];
        // 3
        if ([self.selectedPhotos count] > 0) { 
            [self showMailComposerAndSend];
        }
        // 4
        for(NSIndexPath *indexPath in self.collectionView.indexPathsForSelectedItems) { 
            [self.collectionView deselectItemAtIndexPath:indexPath animated:NO]; 
        }
        [self.selectedPhotos removeAllObjects]; 
    }
}

Here’s what’s happening in this code:

  1. If the user currently isn’t in sharing mode, this code sets the UICollectionView to allow multiple selection and changes the Share button title to Done.
  2. If you got here, then the user is already in sharing mode and has tapped on the Done button. So switch the button title back to Share and disable UICollectionView multi-selection.
  3. Check if the user has any selected photos, and if so, call showMailComposerAndSend.
  4. Deselect all of the selected cells and remove all photos from the selectedPhotos array.

You won’t be able to run the application yet since you still have to implement showMailComposerAndSend. You’ll get to that momentarily.

Since this code uses MFMailComposeViewController, you must import the MessageUI framework into your project. To do this, click on the project root in the Project Navigator and then select the Flickr Search target. Then, click the Build Phases tab and expand the Link Binary With Libraries menu. Tap the (+) button, search for the MessageUI framework and click Add when you find it.

Also, be sure to add the MessageUI framework to the list of imports at the top of ViewController.m:

#import <MessageUI/MessageUI.h>

Then declare ViewController as supporting the MFMailComposeViewControllerDelegate protocol by changing the @interface line below the imports to:

@interface ViewController ()<UITextFieldDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, MFMailComposeViewControllerDelegate>

With these preliminaries out of the way, go ahead and add showMailComposerAndSend to the end of the file:

-(void)showMailComposerAndSend {
    if ([MFMailComposeViewController canSendMail]) {
        MFMailComposeViewController *mailer = [[MFMailComposeViewController alloc] init];
        mailer.mailComposeDelegate = self;
        [mailer setSubject:@"Check out these Flickr Photos"];
        NSMutableString *emailBody = [NSMutableString string]; 
        for(FlickrPhoto *flickrPhoto in self.selectedPhotos)
        {
            NSString *url = [Flickr flickrPhotoURLForFlickrPhoto: flickrPhoto size:@"m"];
            [emailBody appendFormat:@"<div><img src='%@'></div><br>",url];
        }
        [mailer setMessageBody:emailBody isHTML:YES];
        [self presentViewController:mailer animated:YES completion:^{}];
    } else {
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Mail Failure" message:@"Your device doesn't support in-app email" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
        [alert show]; 
    }
}

This code first checks to see if the user is able to send mail. It should only return false if the user hasn’t set up any mail accounts on their device. If that’s the case, it alerts the user.

The body of the email message will be some basic HTML allowing you to display the images right in the email without adding them as attachments. Once the mail subject and body are set, the mail composer is displayed to the user.

You also need to handle user actions when the user sends the email or taps Cancel. Add the following delegate method for the mail composer at the bottom of ViewController.m:

- (void)mailComposeController: (MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error {
    [controller dismissViewControllerAnimated:YES completion:^{}];
}

Now the mail composer will dismiss itself after the user is done. Do a build and run and play around with sharing multiple photos.

There is one issue – when you select a photo, there’s no visual indicator. The user will have no way to tell just by looking which ones they’ve selected and which ones they haven’t. This can be fixed very easily by setting the selectedBackgroundView of your FlickrPhotoCell.

Open FlickrPhotoCell.m, remove initWithFrame:, and in its place add the following code:

-(id)initWithCoder:(NSCoder *)aDecoder { 
    self = [super initWithCoder:aDecoder]; 
    if (self) {
        UIView *bgView = [[UIView alloc] initWithFrame:self.backgroundView.frame]; 
        bgView.backgroundColor = [UIColor blueColor]; 
        bgView.layer.borderColor = [[UIColor whiteColor] CGColor]; 
        bgView.layer.borderWidth = 4;
        self.selectedBackgroundView = bgView; 
    }
    return self; 
}

When the view is initialized from a xib file, initWithCoder: fires. The code creates a view with a blue background color and a white border and sets it as the selectedBackgroundView of the cell. Whenever the cell is in the selected state, the backgroundView is automatically swapped out for the selectedBackgroundView to indicate that the cell is selected.

Build and run, tap Share, and select some photos. It should look something like this:

Woot! That’s a very clear indicator. Make sure deselection works as well by tapping the photos again; the blue highlighting should disappear.

Where To Go From Here?

Here is the complete example project that you developed in the tutorial series.

Congratulations, you have finished creating your very own cool and stylish Flickr photo browser, complete with a cool UICollectionView based grid view!

In the process, you learned how to make custom UICollectionViewCells, create headers with UICollectionReusableView, detect when rows are tapped, implement multi-cell selection, and much more!

Implementing this same app prior to iOS 6 would have been a major pain and likely would have made you start searching for third party library solutions. Now, it’s just as easy as using the UITableView you know and love!

But guess what – there’s more! You’ve only scratched the surface of what a UICollectionView can do. In the next chapter of iOS 6 By Tutorials, you’ll expand this project to support custom layouts, such as a “pinch to photo stack” layout, a “Pinterest style” layout, and a “cover flow style” layout. In the process, you’ll learn how to add and delete elements from the collection view and a lot more!

I hope this chapter has got you excited about the ease of use and possibilities with the new iOS 6 collection views, and I hope to show you even more cool things you can do!

If you have any questions or comments about UICollectionViews or this tutorial, please join the forum discussion below!


This is a blog post by iOS Tutorial Team member Brandon Trebitowski, a software developer and author who regularly blogs at brandontreb.com.

Brandon Trebitowski
Brandon Trebitowski

Brandon Trebitowski is a software developer and author from Albuquerque, New Mexico. Brandon holds a BS in Computer Science from The University of New Mexico and has been developing software for the last 10 years. In 2010, he coauthored the book iPhone & iPad In Action published by Manning publishing.

Brandon is currently the Director of Mobile Engineering for ELC Technologies and a regularly blogs at http://brandontreb.com.

User Comments

26 Comments

[ 1 , 2 ]
  • Is there a way to add a header view before the first section header view, very much like the
    Code: Select all
    tableHeaderView
    property on
    Code: Select all
    UITableView
    ?

    I've managed to create a subclass of UICollectionReusableView for the first section header and increase its size, however I can not get touches inside the section header and the approach also seems like a hack and there must be a proper way.

    Thanks.
    christian-hansen
  • FYI - I get multiple errors if I try to build the example project that is linked from the Tutorial with XCode 4.5 and an iOS 6.0 deployment target.

    Adding the QuartzCore framework, and an import of:
    #import <QuartzCore/QuartzCore.h>

    to the FlickrPhotoCell.m class seemed to address it.
    DenVog
  • im just going over this tutorial now. in addition to the quartz import i had to add (see above) i also had to add the MFMailComposeViewControllerDelegate in ViewController.m to the line:

    Code: Select all
    @interface ViewController () <UITextFieldDelegate,UICollectionViewDataSource,UICollectionViewDelegate>


    now it compiles without any errors or warnings! thanks guys!
    jon01
  • Is there a way to center this two-part tutorial around one single Flickr album? I'm attempting to create an app out of just the photos in my flickr feed, which I will add to daily for users to view daily. I assume this will mean skipping over the search function and just loading up the album in a table, tap to view image, format.

    I'm pretty new to this, so layman's terms, please :p

    Any help would be appreciated. Thanks!
    a55
  • There's a bug that may cause 2nd, 3rd, etc of the header view to disappear, as some in this thread pointed out.
    The simplest is to comment this line out:

    // self.backgroundImageView.center = self.center;

    self.center seemed to be more "global", while self.backgroundImageView.center is local to the header view (its parent).

    Once you do this, you will see header view rendered for all sections.
    kechan
  • Hi! I cannot get the label to expand and it truncates the letters instead of resizing to fit. It also does not expand the background... what could I be doing wrong... I pretty much copy and pasted your code, but that did not help. (Just a newbie question... sorry!)
    Julianee
  • Hi Gabor,
    Did you find a solution to this? I'm getting the same error.

    Thanks,
    Julianee

    gabor.orosz wrote:Hello raywenderlich.com Team!

    Tested the final version of Immediate FlikrSearch project, and experienced an issue with switching layouts. This is how the use case looks like:
    - Start app
    - Search for anything (eg. "hello")
    - Switch to second layout
    - App will crash with the following error message
    2012-10-29 14:48:24.329 FlickrSearch[3853:c07] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index 1 beyond bounds [0 .. 0]'
    *** First throw call stack:

    Do you have any idea what the problem is? How can it be fixed?

    Thanks in advance,
    Gabor Orosz
    Julianee
  • Thanks for this article. I am having some major problems with the UICollectionView. I am essentially trying to subclass it because I have numerous pages that require the same view. I describe my code and issue here http://stackoverflow.com/q/19123984/2804909

    any help you could offer would be fantastic..
    mdaytrades
  • Because you have used AutoLayout,So comment the line "self.backgroundImageView.center = self.center;",else you can't see the HeadView's background image when existing multiple sections.
    Jagie
  • Because you have used AutoLayout,so comment the line " self.backgroundImageView.center = self.center;",else you can't see the HeadView's background image when existing multiple sections.
    Jagie
  • [quote="rwenderlich"]This is the official thread to discuss the following blog post: Beginning UICollectionView In iOS 6: Part 2/2[/quote

    i want to move cells in collectionview. please help me out???
    ankit.n.jain
[ 1 , 2 ]

Other Items of Interest

Ray's Monthly Newsletter

Sign up to receive a monthly newsletter with my favorite dev links, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

Hang Out With Us!

Every month, we have a free live Tech Talk - come hang out with us!


Coming up in July: Facebook Pop Tech Talk!

Sign Up - July

RWDevCon Conference?

We are considering having an official raywenderlich.com conference called RWDevCon in DC in early 2015.

The conference would be focused on high quality Swift/iOS 8 technical content, and connecting as a community.

Would this be something you'd be interested in?

    Loading ... Loading ...

Our Books

Our Team

Tutorial Team

  • Jake Gundersen

... 49 total!

Update Team

  • Andy Pereira
  • Riccardo D'Antoni

Editorial Team

  • John Clem

... 23 total!

Code Team

  • Orta Therox

... 1 total!

Translation Team

  • David Xie
  • Myeong Hoon
  • Sungwook Yeom

... 33 total!

Subject Matter Experts

  • Richard Casey

... 4 total!