UICollectionView 基本使用(配合Flickr API)

在iOS6的新特性中,UICollectionView是最吸引人的新控件之一。有了这个,就不需要再去git上面找各种GridView的源码,也不需要自己用UITableView去模拟一个了。

本文主要通过一个demo介绍UICollectionView的基本使用。

首先看下demo完成后的截图


demo的主要组成部分就是上面的UITextField和下面的UICollectionView

这个应用是通过Flickr的API,根据输入的关键词获取图片,并显示在UICollectionView中。

下面开始简单地说明这个demo,一些和collectionView相关性不强的内容,比如Flickr API的调用,我就简单说下就好,最后会给出源码

Storyboard


其中顶部的toolBar和BarButtonItem和这次的demo没什么关系,这次用到的是在search隔壁的UItextField还有下面的绿色部分就是UICollectionView,在Collection里面的那块就是UICollectionViewCell。

熟悉UITableView的同学都这些应该都比较熟悉,都是View+Cell的组合,等会看到UICollectionView的dataSource和Delegate也会觉得很熟悉的。


下面先看看设置Toolbar背景的一段代码

    UIImage *navBarImage = [[UIImage imageNamed:@"navbar.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(27, 27, 27, 27)];
    [self.toolbar setBackgroundImage:navBarImage forToolbarPosition:UIToolbarPositionAny barMetrics:UIBarMetricsDefault];

其中使用了- (UIImage *)resizableImageWithCapInsets:(UIEdgeInsets)Insets;这个方法的作用是在图片中规定一块矩形区域,当图片在拉伸的时候,只有矩形区域内的部分会被拉伸,矩形区域外的部分保持原状。使用这个可拉伸的图片的一个好处就是图片的容量小了,在需要很多图片的App里,容量也会下降啦(还可以和用户吹说是优化了.....哈哈,开玩笑)。



在说UICollectionView之前,需要先说清楚图片的数据是怎么来的。

下面是viewController.m中定义的property

@property (nonatomic, weak) IBOutlet UIToolbar *toolbar;
@property (nonatomic, weak) IBOutlet UIBarButtonItem *shareButton;
@property (nonatomic, weak) IBOutlet UITextField *textField;
@property (nonatomic, strong) NSMutableDictionary *searchResults;
@property (nonatomic, strong) NSMutableArray *searches;
@property (nonatomic, strong) Flickr *flickr;
@property (nonatomic, weak) IBOutlet UICollectionView *collectionView;

前面已经说过,数据是通过调用Flickr的API来获取的,要调用Flickr的API需要一个AppKey,可以去这里申请Flickr AppKey申请

使用TextVField的delegate方法

- (BOOL)textFieldShouldReturn:(UITextField *)textField

来开启搜索,使用用户输入的关键词进行搜索操作。

- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
    [self.flickr searchFlickrForTerm:textField.text completionBlock:^(NSString *searchTerm,NSArray *results,NSError *error){
        if(results && results.count>0) {

            if(![self.searches containsObject:searchTerm]) {
                NSLog(@"Found %d photos matching %@",[results count],searchTerm);
                [self.searches insertObject:searchTerm atIndex:0];
                self.searchResults[searchTerm] = results;
            }

            dispatch_async(dispatch_get_main_queue(), ^{
                //reload collectionView data
                [self.collectionView reloadData];
            });
        } else {
            NSLog(@"Error searching Flickr: %@",error.localizedDescription);
        }

    }];

    [textField resignFirstResponder];
    return YES;
}
搜索是通过调用Flickr类的方法

- (void)searchFlickrForTerm:(NSString *) term completionBlock:(FlickrSearchCompletionBlock) completionBlock;

其中 FlickrSearchCompletionBlock的定义是:typedef void (^FlickrSearchCompletionBlock)(NSString *searchTerm, NSArray *results, NSError *error);

包含搜索的关键词、返回的结果以及错误。

在看这个函数的实现之前,我们需要先看看我们调用的Flickr的API返回的数据是怎么样的,才能看懂下面的操作


上图,就是调用Flickr的搜索API并传进相应参数之后返回的结果,最底下的stat就是判断请求结果是否成功的标志,数组Photo就是返回的图片的数据,要拿到图片还要再使用Photo中的参数发送另一条请求,详细是哪个API可以自己看源码,我这里不贴出

- (void)searchFlickrForTerm:(NSString *) term completionBlock:(FlickrSearchCompletionBlock) completionBlock
{
    NSString *searchURL = [Flickr flickrSearchURLForSearchTerm:term];
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_async(queue, ^{
        NSError *error = nil;
        NSString *searchResultString = [NSString stringWithContentsOfURL:[NSURL URLWithString:searchURL]
                                                           encoding:NSUTF8StringEncoding
                                                              error:&error];
        if (error != nil) {
            completionBlock(term,nil,error);
        }
        else
        {
            // Parse the JSON Response
            NSData *jsonData = [searchResultString dataUsingEncoding:NSUTF8StringEncoding];
            NSDictionary *searchResultsDict = [NSJSONSerialization JSONObjectWithData:jsonData
                                                                              options:kNilOptions
                                                                                error:&error];
            if(error != nil)
            {
                completionBlock(term,nil,error);
            }
            else
            {
                NSString * status = searchResultsDict[@"stat"];
                if ([status isEqualToString:@"fail"]) {
                    NSError * error = [[NSError alloc] initWithDomain:@"FlickrSearch" code:0 userInfo:@{NSLocalizedFailureReasonErrorKey: searchResultsDict[@"message"]}];
                    completionBlock(term, nil, error);
                } else {
                
                    NSArray *objPhotos = searchResultsDict[@"photos"][@"photo"];
                    NSMutableArray *flickrPhotos = [@[] mutableCopy];
                    for(NSMutableDictionary *objPhoto in objPhotos)
                    {
                        FlickrPhoto *photo = [[FlickrPhoto alloc] init];
                        photo.farm = [objPhoto[@"farm"] intValue];
                        photo.server = [objPhoto[@"server"] intValue];
                        photo.secret = objPhoto[@"secret"];
                        photo.photoID = [objPhoto[@"id"] longLongValue];
                        
                        NSString *searchURL = [Flickr flickrPhotoURLForFlickrPhoto:photo size:@"m"];
                        NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:searchURL]
                                                                  options:0
                                                                    error:&error];
                        UIImage *image = [UIImage imageWithData:imageData];
                        photo.thumbnail = image;
                        
                        [flickrPhotos addObject:photo];
                    }
                    
                    completionBlock(term,flickrPhotos,nil);
                }
            }
        }
    });
}

