一句代码搞定启动引导页

作者: MrFire_ | 来源:发表于2016-04-07 20:30 被阅读16377次
  • 前言
    引导页,一个酷炫的页面,自从微博用了之后一下就火起来了,对于现在来说一个app如果没有引导页似乎总显那么不接地气,那么为了让我们的app也“高大上”一次,我写了一个demo来实现启动引导页的实现,鉴于我的强迫症,使用起来也是尽可能的简单才算罢休,这不,一句代码就搞定了,而且支持版本更新后显示新的引导页,先看效果:
LaunchIntroduction.gif

demo中封装了两个方法以供调用,一个是在滚动视图的最后一个页面带有进入按钮,一个是不带按钮,直接滚动就可隐藏引导页。


  • 特点
    1、使用简单,一句代码搞定
    2、支持自定义指示器的颜色
    3、支持应用升级后显示新的引导页
  • 下载地址:LaunchIntroductionDemo 下载
    先说使用方法,来不及的童靴可以先尝尝鲜:

1、导入LaunchIntroductionView.m 和 LaunchIntroductionView.h 文件到工程中
2、在APPDelegate中包含头文件 #import "LaunchIntroductionView.h"
3、调用:
[LaunchIntroductionView sharedWithImages:@[@"launch0.jpg",@"launch1.jpg",@"launch2.jpg",@"launch3"]];
4、还有一种调用方法,是带button的:
[LaunchIntroductionView sharedWithImages:@[@"launch0.jpg",@"launch1.jpg",@"launch2.jpg",@"launch3"] buttonImage:@"login" buttonFrame:CGRectMake(kScreen_width/2 - 551/4, kScreen_height - 150, 551/2, 45)];


  • 讲解

  • 头文件
/**
 *  选中page的指示器颜色,默认白色
 */
@property (nonatomic, strong) UIColor *currentColor;
/**
 *  其他状态下的指示器的颜色,默认
 */
@property (nonatomic, strong) UIColor *nomalColor;
/**
 *  不带按钮的引导页,滑动到最后一页,再向右滑直接隐藏引导页
 *
 *  @param imageNames 背景图片数组
 *
 *  @return   LaunchIntroductionView对象
 */
+(instancetype)sharedWithImages:(NSArray *) imageNames;
/**
 *  带按钮的引导页
 *
 *  @param imageNames      背景图片数组
 *  @param buttonImageName 按钮的图片
 *  @param frame           按钮的frame
 *
 *  @return LaunchIntroductionView对象
 */
+(instancetype)sharedWithImages:(NSArray *) imageNames buttonImage:(NSString *) buttonImageName buttonFrame:(CGRect ) frame;
  • 核心实现
  • 实现原理
    创建一个UIView对象view,然后在view上添加一个scrollview,scrollview上添加需要的图片及按钮等,然后程序第一次启动或者应用升级后把view添加到当前的window上,这样view就显示了出来,当滑动到最后或者点击进入程序的按钮时将view从window上移除,为了更好的体验效果,在view移除时加了一个0.5s的动画,产生了一个view渐渐消失的效果。
  • 详细讲解
    1、下面是调用接口的实现,均采用的是单例形式
    得益于i_Steven的一席话,已修改,详情见文章底部的更新,不再使用单例的形式,也欢迎围观我的这篇文章单例的使用及避免对单例的滥用
    不带按钮时默认是滑动到最后时将引导页隐藏
#pragma mark - 创建单例-->>不带button
+(instancetype)sharedWithImages:(NSArray *)imageNames{
    static LaunchIntroductionView *launch = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        images = imageNames;//背景数组
        isScrollOut = YES;//不带按钮时默认是滑动到最后时将引导页隐藏
        launch = [[LaunchIntroductionView alloc] initWithFrame:CGRectMake(0, 0, kScreen_width, kScreen_height)];
        launch.backgroundColor = [UIColor whiteColor];
    });
    return launch;
}

