版本記錄
版本號 | 時間 |
---|---|
V1.0 | 2019.06.14 星期五 |
前言
SwiftUI是2019年WWDC新推出的UI相關的API,相信大家都已經知道并想體驗一下,下面我們就去一起了解和學習了。
開始
首先看下寫作環境
Swift 5, iOS 13, Xcode 11
在這個SwiftUI
教程中,您將學習如何通過聲明和修改視圖來布局UI,以及如何使用狀態變量來更新UI。 您將使用Xcode的新預覽和實時預覽,體驗保持代碼和WYSIWYG
布局同步的樂趣。
自Apple于2014年宣布推出Swift以來,SwiftUI
是最激動人心的消息。這是邁向Apple實現每個人都可以編碼,簡化基礎知識的目標邁出了一大步,因此您可以將更多時間花在滿足用戶需求的自定義功能上。一些開發人員開玩笑說他們可能被SwiftUI
Sherlocked了!
SwiftUI
允許您忽略Interface Builder(IB)
和storyboards
,而無需編寫詳細的逐步說明來布置UI。 IB
和Xcode
在Xcode 4
之前是單獨的應用程序,每次編輯IBAction
或IBOutlet
的名稱或從代碼中刪除它時,接縫仍會顯示,并且您的應用程序崩潰,因為IB
沒有看到代碼更改。或者你已經對你必須在你的代碼中使用的segues
或表格視圖單元格的字符串類型標識符感興趣,但Xcode無法檢查你,因為它們是字符串。而且,雖然在WYSIWYG
編輯器中設計新UI可能更快更容易,但在用代碼編寫時,復制或編輯UI會更有效率。
SwiftUI
來了可以做好這點!您可以與其代碼并排預覽SwiftUI
視圖 - 對一側的更改將更新另一側,因此它們始終保持同步。沒有任何標識符字符串出錯。它是代碼,但比你為UIKit
編寫的要少得多,因此它更容易理解,編輯和調試。
SwiftUI
不會取代UIKit
- 比如Swift
和Objective-C
,你可以在同一個應用程序中使用它們。您將無法在macOS
上運行SwiftUI iOS
應用程序 - 這就是Catalyst。但是,SwiftUI API
在不同平臺上是一致的,因此在多個平臺上使用相同的源代碼開發相同應用程序將更容易。
在本教程中,您將使用SwiftUI
從iOS Apprentice
構建我們著名的BullsEye
游戲的變體。您將學習如何通過聲明和修改視圖來布局UI,以及如何使用狀態變量來更新UI。您將使用Xcode的一些新工具,尤其是預覽和實時預覽,并體驗保持代碼和WYSIWYG
布局同步的樂趣。
注意:本教程假設您習慣使用Xcode開發iOS應用程序。 你需要
Xcode 11 beta
。 要查看SwiftUI
預覽,您需要macOS 10.15 beta
。 因此,使用beta
版軟件也需要一定的舒適度。如果您沒有備用Mac,則可以 install Catalina beta on a separate APFS volume。
下載項目,并在RGBullsEye-Starter
文件夾中構建并運行UIKit應用程序。 此游戲使用三個滑塊sliders
- RGB顏色空間中的紅色,綠色和藍色值 - 以匹配目標顏色。
我為RWDevCon 2016
編寫了這個應用程序,并在本教程中將代碼更新為Swift 5
。 它運行在Xcode 10
和Xcode 11 beta
中。 在本教程中,您將使用SwiftUI
創建此游戲的基本版本。
在Xcode 11 beta
中,創建一個新的Xcode項目(Shift-Command-N)
,選擇iOS?Single View App
,將項目命名為RGBullsEye
,然后選中Use SwiftUI
復選框:
將項目保存在RGBullsEye-Starter
文件夾之外的某個位置。
Entering the New World of SwiftUI
在項目導航器中,打開RGBullsEye
組以查看您的內容:舊的AppDelegate.swift
現在拆分為AppDelegate.swift
和SceneDelegate.swift
,SceneDelegate
具有window
:
SceneDelegate
不是特定于SwiftUI
,但這一行是:
window.rootViewController = UIHostingController(rootView: ContentView())
UIHostingController
為SwiftUI
視圖ContentView
創建一個視圖控制器。
注意:
UIHostingController
使您可以將SwiftUI
視圖集成到現有應用程序中。 您可以在storyboard
中添加一個Hosting View Controller
,并從UIViewController
創建一個segue
。 然后按住Ctrl鍵從segue
拖動到視圖控制器代碼中以創建IBSegueAction
,您可以在其中指定hosting controller
的rootView
值 -SwiftUI
視圖。
當應用程序啟動時,window
會顯示ContentView
的一個實例,該實例在ContentView.swift
中定義。 它是一個符合View
協議的struct
:
struct ContentView: View {
var body: some View {
Text("Hello World")
}
}
這是SwiftUI
聲明ContentView
的主體body
包含顯示Hello World
的Text
視圖。
在DEBUG
塊中,ContentView_Previews
生成一個包含ContentView
實例的視圖。
#if DEBUG
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
#endif
您可以在此處為預覽指定樣本數據。 但是這個預覽在哪里好像聽過?
代碼旁邊有一個很大的空白區域,位于頂部:
單擊Resume
,稍等片刻以查看預覽:
注意:如果沒有看到
Resume
按鈕,請單擊editor options
按鈕,然后選擇Editor and Canvas
:
如果您仍然沒有看到
Resume
按鈕,請確保您正在運行macOS 10.15 beta
。
Outlining Your UI
您沒有看到的一個文件是Main.storyboard
- 您將在SwiftUI
代碼中創建您的UI,密切關注預覽以查看它的外觀。 但不要擔心 - 您不會編寫數百行代碼來創建視圖!
SwiftUI
是聲明性的:您聲明了UI的外觀,SwiftUI將您的聲明轉換為可以完成工作的高效代碼。 Apple鼓勵您根據需要創建任意數量的視圖,以使代碼易于閱讀和維護。 特別推薦使用可重用的參數化視圖 - 就像將代碼提取到函數中一樣,您將在本教程的后面創建一個。
RGBullsEye
的UI有很多子視圖,所以你首先要創建一個outline
,使用Text
視圖作為占位符。
首先用以下代碼替換Text(“Hello World”)
:
Text("Target Color Block")
如有必要,請單擊Resume
以刷新預覽。
現在Command-Click
預覽中的Text
視圖,然后選擇Embed in HStack
:
請注意您的代碼更新以匹配:
HStack {
Text("Target Color Block")
}
你在這里使用horizontal stack
是因為你想要顯示目標并且并排猜測顏色塊。
復制并粘貼Text
語句,然后對其進行編輯,使HStack
如下所示。 請注意,您不要使用逗號分隔兩個語句 - 只需在各自的行中寫入:
HStack {
Text("Target Color Block")
Text("Guess Color Block")
}
它在預覽中:
現在準備通過在VStack
中嵌入HStack
來添加滑塊占位符 - 這次,在代碼中按命令單擊HStack
:
選擇Embed in VStack
,出現新代碼,但預覽不會更改 - 您需要在顏色塊下面添加視圖。
在HStack
閉包下面打開一個新行,單擊工具欄中的+
按鈕打開Library
,然后將Vertical Stack
拖到新行中:
正如您所期望的那樣,代碼和預覽更新:
注意:迷你地圖不會出現在我的屏幕截圖中,因為我隱藏了它:
Editor ? Hide Minimap
。
現在完成大綱,看起來像這樣:
VStack {
HStack {
Text("Target Color Block")
Text("Guess Color Block")
}
Text("Hit me button")
VStack {
Text("Red slider")
Text("Green slider")
Text("Blue slider")
}
}
新的VStack
將包含三個滑塊sliders
,顏色塊和滑塊之間會有一個按鈕。
注意:
Xcode 11
是測試版軟件,在編寫本教程時,很難弄清楚我做錯了什么。它有一些無用的錯誤消息,包括Unable to infer complex closure return type; add explicit type to disambiguate, ‘(LocalizedStringKey) -> Text’ is not convertible to ‘(LocalizedStringKey, String?, Bundle?, StaticString?) -> Text’ and Function declares an opaque return type, but has no return statements in its body from which to infer an underlying type, which basically meant “huh??”
。這些消息很少出現在我剛剛添加的代碼附近。如果您看到的消息似乎沒有幫助,或者指向以前完全正常的代碼,請嘗試注釋掉剛才添加的內容。還要檢查拼寫并確保您的大括號和括號匹配。最后,在beta
期間重新啟動Xcode總是一個好主意!
Filling in Your Outline
現在,練習你的新SwiftUI-fu
技能,開始填充HStack
顏色塊,所以它看起來像這樣:
HStack {
// Target color block
VStack {
Rectangle()
Text("Match this color")
}
// Guess color block
VStack {
Rectangle()
HStack {
Text("R: xxx")
Text("G: xxx")
Text("B: xxx")
}
}
}
每個顏色塊都有一個Rectangle
。 目標顏色塊在其Rectangle
下方有一個Text
視圖,而guess color block
有三個Text
視圖 - 在本教程的后面,您將替換每個xxx
以顯示當前滑塊值。
不要擔心黑色矩形占據場景 - 它們會為滑塊騰出空間,你現在就可以設置它們的前景顏色了。
Using @State Variables
您可以在SwiftUI
中使用normal
常量和變量,但是如果UI在其值發生更改時應該更新,則將變量指定為@State
變量。 這個游戲都是關于顏色的,所以影響guess rectangle
顏色的所有變量都是@State
變量。
在struct ContentView
的頂部,在body
閉包上方添加這些行:
let rTarget = Double.random(in: 0..<1)
let gTarget = Double.random(in: 0..<1)
let bTarget = Double.random(in: 0..<1)
@State var rGuess: Double
@State var gGuess: Double
@State var bGuess: Double
R,G和B值介于0和1之間。將目標值初始化為隨機值。 您也可以將猜測值初始化為0.5,但如果您沒有初始化某些變量,我會將它們保留為未初始化以顯示您必須執行的操作。
向下滾動到DEBUG
塊,該塊實例化要在預覽中顯示的ContentView
。 初始化程序現在需要猜測值的參數值。 將ContentView()
更改為:
ContentView(rGuess: 0.5, gGuess: 0.5, bGuess: 0.5)
創建滑塊時,它們將以居中顯示在預覽中。
您還必須修改SceneDelegate
中的初始值,在scene(_:willConnectTo:options:)
,將ContentView()
替換成下面這行:
window.rootViewController = UIHostingController(rootView:
ContentView(rGuess: 0.5, gGuess: 0.5, bGuess: 0.5))
當應用加載其根場景時,滑塊將居中。
現在將前景色修改器添加到目標Rectangle
:
Rectangle()
.foregroundColor(Color(red: rTarget, green: gTarget, blue: bTarget, opacity: 1.0))
修改器.foregroundColor
創建一個新的Rectangle
視圖,現在使用由隨機生成的RGB值指定的前景顏色。
同樣,修改guess Rectangle
:
Rectangle()
.foregroundColor(Color(red: rGuess, green: gGuess, blue: bGuess, opacity: 1.0))
當R,G和B值均為0.5時,您會得到灰色。
單擊Resume
,稍等片刻以進行預覽更新。
注意:預覽會定期刷新,以及單擊
Resume
或“實時預覽”按鈕時,所以不要驚訝地看到目標顏色經常變化。
Making Reusable Views
我向幾個人展示了這個游戲,他們發現它很容易讓人上癮 - 特別是平面設計師。 然后他們會要求我實現其他顏色空間之一,比如YUV。 但RGB是本教程的不錯選擇,因為滑塊基本相同,因此您將定義一個滑塊視圖,然后將其重用于其他兩個滑塊。
首先,假裝你沒有考慮重用,只需創建紅色滑塊。 在滑塊VStack
中,用這個HStack
替換Text(“Red slider”)
占位符:
HStack {
Text("0").color(.red)
Slider(value: $rGuess, from: 0.0, through: 1.0)
Text("255").color(.red)
}
您可以修改Text
視圖以將文本顏色更改為紅色。 并且您使用值 - thumb
的位置 - 在from
和through
值之間的范圍內初始化Slider
。
但是什么是$
? 你剛剛適應了嗎?
和!
對于期權,現在是$
?
對于這樣一個小符號來說,它實際上非常酷而且非常強大。rGuess
本身就是值 - 只讀。 $ rGuess
是一個讀寫綁定 - 你需要它,在用戶更改滑塊的值時更新猜測矩形的前景色。
要查看差異,請在猜測矩形下方的三個Text
視圖中設置值:
HStack {
Text("R: \(Int(rGuess * 255.0))")
Text("G: \(Int(gGuess * 255.0))")
Text("B: \(Int(bGuess * 255.0))")
}
在這里,您只使用值而不是更改它們,因此您不需要$
前綴。
注意:您和我知道滑塊從0變為1,但
255
結束標簽和0到255 RGB值適用于您的用戶,他們可能會覺得更喜歡在0到255之間的RGB值,如十六進制顏色的表示。
等待預覽刷新,看到你的第一個滑塊:
為了騰出空間,顏色塊略有縮小,但是滑塊看起來仍然有點局促 - 末端標簽太靠近窗口邊緣 - 所以在HStack
中添加一些padding
(另一個修改器):
HStack {
Text("0").color(.red)
Slider(value: $rGuess, from: 0.0, through: 1.0)
Text("255").color(.red)
}.padding()
這就好多了
現在,如果您要復制粘貼編輯此HStack
以創建綠色滑塊,則將.red
更改為.green
,將$ rGuess
更改為$ gGuess
。 這就是參數的所在。
Command-Click
紅色滑塊HStack
,然后選擇Extract Subview
:
這與Refactor ? Extract to Function
的工作方式相同,但適用于SwiftUI
視圖。
不要擔心出現的所有錯誤消息 - 當您編輯完新的子視圖后,它們會消失。
命名ExtractedView
ColorSlider
,然后在body
閉合之前在頂部添加這些行:
@Binding var value: Double
var textColor: Color
然后用$ value
替換$ rGuess
,用textColor
替換.red
:
Text("0").color(textColor)
Slider(value: $value, from: 0.0, through: 1.0)
Text("255").color(textColor)
然后返回到VStack
中對ColorSlider()
的調用,并添加您的參數:
ColorSlider(value: $rGuess, textColor: .red)
檢查預覽是否顯示紅色滑塊,然后復制粘貼編輯此行以將Text
占位符替換為其他兩個滑塊:
ColorSlider(value: $gGuess, textColor: .green)
ColorSlider(value: $bGuess, textColor: .blue)
單擊Resume
以更新預覽:
注意:您可能已經注意到您經常單擊
Resume
。 如果您不想從鍵盤上把手拿開,Option-Command-P
將是您學習的最有用的鍵盤快捷鍵之一!
這就是整個應用程序的完成!
現在有趣的事情:在預覽設備的右下角,點擊實時預覽(live preview)
按鈕:
實時預覽可讓您與預覽進行交互,就像您的應用程序在模擬器中運行一樣 - 太棒了!
等待預覽微調器(Preview spinner)
停止,如有必要,請單擊Try Again
。
現在移動那些滑塊以匹配顏色!
精彩! 您是否喜歡Goodnight Developers video from the WWDC Keynote中的程序員? 太令人滿意了!
Presenting an Alert
在使用滑塊獲得良好的顏色匹配后,您的用戶可以點擊Hit Me
按鈕,就像在原始的BullsEye
游戲中一樣。 就像在BullsEye
中一樣,會出現一個Alert
,顯示分數。
首先,向ContentView
添加一個方法來計算分數。 在@State
變量和body
之間,添加以下方法:
func computeScore() -> Int {
let rDiff = rGuess - rTarget
let gDiff = gGuess - gTarget
let bDiff = bGuess - bTarget
let diff = sqrt(rDiff * rDiff + gDiff * gDiff + bDiff * bDiff)
return Int((1.0 - diff) * 100.0 + 0.5)
}
diff
值只是三維空間中兩點之間的距離 - 用戶的錯誤。 要獲得分數,請從1減去diff
,然后將其縮放到100
以外的值。較小的diff
會產生較高的分數。
接下來,使用Button
視圖替換Text(“Hit me button”)
占位符:
Button(action: {
}) {
Text("Hit Me!")
}
Button
有一個動作和一個標簽,就像一個UIButton
。 您希望發生的操作是提供Alert
視圖。 但是如果你只是在Button
動作中創建一個Alert
,它將不會做任何事情。
而是將Alert
創建為ContentView
的子視圖之一,并添加Bool
類型的@State
變量。 然后,在希望顯示Alert
時將此變量的值設置為true
- 在這種情況下,在Button
操作中。 當用戶移除Alert
時,該值將重置為false
。
所以添加這個@State
變量,初始化為false
:
@State var showAlert = false
然后將此行添加為Button
操作:
self.showAlert = true
你需要self
,因為showAlert
在一個閉包內。
最后,向Button
添加一個presentation
修飾符,這樣你的Button
視圖如下所示:
Button(action: {
self.showAlert = true
}) {
Text("Hit Me!")
}
.presentation($showAlert) {
Alert(title: Text("Your Score"), message: Text("\(computeScore())"))
}
您傳遞綁定$ showAlert
,因為當用戶移除alert
時,其值將更改。
SwiftUI
具有用于Alert
視圖的簡單初始化器,就像許多開發人員在UIAlertViewController
擴展中為自己創建的初始化器一樣。 這個有一個默認的OK
按鈕,所以你甚至不需要將它作為參數包含在內。
關閉live preview
,單擊Resume
以刷新預覽,然后啟用實時預覽live preview
,并嘗試匹配目標顏色:
嘿,當你有實時預覽時,誰需要iOS模擬器? 盡管如果您在模擬器中運行應用程序,可以將其旋轉為橫向:
本教程幾乎沒有涉及SwiftUI
的表面,但您現在已經了解了如何使用一些新的Xcode工具來布局和預覽視圖,以及如何使用@State
變量來更新UI。更不用說那個驚人的Alert
了!
您現在已做好充分準備,深入了解Apple的豐富資源 - 其教程和WWDC會議。教程tutorials和WWDC會議通過不同的示例項目。例如,Introducing SwiftUI
(#204)為會議構建了一個列表應用程序 - 它比教程的Landmarks
應用程序更簡單。 SwiftUI Essentials
(#216)向您展示了如何使用Form
容器視圖輕松獲取iOS
表單的外觀。
為了簡化本教程,我沒有為RGB顏色創建數據模型。但大多數應用程序將其數據建模為結構體或類。如果需要SwiftUI
來跟蹤模型實例中的更改,則必須通過實現發布更改事件的didChange
屬性來符合BindableObject
。看看Apple的示例項目,特別是Data Flow Through SwiftUI
(#226)
為了使自己更容易進入SwiftUI,您可以將SwiftUI
視圖添加到現有應用程序,或者在新的SwiftUI應用程序中使用現有視圖 - 觀看Integrating SwiftUI
(#231)以查看這樣做的快捷方式。
另外,瀏覽SwiftUI documentation以查看其他可用內容 - 有很多內容!
后記
本篇主要講述了SwiftUI相關,感興趣的給個贊或者關注~~~