2019年 WWDC 大會上,蘋果宣佈了基於Swift語言構建的全新UI框架-SwiftUI。其界面佈局徹底拋棄了Storyboard和Autolayout,採用了聲明式的界面語言(DSL,即Domain Specific Language),加上 Canvas 的實時預覽功能,開發體驗有了很大的提高。git
自 iOS SDK 2.0 開始,UIKIt已經伴隨開發者近十年,其思想繼承了成熟的 AppKit 和 MVC, 爲iOS開發提升了良好的學習曲線。UIKit提供的是一套符合直覺的、命令式的編程方式,可是因爲UIKit的基本思想要求 View Controller 承擔絕大部分職責,它須要協調 model,view 以及用戶交互,使得在較大型的項目裏 View Controller 很臃腫,若是狀態管理複雜甚至致使後期代碼沒法維護。近年來隨着編程思想、技術的進步,愈來愈多的開始使用聲明式或函數式的方式來進行界面開發,如今大熱的 React 和 Flutter 即是採起了聲明式編程。github
在這種狀況下,今年發佈的SwiftUI固然也採起了聲明式編程。編程
維基百科對命令式、聲明式編程的描述以下:swift
聲明式編程:是一種不使用控制流來表示計算邏輯的編程範式。
命令式編程:是一種經過語句來改變程序狀態的編程範式。
複製代碼
舉個栗子,當咱們要設置一個Text的時候,命令式編程以下bash
override func viewDidLoad() {
super.viewDidLoad()
let label = UILabel(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
label.text = "世界和平"
label.textColor = .purple
view.addSubview(label)
}
複製代碼
使用SwiftUI聲明式編程時,則:session
var body: some View {
Text("世界和平")
.color(.purple)
}
複製代碼
從上面能夠看出,UIKit命令式編程是經過一句句的代碼來指導「要怎麼構建UI」,而聲明式編程則是使用各自的DSL來描述「UI應該是什麼樣子」數據結構
在SwiftUI裏上面Text的聲明只是純數據結構的描述,並非實際顯示出來的視圖,SwiftUI 會直接讀取 DSL 內部描述信息並收集起來,而後轉換成基本的圖形單元,最終交給底層 Metal 或 OpenGL 渲染出來。閉包
在 SwiftUI 中Text屬性的設置在內部都會用一個虛擬的 View 來承載,而後在開始佈局的時候再進行佈局計算,這樣作的目的主要是方便底層在設計渲染函數時更容易作到 monomorphic call,省去無用的分支判斷,提升效率。app
備註:要使用 SwiftUI 的完整功能, 需使用 Xcode 11, 而且將 macOS 系統升級到最新的10.15框架
此次SwiftUI的官方教程能夠說蘋果很給力,每一步代碼都有講解說明及效果圖片,強烈推薦跟着教程敲一遍!這裏簡單說明一下教程中的一些細節
在示例中,app在AppDelegate
中經過application(_:configurationForConnecting:options)
返回一個UISceneConfiguration
實例:
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
複製代碼
這部份內容是 iOS 13 中新加入的經過 Scene
管理 app 生命週期的方式,以及多窗口支持部分所須要的代碼。在 app 完成啓動後,控制權被交接給 SceneDelegate
,它的 scene(_:willConnectTo:options:)
將會被調用,進行 UI 的配置:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
let window = UIWindow(frame: UIScreen.main.bounds)
window.rootViewController = UIHostingController(rootView: ContentView())
self.window = window
window.makeKeyAndVisible()
}
複製代碼
這段代碼則跟以前iOS app啓動很相似,惟一不一樣的就是rootViewController
爲UIHostingController
類型的,UIHostingController
是UIViewController
的子類,主要負責接受一個SwiftUI的View的描述並將其用UIKit進行渲染,因爲這種繼承關係,因此能夠作到將SwiftUI構建的界面能夠集成到已有的UIKit app中,不須要從頭開始就是基於SwiftUI的構建
struct ContentView: View {
var body: some View {
Text("世界和平")
.color(.purple)
}
}
複製代碼
從SceneDelegate
進入到ContentView
,會發現一個奇怪的some view
,要解釋這個咱們就須要先了解下View
。
View
是SwiftUI的最核心的一個協議,表明了一個屏幕上元素的描述。這個協議中含有一個 associatedtype
:
public protocol View : _View {
associatedtype Body : View
var body: Self.Body { get }
}
複製代碼
這種帶有associatedtype
的協議不能做爲類型來使用,而只能做爲類型約束使用,即不能寫成var body: View
不然會報Error
,提示「Protocol 'View' can only be used as a generic constraint」
。
其實若是咱們指明 Body 的類型,也是能夠的,例如
struct ContentView: View {
var body: Text {
Text("世界和平")
.color(.purple)
}
}
複製代碼
可是有個問題就是若是每次修改Body的返回時,咱們都須要收到調整相應的類型,會很麻煩也無必要。 some View
這種寫法使用了 Swift 5.1 的 Opaque return types 特性。它向編譯器做出保證,每次 body 獲得的必定是某一個肯定的,遵照 View 協議的類型,可是請編譯器「網開一面」,不要再細究具體的類型。這一個編譯期間的特性,在保證associatedtype protocol
的功能的前提下,使用 some
能夠抹消具體的類型。這個特性用在 SwiftUI 上簡化了書寫難度,讓不一樣 View 聲明的語法上更加統一。
VStack(alignment: .leading) {
Text("世界和平")
.font(.title)
}
複製代碼
除了View
以外,Modifier
是 SwiftUI 另外一個重要概念。在上面的代碼中, .font(...)
和 .foregroundColor(...)
都修飾了 Text()
視圖的某些屬性——字體和顏色
。每個單獨的 Modifier
並不會對 View
類型實例進行操做,而是一個返回 some View
類型的閉包。所以 Modifier
的運行機制與咱們熟悉的 UIKit 中對視圖屬性進行修改的方式是相反的,咱們構建出一個視圖時並不會先初始化出一個 View 實例
再對其進行修飾,而是經過聲明的各類Modifier
構建出View 實例
。
當容器內視圖具備相同的屬性,能夠將其提取到容器外進行定義,從而減小大量的重複代碼:
HStack(alignment: .top) {
Text("世界和平")
.font(.subheadline)
Spacer()
Text("地球無戰事")
.font(.subheadline)
}
.foregroundColor(.yellow)
複製代碼
SwiftUI中一個很便捷的功能是視圖與數據的綁定。在官方示例中,Toggle
控件就綁定了showFavoritesOnly
,並且綁定數據的方式很是的優雅,在初始化方法中經過 $ 聲明某個屬性即可以讓視圖自動綁定此變量
注:State
是一個值或一組值,能夠隨時間發生變化並影響視圖的行爲、內容或佈局。 👇使用具備 @State
聲明的屬性將狀態添加到視圖。
@State var showFavoritesOnly = true
var body: some View {
Toggle(isOn: $showFavoritesOnly){
Text("Favorites only")
}
}
複製代碼
ForEach
能以與 List
相同的方式對集合進行,也就是說能夠在任何使用子視圖的地方使用它,例如堆棧、列表、groups中,當數據元素爲簡單的值類型,能夠直接將\.self
當作標識符的關鍵路徑
struct LandmarkList_Previews: PreviewProvider {
static var previews: some View {
ForEach(["iPhone SE", "iPhone XS Max"].identified(by: \.self)) { deviceName in
LandmarkList()
.previewDevice(PreviewDevice(rawValue: deviceName))
}
}
}
複製代碼
CMD + OPTION + ENTER
,取消顯示快捷鍵CMD + ENTER
Editor → Previews → Show View Bounds
展現視圖邊界,方便 UI 校對今年WWDC發佈的SwiftUI,是一個令iOS開發者興奮的全新UI框架。雖然如今還有些遺憾,如僅支持iOS13及以上且其跨平臺目前也僅僅只停留在蘋果內部的生態圈裏,可是因爲Swift是開源的且Swift5的ABI也已經穩定下來,當2 - 3年後咱們能夠大面積使用它的時候,SwiftUI應該會有很不錯的反響。