带按钮时默认是点击按钮时将引导页隐藏,滑动并不会隐藏引导页,按钮出现在最后一个页面

#pragma mark - 创建单例-->>带button
+(instancetype)sharedWithImages:(NSArray *)imageNames buttonImage:(NSString *)buttonImageName buttonFrame:(CGRect)frame{
    static LaunchIntroductionView *launch = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        images = imageNames;
        isScrollOut = NO;//带按钮默认是点击按钮时将引导页隐藏
        enterBtnFrame = frame;//button的frame
        enterBtnImage = buttonImageName;//button的图片名字
        launch = [[LaunchIntroductionView alloc] initWithFrame:CGRectMake(0, 0, kScreen_width, kScreen_height)];
        launch.backgroundColor = [UIColor whiteColor];
    });
    return launch;
}

2、初始化时根据是不是首次启动或者更新版本后首次启动来判断要不要添加引导页到window上

#pragma mark - 初始化
-(instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        //使用KVO监听指示器颜色的变化
        [self addObserver:self forKeyPath:@"currentColor" options:NSKeyValueObservingOptionNew context:nil];
        [self addObserver:self forKeyPath:@"nomalColor" options:NSKeyValueObservingOptionNew context:nil];
        if ([self isFirstLauch]) {
            UIWindow *window = [[UIApplication sharedApplication] windows].lastObject;
            [window addSubview:self];
            [self addImages];
        }else{
            [self removeFromSuperview];
            self = nil;
        }
    }
    return self;
}

3、版本更新或者首次启动的判断

#pragma mark - 判断是不是首次登录或者版本更新
-(BOOL )isFirstLauch{
    //获取当前版本号
    NSDictionary *infoDic = [[NSBundle mainBundle] infoDictionary];
    NSString *currentAppVersion = infoDic[@"CFBundleShortVersionString"];
    //获取上次启动应用保存的appVersion
    NSString *version = [[NSUserDefaults standardUserDefaults] objectForKey:kAppVersion];
    //版本升级或首次登录
    if (version == nil || ![version isEqualToString:currentAppVersion]) {
        [[NSUserDefaults standardUserDefaults] setObject:currentAppVersion forKey:kAppVersion];
        [[NSUserDefaults standardUserDefaults] synchronize];
        return YES;
    }else{
        return NO;
    }
}

4、滚动视图的创建就很简单了,不具体再介绍,大家可以下载代码看具体的实现,接下来主要说一下我在demo中遇到的稍微费点劲的问题:滚动方向的判断
demo中现在用的是这样的判断:

#pragma mark - 判断滚动方向
-(BOOL )isScrolltoLeft:(UIScrollView *) scrollView{
    //返回YES为向左反动,NO为右滚动
    if ([scrollView.panGestureRecognizer translationInView:scrollView.superview].x < 0) {
        return YES;
    }else{
        return NO;
    }
}

其中translationInView的官方解释是这样的:

translation in the coordinate system of the specified view

英文不太好,我的理解大概就是在一个特定view中的坐标,那么到底是干什么用的呢?log一下看看,结果返现只要往左滑动屏幕,由

[scrollView.panGestureRecognizer translationInView:scrollView.superview].x

得到的值就为负数,反之则为正数,而且在手指触摸的起始地方为坐标的(0,0)点,那么好了,这样我就能判断左右滑动的方向了,然后就可以根据带不带button,在scrollview的delegate中在最后一个界面上来判断要不要隐藏引导页了:

#pragma mark - scrollView Delegate
-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
    int cuttentIndex = (int)(scrollView.contentOffset.x + kScreen_width/2)/kScreen_width;
    if (cuttentIndex == images.count - 1) {//保证是在最后一个页面
        if ([self isScrolltoLeft:scrollView]) {//是否为左滑
            if (!isScrollOut) {//在设置为滑动不能隐藏引导页的情况下直接返回
                return ;
            }
            [self hideGuidView];//反之在左滑成立的情况下隐藏引导页
        }
    }
}

