一允趟、Telegram 控制器
image.png
ViewController 使 UIViewController
的工作變成了 nodes 層次結(jié)構(gòu)的容器洞慎;與官方 node 控制器 ASViewController 不同,它沒(méi)有 可見(jiàn)深度 和 智能預(yù)加載 等功能酝润。
@objc open class ViewController: UIViewController, ContainableController {
// the root content node
private var _displayNode: ASDisplayNode?
public final var displayNode: ASDisplayNode {
get {
if let value = self._displayNode {
return value
}
else {
self.loadDisplayNode()
...
return self._displayNode!
}
}
...
}
open func loadDisplayNode()
open func displayNodeDidLoad()
// shared components
public let statusBar: StatusBar
public let navigationBar: NavigationBar?
private(set) var toolbar: Toolbar?
private var scrollToTopView: ScrollToTopView?
// customizations of navigationBar
public var navigationOffset: CGFloat
open var navigationHeight: CGFloat
open var navigationInsetHeight: CGFloat
open var cleanNavigationHeight: CGFloat
open var visualNavigationInsetHeight: CGFloat
public var additionalNavigationBarHeight: CGFloat
}
- 每個(gè)
ViewController
通過(guò)一個(gè)root node
來(lái)管理node
層栏饮,該root node
存儲(chǔ)在displayNode
該類的屬性中;loadDisplayNode
和displayNodeDidLoad
函數(shù)里實(shí)現(xiàn)懶加載。 - 作為基類绪撵,它為子類準(zhǔn)備了幾個(gè)共享的 node 組件:
狀態(tài)欄
、導(dǎo)航欄
祝蝠、工具欄
和返回到頂部功能
音诈;還提供簡(jiǎn)便屬性來(lái)支持自定義導(dǎo)航欄。 -
ViewController
很少單獨(dú)使用绎狭,項(xiàng)目中有上百個(gè)它的子類用于實(shí)現(xiàn)不同的用戶界面细溅; UIKit 中兩個(gè)最常用的容器控制器:UINavigationController
與UITabBarController
,被重新實(shí)現(xiàn)為NavigationController
與TabBarController
儡嘶。
open class NavigationController: UINavigationController, ContainableController, UIGestureRecognizerDelegate {
private var _viewControllers: [ViewController] = []
// NavigationControllerNode
private var _displayNode: ASDisplayNode?
private var theme: NavigationControllerTheme
// manage layout and transition animation
private func updateContainers(layout rawLayout: ContainerViewLayout, transition: ContainedViewLayoutTransition)
// push with a completion handler
public func pushViewController(_ controller: ViewController, animated: Bool = true, completion: @escaping () -> Void)
}
// NavigationLayout.swift
enum RootNavigationLayout {
case split([ViewController], [ViewController])
case flat([ViewController])
}
// NavigationContainer.swift
final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate
override func didLoad() {
// the interactive pop gesture
let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: ...)
}
}
NavigationController 擴(kuò)展 UINavigationController
以借用其公共API喇聊,使得它可以像普通的 UIViewController 一樣被使用,它在內(nèi)部重寫了以下內(nèi)容:
- 直接管理子控制器蹦狂;由于它只是一個(gè)簡(jiǎn)單的數(shù)組誓篱,因此可以對(duì)堆棧操作進(jìn)行自由調(diào)整。
- 過(guò)渡動(dòng)畫(huà)凯楔;你可以在 ContainedViewLayoutTransition 中找到所有動(dòng)畫(huà)詳細(xì)信息窜骄。
- 交互式pop手勢(shì);InteractiveTransitionGestureRecognizer 可以在整個(gè)屏幕范圍響應(yīng) pop 手勢(shì)摆屯。
- 像 iPad 這樣的大屏設(shè)備分屏布局邻遏,它支持兩種類型的布局:
flat
和split
; - 主題;通過(guò)
theme
屬性可以很輕松的自定義外觀准验。
二赎线、Telegram 布局
AsyncDisplayKit
中的FlexBox
布局系統(tǒng)被混合布局機(jī)制所取代:
// NavigationBar.swift
// layout in the main thread
open class NavigationBar: ASDisplayNode {
override open func layout() {
super.layout()
if let validLayout = self.validLayout, self.requestedLayout {
self.requestedLayout = false
self.updateLayout(size: validLayout.0, defaultHeight: validLayout.1, additionalHeight: validLayout.2, leftInset: validLayout.3, rightInset: validLayout.4, appearsHidden: validLayout.5, transition: .immediate)
}
}
func updateLayout(size: CGSize, defaultHeight: CGFloat, additionalHeight: CGFloat, leftInset: CGFloat, rightInset: CGFloat, appearsHidden: Bool, transition: ContainedViewLayoutTransition)
}
// TabBarController.swift
// layout in the main thread
open class TabBarController: ViewController {
override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
self.tabBarControllerNode.containerLayoutUpdated(layout, toolbar: self.currentController?.toolbar, transition: transition)
...
}
}
// ListView.swift
// asynchronously load visible items by the scrolling event
open class ListView: ASDisplayNode, ... {
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
self.updateScrollViewDidScroll(scrollView, synchronous: false)
}
private func updateScrollViewDidScroll(_ scrollView: UIScrollView, synchronous: Bool) {
...
self.enqueueUpdateVisibleItems(synchronous: synchronous)
}
private func enqueueUpdateVisibleItems(synchronous: Bool) {
...
strongSelf.updateVisibleItemsTransaction(synchronous: synchronous, completion:...)
}
private func updateVisibleItemsTransaction(synchronous: Bool, completion: @escaping () -> Void)
}
- 所有布局都是手動(dòng)完成的。
- 簡(jiǎn)單 UI 的布局計(jì)算在主線程中進(jìn)行糊饱;布局代碼可以放在
node
的layout
方法中垂寥,也可以放在視圖控制器的containerLayoutUpdated
方法中。 -
ListView
為其item nodes
構(gòu)建靈活的布局機(jī)制济似,該機(jī)制支持同步和異步計(jì)算矫废。
后記
Telegram 集成 AsyncDisplayKit
的方式讓人驚嘆,它基于 ASDK 的 node
重構(gòu)了整個(gè) UIKit
用以提高效率和自由度砰蠢,雖然它的聊天 UI 界面非常復(fù)雜蓖扑,但在老機(jī)型上卻也有流暢體驗(yàn)。
參考資料:
-
Source Code Walkthrough of Telegram-iOS Part 5: AsyncDisplayKit
資料版本:Telegram - 6.1.2
校正版本:Telegram - 8.7.1