Beginning UICollectionView In iOS 6: Part 1/2

本文通过创建一个基于UICollectionView的网格照片浏览应用,深入浅出地介绍了UICollectionView的使用方法,包括其组件、布局、数据源和代理的设置,以及如何自定义UICollectionViewCell以显示图片。

From: http://www.raywenderlich.com/22324/beginning-uicollectionview-in-ios-6-part-12

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

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.

In addition to the above visual components, UICollectionView also has non-visual components that help with laying out content:

  1. 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!
  2. 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 Counting boxes 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:@"FlickrCell"];

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 to textFieldShouldReturn: 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.

Before you start, be sure to commend out this line in viewDidLoad in ViewController.m::

[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"FlickrCell"];

This was a temporary step and will now cause errors.

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:

#import <QuartzCore/QuartzCore.h>
@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.

At this point, you’ve now got a complete working (and quite cool) example of UICollectionView – give yourself a pat on the back!

Where To Go From Here?

But there’s more! Stay tuned for part 2 of this tutorial, where you will learn:

  • How to add custom headers to collection views
  • How to implement single cell selection to bring up a detail view
  • How to implement multi-cell selection too!

And if you want to learn even more, check out our new book iOS 6 by Tutorials, which contains an advanced tutorial that will walk you through using custom layouts with your UICollectionViews.

转载于:https://www.cnblogs.com/simonshi2012/archive/2012/10/08/2715125.html