当然,并不是所有的事情看起来都那么理所当然,我刚开始用到的判断滑动方向的方法并不是这样的,而是:

-(BOOL )isScrolltoLeft:(UIScrollView *) scrollView{
    BOOL ret = NO;
    static CGFloat newX = 0;
    static CGFloat oldX = 0;
    newX = scrollView.contentOffset.x;
    if (newX > oldX) {
        ret = YES;
    }else{
        ret = NO;
    }
    oldX = newX;
    return ret;//返回YES就是向左滑动,返货NO就是向右滑动
}

根据scrollView的contentOffset来判断,这样做在scrollview的bounce为yes的情况下是完全没问题的,bounce如果为NO在最后一个页面是无法判断作画效果的,原因也很简单,但却让我费了一番功夫,bounce为no的情况下,在最后一个页面向左滑动时scrollview的contentOffset是不会发生发生辩护的,所以导致在无按钮的情况下无论怎么滑引导页都不会隐藏,那bounce就设为yes不就行了?但是引导页如果带个bounce效果,那实在是让人无法接受的,查找了多方资料才找到上面的那个方法,希望大家以后避免这个坑,少走弯路。


  • 详细使用方法
    1、不带按钮、不定义指示器的颜色:
[LaunchIntroductionView sharedWithImages:@[@"launch0.jpg",@"launch1.jpg",@"launch2.jpg",@"launch3"]];

是的,就这么一句代码就搞定了,我们只需要传入背景图片即可。
2、不带按钮,定制指示器的颜色:

    LaunchIntroductionView *launch = [LaunchIntroductionView sharedWithImages:@[@"launch0.jpg",@"launch1.jpg",@"launch2.jpg",@"launch3"]];
    launch.currentColor = [UIColor redColor];
    launch.nomalColor = [UIColor greenColor];

3、带按钮、不定制指示器的颜色:

[LaunchIntroductionView sharedWithImages:@[@"launch0.jpg",@"launch1.jpg",@"launch2.jpg",@"launch3"] buttonImage:@"login" buttonFrame:CGRectMake(kScreen_width/2 - 551/4, kScreen_height - 150, 551/2, 45)];

传的参数稍微多了点,不过也是应该并且值得的:背景图片数组,进入应用的按钮图片,按钮的frame,共三个参数
4、带按钮,定制指示器的颜色

    LaunchIntroductionView *launch = [LaunchIntroductionView sharedWithImages:@[@"launch0.jpg",@"launch1.jpg",@"launch2.jpg",@"launch3"] buttonImage:@"login" buttonFrame:CGRectMake(kScreen_width/2 - 551/4, kScreen_height - 150, 551/2, 45)];
    launch.currentColor = [UIColor redColor];
    launch.nomalColor = [UIColor greenColor];


  • End

  • 更新1
    首先感谢i_Steven的意见,刚开始实现这个效果的时候想着用单例挺方便的,但随之而来的内存问题确实是存在的,或许占得内存确实是不大的,但是这确实是一个问题,是问题就得改,于是我改了:
#pragma mark - 创建对象-->>不带button
+(instancetype)sharedWithImages:(NSArray *)imageNames{
    images = imageNames;
    isScrollOut = YES;
    launch = [[LaunchIntroductionView alloc] initWithFrame:CGRectMake(0, 0, kScreen_width, kScreen_height)];
    launch.backgroundColor = [UIColor whiteColor];
    return launch;
}
#pragma mark - 创建对象-->>带button
+(instancetype)sharedWithImages:(NSArray *)imageNames buttonImage:(NSString *)buttonImageName buttonFrame:(CGRect)frame{
    images = imageNames;
    isScrollOut = NO;
    enterBtnFrame = frame;
    enterBtnImage = buttonImageName;
    launch = [[LaunchIntroductionView alloc] initWithFrame:CGRectMake(0, 0, kScreen_width, kScreen_height)];
    launch.backgroundColor = [UIColor whiteColor];
    return launch;
}

