iOS Swift5从0到1系列(八):双UIWindow +
声明:文中的图片虽然是来自掘金的,也是来自我的掘金号中的图片,并非抄袭!
一、前言
我们知道,苹果在2019年WWDC要求,2020.4月开始上架的 APP 都强制要求使用 LaunchScreen.storyboard,删除该 storyboard ,改为各种自定义的广告启动页时代已经过去了,那么,对于各大电商来说,每年有各种大、中、小促(越来越频繁,是个节日就促销),因此,活动广告页仍旧必不可少。启动页不能删除,同时还需要广告页,那我们该如何去做呢?
本篇,你将学到如下知识点:
- 简单了解 LaunchScreen;
- 制作启动页 + XIB 中设置约束;
- 双 UIWindow / 单 UIWindow 切换;
二、简单了解 LaunchScreen
LaunchScreen 很简单,网上有大把的适配方案。你不能动态去设置该 storyboard ,只能提前在 Xcode 中设置。因为要考虑到不同机型分辨率的适配问题,因此,不建议使用一整张图 + 约束,除非你添加一整套不同分辨率的图到工程中,但这样的话,你的整个 app 包就大了。
个人建议:
- 背景为纯色填充;
- 放置小图 + 约束;
- 放置文字 + 约束;
出于 Demo 好看,我设置了一整张图片 + 两行文字:
LaunchScreen.png- 图片采用『Aspect Fill』按比例来充满整屏(会被截取),所以为何我会建议用纯背景色了吧,当然,不怕被截的话,那就可以用图片没有问题;
- 不同颜色的文字设置,如下图:
xib-set-constraints.gif如何在 XIB 中添加约束?
拖线的时候,要先按住『 control 』键才行!
三、UIWindow 与 广告页
在 AppDelegate 中,我们已经有了一个 window,它的 rootViewController 已经设置为我们的 MainTabBarController,那我们如何先启动我们的广告页,然后再进入我们真正的 TabBarController 呢?
通常,我们有两种办法:
- 单 window,rootViewController 先设置广告页VC,之后再换成 TabBarController;
- 双 window,rootViewController 分别设置广告页VC 和 TabBarController,只不过,广告页 window 在最上面,然后再切换到 TabBarController 的 window;
无论哪种方式,最终的效果都一样,如下图:
switch-from-adv-to-main.gif3.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()
// switchRootController()
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 技术。
既然是广告页,一定会有倒计时的控件不断时间递减,倒计时结束后才会进入我们的首页。下一篇,我将介绍本系列的第一个组件:倒计时组件!(本系列会介绍非常多的常用组件的自定义开发)
欢迎交流,敬请期待,谢谢!