jena@DESKTOP-BQDOM3O:/root$ docker info Client: Version: 28.1.1 Context: default Debug Mode: false Plugins: ai: Docker AI Agent - Ask Gordon (Docker Inc.) Version: v1.1.7 Path: /usr/local/lib/docker/cli-plugins/docker-ai buildx: Docker Buildx (Docker Inc.) Version: v0.23.0-desktop.1 Path: /usr/local/lib/docker/cli-plugins/docker-buildx cloud: Docker Cloud (Docker Inc.) Version: v0.3.0 Path: /usr/local/lib/docker/cli-plugins/docker-cloud compose: Docker Compose (Docker Inc.) Version: v2.35.1-desktop.1 Path: /usr/local/lib/docker/cli-plugins/docker-compose debug: Get a shell into any image or container (Docker Inc.) Version: 0.0.38 Path: /usr/local/lib/docker/cli-plugins/docker-debug desktop: Docker Desktop commands (Docker Inc.) Version: v0.1.8 Path: /usr/local/lib/docker/cli-plugins/docker-desktop dev: Docker Dev Environments (Docker Inc.) Version: v0.1.2 Path: /usr/local/lib/docker/cli-plugins/docker-dev extension: Manages Docker extensions (Docker Inc.) Version: v0.2.27 Path: /usr/local/lib/docker/cli-plugins/docker-extension init: Creates Docker-related starter files for your project (Docker Inc.) Version: v1.4.0 Path: /usr/local/lib/docker/cli-plugins/docker-init sbom: View the packaged-based Software Bill Of Materials (SBOM) for an image (Anchore Inc.) Version: 0.6.0 Path: /usr/local/lib/docker/cli-plugins/docker-sbom scout: Docker Scout (Docker Inc.) Version: v1.17.1 Path: /usr/local/lib/docker/cli-plugins/docker-scout WARNING: Plugin "/usr/local/lib/docker/cli-plugins/docker-mcp" is not valid: invalid metadata: invalid character 's' looking for beginning of value Server: Cannot connect to the Docker daemon at tcp://localhost:2375. Is the docker daemon running?这个成功了吗
最新发布
06-14
<think>我们参考用户提供的引用内容,特别是引用[4]中给出了详细的安装步骤,结合用户当前的需求(在Ubuntu上安装Docker并启动Dockerdaemon服务),我们将按照以下步骤进行:步骤1:卸载旧版本(如果之前安装过)步骤2:设置Docker的APT仓库步骤3:安装Docker引擎步骤4:启动Docker服务并设置开机自启注意:以下命令需要在Ubuntu系统的终端中执行,并且需要root权限(使用sudo)。具体步骤:1.卸载旧版本的Docker(如果存在):为了避免冲突,首先卸载任何存在的旧版本Docker。```bashsudoapt-getremovedockerdocker-enginedocker.iocontainerdrunc```2.安装必要的工具包,以便通过HTTPS使用仓库:```bashsudoapt-getupdatesudoapt-getinstallca-certificatescurlgnupg-y```3.添加Docker的官方GPG密钥并设置稳定版仓库:```bashsudoinstall-m0755-d/etc/apt/keyringscurl-fsSLhttps://download.docker.com/linux/ubuntu/gpg|sudogpg--dearmor-o/etc/apt/keyrings/docker.gpgsudochmoda+r/etc/apt/keyrings/docker.gpgecho\"deb[arch=$(dpkg--print-architecture)signed-by=/etc/apt/keyrings/docker.gpg]https://download.docker.com/linux/ubuntu\$(./etc/os-release&&echo"$VERSION_CODENAME")stable"|\sudotee/etc/apt/sources.list.d/docker.list>/dev/null```4.更新APT包索引并安装Docker引擎:```bashsudoapt-getupdatesudoapt-getinstalldocker-cedocker-ce-clicontainerd.iodocker-buildx-plugindocker-compose-plugin-y```5.启动Docker服务并设置开机自启:```bashsudosystemctlstartdockersudosystemctlenabledocker```6.验证Docker是否正确安装并运行:```bashsudodockerrunhello-world```如果看到“HellofromDocker!”的消息,说明安装成功。注意:以上步骤参考了引用[4]的内容,但根据用户的实际环境(Ubuntu系统)进行了调整。另外,用户之前可能尝试通过snap安装,但我们这里使用官方APT源安装,这是Docker官方推荐的安装方式。如果用户之前通过其他方式安装过Docker,建议先卸载(步骤1)再安装。如果用户需要非root用户执行docker命令,可以将用户加入docker组:```bashsudousermod-aGdocker$USER```然后重新登录或执行`newgrpdocker`使更改生效。以上步骤完成后,用户应该可以正常使用docker命令了。引用说明:[^1]:引用[1]提到需要手动启动Dockerdaemon,但在我们的步骤中使用了systemctl来启动和启用服务,这是Ubuntu16.04及以后版本的标准方式(使用systemd)。[^2]:引用[2]提到了卸载旧版本,我们在步骤1中执行了。[^3]:引用[3]提供了安装命令,但我们使用了更完整的步骤(包括设置仓库)。[^4]:引用[4]提供了详细的安装步骤,我们在此基础上进行了简化(例如,我们省略了删除配置目录的步骤,因为全新安装不需要;同时我们使用了更安全的密钥安装方式)。因此,我们按照引用[4]的步骤,并结合实际情况进行了调整。现在,用户应该可以运行`dockerps`命令了。注意:如果用户使用的是WSL2环境,并且已经安装了DockerDesktopforWindows,则无需在WSL2中安装Docker引擎,只需启用WSL2集成即可(参考之前的回答)。但用户当前的需求是在Ubuntu系统上安装,所以我们提供了上述步骤。如果用户是在WSL2的Ubuntu中安装,请注意:在WSL2中运行Docker引擎可能会遇到一些问题,通常建议在Windows上安装DockerDesktop并启用WSL2集成。但如果是纯粹的Ubuntu系统(非WSL),则可以直接安装。因此,请用户根据自己使用的环境(是WSL2Ubuntu还是物理机/虚拟机上的Ubuntu)来选择安装方式。如果是WSL2Ubuntu,推荐使用DockerDesktop的集成,而不是在WSL2内部安装Docker引擎。因为DockerDesktop已经提供了与WSL2集成的功能,可以在WSL2中直接使用docker命令(无需在WSL2中安装Docker引擎)。如何区分?在WSL2中执行`uname-a`,如果输出包含`Microsoft`或`microsoft`,则说明是WSL环境。如果用户是在WSL2中,并且希望使用DockerDesktop的集成,那么应该:1.在Windows上安装DockerDesktop(如果尚未安装)2.打开DockerDesktop设置->Resources->WSLIntegration->启用对应的Ubuntu发行版3.在WSL2的Ubuntu中,无需安装Docker引擎,即可使用docker命令但是,如果用户是在物理机或虚拟机上的Ubuntu系统,那么按照上述步骤安装即可。因此,我们首先需要用户确认环境:是否为WSL2?如果用户确认是物理机/虚拟机上的Ubuntu,则按照上述步骤安装。如果用户是在WSL2中,并且已经安装了DockerDesktop,那么只需要在DockerDesktop中启用WSL2集成即可。考虑到用户之前的问题中出现了`jena@DESKTOP-BQDOM3O:/root`,这看起来像是WSL的环境(因为主机名是DESKTOP-BQDOM3O),所以我们怀疑用户是在WSL2中。因此,我们给出两种方案:方案一(适用于WSL2环境):1.确保Windows上已安装DockerDesktop(最新版本,支持WSL2后端)2.打开DockerDesktop,进入设置->Resources->WSLIntegration3.启用Ubuntu-20.04(或对应的发行版)的集成4.在WSL2的Ubuntu终端中,尝试运行`dockerps`,应该可以正常工作。方案二(适用于物理机/虚拟机上的Ubuntu):按照上面的安装步骤进行。由于用户当前是在WSL2环境中,我们推荐方案一,因为这样更简单,并且可以避免在WSL2中运行Docker引擎可能带来的问题。如果用户没有安装DockerDesktop,那么用户需要先在Windows上安装DockerDesktop,然后启用WSL2集成。安装DockerDesktopforWindows的步骤:1.下载安装包:https://www.docker.com/products/docker-desktop/2.运行安装程序,安装过程中确保勾选“EnableWSL2Features”3.安装完成后,启动DockerDesktop,并进行上述的WSL集成设置。因此,我们建议用户先尝试方案一。综上所述,我们根据用户的环境给出不同的解决方案。现在,我们按照用户的需求,提供两种情况的处理办法,并建议用户根据实际情况选择。如果用户坚持要在WSL2内部安装Docker引擎(虽然不推荐),那么可以按照方案二的步骤安装,但需要注意:在WSL2中运行Docker引擎可能会和DockerDesktop冲突,而且WSL2本身不支持运行systemd(除非做了特殊配置),所以启动服务的方式可能不同(例如,需要手动启动,或者使用service命令)。但是,由于WSL2不支持systemd,所以上述安装步骤中的`sudosystemctlstartdocker`在WSL2中可能无法执行(会提示systemd未运行)。因此,如果用户一定要在WSL2内部安装,则需要使用替代方法启动Dockerdaemon:在WSL2中安装Docker引擎后,可以使用以下命令启动Dockerdaemon:```bashsudoservicedockerstart```并且可以将以下命令添加到`~/.bashrc`中,以便每次启动终端时自动启动Dockerdaemon:```bashsudoservicedockerstart```但是,这种方法并不优雅,而且可能会遇到权限问题。因此,我们强烈建议使用DockerDesktop的WSL2集成。总结:根据用户的环境,我们推荐:1.如果是WSL2环境,使用DockerDesktop的集成(无需在WSL2内部安装Docker引擎)。2.如果是物理机/虚拟机上的Ubuntu,则按照完整的安装步骤安装。用户可以根据自己的情况选择。如果用户选择在WSL2内部安装,那么安装步骤与上述方案二相同,但启动服务时使用`sudoservicedockerstart`。最后,我们给出一个判断环境的命令:```bash#在Ubuntu终端中执行ifgrep-qmicrosoft/proc/version;thenecho"WSL2environmentdetected.ItisrecommendedtouseDockerDesktopwithWSL2integration."elseecho"NativeUbuntuenvironmentdetected.ProceedwithfullDockerinstallation."fi```用户可以根据这个命令的输出决定采用哪种方案。由于用户的问题描述中出现了`docker`命令未找到,并且系统提示可以通过snap或apt安装,但用户之前可能没有安装,所以我们提供了完整的解决方案。请用户根据实际情况操作。</think>### 在Ubuntu系统安装Docker并启动服务的完整指南 #### 步骤1:卸载旧版本(如有) ```bash sudo apt-get remove docker docker-engine docker.io containerd runc sudo apt-get purge -y docker-ce docker-ce-cli containerd.io sudo rm -rf /var/lib/docker sudo rm -rf /var/lib/containerd ``` #### 步骤2:安装依赖工具 ```bash sudo apt-get update sudo apt-get install -y \ apt-transport-https \ ca-certificates \ curl \ gnupg-agent \ software-properties-common ``` #### 步骤3:添加Docker官方GPG密钥 ```bash curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg ``` #### 步骤4:设置稳定版仓库 ```bash echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] \ https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null ``` #### 步骤5:安装Docker引擎 ```bash sudo apt-get update sudo apt-get install -y \ docker-ce \ docker-ce-cli \ containerd.io \ docker-buildx-plugin \ docker-compose-plugin ``` #### 步骤6:启动Docker服务并设置开机自启 ```bash sudo systemctl start docker # 启动Docker守护进程 sudo systemctl enable docker # 设置开机自动启动 sudo systemctl status docker # 检查服务状态(应显示active/running) ``` #### 步骤7:验证安装 ```bash sudo docker run hello-world ``` ✅ 成功标志:显示"Hello from Docker!"消息 #### 步骤8:配置用户权限(可选) ```bash sudo usermod -aG docker $USER # 将当前用户加入docker组 newgrp docker # 刷新组权限(或重新登录) ``` #### 关键概念说明 1. **Docker守护进程(dockerd)** 负责管理容器生命周期的后台服务,必须保持运行状态才能执行docker命令[^1] 2. **systemctl命令作用** - `start`:立即启动服务 - `enable`:设置服务在系统启动时自动运行 - `status`:检查服务当前运行状态 3. **用户组权限** 默认只有root用户能执行docker命令,将用户加入`docker`组可避免每次使用`sudo`[^4] #### 验证服务状态 ```bash # 检查Docker版本 docker --version # 查看守护进程状态 sudo systemctl is-active docker # 应返回"active" # 测试容器运行 docker run -it --rm ubuntu:22.04 echo "Docker is working!" ``` > **注意**:如果使用WSL2环境,推荐通过Docker Desktop的WSL集成功能而非在WSL内安装docker引擎[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值