于是也催生了我这篇单例的使用及避免对单例的滥用文章,欢迎围观。

  • 更新2
    更新时间: 2017.01.12
    更新内容:添加storyboard支持
使用storyboard时调用的方法

需要注意的是,如果项目不是使用的storyboard创建时调用此方法会导致crash。

相关文章

  • 一句代码搞定启动引导页

    前言引导页,一个酷炫的页面,自从微博用了之后一下就火起来了,对于现在来说一个app如果没有引导页似乎总显那么不接地...

  • iOS引导页封装

    插眼传送 引导页是App必不可少的功能之一;本Demo用简单有效的代码,封装引导页,仅需3行代码搞定,同时支持GI...

  • 功能调研|启动页和引导页

    一、启动页 每次启动都会出现。 二、引导页 若APP是第一次打开,则在启动页之后出现引导页。

  • 启动页,引导页

    在首次打开App时,一般都是一张大图,在弹出对话框用户协议等(popupwindow ) ,当点击确定后跳转到引导...

  • 启动页 引导页

    https://www.cnblogs.com/plBlog/p/13876792.html

  • 启动引导页

    http://blog.csdn.net/yudandan10/article/details/42009511 ...

  • [系列]APP设计之二:引导页

    上次我写了自己关于启动页的一些观察和想法。这次,来谈谈引导页,主要是app启动后展现的引导页,也有人称为前置引导页...

  • Swift4.2——组件化:APP启动引导页页

    介绍 App启动引导页,支持播放gif/png/jpg等类型的资源数组。github代码 Swift 4.2iOS...

  • iOS引导页、启动页

    前言 这里使用 launchScreen 、.storyboard 文件创建启动图和引导页。首次打开项目或者更新后...

  • 启动引导代码

    程序启动时先执行 start.S start.S 中第一段程序 第一行 csrc CSR_MSTATUS, MST...

