// // YBImageBrowser.m // YBImageBrowserDemo // // Created by 波儿菜 on 2019/6/5. // Copyright © 2019 波儿菜. All rights reserved. // #import "YBImageBrowser.h" #import "YBIBUtilities.h" #import "YBIBCellProtocol.h" #import "YBIBDataMediator.h" #import "YBIBScreenRotationHandler.h" #import "NSObject+YBImageBrowser.h" #import "YBImageBrowser+Internal.h" #if __has_include("YBIBDefaultWebImageMediator.h") #import "YBIBDefaultWebImageMediator.h" #endif @interface YBImageBrowser () @property (nonatomic, strong) YBIBCollectionView *collectionView; @property (nonatomic, strong) YBIBDataMediator *dataMediator; @property (nonatomic, strong) YBIBScreenRotationHandler *rotationHandler; @end @implementation YBImageBrowser { BOOL _originStatusBarHidden; } #pragma mark - life cycle - (void)dealloc { self.hiddenProjectiveView = nil; [self showStatusBar]; } - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { self.backgroundColor = UIColor.blackColor; // UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(respondsToLongPress:)]; // [self addGestureRecognizer:longPress]; [self initValue]; } return self; } - (void)initValue { _transitioning = _showTransitioning = _hideTransitioning = NO; _defaultAnimatedTransition = _animatedTransition = [YBIBAnimatedTransition new]; _toolViewHandlers = @[[YBIBToolViewHandler new]]; _defaultToolViewHandler = _toolViewHandlers[0]; _auxiliaryViewHandler = [YBIBAuxiliaryViewHandler new]; _shouldHideStatusBar = YES; _autoHideProjectiveView = YES; #if __has_include("YBIBDefaultWebImageMediator.h") _webImageMediator = [YBIBDefaultWebImageMediator new]; #endif } #pragma mark - private - (void)build { [self addSubview:self.collectionView]; self.collectionView.frame = self.bounds; self.collectionView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; [self addSubview:self.containerView]; self.containerView.frame = self.bounds; self.containerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; [self buildToolView]; [self layoutIfNeeded]; [self collectionViewScrollToPage:self.currentPage]; [self.rotationHandler startObserveDeviceOrientation]; } - (void)buildToolView { for (id handler in self.toolViewHandlers) { [self implementGetBaseInfoProtocol:handler]; [self implementOperateBrowserProtocol:handler]; __weak typeof(self) wSelf = self; if ([handler respondsToSelector:@selector(setYb_currentData:)]) { [handler setYb_currentData:^id{ __strong typeof(wSelf) self = wSelf; if (!self) return nil; return self.currentData; }]; } [handler yb_containerViewIsReadied]; [handler yb_hide:NO]; } } - (void)rebuild { self.hiddenProjectiveView = nil; [self showStatusBar]; [self.containerView removeFromSuperview]; _containerView = nil; [self.collectionView removeFromSuperview]; _collectionView = nil; [self.dataMediator clear]; [self.rotationHandler clear]; } - (void)collectionViewScrollToPage:(NSInteger)page { [self.collectionView scrollToPage:page]; [self pageNumberChanged]; } - (void)pageNumberChanged { id data = self.currentData; UIView *projectiveView = nil; if ([data respondsToSelector:@selector(yb_projectiveView)]) { projectiveView = [data yb_projectiveView]; } self.hiddenProjectiveView = projectiveView; if (self.delegate && [self.delegate respondsToSelector:@selector(yb_imageBrowser:pageChanged:data:)]) { [self.delegate yb_imageBrowser:self pageChanged:self.currentPage data:data]; } for (id handler in self.toolViewHandlers) { if ([handler respondsToSelector:@selector(yb_pageChanged)]) { [handler yb_pageChanged]; } } NSArray *visibleCells = self.collectionView.visibleCells; for (UICollectionViewCell *cell in visibleCells) { if ([cell respondsToSelector:@selector(yb_pageChanged)]) { [cell yb_pageChanged]; } } } - (void)showStatusBar { if (self.shouldHideStatusBar) { [UIApplication sharedApplication].statusBarHidden = _originStatusBarHidden; } } - (void)hideStatusBar { if (self.shouldHideStatusBar) { [UIApplication sharedApplication].statusBarHidden = YES; } } #pragma mark - public - (void)show { [self showToView:[UIApplication sharedApplication].keyWindow]; } - (void)showToView:(UIView *)view { [self showToView:view containerSize:view.bounds.size]; } - (void)showToView:(UIView *)view containerSize:(CGSize)containerSize { [self.rotationHandler startObserveStatusBarOrientation]; [view addSubview:self]; self.frame = view.bounds; self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; _originStatusBarHidden = [UIApplication sharedApplication].isStatusBarHidden; [self.rotationHandler configContainerSize:containerSize]; [self.dataMediator preloadWithPage:self.currentPage]; __kindof UIView *startView; UIImage *startImage; CGRect endFrame = CGRectZero; id data = [self.dataMediator dataForCellAtIndex:self.currentPage]; if ([data respondsToSelector:@selector(yb_projectiveView)]) { startView = data.yb_projectiveView; self.hiddenProjectiveView = startView; if ([startView isKindOfClass:UIImageView.class]) { startImage = ((UIImageView *)startView).image; } else { startImage = YBIBSnapshotView(startView); } } if ([data respondsToSelector:@selector(yb_imageViewFrameWithContainerSize:imageSize:orientation:)]) { endFrame = [data yb_imageViewFrameWithContainerSize:self.bounds.size imageSize:startImage.size orientation:self.rotationHandler.currentOrientation]; } [self setTransitioning:YES isShow:YES]; [self.animatedTransition yb_showTransitioningWithContainer:self startView:startView startImage:startImage endFrame:endFrame orientation:self.rotationHandler.currentOrientation completion:^{ [self hideStatusBar]; [self build]; [self setTransitioning:NO isShow:YES]; }]; } - (void)hide { __kindof UIView *startView; __kindof UIView *endView; UICollectionViewCell *cell = (UICollectionViewCell *)self.collectionView.centerCell; if ([cell respondsToSelector:@selector(yb_foregroundView)]) { startView = cell.yb_foregroundView; } if ([cell.yb_cellData respondsToSelector:@selector(yb_projectiveView)]) { endView = cell.yb_cellData.yb_projectiveView; } for (id handler in self.toolViewHandlers) { [handler yb_hide:YES]; } [self showStatusBar]; [self setTransitioning:YES isShow:NO]; [self.animatedTransition yb_hideTransitioningWithContainer:self startView:startView endView:endView orientation:self.rotationHandler.currentOrientation completion:^{ [self rebuild]; [self removeFromSuperview]; [self setTransitioning:NO isShow:NO]; }]; } - (void)reloadData { [self.dataMediator clear]; NSInteger page = self.currentPage; [self.collectionView reloadData]; self.currentPage = page; } - (id)currentData { return [self.dataMediator dataForCellAtIndex:self.currentPage]; } #pragma mark - internal - (void)setHiddenProjectiveView:(NSObject *)hiddenProjectiveView { if (_hiddenProjectiveView && [_hiddenProjectiveView respondsToSelector:@selector(setAlpha:)]) { CGFloat originAlpha = _hiddenProjectiveView.ybib_originAlpha; if (originAlpha != 1) { [_hiddenProjectiveView setValue:@(1) forKey:@"alpha"]; [UIView animateWithDuration:0.2 animations:^{ [self->_hiddenProjectiveView setValue:@(originAlpha) forKey:@"alpha"]; }]; } else { [_hiddenProjectiveView setValue:@(originAlpha) forKey:@"alpha"]; } } _hiddenProjectiveView = hiddenProjectiveView; if (!self.autoHideProjectiveView) return; if (hiddenProjectiveView && [hiddenProjectiveView respondsToSelector:@selector(setAlpha:)]) { hiddenProjectiveView.ybib_originAlpha = ((NSNumber *)[hiddenProjectiveView valueForKey:@"alpha"]).floatValue; [hiddenProjectiveView setValue:@(0) forKey:@"alpha"]; } } - (void)implementOperateBrowserProtocol:(id)obj { __weak typeof(self) wSelf = self; if ([obj respondsToSelector:@selector(setYb_hideBrowser:)]) { [obj setYb_hideBrowser:^{ __strong typeof(wSelf) self = wSelf; if (!self) return; [self hide]; }]; } if ([obj respondsToSelector:@selector(setYb_hideStatusBar:)]) { [obj setYb_hideStatusBar:^(BOOL hide) { __strong typeof(wSelf) self = wSelf; if (!self) return; hide ? [self hideStatusBar] : [self showStatusBar]; }]; } if ([obj respondsToSelector:@selector(setYb_hideToolViews:)]) { [obj setYb_hideToolViews:^(BOOL hide) { __strong typeof(wSelf) self = wSelf; if (!self) return; for (id handler in self.toolViewHandlers) { [handler yb_hide:hide]; } }]; } } - (void)implementGetBaseInfoProtocol:(id)obj { __weak typeof(self) wSelf = self; if ([obj respondsToSelector:@selector(setYb_currentOrientation:)]) { [obj setYb_currentOrientation:^UIDeviceOrientation{ __strong typeof(wSelf) self = wSelf; if (!self) return UIDeviceOrientationPortrait; return self.rotationHandler.currentOrientation; }]; } if ([obj respondsToSelector:@selector(setYb_containerSize:)]) { [obj setYb_containerSize:^CGSize(UIDeviceOrientation orientation) { __strong typeof(wSelf) self = wSelf; if (!self) return CGSizeZero; return [self.rotationHandler containerSizeWithOrientation:orientation]; }]; } if ([obj respondsToSelector:@selector(setYb_auxiliaryViewHandler:)]) { [obj setYb_auxiliaryViewHandler:^id{ __strong typeof(wSelf) self = wSelf; if (!self) return nil; return self.auxiliaryViewHandler; }]; } if ([obj respondsToSelector:@selector(setYb_webImageMediator:)]) { [obj setYb_webImageMediator:^id { __strong typeof(wSelf) self = wSelf; if (!self) return nil; NSAssert(self.webImageMediator, @"'webImageMediator' should not be nil."); return self.webImageMediator; }]; } if ([obj respondsToSelector:@selector(setYb_currentPage:)]) { [obj setYb_currentPage:^NSInteger{ __strong typeof(wSelf) self = wSelf; if (!self) return 0; return self.currentPage; }]; } if ([obj respondsToSelector:@selector(setYb_totalPage:)]) { [obj setYb_totalPage:^NSInteger{ __strong typeof(wSelf) self = wSelf; if (!self) return 0; return [self.dataMediator numberOfCells]; }]; } if ([obj respondsToSelector:@selector(setYb_backView:)]) { obj.yb_backView = self; } if ([obj respondsToSelector:@selector(setYb_containerView:)]) { obj.yb_containerView = self.containerView; } if ([obj respondsToSelector:@selector(setYb_collectionView:)]) { [obj setYb_collectionView:^__kindof UICollectionView *{ __strong typeof(wSelf) self = wSelf; if (!self) return nil; return self.collectionView; }]; } if ([obj respondsToSelector:@selector(setYb_cellIsInCenter:)]) { [obj setYb_cellIsInCenter:^BOOL{ __strong typeof(wSelf) self = wSelf; CGFloat pageF = self.collectionView.contentOffset.x / self.collectionView.bounds.size.width; // '0.001' is admissible error. return ABS(pageF - (NSInteger)pageF) <= 0.001; }]; } if ([obj respondsToSelector:@selector(setYb_isTransitioning:)]) { [obj setYb_isTransitioning:^BOOL{ __strong typeof(wSelf) self = wSelf; if (!self) return NO; return self.isTransitioning; }]; } if ([obj respondsToSelector:@selector(setYb_isShowTransitioning:)]) { [obj setYb_isShowTransitioning:^BOOL{ __strong typeof(wSelf) self = wSelf; if (!self) return NO; return self.isShowTransitioning; }]; } if ([obj respondsToSelector:@selector(setYb_isHideTransitioning:)]) { [obj setYb_isHideTransitioning:^BOOL{ __strong typeof(wSelf) self = wSelf; if (!self) return NO; return self.isHideTransitioning; }]; } if ([obj respondsToSelector:@selector(setYb_isRotating:)]) { [obj setYb_isRotating:^BOOL{ __strong typeof(wSelf) self = wSelf; if (!self) return NO; return self.rotationHandler.isRotating; }]; } } #pragma mark - - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { return [self.dataMediator numberOfCells]; } - (UICollectionViewCell *)collectionView:(YBIBCollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { id data = [self.dataMediator dataForCellAtIndex:indexPath.row]; UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:[collectionView reuseIdentifierForCellClass:data.yb_classOfCell] forIndexPath:indexPath]; [self implementGetBaseInfoProtocol:cell]; [self implementOperateBrowserProtocol:cell]; if ([cell respondsToSelector:@selector(setYb_selfPage:)]) { [cell setYb_selfPage:^NSInteger{ return indexPath.row; }]; } cell.yb_cellData = data; if ([cell respondsToSelector:@selector(yb_pageChanged)]) { [cell yb_pageChanged]; } [self.dataMediator preloadWithPage:indexPath.row]; return cell; } #pragma mark - - (void)scrollViewDidScroll:(UIScrollView *)scrollView { CGFloat pageF = scrollView.contentOffset.x / scrollView.bounds.size.width; NSInteger page = (NSInteger)(pageF + 0.5); for (id handler in self.toolViewHandlers) { if ([handler respondsToSelector:@selector(yb_offsetXChanged:)]) { [handler yb_offsetXChanged:pageF]; } } if (!scrollView.isDecelerating && !scrollView.isDragging) { // Return if not scrolled by finger. return; } if (page < 0 || page > [self.dataMediator numberOfCells] - 1) return; if (self.rotationHandler.isRotating) return; if (page != _currentPage) { _currentPage = page; [self pageNumberChanged]; } } #pragma mark - event - (void)respondsToLongPress:(UILongPressGestureRecognizer *)sender { if (sender.state == UIGestureRecognizerStateBegan) { if ([self.delegate respondsToSelector:@selector(yb_imageBrowser:respondsToLongPressWithData:)]) { [self.delegate yb_imageBrowser:self respondsToLongPressWithData:[self currentData]]; } else { for (id handler in self.toolViewHandlers) { if ([handler respondsToSelector:@selector(yb_respondsToLongPress)]) { [handler yb_respondsToLongPress]; } } } } } #pragma mark - getters & setters - (YBIBContainerView *)containerView { if (!_containerView) { _containerView = [YBIBContainerView new]; _containerView.backgroundColor = UIColor.clearColor; _containerView.layer.masksToBounds = YES; } return _containerView; } - (YBIBCollectionView *)collectionView { if (!_collectionView) { _collectionView = [YBIBCollectionView new]; _collectionView.delegate = self; _collectionView.dataSource = self; } return _collectionView; } - (void)setCurrentPage:(NSInteger)currentPage { NSInteger maxPage = self.dataMediator.numberOfCells - 1; if (currentPage > maxPage) { currentPage = maxPage; } _currentPage = currentPage; if (self.collectionView.superview) { [self collectionViewScrollToPage:currentPage]; } } - (void)setDistanceBetweenPages:(CGFloat)distanceBetweenPages { self.collectionView.layout.distanceBetweenPages = distanceBetweenPages; } - (CGFloat)distanceBetweenPages { return self.collectionView.layout.distanceBetweenPages; } - (void)setTransitioning:(BOOL)transitioning isShow:(BOOL)isShow { _transitioning = transitioning; _showTransitioning = transitioning && isShow; _hideTransitioning = transitioning && !isShow; // Make 'self.userInteractionEnabled' always 'YES' to block external interaction. self.containerView.userInteractionEnabled = !transitioning; self.collectionView.userInteractionEnabled = !transitioning; if (transitioning) { if ([self.delegate respondsToSelector:@selector(yb_imageBrowser:beginTransitioningWithIsShow:)]) { [self.delegate yb_imageBrowser:self beginTransitioningWithIsShow:isShow]; } } else { if ([self.delegate respondsToSelector:@selector(yb_imageBrowser:endTransitioningWithIsShow:)]) { [self.delegate yb_imageBrowser:self endTransitioningWithIsShow:isShow]; } } } - (YBIBDataMediator *)dataMediator { if (!_dataMediator) { _dataMediator = [[YBIBDataMediator alloc] initWithBrowser:self]; _dataMediator.dataCacheCountLimit = YBIBLowMemory() ? 9 : 27; _dataMediator.preloadCount = YBIBLowMemory() ? 0 : 2; } return _dataMediator; } - (void)setPreloadCount:(NSUInteger)preloadCount { self.dataMediator.preloadCount = preloadCount; } - (NSUInteger)preloadCount { return self.dataMediator.preloadCount; } - (YBIBScreenRotationHandler *)rotationHandler { if (!_rotationHandler) { _rotationHandler = [[YBIBScreenRotationHandler alloc] initWithBrowser:self]; } return _rotationHandler; } - (void)setSupportedOrientations:(UIInterfaceOrientationMask)supportedOrientations { self.rotationHandler.supportedOrientations = supportedOrientations; } - (UIInterfaceOrientationMask)supportedOrientations { return self.rotationHandler.supportedOrientations; } - (UIDeviceOrientation)currentOrientation { return self.rotationHandler.currentOrientation; } @end