EAIntroView.m 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072
  1. //
  2. // EAIntroView.m
  3. //
  4. // Copyright (c) 2013-2015 Evgeny Aleksandrov. License: MIT.
  5. #import "EAIntroView.h"
  6. #import "EARestrictedScrollView.h"
  7. @interface EAIntroView()
  8. @property (nonatomic, strong) UIImageView *bgImageView;
  9. @property (nonatomic, strong) UIImageView *pageBgBack;
  10. @property (nonatomic, strong) UIImageView *pageBgFront;
  11. @property (nonatomic, strong) NSMutableArray<NSLayoutConstraint *> *footerConstraints;
  12. @property (nonatomic, strong) NSMutableArray<NSLayoutConstraint *> *titleViewConstraints;
  13. @property (nonatomic, assign) BOOL skipped;
  14. @end
  15. @interface EAIntroPage()
  16. @property (nonatomic, strong, readwrite) UIView *pageView;
  17. @end
  18. @implementation EAIntroView
  19. @synthesize pageControl = _pageControl;
  20. @synthesize skipButton = _skipButton;
  21. #pragma mark - Init
  22. - (id)initWithFrame:(CGRect)frame {
  23. self = [super initWithFrame:frame];
  24. if (self) {
  25. [self applyDefaultsToSelfDuringInitializationWithFrame:frame pages:nil];
  26. }
  27. return self;
  28. }
  29. - (id)initWithCoder:(NSCoder *)aDecoder {
  30. self = [super initWithCoder:aDecoder];
  31. if (self) {
  32. [self applyDefaultsToSelfDuringInitializationWithFrame:self.frame pages:nil];
  33. }
  34. return self;
  35. }
  36. - (id)initWithFrame:(CGRect)frame andPages:(NSArray<EAIntroPage *> *)pagesArray {
  37. self = [super initWithFrame:frame];
  38. if (self) {
  39. [self applyDefaultsToSelfDuringInitializationWithFrame:self.frame pages:pagesArray];
  40. }
  41. return self;
  42. }
  43. #pragma mark - Private
  44. - (void)applyDefaultsToSelfDuringInitializationWithFrame:(CGRect)frame pages:(NSArray<EAIntroPage *> *)pagesArray {
  45. self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  46. self.swipeToExit = YES;
  47. self.easeOutCrossDisolves = YES;
  48. self.hideOffscreenPages = YES;
  49. self.bgViewContentMode = UIViewContentModeScaleAspectFill;
  50. self.motionEffectsRelativeValue = 40.f;
  51. self.backgroundColor = [UIColor blackColor];
  52. _scrollingEnabled = YES;
  53. _titleViewY = 20.f;
  54. _pageControlY = 70.f;
  55. _skipButtonY = EA_EMPTY_PROPERTY;
  56. _skipButtonSideMargin = 10.f;
  57. _skipButtonAlignment = EAViewAlignmentRight;
  58. _skipped = NO;
  59. _limitPageIndex = -1;
  60. [self buildBackgroundImage];
  61. self.pages = [pagesArray copy];
  62. [self buildFooterView];
  63. }
  64. - (void)applyDefaultsToBackgroundImageView:(UIImageView *)backgroundImageView {
  65. backgroundImageView.backgroundColor = [UIColor clearColor];
  66. backgroundImageView.contentMode = self.bgViewContentMode;
  67. backgroundImageView.autoresizesSubviews = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
  68. backgroundImageView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
  69. }
  70. - (void)makePanelVisibleAtIndex:(NSUInteger)panelIndex{
  71. [UIView animateWithDuration:0.3 animations:^{
  72. for (int idx = 0; idx < _pages.count; idx++) {
  73. if (idx == panelIndex) {
  74. [[self viewForPageIndex:idx] setAlpha:[self alphaForPageIndex:idx]];
  75. } else {
  76. if(!self.hideOffscreenPages) {
  77. [[self viewForPageIndex:idx] setAlpha:0];
  78. }
  79. }
  80. }
  81. }];
  82. }
  83. - (EAIntroPage *)pageForIndex:(NSUInteger)idx {
  84. if(idx >= _pages.count) {
  85. return nil;
  86. }
  87. return (EAIntroPage *)_pages[idx];
  88. }
  89. - (CGFloat)alphaForPageIndex:(NSUInteger)idx {
  90. if(![self pageForIndex:idx]) {
  91. return 1.f;
  92. }
  93. return [self pageForIndex:idx].alpha;
  94. }
  95. - (BOOL)showTitleViewForPage:(NSUInteger)idx {
  96. if(![self pageForIndex:idx]) {
  97. return NO;
  98. }
  99. return [self pageForIndex:idx].showTitleView;
  100. }
  101. - (UIView *)viewForPageIndex:(NSUInteger)idx {
  102. return [self pageForIndex:idx].pageView;
  103. }
  104. - (UIImage *)bgImageForPage:(NSUInteger)idx {
  105. return [self pageForIndex:idx].bgImage;
  106. }
  107. - (UIColor *)bgColorForPage:(NSUInteger)idx {
  108. return [self pageForIndex:idx].bgColor;
  109. }
  110. - (void)showPanelAtPageControl {
  111. [self makePanelVisibleAtIndex:self.currentPageIndex];
  112. [self setCurrentPageIndex:self.pageControl.currentPage animated:YES];
  113. }
  114. - (void)checkIndexForScrollView:(EARestrictedScrollView *)scrollView {
  115. NSUInteger newPageIndex = (scrollView.contentOffset.x + scrollView.bounds.size.width/2)/self.scrollView.frame.size.width;
  116. [self notifyDelegateWithPreviousPage:self.currentPageIndex andCurrentPage:newPageIndex];
  117. _currentPageIndex = newPageIndex;
  118. if (self.currentPageIndex == (_pages.count)) {
  119. //if run here, it means you can't call _pages[self.currentPageIndex],
  120. //to be safe, set to the biggest index
  121. _currentPageIndex = _pages.count - 1;
  122. if ([self.delegate respondsToSelector:@selector(introWillFinish:wasSkipped:)]) {
  123. [self.delegate introWillFinish:self wasSkipped:self.skipped];
  124. }
  125. [self finishIntroductionAndRemoveSelf];
  126. }
  127. }
  128. - (void)finishIntroductionAndRemoveSelf {
  129. //prevent last page flicker on disappearing
  130. self.alpha = 0;
  131. //Calling removeFromSuperview from scrollViewDidEndDecelerating: method leads to crash on iOS versions < 7.0.
  132. //removeFromSuperview should be called after a delay
  133. dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)0);
  134. dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
  135. if ([(id)self.delegate respondsToSelector:@selector(introDidFinish:wasSkipped:)]) {
  136. [self.delegate introDidFinish:self wasSkipped:self.skipped];
  137. }
  138. [self removeFromSuperview];
  139. });
  140. }
  141. - (void)skipIntroduction {
  142. self.skipped = YES;
  143. [self hideWithFadeOutDuration:0.3];
  144. }
  145. - (void)notifyDelegateWithPreviousPage:(NSUInteger)previousPageIndex andCurrentPage:(NSUInteger)currentPageIndex {
  146. if(currentPageIndex!=_currentPageIndex && currentPageIndex < _pages.count) {
  147. EAIntroPage* previousPage = _pages[previousPageIndex];
  148. EAIntroPage* currentPage = _pages[currentPageIndex];
  149. if(previousPage.onPageDidDisappear) previousPage.onPageDidDisappear();
  150. if(currentPage.onPageDidAppear) currentPage.onPageDidAppear();
  151. if ([(id)self.delegate respondsToSelector:@selector(intro:pageAppeared:withIndex:)]) {
  152. [self.delegate intro:self pageAppeared:_pages[currentPageIndex] withIndex:currentPageIndex];
  153. }
  154. }
  155. }
  156. #pragma mark - Properties
  157. - (EARestrictedScrollView *)scrollView {
  158. if (!_scrollView) {
  159. _scrollView = [[EARestrictedScrollView alloc] initWithFrame:self.bounds];
  160. _scrollView.accessibilityIdentifier = @"intro_scroll";
  161. _scrollView.pagingEnabled = YES;
  162. _scrollView.alwaysBounceHorizontal = YES;
  163. _scrollView.showsHorizontalScrollIndicator = NO;
  164. _scrollView.showsVerticalScrollIndicator = NO;
  165. _scrollView.delegate = self;
  166. _scrollView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
  167. }
  168. return _scrollView;
  169. }
  170. - (NSUInteger)visiblePageIndex {
  171. return (NSUInteger) ((self.scrollView.contentOffset.x + self.scrollView.bounds.size.width/2) / self.scrollView.frame.size.width);
  172. }
  173. - (UIImageView *)bgImageView {
  174. if (!_bgImageView) {
  175. _bgImageView = [[UIImageView alloc] initWithFrame:self.bounds];
  176. [self applyDefaultsToBackgroundImageView:_bgImageView];
  177. }
  178. return _bgImageView;
  179. }
  180. - (UIImageView *)pageBgBack {
  181. if (!_pageBgBack) {
  182. _pageBgBack = [[UIImageView alloc] initWithFrame:self.bounds];
  183. [self applyDefaultsToBackgroundImageView:_pageBgBack];
  184. _pageBgBack.alpha = 0;
  185. }
  186. return _pageBgBack;
  187. }
  188. - (UIImageView *)pageBgFront {
  189. if (!_pageBgFront) {
  190. _pageBgFront = [[UIImageView alloc] initWithFrame:self.bounds];
  191. [self applyDefaultsToBackgroundImageView:_pageBgFront];
  192. _pageBgFront.alpha = 0;
  193. }
  194. return _pageBgFront;
  195. }
  196. - (UIPageControl *)pageControl {
  197. if (!_pageControl) {
  198. _pageControl = [[UIPageControl alloc] init];
  199. [self applyDefaultsToPageControl];
  200. }
  201. return _pageControl;
  202. }
  203. - (void)applyDefaultsToPageControl {
  204. _pageControl.defersCurrentPageDisplay = YES;
  205. _pageControl.numberOfPages = _pages.count;
  206. _pageControl.translatesAutoresizingMaskIntoConstraints = NO;
  207. [_pageControl addTarget:self action:@selector(showPanelAtPageControl) forControlEvents:UIControlEventValueChanged];
  208. }
  209. - (UIButton *)skipButton {
  210. if (!_skipButton) {
  211. _skipButton = [[UIButton alloc] init];
  212. [_skipButton setTitle:NSLocalizedString(@"Skip", nil) forState:UIControlStateNormal];
  213. [self applyDefaultsToSkipButton];
  214. }
  215. return _skipButton;
  216. }
  217. - (void)applyDefaultsToSkipButton {
  218. _skipButton.translatesAutoresizingMaskIntoConstraints = NO;
  219. [_skipButton addTarget:self action:@selector(skipIntroduction) forControlEvents:UIControlEventTouchUpInside];
  220. }
  221. - (NSMutableArray *)footerConstraints {
  222. if (!_footerConstraints) {
  223. _footerConstraints = [NSMutableArray array];
  224. }
  225. return _footerConstraints;
  226. }
  227. - (NSMutableArray *)titleViewConstraints {
  228. if (!_titleViewConstraints) {
  229. _titleViewConstraints = [NSMutableArray array];
  230. }
  231. return _titleViewConstraints;
  232. }
  233. #pragma mark - UI building
  234. - (void)buildBackgroundImage {
  235. [self addSubview:self.bgImageView];
  236. [self addSubview:self.pageBgBack];
  237. [self addSubview:self.pageBgFront];
  238. if (self.useMotionEffects) {
  239. [self addMotionEffectsOnBg];
  240. }
  241. }
  242. - (void)buildScrollView {
  243. CGFloat contentXIndex = 0;
  244. for (NSUInteger idx = 0; idx < _pages.count; idx++) {
  245. EAIntroPage *page = _pages[idx];
  246. page.pageView = [self viewForPage:page atXIndex:contentXIndex];
  247. contentXIndex += self.scrollView.frame.size.width;
  248. [self.scrollView addSubview:page.pageView];
  249. if(page.onPageDidLoad) page.onPageDidLoad();
  250. }
  251. [self makePanelVisibleAtIndex:0];
  252. if (self.swipeToExit) {
  253. [self appendCloseViewAtXIndex:&contentXIndex];
  254. }
  255. [self insertSubview:self.scrollView aboveSubview:self.pageBgFront];
  256. self.scrollView.contentSize = CGSizeMake(contentXIndex, self.scrollView.frame.size.height);
  257. self.pageBgBack.alpha = 0;
  258. self.pageBgBack.image = [self bgImageForPage:1];
  259. self.pageBgBack.backgroundColor = [self bgColorForPage:1];
  260. self.pageBgFront.alpha = [self alphaForPageIndex:0];
  261. self.pageBgFront.image = [self bgImageForPage:0];
  262. self.pageBgFront.backgroundColor = [self bgColorForPage:0];
  263. }
  264. - (UIView *)viewForPage:(EAIntroPage *)page atXIndex:(CGFloat)xIndex {
  265. UIView *pageView = [self createViewForPage:page atXIndex:xIndex];
  266. if(page.customView) {
  267. [self configurePageView:pageView withCustomView:page.customView];
  268. } else {
  269. [self configurePageView:pageView forPage:page];
  270. }
  271. return pageView;
  272. }
  273. - (UIView *)createViewForPage:(EAIntroPage *)page atXIndex:(CGFloat)xIndex {
  274. UIView *pageView = [[UIView alloc] initWithFrame:CGRectMake(xIndex, 0, self.scrollView.frame.size.width, self.scrollView.frame.size.height)];
  275. pageView.accessibilityLabel = [NSString stringWithFormat:@"intro_page_%lu",(unsigned long)[self.pages indexOfObject:page]];
  276. if(page.alpha < 1.f || !page.bgImage) {
  277. self.backgroundColor = [UIColor clearColor];
  278. }
  279. return pageView;
  280. }
  281. - (void)configurePageView:(UIView *)pageView withCustomView:(UIView *)customView {
  282. [self addTapToNextActionToPageView:customView];
  283. [pageView addSubview:customView];
  284. NSMutableArray *constraints = @[].mutableCopy;
  285. [constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[customView]-0-|" options:0 metrics:nil views:@{@"customView": customView}]];
  286. [constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[customView]-0-|" options:0 metrics:nil views:@{@"customView": customView}]];
  287. [pageView addConstraints:constraints];
  288. }
  289. - (void)configurePageView:(UIView *)pageView forPage:(EAIntroPage *)page {
  290. [self addTapToNextActionToPageView:pageView];
  291. [self applyAccessibilityLabelForPage:page toView:pageView];
  292. UIView *titleImageView;
  293. if(page.titleIconView) {
  294. titleImageView = page.titleIconView;
  295. titleImageView.tag = kTitleImageViewTag;
  296. titleImageView.translatesAutoresizingMaskIntoConstraints = NO;
  297. [pageView addSubview:titleImageView];
  298. [pageView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-topSpace@250-[titleImageView(imageHeight)]" options:NSLayoutFormatAlignAllTop metrics:@{@"imageHeight" : @(page.titleIconView.frame.size.height), @"topSpace" : @(page.titleIconPositionY)} views:@{@"titleImageView" : titleImageView}]];
  299. [pageView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[titleImageView(imageWidth)]" options:0 metrics:@{@"imageWidth" : @(page.titleIconView.frame.size.width)} views:@{@"titleImageView" : titleImageView}]];
  300. [pageView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[superview]-(<=1)-[titleImageView]" options:NSLayoutFormatAlignAllCenterX metrics:nil views:@{@"superview" : pageView, @"titleImageView" : titleImageView}]];
  301. }
  302. UILabel *titleLabel;
  303. if(page.title.length) {
  304. titleLabel = [[UILabel alloc] init];
  305. titleLabel.text = page.title;
  306. titleLabel.font = page.titleFont;
  307. titleLabel.textColor = page.titleColor;
  308. titleLabel.backgroundColor = [UIColor clearColor];
  309. titleLabel.textAlignment = page.titleAlignment;
  310. titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
  311. titleLabel.numberOfLines = 0;
  312. titleLabel.tag = kTitleLabelTag;
  313. titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
  314. titleLabel.isAccessibilityElement = NO;
  315. [pageView addSubview:titleLabel];
  316. NSLayoutConstraint *weakConstraint = [NSLayoutConstraint constraintWithItem:pageView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:titleLabel attribute:NSLayoutAttributeTop multiplier:1.0 constant:page.titlePositionY];
  317. weakConstraint.priority = UILayoutPriorityDefaultLow;
  318. [pageView addConstraint:weakConstraint];
  319. [pageView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-10-[titleLabel]-10-|" options:NSLayoutFormatAlignAllTop metrics:nil views:@{@"titleLabel" : titleLabel}]];
  320. }
  321. UITextView *descLabel;
  322. if(page.desc.length) {
  323. descLabel = [[UITextView alloc] init];
  324. descLabel.text = page.desc;
  325. descLabel.scrollEnabled = NO;
  326. descLabel.font = page.descFont;
  327. descLabel.textColor = page.descColor;
  328. descLabel.backgroundColor = [UIColor clearColor];
  329. descLabel.textAlignment = page.descAlignment;
  330. descLabel.userInteractionEnabled = NO;
  331. descLabel.tag = kDescLabelTag;
  332. descLabel.translatesAutoresizingMaskIntoConstraints = NO;
  333. descLabel.isAccessibilityElement = NO;
  334. [pageView addSubview:descLabel];
  335. NSLayoutConstraint *weakConstraint = [NSLayoutConstraint constraintWithItem:pageView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:descLabel attribute:NSLayoutAttributeTop multiplier:1.0 constant:page.descPositionY];
  336. weakConstraint.priority = UILayoutPriorityDefaultLow;
  337. [pageView addConstraint:weakConstraint];
  338. [pageView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-descMargin-[descLabel]-descMargin-|" options:NSLayoutFormatAlignAllTop metrics:@{@"descMargin" : @(page.descSideMargin)} views:@{@"descLabel" : descLabel}]];
  339. }
  340. // Constraints for handling landscape orientation
  341. if(titleImageView && titleLabel && descLabel) {
  342. [pageView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|->=0-[titleImageView]->=0-[titleLabel]->=0-[descLabel]" options:0 metrics:nil views:@{@"titleImageView" : titleImageView, @"titleLabel" : titleLabel, @"descLabel" : descLabel}]];
  343. } else if(!titleImageView && titleLabel && descLabel) {
  344. [pageView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|->=0-[titleLabel]->=0-[descLabel]" options:0 metrics:nil views:@{@"titleLabel" : titleLabel, @"descLabel" : descLabel}]];
  345. }
  346. if(page.subviews) {
  347. for (UIView *subV in page.subviews) {
  348. [pageView addSubview:subV];
  349. }
  350. }
  351. pageView.alpha = page.alpha;
  352. }
  353. - (void)addTapToNextActionToPageView:(UIView *)pageView {
  354. UITapGestureRecognizer *tapRecognizer =
  355. [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleBackgroundTap:)];
  356. [pageView addGestureRecognizer:tapRecognizer];
  357. }
  358. - (void)applyAccessibilityLabelForPage:(EAIntroPage *)page toView:(UIView *)view {
  359. NSString *accessibilityLabel = [self accessibilityLabelForPage:page];
  360. if (accessibilityLabel.length > 0) {
  361. view.isAccessibilityElement = YES;
  362. view.accessibilityLabel = accessibilityLabel;
  363. view.accessibilityTraits = UIAccessibilityTraitButton;
  364. }
  365. }
  366. - (NSString*)accessibilityLabelForPage:(EAIntroPage*)page {
  367. NSString *accessibilityLabel = nil;
  368. if (page.title) {
  369. if (page.desc) {
  370. accessibilityLabel = [NSString stringWithFormat:@"%@, %@", page.title, page.desc];
  371. } else {
  372. accessibilityLabel = page.title;
  373. }
  374. } else {
  375. accessibilityLabel = page.desc;
  376. }
  377. return accessibilityLabel;
  378. }
  379. - (void)appendCloseViewAtXIndex:(CGFloat*)xIndex {
  380. UIView *closeView = [[UIView alloc] initWithFrame:CGRectMake(*xIndex, 0, self.bounds.size.width, self.bounds.size.height)];
  381. closeView.tag = 124;
  382. [self.scrollView addSubview:closeView];
  383. *xIndex += self.scrollView.frame.size.width;
  384. }
  385. - (void)removeCloseViewAtXIndex:(CGFloat*)xIndex {
  386. UIView *closeView = [self.scrollView viewWithTag:124];
  387. if(closeView) {
  388. [closeView removeFromSuperview];
  389. }
  390. *xIndex -= self.scrollView.frame.size.width;
  391. }
  392. - (void)buildTitleView {
  393. if (!self.titleView.superview) {
  394. [self addSubview:self.titleView];
  395. }
  396. if (self.titleViewConstraints.count) {
  397. [self removeConstraints:self.titleViewConstraints];
  398. [self.titleViewConstraints removeAllObjects];
  399. }
  400. NSDictionary *views = @{@"titleView" : self.titleView};
  401. NSDictionary *metrics = @{@"titleViewTopPadding" : @(self.titleViewY), @"titleViewHeight" : @(self.titleView.frame.size.height), @"titleViewWidth" : @(self.titleView.frame.size.width)};
  402. [self.titleViewConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-titleViewTopPadding@250-[titleView(titleViewHeight)]" options:NSLayoutFormatAlignAllLeft metrics:metrics views:views]];
  403. [self.titleViewConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[titleView(titleViewWidth)]" options:NSLayoutFormatAlignAllTop metrics:metrics views:views]];
  404. [self.titleViewConstraints addObject:[NSLayoutConstraint constraintWithItem:self.titleView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0]];
  405. self.titleView.translatesAutoresizingMaskIntoConstraints = NO;
  406. [self addConstraints:self.titleViewConstraints];
  407. [self.titleView setNeedsUpdateConstraints];
  408. }
  409. - (void)buildFooterView {
  410. if (!self.pageControl.superview) {
  411. [self insertSubview:self.pageControl aboveSubview:self.scrollView];
  412. }
  413. if (!self.skipButton.superview) {
  414. [self insertSubview:self.skipButton aboveSubview:self.scrollView];
  415. }
  416. [self.pageControl.superview bringSubviewToFront:self.pageControl];
  417. [self.skipButton.superview bringSubviewToFront:self.skipButton];
  418. if (self.footerConstraints.count) {
  419. [self removeConstraints:self.footerConstraints];
  420. [self.footerConstraints removeAllObjects];
  421. }
  422. CGFloat pageControlHeight = self.pageControl.frame.size.height > 0 ? self.pageControl.frame.size.height : PAGE_CTRL_DEFAULT_HEIGHT;
  423. CGFloat skipButtonWidth = self.skipButton.frame.size.width > 0 ? self.skipButton.frame.size.width : SKIP_BTN_DEFAULT_WIDTH;
  424. CGFloat skipButtonHeight = self.skipButton.frame.size.height > 0 ? self.skipButton.frame.size.height : SKIP_BTN_DEFAULT_HEIGHT;
  425. NSDictionary *views = @{@"pageControl" : self.pageControl, @"skipButton" : self.skipButton};
  426. NSDictionary *metrics = @{@"pageControlBottomPadding" : @(self.pageControlY - pageControlHeight), @"pageControlHeight" : @(pageControlHeight), @"skipButtonBottomPadding" : @(self.skipButtonY - skipButtonHeight), @"skipButtonSideMargin" : @(self.skipButtonSideMargin), @"skipButtonWidth" : @(skipButtonWidth)};
  427. [self.footerConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[pageControl]-|" options:NSLayoutFormatAlignAllCenterX metrics:metrics views:views]];
  428. [self.footerConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[pageControl(pageControlHeight)]-pageControlBottomPadding@250-|" options:NSLayoutFormatAlignAllBottom metrics:metrics views:views]];
  429. if (self.skipButton && !self.skipButton.hidden) {
  430. if(self.skipButtonAlignment == EAViewAlignmentCenter) {
  431. [self.footerConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[skipButton(skipButtonWidth)]" options:NSLayoutFormatAlignAllTop metrics:metrics views:views]];
  432. [self.footerConstraints addObject:[NSLayoutConstraint constraintWithItem:self.skipButton attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0]];
  433. } else if(self.skipButtonAlignment == EAViewAlignmentLeft) {
  434. [self.footerConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-skipButtonSideMargin-[skipButton]" options:NSLayoutFormatAlignAllLeft metrics:metrics views:views]];
  435. } else if(self.skipButtonAlignment == EAViewAlignmentRight) {
  436. [self.footerConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[skipButton]-skipButtonSideMargin-|" options:NSLayoutFormatAlignAllRight metrics:metrics views:views]];
  437. }
  438. if(self.skipButtonY == EA_EMPTY_PROPERTY) {
  439. [self.footerConstraints addObject:[NSLayoutConstraint constraintWithItem:self.pageControl attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.skipButton attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];
  440. } else {
  441. [self.footerConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[skipButton]-skipButtonBottomPadding-|" options:NSLayoutFormatAlignAllBottom metrics:metrics views:views]];
  442. }
  443. }
  444. [self addConstraints:self.footerConstraints];
  445. [self.pageControl setNeedsUpdateConstraints];
  446. [self.skipButton setNeedsUpdateConstraints];
  447. }
  448. #pragma mark - UIScrollView Delegate
  449. - (void)scrollViewWillBeginDragging:(EARestrictedScrollView *)scrollView {
  450. if ([self.delegate respondsToSelector:@selector(intro:pageStartScrolling:withIndex:)] && self.currentPageIndex < [self.pages count]) {
  451. [self.delegate intro:self pageStartScrolling:_pages[self.currentPageIndex] withIndex:self.currentPageIndex];
  452. }
  453. }
  454. - (void)scrollViewDidEndDecelerating:(EARestrictedScrollView *)scrollView {
  455. [self checkIndexForScrollView:scrollView];
  456. if ([self.delegate respondsToSelector:@selector(intro:pageEndScrolling:withIndex:)] && self.currentPageIndex < [self.pages count]) {
  457. [self.delegate intro:self pageEndScrolling:_pages[self.currentPageIndex] withIndex:self.currentPageIndex];
  458. }
  459. }
  460. - (void)scrollViewDidEndScrollingAnimation:(EARestrictedScrollView *)scrollView {
  461. [self checkIndexForScrollView:scrollView];
  462. }
  463. - (void)scrollViewDidScroll:(EARestrictedScrollView *)scrollView {
  464. if(!self.scrollingEnabled) {
  465. return;
  466. }
  467. CGFloat offset = scrollView.contentOffset.x / self.scrollView.frame.size.width;
  468. NSUInteger page = (NSUInteger)(offset);
  469. if (page == (_pages.count - 1) && self.swipeToExit) {
  470. self.alpha = ((self.scrollView.frame.size.width*_pages.count)-self.scrollView.contentOffset.x)/self.scrollView.frame.size.width;
  471. } else {
  472. if([self pageForIndex:page]) {
  473. self.alpha = 1.f;
  474. }
  475. }
  476. [self crossDissolveForOffset:offset];
  477. if (self.visiblePageIndex < _pages.count) {
  478. self.pageControl.currentPage = self.visiblePageIndex;
  479. [self makePanelVisibleAtIndex:self.visiblePageIndex];
  480. }
  481. if ([self.delegate respondsToSelector:@selector(intro:didScrollWithOffset:)]) {
  482. [self.delegate intro:self didScrollWithOffset:offset];
  483. }
  484. }
  485. CGFloat easeOutValue(CGFloat value) {
  486. CGFloat inverse = value - 1.f;
  487. return (CGFloat) (1.f + inverse * inverse * inverse);
  488. }
  489. - (void)crossDissolveForOffset:(CGFloat)offset {
  490. NSUInteger page = (NSUInteger)(offset);
  491. CGFloat alphaValue = offset - page;
  492. if (alphaValue < 0 && self.visiblePageIndex == 0){
  493. self.pageBgBack.image = nil;
  494. return;
  495. }
  496. self.pageBgFront.alpha = [self alphaForPageIndex:page];
  497. self.pageBgFront.image = [self bgImageForPage:page];
  498. self.pageBgFront.backgroundColor = [self bgColorForPage:page];
  499. self.pageBgBack.alpha = 0;
  500. self.pageBgBack.image = [self bgImageForPage:page+1];
  501. self.pageBgBack.backgroundColor = [self bgColorForPage:page+1];
  502. CGFloat backLayerAlpha = alphaValue;
  503. CGFloat frontLayerAlpha = (1 - alphaValue);
  504. if (self.easeOutCrossDisolves) {
  505. backLayerAlpha = easeOutValue(backLayerAlpha);
  506. frontLayerAlpha = easeOutValue(frontLayerAlpha);
  507. }
  508. self.pageBgBack.alpha = MIN(backLayerAlpha,[self alphaForPageIndex:page+1]);
  509. self.pageBgFront.alpha = MIN(frontLayerAlpha,[self alphaForPageIndex:page]);
  510. if(self.titleView) {
  511. if([self showTitleViewForPage:page] && [self showTitleViewForPage:page+1]) {
  512. [self.titleView setAlpha:1.0];
  513. } else if(![self showTitleViewForPage:page] && ![self showTitleViewForPage:page+1]) {
  514. [self.titleView setAlpha:0.0];
  515. } else if([self showTitleViewForPage:page]) {
  516. [self.titleView setAlpha:(1 - alphaValue)];
  517. } else {
  518. [self.titleView setAlpha:alphaValue];
  519. }
  520. }
  521. if(self.skipButton && self.showSkipButtonOnlyOnLastPage) {
  522. if(page < (long)[self.pages count] - 2) {
  523. [self.skipButton setAlpha:0.0];
  524. } else if(page == [self.pages count] - 1) {
  525. [self.skipButton setAlpha:(1 - alphaValue)];
  526. } else {
  527. [self.skipButton setAlpha:alphaValue];
  528. }
  529. }
  530. }
  531. #pragma mark - UIView lifecycle calls
  532. - (void)layoutSubviews {
  533. [super layoutSubviews];
  534. // Get amount of pages:
  535. NSInteger numberOfPages = _pages.count;
  536. // Increase with 1 page when feature enabled:
  537. if (self.swipeToExit) {
  538. numberOfPages = numberOfPages + 1;
  539. }
  540. // Descrease to limited index when scrolling is restricted:
  541. if (self.limitPageIndex != -1) {
  542. numberOfPages = self.limitPageIndex + 1;
  543. }
  544. // Adjust contentSize of ScrollView:
  545. CGSize newContentSize = CGSizeMake(numberOfPages * self.scrollView.frame.size.width, self.scrollView.frame.size.height);
  546. if(self.scrollView.contentOffset.x > newContentSize.width) {
  547. CGPoint newOffset = self.scrollView.contentOffset;
  548. if (self.swipeToExit) {
  549. newOffset.x = newContentSize.width - (self.scrollView.frame.size.width * 2);
  550. } else {
  551. newOffset.x = newContentSize.width - self.scrollView.frame.size.width;
  552. }
  553. self.scrollView.contentOffset = newOffset;
  554. }
  555. self.scrollView.contentSize = newContentSize;
  556. // Adjust frame of each page:
  557. NSUInteger i = 0;
  558. for (EAIntroPage *page in _pages) {
  559. page.pageView.frame = CGRectMake(i * self.scrollView.bounds.size.width,
  560. 0,
  561. self.scrollView.bounds.size.width,
  562. self.scrollView.bounds.size.height);
  563. i++;
  564. }
  565. // Adjust scrolling to fit resized page:
  566. CGFloat offset = self.currentPageIndex * self.scrollView.frame.size.width;
  567. CGRect pageRect = { .origin.x = offset, .origin.y = 0.0, .size.width = self.scrollView.frame.size.width, .size.height = self.scrollView.frame.size.height };
  568. [self.scrollView scrollRectToVisible:pageRect animated:NO];
  569. // Adjust restricted scroll area:
  570. if(!self.scrollingEnabled) {
  571. self.scrollView.restrictionArea = CGRectMake(self.visiblePageIndex * self.bounds.size.width, 0, self.scrollView.bounds.size.width, self.scrollView.bounds.size.height);
  572. } else {
  573. self.scrollView.restrictionArea = CGRectZero;
  574. }
  575. }
  576. #pragma mark - Custom setters
  577. - (void)setScrollingEnabled:(BOOL)scrollingEnabled {
  578. if(!scrollingEnabled) {
  579. self.scrollView.restrictionArea = CGRectMake(self.visiblePageIndex * self.bounds.size.width, 0, self.scrollView.bounds.size.width, self.scrollView.bounds.size.height);
  580. } else {
  581. self.scrollView.restrictionArea = CGRectZero;
  582. }
  583. _scrollingEnabled = scrollingEnabled;
  584. }
  585. - (void)setPages:(NSArray<EAIntroPage *> *)pages {
  586. _pages = [pages copy];
  587. [self.scrollView removeFromSuperview];
  588. self.scrollView = nil;
  589. _currentPageIndex = 0;
  590. self.pageControl.numberOfPages = _pages.count;
  591. self.pageControl.currentPage = self.currentPageIndex;
  592. [self buildScrollView];
  593. }
  594. - (void)setBgImage:(UIImage *)bgImage {
  595. _bgImage = bgImage;
  596. self.bgImageView.image = _bgImage;
  597. [self setNeedsDisplay];
  598. }
  599. - (void)setBgViewContentMode:(UIViewContentMode)bgViewContentMode {
  600. _bgViewContentMode = bgViewContentMode;
  601. self.bgImageView.contentMode = bgViewContentMode;
  602. self.pageBgBack.contentMode = bgViewContentMode;
  603. self.pageBgFront.contentMode = bgViewContentMode;
  604. [self setNeedsDisplay];
  605. }
  606. - (void)setSwipeToExit:(BOOL)swipeToExit {
  607. if (swipeToExit != _swipeToExit) {
  608. CGFloat contentXIndex = self.scrollView.contentSize.width;
  609. if(swipeToExit) {
  610. [self appendCloseViewAtXIndex:&contentXIndex];
  611. } else {
  612. [self removeCloseViewAtXIndex:&contentXIndex];
  613. }
  614. self.scrollView.contentSize = CGSizeMake(contentXIndex, self.scrollView.frame.size.height);
  615. }
  616. _swipeToExit = swipeToExit;
  617. }
  618. - (void)setTitleView:(UIView *)titleView {
  619. [_titleView removeFromSuperview];
  620. _titleView = titleView;
  621. if ([_titleView respondsToSelector:@selector(setTranslatesAutoresizingMaskIntoConstraints:)]) {
  622. _titleView.translatesAutoresizingMaskIntoConstraints = NO;
  623. }
  624. CGFloat offset = self.scrollView.contentOffset.x / self.scrollView.frame.size.width;
  625. [self crossDissolveForOffset:offset];
  626. [self buildTitleView];
  627. [self setNeedsDisplay];
  628. }
  629. - (void)setTitleViewY:(CGFloat)titleViewY {
  630. _titleViewY = titleViewY;
  631. [self buildTitleView];
  632. [self setNeedsDisplay];
  633. }
  634. - (void)setPageControl:(UIPageControl *)pageControl {
  635. if(!pageControl) {
  636. _pageControl.hidden = YES;
  637. return;
  638. }
  639. [_pageControl removeFromSuperview];
  640. _pageControl = pageControl;
  641. [self applyDefaultsToPageControl];
  642. [self buildFooterView];
  643. [self setNeedsDisplay];
  644. }
  645. - (void)setPageControlY:(CGFloat)pageControlY {
  646. _pageControlY = pageControlY;
  647. [self buildFooterView];
  648. [self setNeedsDisplay];
  649. }
  650. - (void)setSkipButton:(UIButton *)skipButton {
  651. if(!skipButton) {
  652. _skipButton.hidden = YES;
  653. return;
  654. }
  655. [_skipButton removeFromSuperview];
  656. _skipButton = skipButton;
  657. _skipButton.hidden = NO;
  658. [self applyDefaultsToSkipButton];
  659. [self buildFooterView];
  660. [self setNeedsDisplay];
  661. }
  662. - (void)setSkipButtonY:(CGFloat)skipButtonY {
  663. _skipButtonY = skipButtonY;
  664. [self buildFooterView];
  665. [self setNeedsDisplay];
  666. }
  667. - (void)setSkipButtonSideMargin:(CGFloat)skipButtonSideMargin {
  668. _skipButtonSideMargin = skipButtonSideMargin;
  669. [self buildFooterView];
  670. [self setNeedsDisplay];
  671. }
  672. - (void)setSkipButtonAlignment:(EAViewAlignment)skipButtonAlignment {
  673. _skipButtonAlignment = skipButtonAlignment;
  674. [self buildFooterView];
  675. [self setNeedsDisplay];
  676. }
  677. - (void)setShowSkipButtonOnlyOnLastPage:(BOOL)showSkipButtonOnlyOnLastPage {
  678. _showSkipButtonOnlyOnLastPage = showSkipButtonOnlyOnLastPage;
  679. CGFloat offset = self.scrollView.contentOffset.x / self.scrollView.frame.size.width;
  680. [self crossDissolveForOffset:offset];
  681. }
  682. - (void)setUseMotionEffects:(BOOL)useMotionEffects {
  683. if(_useMotionEffects == useMotionEffects) {
  684. return;
  685. }
  686. _useMotionEffects = useMotionEffects;
  687. if(useMotionEffects) {
  688. [self addMotionEffectsOnBg];
  689. } else {
  690. [self removeMotionEffectsOnBg];
  691. }
  692. }
  693. - (void)setMotionEffectsRelativeValue:(CGFloat)motionEffectsRelativeValue {
  694. _motionEffectsRelativeValue = motionEffectsRelativeValue;
  695. if(self.useMotionEffects) {
  696. [self addMotionEffectsOnBg];
  697. }
  698. }
  699. #pragma mark - Motion effects actions
  700. - (void)addMotionEffectsOnBg {
  701. if(![self respondsToSelector:@selector(setMotionEffects:)]) {
  702. return;
  703. }
  704. CGRect parallaxFrame = CGRectMake(-self.motionEffectsRelativeValue, -self.motionEffectsRelativeValue, self.bounds.size.width + (self.motionEffectsRelativeValue * 2), self.bounds.size.height + (self.motionEffectsRelativeValue * 2));
  705. [self.pageBgFront setFrame:parallaxFrame];
  706. [self.pageBgBack setFrame:parallaxFrame];
  707. [self.bgImageView setFrame:parallaxFrame];
  708. // Set vertical effect
  709. UIInterpolatingMotionEffect *verticalMotionEffect =
  710. [[UIInterpolatingMotionEffect alloc]
  711. initWithKeyPath:@"center.y"
  712. type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis];
  713. verticalMotionEffect.minimumRelativeValue = @(self.motionEffectsRelativeValue);
  714. verticalMotionEffect.maximumRelativeValue = @(-self.motionEffectsRelativeValue);
  715. // Set horizontal effect
  716. UIInterpolatingMotionEffect *horizontalMotionEffect =
  717. [[UIInterpolatingMotionEffect alloc]
  718. initWithKeyPath:@"center.x"
  719. type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis];
  720. horizontalMotionEffect.minimumRelativeValue = @(self.motionEffectsRelativeValue);
  721. horizontalMotionEffect.maximumRelativeValue = @(-self.motionEffectsRelativeValue);
  722. // Create group to combine both
  723. UIMotionEffectGroup *group = [UIMotionEffectGroup new];
  724. group.motionEffects = @[horizontalMotionEffect, verticalMotionEffect];
  725. // Add both effects to all background image views
  726. [UIView animateWithDuration:0.5f animations:^{
  727. [self.pageBgFront setMotionEffects:@[group]];
  728. [self.pageBgBack setMotionEffects:@[group]];
  729. [self.bgImageView setMotionEffects:@[group]];
  730. }];
  731. }
  732. - (void)removeMotionEffectsOnBg {
  733. if(![self respondsToSelector:@selector(removeMotionEffect:)]) {
  734. return;
  735. }
  736. [UIView animateWithDuration:0.5f animations:^{
  737. [self.pageBgFront removeMotionEffect:self.pageBgFront.motionEffects[0]];
  738. [self.pageBgBack removeMotionEffect:self.pageBgBack.motionEffects[0]];
  739. [self.bgImageView removeMotionEffect:self.bgImageView.motionEffects[0]];
  740. }];
  741. }
  742. #pragma mark - Actions
  743. - (void)showFullscreen {
  744. [self showFullscreenWithAnimateDuration:0.3f andInitialPageIndex:0];
  745. }
  746. - (void)showFullscreenWithAnimateDuration:(CGFloat)duration {
  747. [self showFullscreenWithAnimateDuration:duration andInitialPageIndex:0];
  748. }
  749. - (void)showFullscreenWithAnimateDuration:(CGFloat)duration andInitialPageIndex:(NSUInteger)initialPageIndex {
  750. UIView *selectedView;
  751. NSEnumerator *frontToBackWindows = [UIApplication.sharedApplication.windows reverseObjectEnumerator];
  752. for (UIWindow *window in frontToBackWindows) {
  753. BOOL windowOnMainScreen = window.screen == UIScreen.mainScreen;
  754. BOOL windowIsVisible = !window.hidden && window.alpha > 0;
  755. BOOL windowLevelNormal = window.windowLevel == UIWindowLevelNormal;
  756. if (windowOnMainScreen && windowIsVisible && windowLevelNormal) {
  757. selectedView = window;
  758. break;
  759. }
  760. }
  761. [self showInView:selectedView animateDuration:duration withInitialPageIndex:initialPageIndex];
  762. }
  763. - (void)showInView:(UIView *)view {
  764. [self showInView:view animateDuration:0.3f withInitialPageIndex:0];
  765. }
  766. - (void)showInView:(UIView *)view animateDuration:(CGFloat)duration {
  767. [self showInView:view animateDuration:duration withInitialPageIndex:0];
  768. }
  769. - (void)showInView:(UIView *)view animateDuration:(CGFloat)duration withInitialPageIndex:(NSUInteger)initialPageIndex {
  770. if(![self pageForIndex:initialPageIndex]) {
  771. NSLog(@"Wrong initialPageIndex received: %ld",(long)initialPageIndex);
  772. return;
  773. }
  774. self.skipped = NO;
  775. self.currentPageIndex = initialPageIndex;
  776. self.alpha = 0;
  777. if(self.superview != view) {
  778. [view addSubview:self];
  779. } else {
  780. [view bringSubviewToFront:self];
  781. }
  782. [UIView animateWithDuration:duration animations:^{
  783. self.alpha = 1;
  784. } completion:^(BOOL finished) {
  785. EAIntroPage *currentPage = _pages[self.currentPageIndex];
  786. if(currentPage.onPageDidAppear) currentPage.onPageDidAppear();
  787. if ([(id)self.delegate respondsToSelector:@selector(intro:pageAppeared:withIndex:)]) {
  788. [self.delegate intro:self pageAppeared:_pages[self.currentPageIndex] withIndex:self.currentPageIndex];
  789. }
  790. }];
  791. }
  792. - (void)hideWithFadeOutDuration:(CGFloat)duration {
  793. if ([self.delegate respondsToSelector:@selector(introWillFinish:wasSkipped:)]) {
  794. [self.delegate introWillFinish:self wasSkipped:self.skipped];
  795. }
  796. [UIView animateWithDuration:duration animations:^{
  797. self.alpha = 0;
  798. } completion:^(BOOL finished){
  799. [self finishIntroductionAndRemoveSelf];
  800. }];
  801. }
  802. - (void)setCurrentPageIndex:(NSUInteger)currentPageIndex {
  803. [self setCurrentPageIndex:currentPageIndex animated:NO];
  804. }
  805. - (void)setCurrentPageIndex:(NSUInteger)currentPageIndex animated:(BOOL)animated {
  806. if(![self pageForIndex:currentPageIndex]) {
  807. NSLog(@"Wrong currentPageIndex received: %ld",(long)currentPageIndex);
  808. return;
  809. }
  810. _currentPageIndex = currentPageIndex;
  811. [self scrollToPageForIndex:currentPageIndex animated:animated];
  812. }
  813. - (void)scrollToPageForIndex:(NSUInteger)newPageIndex animated:(BOOL)animated
  814. {
  815. if(![self pageForIndex:newPageIndex]) {
  816. NSLog(@"Wrong newPageIndex received: %ld",(long)newPageIndex);
  817. return;
  818. }
  819. CGFloat offset = newPageIndex * self.scrollView.frame.size.width;
  820. CGRect pageRect = { .origin.x = offset, .origin.y = 0.0, .size.width = self.scrollView.frame.size.width, .size.height = self.scrollView.frame.size.height };
  821. [self.scrollView scrollRectToVisible:pageRect animated:animated];
  822. if(!animated) {
  823. [self scrollViewDidScroll:self.scrollView];
  824. }
  825. }
  826. - (void)handleBackgroundTap:(UIGestureRecognizer *)tapRecognizer {
  827. if (tapRecognizer.state == UIGestureRecognizerStateEnded) {
  828. [self goToNext:tapRecognizer];
  829. }
  830. }
  831. - (IBAction)goToNext:(id)sender {
  832. if(!self.tapToNext) {
  833. return;
  834. }
  835. if(self.currentPageIndex + 1 >= [self.pages count]) {
  836. [self hideWithFadeOutDuration:0.3];
  837. } else {
  838. // Just scroll to the new page.
  839. // After scrolling ends, we call -checkIndexForScrollView:, which itself sets the new currentPageIndex.
  840. [self scrollToPageForIndex:self.currentPageIndex + 1 animated:YES];
  841. }
  842. }
  843. - (void)setLimitPageIndex:(NSInteger)limitPageIndex {
  844. _limitPageIndex = limitPageIndex;
  845. if (limitPageIndex < 0 || limitPageIndex >= self.pages.count) {
  846. _limitPageIndex = -1;
  847. self.scrollingEnabled = YES;
  848. return;
  849. } else {
  850. self.scrollView.restrictionArea = CGRectMake(0, 0, (self.limitPageIndex + 1) * self.scrollView.bounds.size.width, self.scrollView.bounds.size.height);
  851. }
  852. }
  853. @end