网友评论

  • 陈友辉:循环引用了,点击按钮后没有释放掉
  • 叫我小哥哥:加个自动轮播就完美了
  • zaq1125:发现代码中,使用了KVO,最好补上这个。不然有时候会闪退- (void)dealloc {
    [self removeObserver:self forKeyPath:@"currentColor"];
    [self removeObserver:self forKeyPath:@"nomalColor"];
    }
  • zaq1125:你好,如果我是要去注册后才显示引导页,而不是一启动app就显示引导页,该怎么修改呢?
    zaq1125:@hungryBoy 我发现原因了,是我手机系统版本的原因。在注册后要显示的页面控制器,调用sharedWithImages,然后LaunchIntroductionView类中initWithFrame方法里,window的获取需要判断一下系统版本。
    UIWindow *window = nil;
    if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 11) {
    window = [[UIApplication sharedApplication].windows firstObject];
    } else {
    window = [[UIApplication sharedApplication].windows lastObject];
    }

    很棒的一个工具类,受益匪浅。
    MrFire_:@zaq1125 Appdelegate调用的方法写在注册成功之后就行了
  • wtcgzh:写的很好,能学到很多知识
  • 我只想做一只蚂蚁:为什么我的引导页就是一直不出现,启动页我是用LaunchScreen.storyboard设置的
    我只想做一只蚂蚁:@hungryBoy 我用了另外一种是可以实现的,我的rootviewcontroller是一个tabbar
    我只想做一只蚂蚁:@hungryBoy 我用的是main的,就是到了图片最后一张,点击进去,就白屏了
    MrFire_:@小藏三郎 两种使用方式,你使用的是哪一种
  • Mr_Sxz:按照楼主的写法, 写了, 然后完全没有调用什么情况
    MrFire_:@Mr_Sxz 有两种方式,使用storyboard 与纯代码作为启动页的使用方式是不一样的,你看看是不是这个地方出问题了
    Mr_Sxz:@hungryBoy 没有, 我主页是用的是一个tabbar三方, 然后用了一个抽屉控件,设置 tabbarVC 作为抽屉的中心视图, 然后 使用了您的方法 , 可是没有执行 您的方法
    MrFire_:@Mr_Sxz 不好意思,这段时间太忙,没来得及看评论,你现在解决了吗
  • 曹小宇丶:IOS11 引导页直接一闪而过 请问你们都这样不?
    曹小宇丶:好使了 :sweat:
  • zl520k:应该在入口加判断是否需要启动页,而不是每一次都去创建这个页面
    zl520k:@hungryBoy 这个看到了,应该把版本号判断提前判断
    MrFire_:@zl520k 会根据版本号进行判断
  • 6f78e843adfa:楼主 引导页只有一页时没法使用
    MrFire_:@AppleSmile 这个还没注意到,我明天看看
  • a1d00a9b9793:这是我见过最好用的引导页:+1: :+1:
    吉s她Hmm: 你好 谢谢你写的东西让我受益匪浅 ,但是我有疑问。为什么我是先出现启动页在出现,在出现引导页。。帮我分析一下 谢谢 我项目使用syoryboard搭建的框架,启动页是LaunchImage设置的。

    吉s她Hmm:你好 谢谢你写的东西让我受益匪浅 ,但是我有疑问。为什么我是先出现启动页在出现,在出现引导页。。帮我分析一下 谢谢 我项目使用syoryboard搭建的框架,启动页是LaunchImage设置的。
    MrFire_:@可可_7190 谢谢
  • 雨影:思路是对的,为了减少耦合度,而使用了单例,虽然这样可以一句话搞定,但是造成了内存浪费.不知道楼主现在有没有新的方法实现?
    MrFire_:@雨影 现在已经不使用单例了,代码我应该是更新了的,你可以看一下,ps:最近一直都很忙,文章很久没来得及更新了
  • 7c52f13a2795:代码copy进去,然后引导页怎么跟bugtags那个图标(跟home键似得)坐标一样了?不是全屏的,还可以跟着bugtags一起拖动,怎么解决啊
  • afyylong:demo运行没问题,导入到新的工程中,删除app重新运行,引导页就是不出来,求指教
    MrFire_:@AliceJordan 什么没写window?
    AliceJordan:是不是没写window啊
    MrFire_:@乆号代码 你可以跟一下
  • SmallHer:总告诉我缺少库是怎么回事
    MrFire_:@Simon_iOS 这几天有点忙,回复的不太及时,已解决了,确实是我代码判断的有点问题,还是syoryboard的问题
    SmallHer:总是编译的时候报错
  • WilliamChou_:'Invalid parameter not satisfying: [name length] > 0' 我该把哪个方法改一下 大神
    WilliamChou_:@hungryBoy 我是先运行你的demo 就是 报那个错哦。。。 我还木有拖进我的项目里面。。是运行demo报错的
    WilliamChou_:@hungryBoy 还是不懂:sob: #pragma mark - 初始化
    - (instancetype)initWithFrame:(CGRect)frame
    {
    self = [super initWithFrame:frame];
    if (self) {
    [self addObserver:self forKeyPath:@"currentColor" options:NSKeyValueObservingOptionNew context:nil];
    [self addObserver:self forKeyPath:@"nomalColor" options:NSKeyValueObservingOptionNew context:nil];
    if ([self isFirstLauch]) {
    UIStoryboard *story = [UIStoryboard storyboardWithName:storyboard bundle:nil];
    UIWindow *window = [UIApplication sharedApplication].windows.lastObject;
    if (story) {
    UIViewController * vc = story.instantiateInitialViewController;
    window.rootViewController = vc;
    [vc.view addSubview:self];
    }else {
    [window addSubview:self];
    }
    [self addImages];
    }else{
    [self removeFromSuperview];
    }
    }
    return self;
    }

    是要改这里面的吗 ? 我不用storyboard 的
    MrFire_:@WilliamChou_ 现在有两种方式,默认是使用storyboard,如果项目中没使用storyboard的会崩溃,可以换成另外一种方式使用,你这个情况看一下是不是在需要衣服穿的地方传入了其它类型的参数
  • WilliamChou_: 怎么办,我的还是出不来 大神 求解救 我一运行进去的就是 VC就是 有那个 点我 的按钮的 界面
    ec4985ea6ba3:我的跟你是一样的情况,请问你解决了吗?
  • 沙子LOVE:一运行demo就 报这个错 ZYGLaunchIntroductionDemo[10627:222765] *** Assertion failure in +[UIStoryboard storyboardWithName:bundle:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3600.6.21/UIStoryboard.m:88
    2017-03-22 15:58:42.193 ZYGLaunchIntroductionDemo[10627:222765] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid parameter not satisfying: [name length] > 0'
    WilliamChou_:怎么办,我的还是出不来 大神 求解救 我一运行进去的就是 VC就是 有那个 点我 的按钮的 界面
    MrFire_:@沙子LOVE 现在默认是storyboard的,你把方法改一下就好了
  • 知了此生:我们的需求里面加了一个说让使用网络图片来实现引导图,我在使用你的这个例子的时候不知道为什么会跳过去,不显示,作者有没有好的处理方法呢?
    知了此生:@hungryBoy 解决问题了,是网络没能及时获取到,已经解决了:blush:
    MrFire_:@知了此生 跳过去是什么意思?本地的图片显示,网络的图片不显示吗?或者网络的那张显示一张空白页
  • 0eccd8bcf57a:你好,你这个demo真的很好用,:+1: 。就是我想提一点建议。你设置的不是第一次登录就没有引导页了,一般都还是会有一张的。谢谢。
    0eccd8bcf57a:恩,对的,我说的就是启动图。
    MrFire_:@两千个秘密 非首次登录引导页一般是不需要的吧,只要一张启动图就可以了
  • 知了此生:楼主的demo,我下载了,运行发现crash,再看代码,发现注释掉initWithFrame方法里面的storyboard就好了,因为我不用storyboard,希望楼主再进行一下优化
    知了此生:@hungryBoy 还是很好用的:blush:
    MrFire_:@知了此生 嗯嗯,我当时写这个demo
    就是测试一下,我会再修改一下,谢谢!
  • 独木舟的木:你好,我按照你的方法在AppDelegate中调用了带button的方法,在真机上删除了APP再运行并没有显示引导页,请问如何解决。另外我在项目中已经设置了LaunchImage,并且项目是用Storyboard创建的。
    独木舟的木:
    感谢分享。:+1:
    #pragma mark - 判断是不是首次登录或者版本更新
    -(BOOL )isFirstLauch{
    return YES; :arrow_left: 这段调试代码忘记删除了
    //获取当前版本号
    NSDictionary *infoDic = [[NSBundle mainBundle] infoDictionary];
    NSString *currentAppVersion = infoDic[@"CFBundleShortVersionString"];
    //获取上次启动应用保存的appVersion
    NSString *version = [[NSUserDefaults standardUserDefaults] objectForKey:kAppVersion];
    //版本升级或首次登录
    if (version == nil || ![version isEqualToString:currentAppVersion]) {
    [[NSUserDefaults standardUserDefaults] setObject:currentAppVersion forKey:kAppVersion];
    [[NSUserDefaults standardUserDefaults] synchronize];
    return YES;
    }else{
    return NO;
    }
    }
    MrFire_:已添加storyboard支持,可重新下载代码查看
    MrFire_:不好意思啊,刚看到,我这个是不支持storyboard的,后续会加上。
  • StanOneX:特别棒,借鉴了,感谢! :blush:
    MrFire_:@唐僧洗头用清扬 谢谢!
  • xxttw:很棒 很棒 学习了
    MrFire_:@Unc1eWang 谢谢!

本文标题:一句代码搞定启动引导页

本文链接:https://www.haomeiwen.com/subject/dnzmlttx.html