YBImageBrowser.m 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538
  1. //
  2. // YBImageBrowser.m
  3. // YBImageBrowserDemo
  4. //
  5. // Created by 波儿菜 on 2019/6/5.
  6. // Copyright © 2019 波儿菜. All rights reserved.
  7. //
  8. #import "YBImageBrowser.h"
  9. #import "YBIBUtilities.h"
  10. #import "YBIBCellProtocol.h"
  11. #import "YBIBDataMediator.h"
  12. #import "YBIBScreenRotationHandler.h"
  13. #import "NSObject+YBImageBrowser.h"
  14. #import "YBImageBrowser+Internal.h"
  15. #if __has_include("YBIBDefaultWebImageMediator.h")
  16. #import "YBIBDefaultWebImageMediator.h"
  17. #endif
  18. @interface YBImageBrowser () <UICollectionViewDelegate, UICollectionViewDataSource>
  19. @property (nonatomic, strong) YBIBCollectionView *collectionView;
  20. @property (nonatomic, strong) YBIBDataMediator *dataMediator;
  21. @property (nonatomic, strong) YBIBScreenRotationHandler *rotationHandler;
  22. @end
  23. @implementation YBImageBrowser {
  24. BOOL _originStatusBarHidden;
  25. }
  26. #pragma mark - life cycle
  27. - (void)dealloc {
  28. self.hiddenProjectiveView = nil;
  29. [self showStatusBar];
  30. }
  31. - (instancetype)initWithFrame:(CGRect)frame {
  32. self = [super initWithFrame:frame];
  33. if (self) {
  34. self.backgroundColor = UIColor.blackColor;
  35. // UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(respondsToLongPress:)];
  36. // [self addGestureRecognizer:longPress];
  37. [self initValue];
  38. }
  39. return self;
  40. }
  41. - (void)initValue {
  42. _transitioning = _showTransitioning = _hideTransitioning = NO;
  43. _defaultAnimatedTransition = _animatedTransition = [YBIBAnimatedTransition new];
  44. _toolViewHandlers = @[[YBIBToolViewHandler new]];
  45. _defaultToolViewHandler = _toolViewHandlers[0];
  46. _auxiliaryViewHandler = [YBIBAuxiliaryViewHandler new];
  47. _shouldHideStatusBar = YES;
  48. _autoHideProjectiveView = YES;
  49. #if __has_include("YBIBDefaultWebImageMediator.h")
  50. _webImageMediator = [YBIBDefaultWebImageMediator new];
  51. #endif
  52. }
  53. #pragma mark - private
  54. - (void)build {
  55. [self addSubview:self.collectionView];
  56. self.collectionView.frame = self.bounds;
  57. self.collectionView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  58. [self addSubview:self.containerView];
  59. self.containerView.frame = self.bounds;
  60. self.containerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  61. [self buildToolView];
  62. [self layoutIfNeeded];
  63. [self collectionViewScrollToPage:self.currentPage];
  64. [self.rotationHandler startObserveDeviceOrientation];
  65. }
  66. - (void)buildToolView {
  67. for (id<YBIBToolViewHandler> handler in self.toolViewHandlers) {
  68. [self implementGetBaseInfoProtocol:handler];
  69. [self implementOperateBrowserProtocol:handler];
  70. __weak typeof(self) wSelf = self;
  71. if ([handler respondsToSelector:@selector(setYb_currentData:)]) {
  72. [handler setYb_currentData:^id<YBIBDataProtocol>{
  73. __strong typeof(wSelf) self = wSelf;
  74. if (!self) return nil;
  75. return self.currentData;
  76. }];
  77. }
  78. [handler yb_containerViewIsReadied];
  79. [handler yb_hide:NO];
  80. }
  81. }
  82. - (void)rebuild {
  83. self.hiddenProjectiveView = nil;
  84. [self showStatusBar];
  85. [self.containerView removeFromSuperview];
  86. _containerView = nil;
  87. [self.collectionView removeFromSuperview];
  88. _collectionView = nil;
  89. [self.dataMediator clear];
  90. [self.rotationHandler clear];
  91. }
  92. - (void)collectionViewScrollToPage:(NSInteger)page {
  93. [self.collectionView scrollToPage:page];
  94. [self pageNumberChanged];
  95. }
  96. - (void)pageNumberChanged {
  97. id<YBIBDataProtocol> data = self.currentData;
  98. UIView *projectiveView = nil;
  99. if ([data respondsToSelector:@selector(yb_projectiveView)]) {
  100. projectiveView = [data yb_projectiveView];
  101. }
  102. self.hiddenProjectiveView = projectiveView;
  103. if (self.delegate && [self.delegate respondsToSelector:@selector(yb_imageBrowser:pageChanged:data:)]) {
  104. [self.delegate yb_imageBrowser:self pageChanged:self.currentPage data:data];
  105. }
  106. for (id<YBIBToolViewHandler> handler in self.toolViewHandlers) {
  107. if ([handler respondsToSelector:@selector(yb_pageChanged)]) {
  108. [handler yb_pageChanged];
  109. }
  110. }
  111. NSArray *visibleCells = self.collectionView.visibleCells;
  112. for (UICollectionViewCell<YBIBCellProtocol> *cell in visibleCells) {
  113. if ([cell respondsToSelector:@selector(yb_pageChanged)]) {
  114. [cell yb_pageChanged];
  115. }
  116. }
  117. }
  118. - (void)showStatusBar {
  119. if (self.shouldHideStatusBar) {
  120. [UIApplication sharedApplication].statusBarHidden = _originStatusBarHidden;
  121. }
  122. }
  123. - (void)hideStatusBar {
  124. if (self.shouldHideStatusBar) {
  125. [UIApplication sharedApplication].statusBarHidden = YES;
  126. }
  127. }
  128. #pragma mark - public
  129. - (void)show {
  130. [self showToView:[UIApplication sharedApplication].keyWindow];
  131. }
  132. - (void)showToView:(UIView *)view {
  133. [self showToView:view containerSize:view.bounds.size];
  134. }
  135. - (void)showToView:(UIView *)view containerSize:(CGSize)containerSize {
  136. [self.rotationHandler startObserveStatusBarOrientation];
  137. [view addSubview:self];
  138. self.frame = view.bounds;
  139. self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  140. _originStatusBarHidden = [UIApplication sharedApplication].isStatusBarHidden;
  141. [self.rotationHandler configContainerSize:containerSize];
  142. [self.dataMediator preloadWithPage:self.currentPage];
  143. __kindof UIView *startView;
  144. UIImage *startImage;
  145. CGRect endFrame = CGRectZero;
  146. id<YBIBDataProtocol> data = [self.dataMediator dataForCellAtIndex:self.currentPage];
  147. if ([data respondsToSelector:@selector(yb_projectiveView)]) {
  148. startView = data.yb_projectiveView;
  149. self.hiddenProjectiveView = startView;
  150. if ([startView isKindOfClass:UIImageView.class]) {
  151. startImage = ((UIImageView *)startView).image;
  152. } else {
  153. startImage = YBIBSnapshotView(startView);
  154. }
  155. }
  156. if ([data respondsToSelector:@selector(yb_imageViewFrameWithContainerSize:imageSize:orientation:)]) {
  157. endFrame = [data yb_imageViewFrameWithContainerSize:self.bounds.size imageSize:startImage.size orientation:self.rotationHandler.currentOrientation];
  158. }
  159. [self setTransitioning:YES isShow:YES];
  160. [self.animatedTransition yb_showTransitioningWithContainer:self startView:startView startImage:startImage endFrame:endFrame orientation:self.rotationHandler.currentOrientation completion:^{
  161. [self hideStatusBar];
  162. [self build];
  163. [self setTransitioning:NO isShow:YES];
  164. }];
  165. }
  166. - (void)hide {
  167. __kindof UIView *startView;
  168. __kindof UIView *endView;
  169. UICollectionViewCell<YBIBCellProtocol> *cell = (UICollectionViewCell<YBIBCellProtocol> *)self.collectionView.centerCell;
  170. if ([cell respondsToSelector:@selector(yb_foregroundView)]) {
  171. startView = cell.yb_foregroundView;
  172. }
  173. if ([cell.yb_cellData respondsToSelector:@selector(yb_projectiveView)]) {
  174. endView = cell.yb_cellData.yb_projectiveView;
  175. }
  176. for (id<YBIBToolViewHandler> handler in self.toolViewHandlers) {
  177. [handler yb_hide:YES];
  178. }
  179. [self showStatusBar];
  180. [self setTransitioning:YES isShow:NO];
  181. [self.animatedTransition yb_hideTransitioningWithContainer:self startView:startView endView:endView orientation:self.rotationHandler.currentOrientation completion:^{
  182. [self rebuild];
  183. [self removeFromSuperview];
  184. [self setTransitioning:NO isShow:NO];
  185. }];
  186. }
  187. - (void)reloadData {
  188. [self.dataMediator clear];
  189. NSInteger page = self.currentPage;
  190. [self.collectionView reloadData];
  191. self.currentPage = page;
  192. }
  193. - (id<YBIBDataProtocol>)currentData {
  194. return [self.dataMediator dataForCellAtIndex:self.currentPage];
  195. }
  196. #pragma mark - internal
  197. - (void)setHiddenProjectiveView:(NSObject *)hiddenProjectiveView {
  198. if (_hiddenProjectiveView && [_hiddenProjectiveView respondsToSelector:@selector(setAlpha:)]) {
  199. CGFloat originAlpha = _hiddenProjectiveView.ybib_originAlpha;
  200. if (originAlpha != 1) {
  201. [_hiddenProjectiveView setValue:@(1) forKey:@"alpha"];
  202. [UIView animateWithDuration:0.2 animations:^{
  203. [self->_hiddenProjectiveView setValue:@(originAlpha) forKey:@"alpha"];
  204. }];
  205. } else {
  206. [_hiddenProjectiveView setValue:@(originAlpha) forKey:@"alpha"];
  207. }
  208. }
  209. _hiddenProjectiveView = hiddenProjectiveView;
  210. if (!self.autoHideProjectiveView) return;
  211. if (hiddenProjectiveView && [hiddenProjectiveView respondsToSelector:@selector(setAlpha:)]) {
  212. hiddenProjectiveView.ybib_originAlpha = ((NSNumber *)[hiddenProjectiveView valueForKey:@"alpha"]).floatValue;
  213. [hiddenProjectiveView setValue:@(0) forKey:@"alpha"];
  214. }
  215. }
  216. - (void)implementOperateBrowserProtocol:(id<YBIBOperateBrowserProtocol>)obj {
  217. __weak typeof(self) wSelf = self;
  218. if ([obj respondsToSelector:@selector(setYb_hideBrowser:)]) {
  219. [obj setYb_hideBrowser:^{
  220. __strong typeof(wSelf) self = wSelf;
  221. if (!self) return;
  222. [self hide];
  223. }];
  224. }
  225. if ([obj respondsToSelector:@selector(setYb_hideStatusBar:)]) {
  226. [obj setYb_hideStatusBar:^(BOOL hide) {
  227. __strong typeof(wSelf) self = wSelf;
  228. if (!self) return;
  229. hide ? [self hideStatusBar] : [self showStatusBar];
  230. }];
  231. }
  232. if ([obj respondsToSelector:@selector(setYb_hideToolViews:)]) {
  233. [obj setYb_hideToolViews:^(BOOL hide) {
  234. __strong typeof(wSelf) self = wSelf;
  235. if (!self) return;
  236. for (id<YBIBToolViewHandler> handler in self.toolViewHandlers) {
  237. [handler yb_hide:hide];
  238. }
  239. }];
  240. }
  241. }
  242. - (void)implementGetBaseInfoProtocol:(id<YBIBGetBaseInfoProtocol>)obj {
  243. __weak typeof(self) wSelf = self;
  244. if ([obj respondsToSelector:@selector(setYb_currentOrientation:)]) {
  245. [obj setYb_currentOrientation:^UIDeviceOrientation{
  246. __strong typeof(wSelf) self = wSelf;
  247. if (!self) return UIDeviceOrientationPortrait;
  248. return self.rotationHandler.currentOrientation;
  249. }];
  250. }
  251. if ([obj respondsToSelector:@selector(setYb_containerSize:)]) {
  252. [obj setYb_containerSize:^CGSize(UIDeviceOrientation orientation) {
  253. __strong typeof(wSelf) self = wSelf;
  254. if (!self) return CGSizeZero;
  255. return [self.rotationHandler containerSizeWithOrientation:orientation];
  256. }];
  257. }
  258. if ([obj respondsToSelector:@selector(setYb_auxiliaryViewHandler:)]) {
  259. [obj setYb_auxiliaryViewHandler:^id<YBIBAuxiliaryViewHandler>{
  260. __strong typeof(wSelf) self = wSelf;
  261. if (!self) return nil;
  262. return self.auxiliaryViewHandler;
  263. }];
  264. }
  265. if ([obj respondsToSelector:@selector(setYb_webImageMediator:)]) {
  266. [obj setYb_webImageMediator:^id<YBIBWebImageMediator> {
  267. __strong typeof(wSelf) self = wSelf;
  268. if (!self) return nil;
  269. NSAssert(self.webImageMediator, @"'webImageMediator' should not be nil.");
  270. return self.webImageMediator;
  271. }];
  272. }
  273. if ([obj respondsToSelector:@selector(setYb_currentPage:)]) {
  274. [obj setYb_currentPage:^NSInteger{
  275. __strong typeof(wSelf) self = wSelf;
  276. if (!self) return 0;
  277. return self.currentPage;
  278. }];
  279. }
  280. if ([obj respondsToSelector:@selector(setYb_totalPage:)]) {
  281. [obj setYb_totalPage:^NSInteger{
  282. __strong typeof(wSelf) self = wSelf;
  283. if (!self) return 0;
  284. return [self.dataMediator numberOfCells];
  285. }];
  286. }
  287. if ([obj respondsToSelector:@selector(setYb_backView:)]) {
  288. obj.yb_backView = self;
  289. }
  290. if ([obj respondsToSelector:@selector(setYb_containerView:)]) {
  291. obj.yb_containerView = self.containerView;
  292. }
  293. if ([obj respondsToSelector:@selector(setYb_collectionView:)]) {
  294. [obj setYb_collectionView:^__kindof UICollectionView *{
  295. __strong typeof(wSelf) self = wSelf;
  296. if (!self) return nil;
  297. return self.collectionView;
  298. }];
  299. }
  300. if ([obj respondsToSelector:@selector(setYb_cellIsInCenter:)]) {
  301. [obj setYb_cellIsInCenter:^BOOL{
  302. __strong typeof(wSelf) self = wSelf;
  303. CGFloat pageF = self.collectionView.contentOffset.x / self.collectionView.bounds.size.width;
  304. // '0.001' is admissible error.
  305. return ABS(pageF - (NSInteger)pageF) <= 0.001;
  306. }];
  307. }
  308. if ([obj respondsToSelector:@selector(setYb_isTransitioning:)]) {
  309. [obj setYb_isTransitioning:^BOOL{
  310. __strong typeof(wSelf) self = wSelf;
  311. if (!self) return NO;
  312. return self.isTransitioning;
  313. }];
  314. }
  315. if ([obj respondsToSelector:@selector(setYb_isShowTransitioning:)]) {
  316. [obj setYb_isShowTransitioning:^BOOL{
  317. __strong typeof(wSelf) self = wSelf;
  318. if (!self) return NO;
  319. return self.isShowTransitioning;
  320. }];
  321. }
  322. if ([obj respondsToSelector:@selector(setYb_isHideTransitioning:)]) {
  323. [obj setYb_isHideTransitioning:^BOOL{
  324. __strong typeof(wSelf) self = wSelf;
  325. if (!self) return NO;
  326. return self.isHideTransitioning;
  327. }];
  328. }
  329. if ([obj respondsToSelector:@selector(setYb_isRotating:)]) {
  330. [obj setYb_isRotating:^BOOL{
  331. __strong typeof(wSelf) self = wSelf;
  332. if (!self) return NO;
  333. return self.rotationHandler.isRotating;
  334. }];
  335. }
  336. }
  337. #pragma mark - <UICollectionViewDataSource>
  338. - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
  339. return [self.dataMediator numberOfCells];
  340. }
  341. - (UICollectionViewCell *)collectionView:(YBIBCollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
  342. id<YBIBDataProtocol> data = [self.dataMediator dataForCellAtIndex:indexPath.row];
  343. UICollectionViewCell<YBIBCellProtocol> *cell = [collectionView dequeueReusableCellWithReuseIdentifier:[collectionView reuseIdentifierForCellClass:data.yb_classOfCell] forIndexPath:indexPath];
  344. [self implementGetBaseInfoProtocol:cell];
  345. [self implementOperateBrowserProtocol:cell];
  346. if ([cell respondsToSelector:@selector(setYb_selfPage:)]) {
  347. [cell setYb_selfPage:^NSInteger{
  348. return indexPath.row;
  349. }];
  350. }
  351. cell.yb_cellData = data;
  352. if ([cell respondsToSelector:@selector(yb_pageChanged)]) {
  353. [cell yb_pageChanged];
  354. }
  355. [self.dataMediator preloadWithPage:indexPath.row];
  356. return cell;
  357. }
  358. #pragma mark - <UICollectionViewDelegate>
  359. - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
  360. CGFloat pageF = scrollView.contentOffset.x / scrollView.bounds.size.width;
  361. NSInteger page = (NSInteger)(pageF + 0.5);
  362. for (id<YBIBToolViewHandler> handler in self.toolViewHandlers) {
  363. if ([handler respondsToSelector:@selector(yb_offsetXChanged:)]) {
  364. [handler yb_offsetXChanged:pageF];
  365. }
  366. }
  367. if (!scrollView.isDecelerating && !scrollView.isDragging) {
  368. // Return if not scrolled by finger.
  369. return;
  370. }
  371. if (page < 0 || page > [self.dataMediator numberOfCells] - 1) return;
  372. if (self.rotationHandler.isRotating) return;
  373. if (page != _currentPage) {
  374. _currentPage = page;
  375. [self pageNumberChanged];
  376. }
  377. }
  378. #pragma mark - event
  379. - (void)respondsToLongPress:(UILongPressGestureRecognizer *)sender {
  380. if (sender.state == UIGestureRecognizerStateBegan) {
  381. if ([self.delegate respondsToSelector:@selector(yb_imageBrowser:respondsToLongPressWithData:)]) {
  382. [self.delegate yb_imageBrowser:self respondsToLongPressWithData:[self currentData]];
  383. } else {
  384. for (id<YBIBToolViewHandler> handler in self.toolViewHandlers) {
  385. if ([handler respondsToSelector:@selector(yb_respondsToLongPress)]) {
  386. [handler yb_respondsToLongPress];
  387. }
  388. }
  389. }
  390. }
  391. }
  392. #pragma mark - getters & setters
  393. - (YBIBContainerView *)containerView {
  394. if (!_containerView) {
  395. _containerView = [YBIBContainerView new];
  396. _containerView.backgroundColor = UIColor.clearColor;
  397. _containerView.layer.masksToBounds = YES;
  398. }
  399. return _containerView;
  400. }
  401. - (YBIBCollectionView *)collectionView {
  402. if (!_collectionView) {
  403. _collectionView = [YBIBCollectionView new];
  404. _collectionView.delegate = self;
  405. _collectionView.dataSource = self;
  406. }
  407. return _collectionView;
  408. }
  409. - (void)setCurrentPage:(NSInteger)currentPage {
  410. NSInteger maxPage = self.dataMediator.numberOfCells - 1;
  411. if (currentPage > maxPage) {
  412. currentPage = maxPage;
  413. }
  414. _currentPage = currentPage;
  415. if (self.collectionView.superview) {
  416. [self collectionViewScrollToPage:currentPage];
  417. }
  418. }
  419. - (void)setDistanceBetweenPages:(CGFloat)distanceBetweenPages {
  420. self.collectionView.layout.distanceBetweenPages = distanceBetweenPages;
  421. }
  422. - (CGFloat)distanceBetweenPages {
  423. return self.collectionView.layout.distanceBetweenPages;
  424. }
  425. - (void)setTransitioning:(BOOL)transitioning isShow:(BOOL)isShow {
  426. _transitioning = transitioning;
  427. _showTransitioning = transitioning && isShow;
  428. _hideTransitioning = transitioning && !isShow;
  429. // Make 'self.userInteractionEnabled' always 'YES' to block external interaction.
  430. self.containerView.userInteractionEnabled = !transitioning;
  431. self.collectionView.userInteractionEnabled = !transitioning;
  432. if (transitioning) {
  433. if ([self.delegate respondsToSelector:@selector(yb_imageBrowser:beginTransitioningWithIsShow:)]) {
  434. [self.delegate yb_imageBrowser:self beginTransitioningWithIsShow:isShow];
  435. }
  436. } else {
  437. if ([self.delegate respondsToSelector:@selector(yb_imageBrowser:endTransitioningWithIsShow:)]) {
  438. [self.delegate yb_imageBrowser:self endTransitioningWithIsShow:isShow];
  439. }
  440. }
  441. }
  442. - (YBIBDataMediator *)dataMediator {
  443. if (!_dataMediator) {
  444. _dataMediator = [[YBIBDataMediator alloc] initWithBrowser:self];
  445. _dataMediator.dataCacheCountLimit = YBIBLowMemory() ? 9 : 27;
  446. _dataMediator.preloadCount = YBIBLowMemory() ? 0 : 2;
  447. }
  448. return _dataMediator;
  449. }
  450. - (void)setPreloadCount:(NSUInteger)preloadCount {
  451. self.dataMediator.preloadCount = preloadCount;
  452. }
  453. - (NSUInteger)preloadCount {
  454. return self.dataMediator.preloadCount;
  455. }
  456. - (YBIBScreenRotationHandler *)rotationHandler {
  457. if (!_rotationHandler) {
  458. _rotationHandler = [[YBIBScreenRotationHandler alloc] initWithBrowser:self];
  459. }
  460. return _rotationHandler;
  461. }
  462. - (void)setSupportedOrientations:(UIInterfaceOrientationMask)supportedOrientations {
  463. self.rotationHandler.supportedOrientations = supportedOrientations;
  464. }
  465. - (UIInterfaceOrientationMask)supportedOrientations {
  466. return self.rotationHandler.supportedOrientations;
  467. }
  468. - (UIDeviceOrientation)currentOrientation {
  469. return self.rotationHandler.currentOrientation;
  470. }
  471. @end