您的位置:首页 > 移动开发 > IOS开发

iOS-SDPhotoBrowser

2016-12-12 00:00 204 查看
一、前言

1、微博、资讯类型的APP客户端应用都会有图片浏览的需求,因此网络上涌现出大量的第三方图片浏览器插件,不管是用什么样的技术,大体的都能满足用户浏览图片的需求,例如单击图片隐藏、双击图片放大、手势缩放、左右切换以及保存图片等功能,就如本文要介绍的SDPhotoBrowser,也是我在研究github代码发现的,然后自己研究了源码;

2、原理大体如下,

(1)点击图片,弹出SDPhotoBrowser视图,此视图中包含scrollview滚动视图;

(2)SDPhotoBrowser视图被添加到window视图中,并在didMoveToSuperview方法中初始化视图;

(3)layoutSubviews布局视图,并加载currentIndex位置的图片,即showFirstImage函数;

(4)关键的部分,SDPhotoBrowser提供两个委托函数,- (UIImage *)photoBrowser:(SDPhotoBrowser *)browser placeholderImageForIndex:(NSInteger)index; - (NSURL *)photoBrowser:(SDPhotoBrowser *)browser highQualityImageURLForIndex:(NSInteger)index;分别获取指定index的缩略图或者是高清图;

(5)SDBrowserImageView自定义UIImageView,用于显示图片以及进行图片缩放与清除缩放等操作;

3、图片放大操作步骤,

(1)添加子视图_zoomingScroolView滚动视图,并在此滚动视图上创建_zoomingImageView显示放大图片的视图;

(2)执行放大操作,_zoomingScroolView的contentSize是临时UIImageView的size的两倍;

(3)所有的视图重新布局。

二、存在的问题与我的改进

1、原来的photoClick方法中,获取sourceView存在比较严重的耦合;

(1)我认为此处应该定义委托方法,执行委托方法获取视图比较稳妥,这个问题其实已有人在github中提出,即- (UIView *)photoBrowser:(SDPhotoBrowser *)browser viewWithIndex:(NSInteger)index;获取当前index的view视图;(同理showFirstImage方法也有这个问题)

- (void)photoClick:(UITapGestureRecognizer *)recognizer
{
_scrollView.hidden = YES;
_willDisappear = YES;

SDBrowserImageView *currentImageView = (SDBrowserImageView *)recognizer.view;
NSInteger currentIndex = currentImageView.tag;

UIView *sourceView = nil;
if ([self.sourceImagesContainerView isKindOfClass:UICollectionView.class]) {
UICollectionView *view = (UICollectionView *)self.sourceImagesContainerView;
NSIndexPath *path = [NSIndexPath indexPathForItem:currentIndex inSection:0];
sourceView = [view cellForItemAtIndexPath:path];
}else {
sourceView = self.sourceImagesContainerView.subviews[currentIndex];
}

CGRect targetTemp = [self.sourceImagesContainerView convertRect:sourceView.frame toView:self];

UIImageView *tempView = [[UIImageView alloc] init];
tempView.contentMode = sourceView.contentMode;
tempView.clipsToBounds = YES;
tempView.image = currentImageView.image;
CGFloat h = (self.bounds.size.width / currentImageView.image.size.width) * currentImageView.image.size.height;

if (!currentImageView.image) { // 防止 因imageview的image加载失败 导致 崩溃
h = self.bounds.size.height;
}

tempView.bounds = CGRectMake(0, 0, self.bounds.size.width, h);
tempView.center = self.center;

[self addSubview:tempView];

_saveButton.hidden = YES;

[UIView animateWithDuration:SDPhotoBrowserHideImageAnimationDuration animations:^{
tempView.frame = targetTemp;
self.backgroundColor = [UIColor clearColor];
_indexLabel.alpha = 0.1;
} completion:^(BOOL finished) {
[self removeFromSuperview];
}];
}


(2)通过委托对象降低耦合,改进如下;

- (void)photoClick:(UITapGestureRecognizer *)recognizer
{
_scrollView.hidden = YES;
_willDisappear = YES;

SDBrowserImageView *currentImageView = (SDBrowserImageView *)recognizer.view;
NSInteger currentIndex = currentImageView.tag;

UIView *sourceView = nil;
if ([self.sourceImagesContainerView isKindOfClass:UICollectionView.class]) {
if ([self.delegate respondsToSelector:@selector(photoBrowser:viewWithIndex:)]) {
sourceView = [self.delegate photoBrowser:self viewWithIndex:currentIndex];
}
} else {
sourceView = self.sourceImagesContainerView.subviews[currentIndex];
}

CGRect targetTemp = [self.sourceImagesContainerView convertRect:sourceView.frame toView:self];

UIImageView *tempView = [[UIImageView alloc] init];
tempView.contentMode = sourceView.contentMode;
tempView.clipsToBounds = YES;
tempView.image = currentImageView.image;
CGFloat h = (self.bounds.size.width / currentImageView.image.size.width) * currentImageView.image.size.height;

if (!currentImageView.image) { // 防止 因imageview的image加载失败 导致 崩溃
h = self.bounds.size.height;
}

tempView.bounds = CGRectMake(0, 0, self.bounds.size.width, h);
tempView.center = self.center;

[self addSubview:tempView];

_saveButton.hidden = YES;

[UIView animateWithDuration:SDPhotoBrowserHideImageAnimationDuration animations:^{
tempView.frame = targetTemp;
self.backgroundColor = [UIColor clearColor];
_indexLabel.alpha = 0.1;
} completion:^(BOOL finished) {
[self removeFromSuperview];
}];
}