- (void)searchFlickrForTerm:(NSString *) term completionBlock:(FlickrSearchCompletionBlock) completionBlock;

的实现中,我们看到,首先是调用 [Flickr flickrSearchURLForSearchTerm:term] 方法,返回包含了搜索关键词的Flickr API

然后通过GCD发起异步请求,在返回的数据中获取photo数组的数据,其中FlickrPhoto是自己定义的一个类,

@interface FlickrPhoto : NSObject
@property(nonatomic,strong) UIImage *thumbnail;
@property(nonatomic,strong) UIImage *largeImage;

// Lookup info
@property(nonatomic) long long photoID;
@property(nonatomic) NSInteger farm;
@property(nonatomic) NSInteger server;
@property(nonatomic,strong) NSString *secret;

将FlickfPhoto每一项存到flickrPhotos中并调用CompletionBlock回传给viewController.m中的

- (BOOL)textFieldShouldReturn:(UITextField *)textField

方法,在该方法中,我们将返回的数据以及关键词分别保存到searchResults中和searches中,拿到新数据之后,应该要刷新我们的CollectionView显示新的数据,所以在主线程中调用[self.collectionView reloadData];

获取数据的过程如果还是不怎么清楚的朋友,可以自己看源码琢磨下,下面开始就进入UICollecionView的部分(饶了我吧....现在才进)

首先第一件事肯定是拉一个UICollectionView的控件啦,这次的demo没有使用UICollectionViewController的原因也很明确啦,因为我们需要在里面加个UITextField嘛,而且这也是更一般的使用场景,直接使用一个UICollectionViewController的情况较少,除非真的对需求非常肯定,不然后期要扩展或者修改的话都比较麻烦。

所以我的建议是,都是拉一个UICollectionView到Controller,UITableView也是同理。

关于UICollectionView,下面是三个最重要的概念,英文表达的很清楚,所以不打算翻译

UICollectionViewCell – similar to a UITableViewCell in UITableView. These cells 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.

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!

UICollectionViewFlowLayout – You can subclass UICollectionViewLayout to create your own custom layouts ,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.

