1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072 |
- //
- // EAIntroView.m
- //
- // Copyright (c) 2013-2015 Evgeny Aleksandrov. License: MIT.
- #import "EAIntroView.h"
- #import "EARestrictedScrollView.h"
- @interface EAIntroView()
- @property (nonatomic, strong) UIImageView *bgImageView;
- @property (nonatomic, strong) UIImageView *pageBgBack;
- @property (nonatomic, strong) UIImageView *pageBgFront;
- @property (nonatomic, strong) NSMutableArray<NSLayoutConstraint *> *footerConstraints;
- @property (nonatomic, strong) NSMutableArray<NSLayoutConstraint *> *titleViewConstraints;
- @property (nonatomic, assign) BOOL skipped;
- @end
- @interface EAIntroPage()
- @property (nonatomic, strong, readwrite) UIView *pageView;
- @end
- @implementation EAIntroView
- @synthesize pageControl = _pageControl;
- @synthesize skipButton = _skipButton;
- #pragma mark - Init
- - (id)initWithFrame:(CGRect)frame {
- self = [super initWithFrame:frame];
- if (self) {
- [self applyDefaultsToSelfDuringInitializationWithFrame:frame pages:nil];
- }
- return self;
- }
- - (id)initWithCoder:(NSCoder *)aDecoder {
- self = [super initWithCoder:aDecoder];
- if (self) {
- [self applyDefaultsToSelfDuringInitializationWithFrame:self.frame pages:nil];
- }
- return self;
- }
- - (id)initWithFrame:(CGRect)frame andPages:(NSArray<EAIntroPage *> *)pagesArray {
- self = [super initWithFrame:frame];
- if (self) {
- [self applyDefaultsToSelfDuringInitializationWithFrame:self.frame pages:pagesArray];
- }
- return self;
- }
- #pragma mark - Private
- - (void)applyDefaultsToSelfDuringInitializationWithFrame:(CGRect)frame pages:(NSArray<EAIntroPage *> *)pagesArray {
- self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
- self.swipeToExit = YES;
- self.easeOutCrossDisolves = YES;
- self.hideOffscreenPages = YES;
- self.bgViewContentMode = UIViewContentModeScaleAspectFill;
- self.motionEffectsRelativeValue = 40.f;
- self.backgroundColor = [UIColor blackColor];
- _scrollingEnabled = YES;
- _titleViewY = 20.f;
- _pageControlY = 70.f;
- _skipButtonY = EA_EMPTY_PROPERTY;
- _skipButtonSideMargin = 10.f;
- _skipButtonAlignment = EAViewAlignmentRight;
- _skipped = NO;
- _limitPageIndex = -1;
-
- [self buildBackgroundImage];
-
- self.pages = [pagesArray copy];
-
- [self buildFooterView];
- }
- - (void)applyDefaultsToBackgroundImageView:(UIImageView *)backgroundImageView {
- backgroundImageView.backgroundColor = [UIColor clearColor];
- backgroundImageView.contentMode = self.bgViewContentMode;
- backgroundImageView.autoresizesSubviews = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
- backgroundImageView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
- }
- - (void)makePanelVisibleAtIndex:(NSUInteger)panelIndex{
- [UIView animateWithDuration:0.3 animations:^{
- for (int idx = 0; idx < _pages.count; idx++) {
- if (idx == panelIndex) {
- [[self viewForPageIndex:idx] setAlpha:[self alphaForPageIndex:idx]];
- } else {
- if(!self.hideOffscreenPages) {
- [[self viewForPageIndex:idx] setAlpha:0];
- }
- }
- }
- }];
- }
- - (EAIntroPage *)pageForIndex:(NSUInteger)idx {
- if(idx >= _pages.count) {
- return nil;
- }
-
- return (EAIntroPage *)_pages[idx];
- }
- - (CGFloat)alphaForPageIndex:(NSUInteger)idx {
- if(![self pageForIndex:idx]) {
- return 1.f;
- }
-
- return [self pageForIndex:idx].alpha;
- }
- - (BOOL)showTitleViewForPage:(NSUInteger)idx {
- if(![self pageForIndex:idx]) {
- return NO;
- }
-
- return [self pageForIndex:idx].showTitleView;
- }
- - (UIView *)viewForPageIndex:(NSUInteger)idx {
- return [self pageForIndex:idx].pageView;
- }
- - (UIImage *)bgImageForPage:(NSUInteger)idx {
- return [self pageForIndex:idx].bgImage;
- }
- - (UIColor *)bgColorForPage:(NSUInteger)idx {
- return [self pageForIndex:idx].bgColor;
- }
- - (void)showPanelAtPageControl {
- [self makePanelVisibleAtIndex:self.currentPageIndex];
-
- [self setCurrentPageIndex:self.pageControl.currentPage animated:YES];
- }
- - (void)checkIndexForScrollView:(EARestrictedScrollView *)scrollView {
- NSUInteger newPageIndex = (scrollView.contentOffset.x + scrollView.bounds.size.width/2)/self.scrollView.frame.size.width;
- [self notifyDelegateWithPreviousPage:self.currentPageIndex andCurrentPage:newPageIndex];
- _currentPageIndex = newPageIndex;
-
- if (self.currentPageIndex == (_pages.count)) {
-
- //if run here, it means you can't call _pages[self.currentPageIndex],
- //to be safe, set to the biggest index
- _currentPageIndex = _pages.count - 1;
- if ([self.delegate respondsToSelector:@selector(introWillFinish:wasSkipped:)]) {
- [self.delegate introWillFinish:self wasSkipped:self.skipped];
- }
- [self finishIntroductionAndRemoveSelf];
- }
- }
- - (void)finishIntroductionAndRemoveSelf {
- //prevent last page flicker on disappearing
- self.alpha = 0;
-
- //Calling removeFromSuperview from scrollViewDidEndDecelerating: method leads to crash on iOS versions < 7.0.
- //removeFromSuperview should be called after a delay
- dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)0);
- dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
- if ([(id)self.delegate respondsToSelector:@selector(introDidFinish:wasSkipped:)]) {
- [self.delegate introDidFinish:self wasSkipped:self.skipped];
- }
-
- [self removeFromSuperview];
- });
- }
- - (void)skipIntroduction {
- self.skipped = YES;
- [self hideWithFadeOutDuration:0.3];
- }
- - (void)notifyDelegateWithPreviousPage:(NSUInteger)previousPageIndex andCurrentPage:(NSUInteger)currentPageIndex {
- if(currentPageIndex!=_currentPageIndex && currentPageIndex < _pages.count) {
- EAIntroPage* previousPage = _pages[previousPageIndex];
- EAIntroPage* currentPage = _pages[currentPageIndex];
- if(previousPage.onPageDidDisappear) previousPage.onPageDidDisappear();
- if(currentPage.onPageDidAppear) currentPage.onPageDidAppear();
-
- if ([(id)self.delegate respondsToSelector:@selector(intro:pageAppeared:withIndex:)]) {
- [self.delegate intro:self pageAppeared:_pages[currentPageIndex] withIndex:currentPageIndex];
- }
- }
- }
- #pragma mark - Properties
- - (EARestrictedScrollView *)scrollView {
- if (!_scrollView) {
- _scrollView = [[EARestrictedScrollView alloc] initWithFrame:self.bounds];
- _scrollView.accessibilityIdentifier = @"intro_scroll";
- _scrollView.pagingEnabled = YES;
- _scrollView.alwaysBounceHorizontal = YES;
- _scrollView.showsHorizontalScrollIndicator = NO;
- _scrollView.showsVerticalScrollIndicator = NO;
- _scrollView.delegate = self;
- _scrollView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
- }
- return _scrollView;
- }
- - (NSUInteger)visiblePageIndex {
- return (NSUInteger) ((self.scrollView.contentOffset.x + self.scrollView.bounds.size.width/2) / self.scrollView.frame.size.width);
- }
- - (UIImageView *)bgImageView {
- if (!_bgImageView) {
- _bgImageView = [[UIImageView alloc] initWithFrame:self.bounds];
- [self applyDefaultsToBackgroundImageView:_bgImageView];
- }
- return _bgImageView;
- }
- - (UIImageView *)pageBgBack {
- if (!_pageBgBack) {
- _pageBgBack = [[UIImageView alloc] initWithFrame:self.bounds];
- [self applyDefaultsToBackgroundImageView:_pageBgBack];
- _pageBgBack.alpha = 0;
- }
- return _pageBgBack;
- }
- - (UIImageView *)pageBgFront {
- if (!_pageBgFront) {
- _pageBgFront = [[UIImageView alloc] initWithFrame:self.bounds];
- [self applyDefaultsToBackgroundImageView:_pageBgFront];
- _pageBgFront.alpha = 0;
- }
- return _pageBgFront;
- }
- - (UIPageControl *)pageControl {
- if (!_pageControl) {
- _pageControl = [[UIPageControl alloc] init];
- [self applyDefaultsToPageControl];
- }
- return _pageControl;
- }
- - (void)applyDefaultsToPageControl {
- _pageControl.defersCurrentPageDisplay = YES;
- _pageControl.numberOfPages = _pages.count;
- _pageControl.translatesAutoresizingMaskIntoConstraints = NO;
- [_pageControl addTarget:self action:@selector(showPanelAtPageControl) forControlEvents:UIControlEventValueChanged];
- }
- - (UIButton *)skipButton {
- if (!_skipButton) {
- _skipButton = [[UIButton alloc] init];
- [_skipButton setTitle:NSLocalizedString(@"Skip", nil) forState:UIControlStateNormal];
- [self applyDefaultsToSkipButton];
- }
- return _skipButton;
- }
- - (void)applyDefaultsToSkipButton {
- _skipButton.translatesAutoresizingMaskIntoConstraints = NO;
- [_skipButton addTarget:self action:@selector(skipIntroduction) forControlEvents:UIControlEventTouchUpInside];
- }
- - (NSMutableArray *)footerConstraints {
- if (!_footerConstraints) {
- _footerConstraints = [NSMutableArray array];
- }
- return _footerConstraints;
- }
- - (NSMutableArray *)titleViewConstraints {
- if (!_titleViewConstraints) {
- _titleViewConstraints = [NSMutableArray array];
- }
- return _titleViewConstraints;
- }
- #pragma mark - UI building
- - (void)buildBackgroundImage {
- [self addSubview:self.bgImageView];
- [self addSubview:self.pageBgBack];
- [self addSubview:self.pageBgFront];
-
- if (self.useMotionEffects) {
- [self addMotionEffectsOnBg];
- }
- }
- - (void)buildScrollView {
- CGFloat contentXIndex = 0;
- for (NSUInteger idx = 0; idx < _pages.count; idx++) {
- EAIntroPage *page = _pages[idx];
- page.pageView = [self viewForPage:page atXIndex:contentXIndex];
- contentXIndex += self.scrollView.frame.size.width;
- [self.scrollView addSubview:page.pageView];
- if(page.onPageDidLoad) page.onPageDidLoad();
- }
-
- [self makePanelVisibleAtIndex:0];
-
- if (self.swipeToExit) {
- [self appendCloseViewAtXIndex:&contentXIndex];
- }
-
- [self insertSubview:self.scrollView aboveSubview:self.pageBgFront];
- self.scrollView.contentSize = CGSizeMake(contentXIndex, self.scrollView.frame.size.height);
-
- self.pageBgBack.alpha = 0;
- self.pageBgBack.image = [self bgImageForPage:1];
- self.pageBgBack.backgroundColor = [self bgColorForPage:1];
- self.pageBgFront.alpha = [self alphaForPageIndex:0];
- self.pageBgFront.image = [self bgImageForPage:0];
- self.pageBgFront.backgroundColor = [self bgColorForPage:0];
- }
- - (UIView *)viewForPage:(EAIntroPage *)page atXIndex:(CGFloat)xIndex {
- UIView *pageView = [self createViewForPage:page atXIndex:xIndex];
- if(page.customView) {
- [self configurePageView:pageView withCustomView:page.customView];
- } else {
- [self configurePageView:pageView forPage:page];
- }
- return pageView;
- }
- - (UIView *)createViewForPage:(EAIntroPage *)page atXIndex:(CGFloat)xIndex {
- UIView *pageView = [[UIView alloc] initWithFrame:CGRectMake(xIndex, 0, self.scrollView.frame.size.width, self.scrollView.frame.size.height)];
- pageView.accessibilityLabel = [NSString stringWithFormat:@"intro_page_%lu",(unsigned long)[self.pages indexOfObject:page]];
- if(page.alpha < 1.f || !page.bgImage) {
- self.backgroundColor = [UIColor clearColor];
- }
- return pageView;
- }
- - (void)configurePageView:(UIView *)pageView withCustomView:(UIView *)customView {
- [self addTapToNextActionToPageView:customView];
- [pageView addSubview:customView];
- NSMutableArray *constraints = @[].mutableCopy;
- [constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[customView]-0-|" options:0 metrics:nil views:@{@"customView": customView}]];
- [constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[customView]-0-|" options:0 metrics:nil views:@{@"customView": customView}]];
- [pageView addConstraints:constraints];
- }
- - (void)configurePageView:(UIView *)pageView forPage:(EAIntroPage *)page {
- [self addTapToNextActionToPageView:pageView];
- [self applyAccessibilityLabelForPage:page toView:pageView];
- UIView *titleImageView;
- if(page.titleIconView) {
- titleImageView = page.titleIconView;
- titleImageView.tag = kTitleImageViewTag;
- titleImageView.translatesAutoresizingMaskIntoConstraints = NO;
- [pageView addSubview:titleImageView];
- [pageView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-topSpace@250-[titleImageView(imageHeight)]" options:NSLayoutFormatAlignAllTop metrics:@{@"imageHeight" : @(page.titleIconView.frame.size.height), @"topSpace" : @(page.titleIconPositionY)} views:@{@"titleImageView" : titleImageView}]];
- [pageView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[titleImageView(imageWidth)]" options:0 metrics:@{@"imageWidth" : @(page.titleIconView.frame.size.width)} views:@{@"titleImageView" : titleImageView}]];
- [pageView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[superview]-(<=1)-[titleImageView]" options:NSLayoutFormatAlignAllCenterX metrics:nil views:@{@"superview" : pageView, @"titleImageView" : titleImageView}]];
- }
- UILabel *titleLabel;
- if(page.title.length) {
- titleLabel = [[UILabel alloc] init];
- titleLabel.text = page.title;
- titleLabel.font = page.titleFont;
- titleLabel.textColor = page.titleColor;
- titleLabel.backgroundColor = [UIColor clearColor];
- titleLabel.textAlignment = page.titleAlignment;
- titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
- titleLabel.numberOfLines = 0;
- titleLabel.tag = kTitleLabelTag;
- titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
- titleLabel.isAccessibilityElement = NO;
- [pageView addSubview:titleLabel];
- NSLayoutConstraint *weakConstraint = [NSLayoutConstraint constraintWithItem:pageView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:titleLabel attribute:NSLayoutAttributeTop multiplier:1.0 constant:page.titlePositionY];
- weakConstraint.priority = UILayoutPriorityDefaultLow;
- [pageView addConstraint:weakConstraint];
- [pageView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-10-[titleLabel]-10-|" options:NSLayoutFormatAlignAllTop metrics:nil views:@{@"titleLabel" : titleLabel}]];
- }
- UITextView *descLabel;
- if(page.desc.length) {
- descLabel = [[UITextView alloc] init];
- descLabel.text = page.desc;
- descLabel.scrollEnabled = NO;
- descLabel.font = page.descFont;
- descLabel.textColor = page.descColor;
- descLabel.backgroundColor = [UIColor clearColor];
- descLabel.textAlignment = page.descAlignment;
- descLabel.userInteractionEnabled = NO;
- descLabel.tag = kDescLabelTag;
- descLabel.translatesAutoresizingMaskIntoConstraints = NO;
- descLabel.isAccessibilityElement = NO;
- [pageView addSubview:descLabel];
- NSLayoutConstraint *weakConstraint = [NSLayoutConstraint constraintWithItem:pageView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:descLabel attribute:NSLayoutAttributeTop multiplier:1.0 constant:page.descPositionY];
- weakConstraint.priority = UILayoutPriorityDefaultLow;
- [pageView addConstraint:weakConstraint];
- [pageView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-descMargin-[descLabel]-descMargin-|" options:NSLayoutFormatAlignAllTop metrics:@{@"descMargin" : @(page.descSideMargin)} views:@{@"descLabel" : descLabel}]];
- }
- // Constraints for handling landscape orientation
- if(titleImageView && titleLabel && descLabel) {
- [pageView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|->=0-[titleImageView]->=0-[titleLabel]->=0-[descLabel]" options:0 metrics:nil views:@{@"titleImageView" : titleImageView, @"titleLabel" : titleLabel, @"descLabel" : descLabel}]];
- } else if(!titleImageView && titleLabel && descLabel) {
- [pageView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|->=0-[titleLabel]->=0-[descLabel]" options:0 metrics:nil views:@{@"titleLabel" : titleLabel, @"descLabel" : descLabel}]];
- }
- if(page.subviews) {
- for (UIView *subV in page.subviews) {
- [pageView addSubview:subV];
- }
- }
- pageView.alpha = page.alpha;
- }
- - (void)addTapToNextActionToPageView:(UIView *)pageView {
- UITapGestureRecognizer *tapRecognizer =
- [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleBackgroundTap:)];
- [pageView addGestureRecognizer:tapRecognizer];
- }
- - (void)applyAccessibilityLabelForPage:(EAIntroPage *)page toView:(UIView *)view {
- NSString *accessibilityLabel = [self accessibilityLabelForPage:page];
- if (accessibilityLabel.length > 0) {
- view.isAccessibilityElement = YES;
- view.accessibilityLabel = accessibilityLabel;
- view.accessibilityTraits = UIAccessibilityTraitButton;
- }
- }
- - (NSString*)accessibilityLabelForPage:(EAIntroPage*)page {
- NSString *accessibilityLabel = nil;
- if (page.title) {
- if (page.desc) {
- accessibilityLabel = [NSString stringWithFormat:@"%@, %@", page.title, page.desc];
- } else {
- accessibilityLabel = page.title;
- }
- } else {
- accessibilityLabel = page.desc;
- }
- return accessibilityLabel;
- }
- - (void)appendCloseViewAtXIndex:(CGFloat*)xIndex {
- UIView *closeView = [[UIView alloc] initWithFrame:CGRectMake(*xIndex, 0, self.bounds.size.width, self.bounds.size.height)];
- closeView.tag = 124;
- [self.scrollView addSubview:closeView];
-
- *xIndex += self.scrollView.frame.size.width;
- }
- - (void)removeCloseViewAtXIndex:(CGFloat*)xIndex {
- UIView *closeView = [self.scrollView viewWithTag:124];
- if(closeView) {
- [closeView removeFromSuperview];
- }
-
- *xIndex -= self.scrollView.frame.size.width;
- }
- - (void)buildTitleView {
- if (!self.titleView.superview) {
- [self addSubview:self.titleView];
- }
-
- if (self.titleViewConstraints.count) {
- [self removeConstraints:self.titleViewConstraints];
- [self.titleViewConstraints removeAllObjects];
- }
-
- NSDictionary *views = @{@"titleView" : self.titleView};
- NSDictionary *metrics = @{@"titleViewTopPadding" : @(self.titleViewY), @"titleViewHeight" : @(self.titleView.frame.size.height), @"titleViewWidth" : @(self.titleView.frame.size.width)};
-
- [self.titleViewConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-titleViewTopPadding@250-[titleView(titleViewHeight)]" options:NSLayoutFormatAlignAllLeft metrics:metrics views:views]];
- [self.titleViewConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[titleView(titleViewWidth)]" options:NSLayoutFormatAlignAllTop metrics:metrics views:views]];
- [self.titleViewConstraints addObject:[NSLayoutConstraint constraintWithItem:self.titleView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0]];
-
- self.titleView.translatesAutoresizingMaskIntoConstraints = NO;
- [self addConstraints:self.titleViewConstraints];
-
- [self.titleView setNeedsUpdateConstraints];
- }
- - (void)buildFooterView {
- if (!self.pageControl.superview) {
- [self insertSubview:self.pageControl aboveSubview:self.scrollView];
- }
-
- if (!self.skipButton.superview) {
- [self insertSubview:self.skipButton aboveSubview:self.scrollView];
- }
-
- [self.pageControl.superview bringSubviewToFront:self.pageControl];
- [self.skipButton.superview bringSubviewToFront:self.skipButton];
-
- if (self.footerConstraints.count) {
- [self removeConstraints:self.footerConstraints];
- [self.footerConstraints removeAllObjects];
- }
- CGFloat pageControlHeight = self.pageControl.frame.size.height > 0 ? self.pageControl.frame.size.height : PAGE_CTRL_DEFAULT_HEIGHT;
- CGFloat skipButtonWidth = self.skipButton.frame.size.width > 0 ? self.skipButton.frame.size.width : SKIP_BTN_DEFAULT_WIDTH;
- CGFloat skipButtonHeight = self.skipButton.frame.size.height > 0 ? self.skipButton.frame.size.height : SKIP_BTN_DEFAULT_HEIGHT;
-
- NSDictionary *views = @{@"pageControl" : self.pageControl, @"skipButton" : self.skipButton};
- NSDictionary *metrics = @{@"pageControlBottomPadding" : @(self.pageControlY - pageControlHeight), @"pageControlHeight" : @(pageControlHeight), @"skipButtonBottomPadding" : @(self.skipButtonY - skipButtonHeight), @"skipButtonSideMargin" : @(self.skipButtonSideMargin), @"skipButtonWidth" : @(skipButtonWidth)};
-
- [self.footerConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[pageControl]-|" options:NSLayoutFormatAlignAllCenterX metrics:metrics views:views]];
- [self.footerConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[pageControl(pageControlHeight)]-pageControlBottomPadding@250-|" options:NSLayoutFormatAlignAllBottom metrics:metrics views:views]];
-
- if (self.skipButton && !self.skipButton.hidden) {
- if(self.skipButtonAlignment == EAViewAlignmentCenter) {
- [self.footerConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[skipButton(skipButtonWidth)]" options:NSLayoutFormatAlignAllTop metrics:metrics views:views]];
- [self.footerConstraints addObject:[NSLayoutConstraint constraintWithItem:self.skipButton attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0]];
- } else if(self.skipButtonAlignment == EAViewAlignmentLeft) {
- [self.footerConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-skipButtonSideMargin-[skipButton]" options:NSLayoutFormatAlignAllLeft metrics:metrics views:views]];
- } else if(self.skipButtonAlignment == EAViewAlignmentRight) {
- [self.footerConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[skipButton]-skipButtonSideMargin-|" options:NSLayoutFormatAlignAllRight metrics:metrics views:views]];
- }
-
- if(self.skipButtonY == EA_EMPTY_PROPERTY) {
- [self.footerConstraints addObject:[NSLayoutConstraint constraintWithItem:self.pageControl attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.skipButton attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];
- } else {
- [self.footerConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[skipButton]-skipButtonBottomPadding-|" options:NSLayoutFormatAlignAllBottom metrics:metrics views:views]];
- }
- }
-
- [self addConstraints:self.footerConstraints];
-
- [self.pageControl setNeedsUpdateConstraints];
- [self.skipButton setNeedsUpdateConstraints];
- }
- #pragma mark - UIScrollView Delegate
- - (void)scrollViewWillBeginDragging:(EARestrictedScrollView *)scrollView {
- if ([self.delegate respondsToSelector:@selector(intro:pageStartScrolling:withIndex:)] && self.currentPageIndex < [self.pages count]) {
- [self.delegate intro:self pageStartScrolling:_pages[self.currentPageIndex] withIndex:self.currentPageIndex];
- }
- }
- - (void)scrollViewDidEndDecelerating:(EARestrictedScrollView *)scrollView {
- [self checkIndexForScrollView:scrollView];
- if ([self.delegate respondsToSelector:@selector(intro:pageEndScrolling:withIndex:)] && self.currentPageIndex < [self.pages count]) {
- [self.delegate intro:self pageEndScrolling:_pages[self.currentPageIndex] withIndex:self.currentPageIndex];
- }
- }
- - (void)scrollViewDidEndScrollingAnimation:(EARestrictedScrollView *)scrollView {
- [self checkIndexForScrollView:scrollView];
- }
- - (void)scrollViewDidScroll:(EARestrictedScrollView *)scrollView {
- if(!self.scrollingEnabled) {
- return;
- }
-
- CGFloat offset = scrollView.contentOffset.x / self.scrollView.frame.size.width;
- NSUInteger page = (NSUInteger)(offset);
-
- if (page == (_pages.count - 1) && self.swipeToExit) {
- self.alpha = ((self.scrollView.frame.size.width*_pages.count)-self.scrollView.contentOffset.x)/self.scrollView.frame.size.width;
- } else {
- if([self pageForIndex:page]) {
- self.alpha = 1.f;
- }
- }
-
- [self crossDissolveForOffset:offset];
-
- if (self.visiblePageIndex < _pages.count) {
- self.pageControl.currentPage = self.visiblePageIndex;
-
- [self makePanelVisibleAtIndex:self.visiblePageIndex];
- }
-
- if ([self.delegate respondsToSelector:@selector(intro:didScrollWithOffset:)]) {
- [self.delegate intro:self didScrollWithOffset:offset];
- }
- }
- CGFloat easeOutValue(CGFloat value) {
- CGFloat inverse = value - 1.f;
- return (CGFloat) (1.f + inverse * inverse * inverse);
- }
- - (void)crossDissolveForOffset:(CGFloat)offset {
- NSUInteger page = (NSUInteger)(offset);
- CGFloat alphaValue = offset - page;
-
- if (alphaValue < 0 && self.visiblePageIndex == 0){
- self.pageBgBack.image = nil;
- return;
- }
-
- self.pageBgFront.alpha = [self alphaForPageIndex:page];
- self.pageBgFront.image = [self bgImageForPage:page];
- self.pageBgFront.backgroundColor = [self bgColorForPage:page];
- self.pageBgBack.alpha = 0;
- self.pageBgBack.image = [self bgImageForPage:page+1];
- self.pageBgBack.backgroundColor = [self bgColorForPage:page+1];
-
- CGFloat backLayerAlpha = alphaValue;
- CGFloat frontLayerAlpha = (1 - alphaValue);
-
- if (self.easeOutCrossDisolves) {
- backLayerAlpha = easeOutValue(backLayerAlpha);
- frontLayerAlpha = easeOutValue(frontLayerAlpha);
- }
-
- self.pageBgBack.alpha = MIN(backLayerAlpha,[self alphaForPageIndex:page+1]);
- self.pageBgFront.alpha = MIN(frontLayerAlpha,[self alphaForPageIndex:page]);
-
- if(self.titleView) {
- if([self showTitleViewForPage:page] && [self showTitleViewForPage:page+1]) {
- [self.titleView setAlpha:1.0];
- } else if(![self showTitleViewForPage:page] && ![self showTitleViewForPage:page+1]) {
- [self.titleView setAlpha:0.0];
- } else if([self showTitleViewForPage:page]) {
- [self.titleView setAlpha:(1 - alphaValue)];
- } else {
- [self.titleView setAlpha:alphaValue];
- }
- }
-
- if(self.skipButton && self.showSkipButtonOnlyOnLastPage) {
- if(page < (long)[self.pages count] - 2) {
- [self.skipButton setAlpha:0.0];
- } else if(page == [self.pages count] - 1) {
- [self.skipButton setAlpha:(1 - alphaValue)];
- } else {
- [self.skipButton setAlpha:alphaValue];
- }
- }
- }
- #pragma mark - UIView lifecycle calls
- - (void)layoutSubviews {
- [super layoutSubviews];
- // Get amount of pages:
- NSInteger numberOfPages = _pages.count;
-
- // Increase with 1 page when feature enabled:
- if (self.swipeToExit) {
- numberOfPages = numberOfPages + 1;
- }
-
- // Descrease to limited index when scrolling is restricted:
- if (self.limitPageIndex != -1) {
- numberOfPages = self.limitPageIndex + 1;
- }
-
- // Adjust contentSize of ScrollView:
- CGSize newContentSize = CGSizeMake(numberOfPages * self.scrollView.frame.size.width, self.scrollView.frame.size.height);
- if(self.scrollView.contentOffset.x > newContentSize.width) {
- CGPoint newOffset = self.scrollView.contentOffset;
- if (self.swipeToExit) {
- newOffset.x = newContentSize.width - (self.scrollView.frame.size.width * 2);
- } else {
- newOffset.x = newContentSize.width - self.scrollView.frame.size.width;
- }
- self.scrollView.contentOffset = newOffset;
- }
- self.scrollView.contentSize = newContentSize;
-
- // Adjust frame of each page:
- NSUInteger i = 0;
- for (EAIntroPage *page in _pages) {
- page.pageView.frame = CGRectMake(i * self.scrollView.bounds.size.width,
- 0,
- self.scrollView.bounds.size.width,
- self.scrollView.bounds.size.height);
- i++;
- }
-
- // Adjust scrolling to fit resized page:
- CGFloat offset = self.currentPageIndex * self.scrollView.frame.size.width;
- CGRect pageRect = { .origin.x = offset, .origin.y = 0.0, .size.width = self.scrollView.frame.size.width, .size.height = self.scrollView.frame.size.height };
- [self.scrollView scrollRectToVisible:pageRect animated:NO];
-
- // Adjust restricted scroll area:
- if(!self.scrollingEnabled) {
- self.scrollView.restrictionArea = CGRectMake(self.visiblePageIndex * self.bounds.size.width, 0, self.scrollView.bounds.size.width, self.scrollView.bounds.size.height);
- } else {
- self.scrollView.restrictionArea = CGRectZero;
- }
- }
- #pragma mark - Custom setters
- - (void)setScrollingEnabled:(BOOL)scrollingEnabled {
- if(!scrollingEnabled) {
- self.scrollView.restrictionArea = CGRectMake(self.visiblePageIndex * self.bounds.size.width, 0, self.scrollView.bounds.size.width, self.scrollView.bounds.size.height);
- } else {
- self.scrollView.restrictionArea = CGRectZero;
- }
-
- _scrollingEnabled = scrollingEnabled;
- }
- - (void)setPages:(NSArray<EAIntroPage *> *)pages {
- _pages = [pages copy];
- [self.scrollView removeFromSuperview];
- self.scrollView = nil;
-
- _currentPageIndex = 0;
- self.pageControl.numberOfPages = _pages.count;
- self.pageControl.currentPage = self.currentPageIndex;
-
- [self buildScrollView];
- }
- - (void)setBgImage:(UIImage *)bgImage {
- _bgImage = bgImage;
- self.bgImageView.image = _bgImage;
-
- [self setNeedsDisplay];
- }
- - (void)setBgViewContentMode:(UIViewContentMode)bgViewContentMode {
- _bgViewContentMode = bgViewContentMode;
- self.bgImageView.contentMode = bgViewContentMode;
- self.pageBgBack.contentMode = bgViewContentMode;
- self.pageBgFront.contentMode = bgViewContentMode;
-
- [self setNeedsDisplay];
- }
- - (void)setSwipeToExit:(BOOL)swipeToExit {
- if (swipeToExit != _swipeToExit) {
- CGFloat contentXIndex = self.scrollView.contentSize.width;
- if(swipeToExit) {
- [self appendCloseViewAtXIndex:&contentXIndex];
- } else {
- [self removeCloseViewAtXIndex:&contentXIndex];
- }
- self.scrollView.contentSize = CGSizeMake(contentXIndex, self.scrollView.frame.size.height);
- }
- _swipeToExit = swipeToExit;
- }
- - (void)setTitleView:(UIView *)titleView {
- [_titleView removeFromSuperview];
- _titleView = titleView;
-
- if ([_titleView respondsToSelector:@selector(setTranslatesAutoresizingMaskIntoConstraints:)]) {
- _titleView.translatesAutoresizingMaskIntoConstraints = NO;
- }
-
- CGFloat offset = self.scrollView.contentOffset.x / self.scrollView.frame.size.width;
- [self crossDissolveForOffset:offset];
-
- [self buildTitleView];
-
- [self setNeedsDisplay];
- }
- - (void)setTitleViewY:(CGFloat)titleViewY {
- _titleViewY = titleViewY;
-
- [self buildTitleView];
-
- [self setNeedsDisplay];
- }
- - (void)setPageControl:(UIPageControl *)pageControl {
- if(!pageControl) {
- _pageControl.hidden = YES;
- return;
- }
-
- [_pageControl removeFromSuperview];
- _pageControl = pageControl;
- [self applyDefaultsToPageControl];
-
- [self buildFooterView];
-
- [self setNeedsDisplay];
- }
- - (void)setPageControlY:(CGFloat)pageControlY {
- _pageControlY = pageControlY;
-
- [self buildFooterView];
-
- [self setNeedsDisplay];
- }
- - (void)setSkipButton:(UIButton *)skipButton {
- if(!skipButton) {
- _skipButton.hidden = YES;
- return;
- }
-
- [_skipButton removeFromSuperview];
- _skipButton = skipButton;
- _skipButton.hidden = NO;
- [self applyDefaultsToSkipButton];
-
- [self buildFooterView];
-
- [self setNeedsDisplay];
- }
- - (void)setSkipButtonY:(CGFloat)skipButtonY {
- _skipButtonY = skipButtonY;
-
- [self buildFooterView];
-
- [self setNeedsDisplay];
- }
- - (void)setSkipButtonSideMargin:(CGFloat)skipButtonSideMargin {
- _skipButtonSideMargin = skipButtonSideMargin;
-
- [self buildFooterView];
-
- [self setNeedsDisplay];
- }
- - (void)setSkipButtonAlignment:(EAViewAlignment)skipButtonAlignment {
- _skipButtonAlignment = skipButtonAlignment;
-
- [self buildFooterView];
-
- [self setNeedsDisplay];
- }
- - (void)setShowSkipButtonOnlyOnLastPage:(BOOL)showSkipButtonOnlyOnLastPage {
- _showSkipButtonOnlyOnLastPage = showSkipButtonOnlyOnLastPage;
-
- CGFloat offset = self.scrollView.contentOffset.x / self.scrollView.frame.size.width;
- [self crossDissolveForOffset:offset];
- }
- - (void)setUseMotionEffects:(BOOL)useMotionEffects {
- if(_useMotionEffects == useMotionEffects) {
- return;
- }
- _useMotionEffects = useMotionEffects;
-
- if(useMotionEffects) {
- [self addMotionEffectsOnBg];
- } else {
- [self removeMotionEffectsOnBg];
- }
- }
- - (void)setMotionEffectsRelativeValue:(CGFloat)motionEffectsRelativeValue {
- _motionEffectsRelativeValue = motionEffectsRelativeValue;
- if(self.useMotionEffects) {
- [self addMotionEffectsOnBg];
- }
- }
- #pragma mark - Motion effects actions
- - (void)addMotionEffectsOnBg {
- if(![self respondsToSelector:@selector(setMotionEffects:)]) {
- return;
- }
-
- CGRect parallaxFrame = CGRectMake(-self.motionEffectsRelativeValue, -self.motionEffectsRelativeValue, self.bounds.size.width + (self.motionEffectsRelativeValue * 2), self.bounds.size.height + (self.motionEffectsRelativeValue * 2));
- [self.pageBgFront setFrame:parallaxFrame];
- [self.pageBgBack setFrame:parallaxFrame];
- [self.bgImageView setFrame:parallaxFrame];
-
- // Set vertical effect
- UIInterpolatingMotionEffect *verticalMotionEffect =
- [[UIInterpolatingMotionEffect alloc]
- initWithKeyPath:@"center.y"
- type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis];
- verticalMotionEffect.minimumRelativeValue = @(self.motionEffectsRelativeValue);
- verticalMotionEffect.maximumRelativeValue = @(-self.motionEffectsRelativeValue);
-
- // Set horizontal effect
- UIInterpolatingMotionEffect *horizontalMotionEffect =
- [[UIInterpolatingMotionEffect alloc]
- initWithKeyPath:@"center.x"
- type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis];
- horizontalMotionEffect.minimumRelativeValue = @(self.motionEffectsRelativeValue);
- horizontalMotionEffect.maximumRelativeValue = @(-self.motionEffectsRelativeValue);
-
- // Create group to combine both
- UIMotionEffectGroup *group = [UIMotionEffectGroup new];
- group.motionEffects = @[horizontalMotionEffect, verticalMotionEffect];
-
- // Add both effects to all background image views
- [UIView animateWithDuration:0.5f animations:^{
- [self.pageBgFront setMotionEffects:@[group]];
- [self.pageBgBack setMotionEffects:@[group]];
- [self.bgImageView setMotionEffects:@[group]];
- }];
- }
- - (void)removeMotionEffectsOnBg {
- if(![self respondsToSelector:@selector(removeMotionEffect:)]) {
- return;
- }
-
- [UIView animateWithDuration:0.5f animations:^{
- [self.pageBgFront removeMotionEffect:self.pageBgFront.motionEffects[0]];
- [self.pageBgBack removeMotionEffect:self.pageBgBack.motionEffects[0]];
- [self.bgImageView removeMotionEffect:self.bgImageView.motionEffects[0]];
- }];
- }
- #pragma mark - Actions
- - (void)showFullscreen {
- [self showFullscreenWithAnimateDuration:0.3f andInitialPageIndex:0];
- }
- - (void)showFullscreenWithAnimateDuration:(CGFloat)duration {
- [self showFullscreenWithAnimateDuration:duration andInitialPageIndex:0];
- }
- - (void)showFullscreenWithAnimateDuration:(CGFloat)duration andInitialPageIndex:(NSUInteger)initialPageIndex {
- UIView *selectedView;
-
- NSEnumerator *frontToBackWindows = [UIApplication.sharedApplication.windows reverseObjectEnumerator];
- for (UIWindow *window in frontToBackWindows) {
- BOOL windowOnMainScreen = window.screen == UIScreen.mainScreen;
- BOOL windowIsVisible = !window.hidden && window.alpha > 0;
- BOOL windowLevelNormal = window.windowLevel == UIWindowLevelNormal;
-
- if (windowOnMainScreen && windowIsVisible && windowLevelNormal) {
- selectedView = window;
- break;
- }
- }
-
- [self showInView:selectedView animateDuration:duration withInitialPageIndex:initialPageIndex];
- }
- - (void)showInView:(UIView *)view {
- [self showInView:view animateDuration:0.3f withInitialPageIndex:0];
- }
- - (void)showInView:(UIView *)view animateDuration:(CGFloat)duration {
- [self showInView:view animateDuration:duration withInitialPageIndex:0];
- }
- - (void)showInView:(UIView *)view animateDuration:(CGFloat)duration withInitialPageIndex:(NSUInteger)initialPageIndex {
- if(![self pageForIndex:initialPageIndex]) {
- NSLog(@"Wrong initialPageIndex received: %ld",(long)initialPageIndex);
- return;
- }
- self.skipped = NO;
- self.currentPageIndex = initialPageIndex;
- self.alpha = 0;
- if(self.superview != view) {
- [view addSubview:self];
- } else {
- [view bringSubviewToFront:self];
- }
-
- [UIView animateWithDuration:duration animations:^{
- self.alpha = 1;
- } completion:^(BOOL finished) {
- EAIntroPage *currentPage = _pages[self.currentPageIndex];
- if(currentPage.onPageDidAppear) currentPage.onPageDidAppear();
-
- if ([(id)self.delegate respondsToSelector:@selector(intro:pageAppeared:withIndex:)]) {
- [self.delegate intro:self pageAppeared:_pages[self.currentPageIndex] withIndex:self.currentPageIndex];
- }
- }];
- }
- - (void)hideWithFadeOutDuration:(CGFloat)duration {
- if ([self.delegate respondsToSelector:@selector(introWillFinish:wasSkipped:)]) {
- [self.delegate introWillFinish:self wasSkipped:self.skipped];
- }
- [UIView animateWithDuration:duration animations:^{
- self.alpha = 0;
- } completion:^(BOOL finished){
- [self finishIntroductionAndRemoveSelf];
- }];
- }
- - (void)setCurrentPageIndex:(NSUInteger)currentPageIndex {
- [self setCurrentPageIndex:currentPageIndex animated:NO];
- }
- - (void)setCurrentPageIndex:(NSUInteger)currentPageIndex animated:(BOOL)animated {
- if(![self pageForIndex:currentPageIndex]) {
- NSLog(@"Wrong currentPageIndex received: %ld",(long)currentPageIndex);
- return;
- }
-
- _currentPageIndex = currentPageIndex;
-
- [self scrollToPageForIndex:currentPageIndex animated:animated];
- }
- - (void)scrollToPageForIndex:(NSUInteger)newPageIndex animated:(BOOL)animated
- {
- if(![self pageForIndex:newPageIndex]) {
- NSLog(@"Wrong newPageIndex received: %ld",(long)newPageIndex);
- return;
- }
- CGFloat offset = newPageIndex * self.scrollView.frame.size.width;
- CGRect pageRect = { .origin.x = offset, .origin.y = 0.0, .size.width = self.scrollView.frame.size.width, .size.height = self.scrollView.frame.size.height };
- [self.scrollView scrollRectToVisible:pageRect animated:animated];
-
- if(!animated) {
- [self scrollViewDidScroll:self.scrollView];
- }
- }
- - (void)handleBackgroundTap:(UIGestureRecognizer *)tapRecognizer {
- if (tapRecognizer.state == UIGestureRecognizerStateEnded) {
- [self goToNext:tapRecognizer];
- }
- }
- - (IBAction)goToNext:(id)sender {
- if(!self.tapToNext) {
- return;
- }
- if(self.currentPageIndex + 1 >= [self.pages count]) {
- [self hideWithFadeOutDuration:0.3];
- } else {
- // Just scroll to the new page.
- // After scrolling ends, we call -checkIndexForScrollView:, which itself sets the new currentPageIndex.
- [self scrollToPageForIndex:self.currentPageIndex + 1 animated:YES];
- }
- }
- - (void)setLimitPageIndex:(NSInteger)limitPageIndex {
- _limitPageIndex = limitPageIndex;
-
- if (limitPageIndex < 0 || limitPageIndex >= self.pages.count) {
- _limitPageIndex = -1;
- self.scrollingEnabled = YES;
- return;
- } else {
- self.scrollView.restrictionArea = CGRectMake(0, 0, (self.limitPageIndex + 1) * self.scrollView.bounds.size.width, self.scrollView.bounds.size.height);
- }
- }
- @end
|