50天iOS挑战(Swift) - 第10天:制作应用启动引导页面

50天iOS挑战(Swift) - 第10天:制作应用启动引导页面

50天,每天一个Swift语言的iOS练手项目,覆盖iOS开发的主要知识。贵在坚持,重在思考


文章列表:http://www.jianshu.com/nb/13566182
Github项目:https://github.com/Minecodecraft/50DaysOfSwift


简介

很多应用在用户初次启动时,会展示一个What's new页面,如果打造一个多屏幕适配的启动界面,同时又保证低耦合性呢?Let's do it!
本节将介绍启动界面的制作,下一节介绍登录界面的多屏幕适配。
由于代码较为复杂,文章中只讲制作过程,建议结合代码同步理解。

主要知识点: Xib设计模式、控制器解耦、代码重构、数据模型、KVC、KVO、CollectionView、PageControl、控件约束(屏幕适配)

竖屏模式:


GIF

横屏模式:


GIF2



设计思路

1、设计架构
由于引导界面只是庞大项目的一个小模块,所以要尽可能减少耦合性。
本着各司其职的思路,我们将引导界面和登录界面分别放到两个故事版中。
引导界面需要用到的cell,采用Xib方式构建。(也可代码构建)
应用启动时,判断启动某个界面的工作,交给NavigationController。对于控制器间通讯,采用代码块内指针方式,以避免强引用循环(也可使用代理、闭方式避免)。

导航页面逻辑架构

2、界面设计
由于要兼容横竖屏,所以界面约束的设计格外重要。
根据自己需求添加界面约束即可,在此不再赘述。

3、配置CollectionView
我们采用Xib构造,所以要在CollectionView注册Cell

collectionView.register(UINib.init(nibName: "PageViewCell", bundle: nil), forCellWithReuseIdentifier: guideCell)
collectionView.register(UINib.init(nibName: "LoginViewCell", bundle: nil), forCellWithReuseIdentifier: loginCell)

3、建立数据模型
开发中我们与服务器通信时经常采用json,数据常以模型的形式存在。为了减少耦合性,我们采用数据模型来表示每一个引导页的内容。
苹果为我们提供了更简单的对属性赋值方式:KVC

var imageName: String?
var title: String?
var detail: String?

init(dict: [String: Any]) {
    super.init()
    setValuesForKeys(dict)
}

override func setValue(_ value: Any?, forUndefinedKey key: String) {
    // Do something
}

记着重写setValue:forUndefinedKey:,因为默认实现会抛出异常,程序crash

完成模型建立后,我们就可以通过键值对的方式对数据模型赋值。

4、最后一页隐藏按钮
从GIF中可以看到,翻到最后一页时我们要隐藏PageControl和两个按钮控件。
我们采用更改控件约束的方式

        if off {
            pageControlBottom.constant = -20
            skipButtonTop.constant = -50
            continueButtonTop.constant = -50
            // disable button to avoid crash
            skipButton.isEnabled = false
            continueButton.isEnabled = false
        } else {
            pageControlBottom.constant = 10
            skipButtonTop.constant = 0
            continueButtonTop.constant = 0
            skipButton.isEnabled = true
            continueButton.isEnabled = true
        }

当然,这只是改变了其frame,系统并不一定立即重绘,我们调用layoutIfNeed方法进行重绘。

        // layout subviews
        UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseOut, animations: {
            self.view.layoutIfNeeded()
        })

5、复用页面跳转方法
由于划屏和点击按钮都需要调用翻页方法,将翻页/页面跳转/按钮隐藏方法封装,提供接口供其他方法调用。

优化/Bug解决

1、横屏旋转时页面位置错误
因为Auto Layout Guide调整layout时,并不会调整对应的index,所以需要在willTransition中手动调用

override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
        let indexPath = IndexPath(item: pageControl.currentPage, section: 0)
        
        self.collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
    }

但是这样还是没有解决,原因在于collectionView在旋转之前完成了scrollToItem方法。
解决方法很简单,提交一个延时0.01秒任务。

    override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
        let indexPath = IndexPath(item: pageControl.currentPage, section: 0)
        
        DispatchQueue.main.asyncAfter(deadline: .now()+0.01) {
            self.collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
        }
    }

2、解决导航控制器无rootViewController的问题
我们在APPDelegate中设置UIWindow为导航控制器,如下面代码

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        
        window = UIWindow(frame: UIScreen.main.bounds)
        window?.makeKeyAndVisible()
        
        let nvc = MainNavigationController()
        window?.rootViewController = nvc
        
        return true
    }

但是,当我们在导航控制器中初始化引导页面控制器/登录界面控制器/登陆成功界面控制器时,可能出现没有view没有成功present的情况。原因和步骤1很类似,将present任务延时提交即可。
如下面代码,导航控制器的初始化过程:

class MainNavigationController: UINavigationController {
    
    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = UIColor.cyan
        
        // 添加delay时间,放置在window初始化之前显示
        if isFirstRun() {
            perform(#selector(showGuideController), with: nil, afterDelay: 0.01)
        }
        else if isLoggedIn() {
            // show
        }
        else {
            perform(#selector(showLoginController), with: nil, afterDelay: 0.01)
        }
    }
    
    func isFirstRun() -> Bool {
        return true
    }
    
    func isLoggedIn() -> Bool {
        return false
    }
    
    @objc func showGuideController() {
        let storyboard = UIStoryboard(name: "Guide", bundle: nil)
        let guideVC = storyboard.instantiateInitialViewController() as! GuideViewController
        
        present(guideVC, animated: false) {
            // do something
        }
    }
    
    func finishShowGuideController() {
        dismiss(animated: false, completion: nil)
        showLoginController()
    }
    
    @objc func showLoginController() {
        let storyboard = UIStoryboard(name: "Login", bundle: nil)
        let loginVC = storyboard.instantiateInitialViewController() as! LoginViewController
        
        present(loginVC, animated: false) {
            // do something
        }
    }
    
}

详细过程请见代码,下一节将介绍登录界面的屏幕适配问题,包括输入时屏幕上移、重绘输入框等功能实现。



项目源码

项目源码已传至Github:(https://github.com/Minecodecraft/50DaysOfSwift)
后续教程、代码、库持续更新,欢迎Star关注。希望可以对大家有所帮助

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,463评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,868评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,213评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,666评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,759评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,725评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,716评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,484评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,928评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,233评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,393评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,073评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,718评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,308评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,538评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,338评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,260评论 2 352

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,089评论 4 62
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,032评论 25 707
  • 工作后再与曾经的老同学们见面聊天很有意思,基本上聊天半小时内会涉及房价问题,以及,“你怎么胖了?!” 肥胖得算工伤...
    陈迹智鹏飞阅读 1,894评论 0 3
  • 沫沫快一岁了,已经成功和两次感冒作斗争。第一次是风寒感冒。第二次是病毒性感冒。 在护理上我实在忍不住分享我入手的这...
    Vinc姐阅读 388评论 0 2