美文网首页iOS--开发技巧程序员iOS Developer
UICollectionView仿微信图片浏览器

UICollectionView仿微信图片浏览器

作者: isletn | 来源:发表于2017-07-05 18:50 被阅读665次

    图片浏览器可以说是绝大多数APP不可或缺的基本框架。因为项目的需求,仿照微信的图片浏览器自己写了一个。

    先放上效果图:
    NLPhotoBrowser.gif
    用法:
    //传入相册图片URL数组与该相册描述即可
    NLPhotoBrowserViewController *photoBrowser = [[NLPhotoBrowserViewController alloc] initWithItems:urls describ:@"你拥有了一个 GitHub 账号之后,就可以自由的 clone 或者下载其他项目,也可以创建自己的项目,但是你没法提交代码。仔细想想也知道,肯定不可能随意就能提交代码的"];
    [self.navigationController pushViewController:photoBrowser animated:YES];
    

    考虑到代码的可用性,这东西用起来可以说是非常方便了;

    下面就记录下这个东西的实现思路吧:

    1. 首先实现图片浏览器最重要的“画布”部分——CollectionViewCell,它的构造为UISCrollView+UIImageView;
    • 首先给Cell添加子视图:
    [self.contentView addSubview:self.scrollView];
    [self.scrollView addSubview:self.imageView];
    
    • 为ScrollView添加单击、双击手势:
    UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(singleTap:)];
        UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTap:)];
        doubleTap.numberOfTapsRequired = 2;
        [singleTap requireGestureRecognizerToFail:doubleTap];
        [self.scrollView addGestureRecognizer:singleTap];
        [self.scrollView addGestureRecognizer:doubleTap];
    
    • 实现各个手势的逻辑:
    //  单击,这个逻辑需要在ViewController中实现,所以使用delegate将任务指派给ViewController,这个先不细说。
          - (void)singleTap:(UITapGestureRecognizer *)singleTap {
              if ([self.delegate respondsToSelector:@selector(singleTap)]) {
                  [self.delegate singleTap];
              }
          }
    //  双击,双击放大图片效果,用UIScrollView的zoomToRect:方法将图片缩放到指定rect里面。
          - (void)doubleTap:(UITapGestureRecognizer *)doubleTap {
              if (self.scrollView.zoomScale > 1) {
                  [self.scrollView setZoomScale:1 animated:YES];
              } else {
                  CGPoint touchPoint = [doubleTap locationInView:self.imageView];
                  CGFloat newZoomScale = self.scrollView.maximumZoomScale;
                  CGFloat xsize = self.width / newZoomScale;
                  CGFloat ysize = self.height / newZoomScale;
                  [self.scrollView zoomToRect:CGRectMake(touchPoint.x - xsize/2,       touchPoint.y - ysize/2, xsize, ysize) animated:YES];
              }
          }
    //  捏合手势
          - (void)pinchAction:(UIPinchGestureRecognizer *)pinch {
              self.imageView.transform = CGAffineTransformScale(self.imageView.transform, pinch.scale, pinch.scale);
              pinch.scale = 1;
          }
    
    • 计算Image的Size并重新设置ImageView的frame与ScrollView的contentSize:
    // 计算不同情况下ImageView的frame
      if (image.size.height / image.size.width > self.contentView.height / self.contentView.width) {
          self.imageView.height = floor(image.size.height / (image.size.width / self.imageView.width));
          self.imageView.y = 0;
      } else {
          CGFloat height = image.size.height / image.size.width * self.contentView.width;
          if (height < 1 || isnan(height)) height = self.contentView.height;
          height = floor(height);
          self.imageView.height = height;
          self.imageView.centerY = self.contentView.height / 2;
      }
      if (self.imageView.height > self.height && self.imageView.height - self.contentView.height <= 1) {
          self.imageView.height = self.height;
      }
    // 给ScrollView赋值contentSize
      _scrollView.contentSize = CGSizeMake(SCREEN_WIDTH, MAX(self.imageView.height, self.contentView.height));
      [_scrollView scrollRectToVisible:_scrollView.bounds animated:NO];
    

    在给ImageView赋值成功时需要调用这个方法。

    • 处理ScrollView滑动代理:
    #pragma mark - UIScrollViewDelegate
      - (void)scrollViewDidZoom:(UIScrollView *)scrollView {
        
        CGFloat offsetX = (scrollView.bounds.size.width > scrollView.contentSize.width)?
        (scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5 : 0.0;
        CGFloat offsetY = (scrollView.bounds.size.height > scrollView.contentSize.height)?
        (scrollView.bounds.size.height - scrollView.contentSize.height) * 0.5 : 0.0;
        self.imageView.center = CGPointMake(scrollView.contentSize.width * 0.5 + offsetX,
                                             scrollView.contentSize.height * 0.5 + offsetY);
    }
    

    通过上面的代码可以知道, 我们在获取图片时重新设置了ScrollView的contentSize。那么通过ScrollView的bounds与contentSize比较我们可以得出ImageView的宽高是否超出屏幕。若ImageView完全处于屏幕中,那么不需要改变ImageView的位置;反之,在滑动ScrollView时我们需要改变ImageView的位置使它跟着“滑动”。

    2. 然后,给“画布”一个“画框”——它的基本构造是一个装着UICollectionView的ViewController,一般通过push访问这个界面;
    // 在ViewController中创建一个控制器并通过数据源类的block给它赋值
    [self.view addSubview:self.collectionView];
    CollectionViewCellConfigure cellConfigure = ^(NLPhotoCell *cell, NLPhotoModel *photo){
        cell.photo = photo;
        cell.delegate = self;
    };
    self.dataSource = [[NLCollectionViewDataSource alloc] initWithItems:self.photos cellReuseIdentifier:kPhotoCellReuseIdentifier configureCellBlock:cellConfigure];
    self.collectionView.dataSource = self.dataSource;
    
    注意:这里的重点在于:
    • 设置Cell到CollectionView的左右边界的间隔
    _flowLayout.sectionInset = UIEdgeInsetsMake(0, kSpacing, 0, kSpacing);
    
    • CollectionView的宽度必须超过屏幕的宽度,目的是实现图片之间存在的间隔。这里可以将屏幕想象成画框,而collectionView则是画布,画框呈现的可能只是整幅画的一部分。
    _collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(-kSpacing, 0, SCREEN_WIDTH + 2 * kSpacing, SCREEN_HEIGHT) collectionViewLayout:self.flowLayout];
    
    QQ20170705-095932.png
    3. ViewController添加toolBar与describView
    [self.view addSubview:self.toolbar];
    [self.view addSubview:self.describView];
    
    image.png

    两个视图都是基于UIView的封装, 比较简单,其中需要注意的是:describView上的文字使用coreText进行绘制,性能应该是要比label高一些的。

    // 创建TextLayer
    static CATextLayer *text_layer(CGFloat contentScale, CTFontRef font, NSString *text) {
        CATextLayer *layer = [CATextLayer layer];
        layer.font = font;
        layer.fontSize = TEXT_FONT_SIZE;
        layer.string = text;
        layer.contentsScale = contentScale;
        layer.wrapped = YES; // 文字自动换行
        layer.alignmentMode = kCAAlignmentLeft;
        return layer;
    }
    // 确定TextLayer的bounds和锚点
    - (void)layoutTextLayer {
        CGRect bounds = self.bounds;
        CGPoint center = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds));
        _textLayer.bounds = CGRectMake(-kSpacing, -kSpacing, SCREEN_WIDTH-2*kSpacing, [NLUtils fetchHeightWithText:_describ font:[UIFont fontWithName:TEXT_FONT_NAME size:TEXT_FONT_SIZE]]);
        _textLayer.position = center;
    }
    // 赋值
    - (void)setDescrib:(NSString *)describ {
        _describ = describ;
        _textLayer.string = describ;
    }
    
    这里需要提到一个动画——单击屏幕隐藏ToolBar与NavigationBar
    toolBarAnimation.gif

    我们把这个动画拆分来看:

    • 首先, 隐藏NavigationBar的动画,系统提供了API:
    [self.navigationController setNavigationBarHidden:!self.navigationController.navigationBar.isHidden animated:YES];
    
    • 之后, 就是ToolBar与describView的下移了,我们使用CoreGraphics的CGAffineTransform即可轻易实现:
    if (!self.toolbar.isHidden) {
          [UIView animateWithDuration:.25 animations:^{
              self.toolbar.transform = CGAffineTransformTranslate(CGAffineTransformIdentity, 0, [UIScreen mainScreen].bounds.size.height - CGRectGetMinY(self.toolbar.frame));
              self.describView.transform = CGAffineTransformTranslate(CGAffineTransformIdentity, 0, CGRectGetHeight(self.toolbar.frame));
          } completion:^(BOOL finished) {
              self.toolbar.hidden = YES;
          }];
      }else {
          self.toolbar.hidden = NO;
          [UIView animateWithDuration:.25 animations:^{
              self.toolbar.transform = CGAffineTransformTranslate(CGAffineTransformIdentity, 0, 0);
              self.describView.transform = CGAffineTransformTranslate(CGAffineTransformIdentity, 0, 0);
          } completion:^(BOOL finished) {
                
          }];
      }
    

    将两者连贯即可实现想要的功能啦。


    Tips:
    • 父View设置透明度不影响子View, 可以用如下方法设置父View的backgroundColor:
    self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.5];
    

    最后放上代码,NLPhotoBrowser,希望能帮到你_

    相关文章

      网友评论

      • Corbin___:zoomToRect: 设置没有效果,请问有什么原因吗
      • Corbin___:我滑动还是会出现白条的间距,是不是pagingEnabled属性影响的
        Corbin___:@isletn 因为我只设置了_flowLayout.sectionInset = UIEdgeInsetsMake(0, kSpacing, 0, kSpacing);和_collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(-kSpacing, 0, SCREEN_WIDTH + 2 * kSpacing, SCREEN_HEIGHT) collectionViewLayout:self.flowLayout];
        但是滑动后,还是会出现白条在屏幕上
        Corbin___:@isletn 我滑动后,白条出现在屏幕内,这是我不想要的,我是否需要加#pragma mark - UIScrollViewDelegate
        - (void)scrollViewDidZoom:(UIScrollView *)scrollView {

        CGFloat offsetX = (scrollView.bounds.size.width > scrollView.contentSize.width)?
        (scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5 : 0.0;
        CGFloat offsetY = (scrollView.bounds.size.height > scrollView.contentSize.height)?
        (scrollView.bounds.size.height - scrollView.contentSize.height) * 0.5 : 0.0;
        self.imageView.center = CGPointMake(scrollView.contentSize.width * 0.5 + offsetX,
        scrollView.contentSize.height * 0.5 + offsetY);
        }
        isletn:滑动的时候图片之间是会有间距的,emmm,你是想实现什么效果呢?
      • 雨影:挺不错的,不过有个小bug,就是手势缩放的时候,没有记住缩放后的尺寸,导致重复缩放的时候,图片会先闪到原始大小

      本文标题:UICollectionView仿微信图片浏览器

      本文链接:https://www.haomeiwen.com/subject/ogmacxtx.html