iOS Swift5从0到1系列(八):双UIWindow + 启动页 vs 广告页

3,154 阅读4分钟

一、前言

我们知道,苹果在2019年WWDC要求,2020.4月开始上架的 APP 都强制要求使用 LaunchScreen.storyboard,删除该 storyboard ,改为各种自定义的广告启动页时代已经过去了,那么,对于各大电商来说,每年有各种大、中、小促(越来越频繁,是个节日就促销),因此,活动广告页仍旧必不可少。启动页不能删除,同时还需要广告页,那我们该如何去做呢?

本篇,你将学到如下知识点:

  1. 简单了解 LaunchScreen;
  2. 制作启动页 + XIB 中设置约束;
  3. 双 UIWindow / 单 UIWindow 切换;

二、简单了解 LaunchScreen

LaunchScreen 很简单,网上有大把的适配方案。你不能动态去设置该 storyboard ,只能提前在 Xcode 中设置。因为要考虑到不同机型分辨率的适配问题,因此,不建议使用一整张图 + 约束,除非你添加一整套不同分辨率的图到工程中,但这样的话,你的整个 app 包就大了。

个人建议:

  • 背景为纯色填充;
  • 放置小图 + 约束;
  • 放置文字 + 约束;

出于 Demo 好看,我设置了一整张图片 + 两行文字:

LaunchScreen.png

  • 图片采用『Aspect Fill』按比例来充满整屏(会被截取),所以为何我会建议用纯背景色了吧,当然,不怕被截的话,那就可以用图片没有问题;
  • 不同颜色的文字设置,如下图:

multi-color-text.png

如何在 XIB 中添加约束?

xib-set-constraints.gif

拖线的时候,要先按住『 control 』键才行!

三、UIWindow 与 广告页

在 AppDelegate 中,我们已经有了一个 window,它的 rootViewController 已经设置为我们的 MainTabBarController,那我们如何先启动我们的广告页,然后再进入我们真正的 TabBarController 呢?

通常,我们有两种办法:

  1. 单 window,rootViewController 先设置广告页VC,之后再换成 TabBarController;
  2. 双 window,rootViewController 分别设置广告页VC 和 TabBarController,只不过,广告页 window 在最上面,然后再切换到 TabBarController 的 window;

无论哪种方式,最终的效果都一样,如下图:

switch-from-adv-to-main.gif

3.1、添加广告页 VC

// AdvertiseViewController.swift

class AdvertiseViewController: BaseViewController {
    
    // 延迟初始化 timer
    lazy var timer = DispatchSource.makeTimerSource(flags: [], queue: DispatchQueue.global())
    // 倒计时的时间
    var seconds = 5

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .kRed
        timeCountDown()
    }
    
    func timeCountDown() {
        timer.schedule(deadline: .now(), repeating: .seconds(1))
        timer.setEventHandler(handler: {
            DispatchQueue.main.async { [weak self] in
                
                // 小于等于 0 时,结束 timer,并进行两个 rootViewController 的切换
                if self!.seconds <= 0 {
                    self!.terminer()
                }
                self!.seconds -= 1
            }
        })
        timer.resume()
    }
    
    func terminer() {
        timer.cancel()
    }
}

dir-structure.png

3.2、单 window 替换法

单 window 替换法如我之前所说,用户点击或者倒计时结束时,将 window.rootViewController = MainTabBarController() 即可,当然,还要加点过渡动画,不然就会显示太过生硬,实现代码如下:

// AdvertiseViewController.swift

class AdvertiseViewController: BaseViewController {
    ......
    
    func terminer() {
        timer.cancel()
        switchRootController()
    }
    
    //
    // 一个 window 的情况:只用切换 rootViewController 就行
    //
    func switchRootController() {
        let window = UIApplication.shared.windows.first!
        
        // 过渡动画:0.5s 淡出
        UIView.transition(with: window,
                          duration: 0.5,
                          options: .transitionCrossDissolve,
                          animations: {
                            
                            let old = UIView.areAnimationsEnabled
                            UIView.setAnimationsEnabled(false)
                            window.rootViewController = MainTabBarController()
                            UIView.setAnimationsEnabled(old)

                          }, completion: { _ in
                            // Do Nothing
                          })
    }
}