2、- (void)scrollViewDidScroll:(UIScrollView *)scrollView,滚动视图委托,清除图片的缩放效果有误差,比如快速拖动已缩放图片缩放效果会被清除以及已缩放图片向左拖动会有很大的几率导致无法清除缩放效果;

(1)原本作者的意图是向左或者是向右拖动150的距离后,已缩放图片清除缩放效果,但我在测试的过程中向左拖动会清除缩放,向右拖动有概率不会清除缩放;

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
int index = (scrollView.contentOffset.x + _scrollView.bounds.size.width * 0.5) / _scrollView.bounds.size.width;

// 有过缩放的图片在拖动一定距离后清除缩放
CGFloat margin = 150;
CGFloat x = scrollView.contentOffset.x;
if ((x - index * self.bounds.size.width) > margin || (x - index * self.bounds.size.width) < - margin) {
SDBrowserImageView *imageView = _scrollView.subviews[index];
if (imageView.isScaled) {
[UIView animateWithDuration:0.5 animations:^{
imageView.transform = CGAffineTransformIdentity;
} completion:^(BOOL finished) {
[imageView eliminateScale];
}];
}
}

if (!_willDisappear) {
_indexLabel.text = [NSString stringWithFormat:@"%d/%ld", index + 1, (long)self.imageCount];
}
[self setupImageOfImageViewForIndex:index];
}


(2)我的改进的思路是当没有成功切换至下一张图片时,不会清除已缩放图片的缩放效果,反之清除;通过visibleZoomingScrollViews保存已经显示的SDBrowserImageView视图,通过计算只保留当前显示在界面上的SDBrowserImageView的缩放效果,然后清除其他视图缩放效果;

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGRect visibleBounds = _scrollView.bounds;

// 当scrollview滚动停止,firstIndex == lastIndex,即当前显示zoomingImageView的tag值
NSInteger firstIndex = floor((CGRectGetMinX(visibleBounds)) / CGRectGetWidth(visibleBounds));
NSInteger lastIndex  = floor((CGRectGetMaxX(visibleBounds)-1) / CGRectGetWidth(visibleBounds));

if (firstIndex < 0) {
firstIndex = 0;
}
if (firstIndex >= self.imageCount) {
firstIndex = self.imageCount - 1;
}
if (lastIndex < 0){
lastIndex = 0;
}
if (lastIndex >= self.imageCount) {
lastIndex = self.imageCount - 1;
}

// 回收不再显示的zoomingImageView
NSInteger zoomingImageViewIndex = 0;
for (SDBrowserImageView *zoomingImageView in self.visibleZoomingScrollViews) {
zoomingImageViewIndex = zoomingImageView.tag;
if (zoomingImageViewIndex < firstIndex || zoomingImageViewIndex > lastIndex) {
zoomingImageView.transform = CGAffineTransformIdentity;
[zoomingImageView eliminateScale];
}
}

int index = (scrollView.contentOffset.x + _scrollView.bounds.size.width * 0.5) / _scrollView.bounds.size.width;

if (!_willDisappear) {
_indexLabel.text = [NSString stringWithFormat:@"%d/%ld", index + 1, (long)self.imageCount];
}
[self setupImageOfImageViewForIndex:index];
}


3、- (void)didMoveToSuperview执行了两次,官方文档Tells the view that its superview changed,意思是当superview改变的时候会执行此方法,也就是说添加子视图或者是移除子视图,都会执行;因此下面的代码逻辑会执行两遍,移除视图在执行以下逻辑会造成内存泄露;

- (void)didMoveToSuperview
{
[self setupScrollView];

[self setupToolbars];
}

因此,可以通过- (void)didMoveToWindow官网文档说明如下;

The
window
property may be
nil
by the time that this method is called, indicating that the receiver does not currently reside in any window. This occurs when the receiver has just been removed from its superview or when the receiver has just been added to a superview that is not attached to a window. Overrides of this method may choose to ignore such cases if they are not of interest.

意思是说,方法被执行后,window对象有可能为nil,发生此类情况时,比如视图已从父视图中被移除或者是被添加至另外一个并不附加任何window的父视图中。因此可以通过判断self.window是否为空判断SDPhotoBrowser是被添加还是被移除window,而我们需要的是添加至window是初始化view,如下;

- (void)didMoveToWindow
{
if (self.window) {

[self setupScrollView];

[self setupToolbars];
}
}


4、由于collectionView的重用机制导致的问题,没有显示在视图界面上的cell是无法获取到的,因此会导致单击关闭图片的动画失效;

我的解决方法,在scrollView切换图片的同时,通过委托方法- (void)photoBrowser:(SDPhotoBrowser *)browser scrollToItemAtIndex:(NSInteger)index滚动collectionView,确保切换图片对应的cell被显示;

// 滚动至指定的indexPath
- (void)photoBrowser:(SDPhotoBrowser *)browser scrollToItemAtIndex:(NSInteger)index
{
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
[self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionTop animated:NO];
}

// 加载图片
- (void)setupImageOfImageViewForIndex:(NSInteger)index
{
SDBrowserImageView *imageView = _scrollView.subviews[index];
self.currentImageIndex = index;
// 加载图片的同时滚动collectionView,确保cell正常显示;
if ([self.delegate respondsToSelector:@selector(photoBrowser:scrollToItemAtIndex:)]) {
[self.delegate photoBrowser:self scrollToItemAtIndex:index];
}
if (imageView.hasLoadedImage) return;
if ([self highQualityImageURLForIndex:index]) {
[imageView setImageWithURL:[self highQualityImageURLForIndex:index] placeholderImage:[self placeholderImageForIndex:index]];
} else {
imageView.image = [self placeholderImageForIndex:index];
}
imageView.hasLoadedImage = YES;
[self.visibleZoomingScrollViews addObject:imageView];
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息