先说下Cell,在tableView中,我们经常需要对Cell进行各种自定义,比较方便的方法就是继承UITableViewCell的类,然后往Cell中拉各种控件,最后将Outlet都映射到cell的子类中去,再替换掉UItableViewCell的类的默认值。

在UICollectionView中也是一样的做法,首先先新建一个UICollectionViewCell的子类

@class FlickrPhoto;

@interface FlickrPhotoCell : UICollectionViewCell

@property (nonatomic, strong) IBOutlet UIImageView *imageView;
@property (nonatomic, strong) FlickrPhoto *photo;

@end

在类中有两个属性,一个是前面拿数据的时候,用来存储每张照片数据的FlickrPhoto以及一个ImageView的Outlet,这个ImageView就是放在Cell中的控件,直接拉进去就行,然后将Outlet映射到这个类的ImageView,还有一个要做的就是要修改默认的UICollectionViewCell的类


在图片的右上方可以看到,在选中了UICollectionViewCell之后,右边的Custom Class有个class的地方,原本填的就是默认的UICollectionViewCell,现在我们就修改它为FlickrPhotoCell,在左边的Scence结构图也可以看到Cell已经修改了。

至于要怎么用自己的Cell,在后面说哈,下面说Layout,不同的Layout会让UICollectionView显示不同的效果,我们完全可以自己写Layout,不过因为这是第一次使用,所以我们就用苹果帮我们写好的FlowLayout来做就好了,自定义的等学了再写(我现在也不会....)

因为有UITableView的经验,所以我们知道要让CollectionView显示数据,需要dataSource和Delegate

下面就先让viewController作为UICollectionView的DataSource和Delegate,我这里是直接在storyboard拉的,也可以用代码写。

@interface ViewController () <UITextFieldDelegate,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout>
可以看到,我们并不是使用UICollectionViewDelegate,而是用了UICollectionViewDelegateFlowLayout。

首先,这三个delegate都是需要的,因为我们要设置每个Cell的大小,所以需要FlowLayout的delegate中的方法,而需要响应每个cell的select之类的时间的话,需要UICollectionViewDelegate的方法,但是因为UICollectionViewDelegateFlowLayout是前者的子类,所以这里只需要写一个就好了。

在dataSource中,我们实现下面四个方法

#pragma mark - UICollectionView DataSource
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
    NSString *searchItem = self.searches[section];
    return [self.searchResults[searchItem] count];
}

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
    return [self.searches count];
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    FlickrPhotoCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"FlickrCell" forIndexPath:indexPath];
    NSString *searchTerm = self.searches[indexPath.section];
    cell.photo = self.searchResults[searchTerm][indexPath.row];
    return cell;
}

- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
    return [[UICollectionReusableView alloc] init];
}
前两个很熟悉了,第三个也很熟悉,这里也看到了如何使用自己的Cell,大家可能也注意到了这里的cell.photo只是一个字典,那我们要怎么让图片显示出来呢,切到FlickrPhotoCell.m中看看photo属性的setter就知道了。

- (void)setPhoto:(FlickrPhoto *)photo
{
    if(_photo != photo) {
        _photo = photo;
    }
    self.imageView.image = _photo.thumbnail;
}

上面的第四个方法返回每个section的Header View和Footer view,因为这个demo不涉及 所以不实现也没什么关系。

#pragma mark - UICollectionView Delegate
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
    //select Item
}

- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath
{
    //deselect item
}

#pragma mark - UICollectionViewDelegateFlowLayout

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
    NSString *searchTerm = self.searches[indexPath.section];
    FlickrPhoto *photo = self.searchResults[searchTerm][indexPath.row];

    CGSize retval = photo.thumbnail.size.width > 0 ? photo.thumbnail.size : CGSizeMake(100, 100);
    retval.height += 35;
    retval.width += 35;
    return retval;
}

-(UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
{
    return UIEdgeInsetsMake(50, 20, 50, 20);
}

前两个方法就是当Item被选中和选中后放开的响应。

第三个方法就是FlowLayout Delagte中的一个方法,返回每一个Cell的大小,我们这里是根据每张图片的大小,来设定Cell的大小的。

最后一个方法是返回cell之见、header和footer之见的间隔。


好了 这个demo差不多就是这些咯 这个demo是在http://www.raywenderlich.com/22324/beginning-uicollectionview-in-ios-6-part-12学的,还有第二个,学完后再写。

源码:UICollectionView demo

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值