修改 AppDelegation

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        
        window = UIWindow(frame: UIScreen.main.bounds)
        window?.backgroundColor = .white
        window?.rootViewController = AdvertiseViewController() // 修改这里
        window?.makeKeyAndVisible()
        
        return true
    }
}

3.3、双 window 切换法

双 window 顾名思义,就是有两个 window,双 window 不像单 window,是修改 rootViewController,而是通过 api 来控制哪个 window 可见的方式来切换,同样也需要有过渡动画。(先还原代码至 3.1 小节,再开始本小节的demo )

修改 AppDelegation

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

    // 多个 window:
    // 第 1 个用于主app;
    // 第 2 个用于显示广告页;
    var windows: [UIWindow]?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        
        // 后一个 window 盖在前一个之上,可以通过:
        // windows?[下标].makeKeyAndVisible() 来切换显示
        windows = [
            addWindowWithVC(MainTabBarController()),
            addWindowWithVC(AdvertiseViewController())
        ]
        
        return true
    }

    func addWindowWithVC(_ vc: UIViewController) -> UIWindow {
        let window = UIWindow.init(frame: UIScreen.main.bounds)
        window.backgroundColor = .white
        window.rootViewController = vc
        window.makeKeyAndVisible()
        return window
    }
}

修改 AdvertiseViewController

class AdvertiseViewController: BaseViewController {
    ......
    
    func terminer() {
        timer.cancel()
        switchWindow() // 修改这里
    }
    
    // 同样两种方式可以实现:广告页 -> 主页面:
    // 1. 两个 window 来分别控制不同的业务,然后基于过渡动画来切换 window;
    // 2. 一个 window,两个 vc,分别是 主vc 和 广告vc,通过修改 window.rootViewController 来完成;
    func switchWindow() {
        // 第2个 window(广告窗口)
        let window = UIApplication.shared.windows.last!
        
        // 过渡动画:淡出
        UIView.transition(with: window,
                          duration: 0.5,
                          options: .transitionCrossDissolve,
                          animations: {

                            // 临时保存 UIView 是否开启动画的状态(默认是开启)
                            // 之所以先禁止,是防止存在其它动画影响了当前动画
                            // ----------------------------------------
                            // 设置了禁止动画后:
                            // 1. 当前所有正在执行动画的没有任何影响
                            // 2. 未执行的将不会执行动画
                            // ----------------------------------------
                            //
                            // 因为我们这个回调是动画已经开始,所以,并不会被强制停止,
                            // 最后再恢复 UIView 是否开启动画的状态即可
                            let old = UIView.areAnimationsEnabled
                            UIView.setAnimationsEnabled(false)
                            window.alpha = 0
                            UIView.setAnimationsEnabled(old)

                          }, completion: { _ in
                            // 切换到主 window,即我们的 MainTabBarController
                            UIApplication.shared.windows.first?.makeKeyAndVisible()
                          })
    }

无论哪种用法,对于用户来说,都是一样;同样,最终取决于用单 window 还是双 window 都由项目(可扩展性)、研发(技术、时间、能力)等来决定;但总归,多一种方案多一条路。

四、总结

本篇虽然是在介绍如何启动广告页,实际则是让大家学习如何去使用多个 window,以及之间的切换过渡动画(正所谓授之以鱼,不如授之以渔)。多个 window 看似场景不多,其实还是有的,比如:视频类APP,非全屏时,页面滚动,视频控件移出到屏幕外不可见时,APP会在当前创建一个悬浮在右下角的一个单独视频窗口,这就用到了多window 技术。

既然是广告页,一定会有倒计时的控件不断时间递减,倒计时结束后才会进入我们的首页。下一篇,我将介绍本系列的第一个组件:倒计时组件!(本系列会介绍非常多的常用组件的自定义开发)

欢迎交流,敬请期待,谢谢!