图片浏览器可以说是绝大多数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,希望能帮到你_
网友评论
但是滑动后,还是会出现白条在屏幕上
- (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);
}