SwiftUI 是 Apple 的宣告式 (declarative) UI 框架。在 WWDC 2021,它又帶來了新的改進和功能,並棄用了一些函數。
SwiftUI 3.0 可用於 iOS 15、iPadOS 15、macOS 12、和 watchOS 12 上。
在正式開始之前,請注意,在 Xcode 13 專案結構中,Info.plist
檔案預設為不可見 (invisible),我們需要在 Project Navigator 中存取它。
在接下來的部分中,我們會看看 SwiftUI 在 iOS 15 上的新功能(雖然大部分功能在其它平台上都可以以其他形式使用)。
Markdown 支援和新的 AttributedString API
Markdown 是用於編寫格式化文本的通用語言,你應該已經在 GitHub 的 Readme 文檔中接觸過它。
從 iOS 15 開始,Apple 為 Foundation
框架和 SwiftUI 引入 Markdown 支援。因此,我們可以在 SwiftUI Text
中使用 Markdown 語法添加字串 (string),如下所示:
Text("**Connect** on [Twitter](url_here)!")
如果應用在 App 上,就會是這樣:
如果你想在上面字串中客製化的一系列字元 (character),在 SwiftUI 上我們可以使用全新的 AttributeString
API,而在 UIKit 上我們就可以使用改進了的 NSAttributedString
。
do {
let thankYouString = try AttributedString(
markdown:"**Welcome** to [website](https://example.com)")
} catch {
print("Couldn't parse: \(error)")
}
您可以在 SwiftUI Text 中傳遞上面的 AttributeString
,或是使用新的 AttributeScope
和 SwiftUIAttributes
來客製化它。
你可以參考 Zheng 的文章,來了解如何在 SwiftUI 為 Attribute String 添加不同的效果。
新的按鈕 Style
SwiftUI 的按鈕 (Button) 也變得更加強大了,現在有新的 role 和樣式 (style) 修飾符 (modifier)。
ButtonRole
讓我們描述按鈕的種類,像是:
cancel
destructive
none
some()
Publisher
另外,我們現在可以設置 SwiftUI 按鈕的樣式。我們可以利用 buttonStyle
視圖修飾符,來應用 BorderedButtonStyle
、BorderlessButtonStyle
、PlainButtonStyle
或 DefaultButtonStyle
。
備註:你也可以利用對應的列舉 (enum) 來取代這些樣式。
至於 BorderedButtons
,你可以使用這個新的 BorderedShape,來描述按鈕的邊框為 capsule
、roundedRectangle
、或 automatic
。
讓我們看看在 iOS 15 中,可應用於 SwiftUI 按鈕的不同樣式和 role 型別:
從以上的程式碼中可以看出,沒有邊框的按鈕不會遵循 tint
的顏色,並會按現時的 OS 系統保留 automatic
樣式。
備註:如果我們對整個 Group Container 設置樣式修飾符(在這個範例中是 VStack
),修飾符將會應用於所有按鈕控件 (control)。
我們還有兩個屬性未討論:
controlSize
:這個屬性是用來設置按鈕的大小。如此一來,按鈕大小就不用局限於幾個標準選擇。controlProminence
:這個屬性是用來定義不透明度。你可以看到最底的兩個按鈕最突出。
除了 SwiftUI 按鈕控件的改變外,Apple 還為 SwiftUI 添加了一個 CoreLocationUI
按鈕,名為 LocationButton
,可以提供一個標準的 Current Location UI,讓我們可以快速獲取位置坐標:
LocationButton(.sendMyCurrentLocation) {// Fetch location with Core Location.}.labelStyle(.titleAndIcon)
SwiftUI AsyncImage 讓我們從 URL 載入圖像
以前,如我們要顯示遠程 URL 的圖像,就需要設置一個載入器 (Loader) 並執行非同步任務。要處理不同的載入狀態,會令樣板程式碼越來越長。
幸好 SwiftUI 3.0 推出 AsyncImage
,簡化了整個過程。
讓我們看看實際操作:
AsyncImage(url: URL(string: <url_here>)!)
我們也可以利用 content
引數 (argument) 來客製化輸出圖像。另外,我們還可以使用 placeholder
引數,來設置載入視圖。
此外,我們也可以使用 content
Block 中的 AsyncImagePhase
列舉,來處理不同狀態的結果。
AsyncImage(url: URL(string: str)!, scale: 1.0){
content in
switch content{
case .empty:
case .success(let image):
case .failure(let error):
}
我們可以利用 AsyncImage
,來指定傳遞動畫的 transaction
。
另外還有一個 scale
參數 (parameter),讓我們控制圖片的大小,還可以方便地放大和縮小,像是這樣:
由於 AsyncImage
使用 URLSession
,為緩存 (caching) 提供開箱即用的支援(雖然我們現在還無法編寫客製化緩存)。
提升 SwiftUI TextField 的鍵盤管理
在 iOS 15 中,我們有一個新的屬性包裝器 (property wrapper) (@FocusState)
,來以編程方式管理當前的 Active Field。這個功能在登入和註冊表單中非常有用,因為現在開發者可以突顯 (highlight) 特定的 Text Field。
要在 SwiftUI 實作對 TextField
的 Focus,我們需要在 focusedStated
修飾符中綁定 FocusState
。它會執行一個 Boolean Check,以確定 FocusState
是否綁定到同一視圖。
看看以下範例:
有了 submitLabel
視圖修飾符,我們可以使用不同的選項來設置鍵盤 Return 按鈕。
在第一個 iOS 15 beta 版本中,SwiftUI TextField 的 FocusState
還無法如預期在 Forms
運作。
新的 onSubmit 視圖修飾符取代 onCommit
既然我們現在對 TextField
有更好的 focus 管理,自然也會有新的方法來處理結果。回傳 key 的 onCommit
callback 會被棄用,取而代之的是 onSubmit
視圖修飾符,讓我們處理視圖階層 (view hierarchy) 中的 Text Field 結果。
看看以下範例:
Form{
TextField("Email", text: $field1)
SecureField("Password", text: $field2)
TextField("Name", text: $field3)
.submitScope(false) //setting true won't submit the values
}
.onSubmit {
print(field1)
print(field2)
print(field3)
}
備註:你可以選擇使用 .onSubmit(of:)
指定視圖型別。此外,如果我們把 submitScope
設置為 true
,階層的結果就不會被回傳。
SwiftUI List 現在支援搜尋功能
SwiftUI 前兩個版本的 List 都沒有搜尋功能。到了 iOS 15,Apple 為 List 帶來 searchable
修飾器。
有一點很重要:要將一個 List 標記為 searchable
,我們需要將其包裝在 NavigationView
中。
讓我們來看看有搜索視圖的 SwiftUI List:
struct Colors : Identifiable{
var id = UUID()
var name : String
}
struct SearchingLists: View {
@State private var searchQuery: String = ""
@State private var colors: [Colors] = [Colors(name: "Blue"),Colors(name: "Red"),Colors(name: "Green")]
var body: some View {
NavigationView {
List($colors) { $color in
Text(color.name)
}
.searchable("Search color", text: $searchQuery, placement: .automatic){
Text("re").searchCompletion("red")
Text("b")
}
.navigationTitle("Colors List")
}
}
}
以上的程式碼有幾點值得討論:
searchable
讓我們指定不同的搜尋建議,點擊建議搜尋欄就會顯示searchCompletion
text。- 把
placement
設置為 Automatic,系統就會根據 Apple 平台決定搜索欄的位置。 - 你可以還沒有注意到,我們現在可以直接在 List 中傳遞
Binding
陣列 (array),並為每一行獲取一個 Binding 數值,這個數值會在 SwiftUITextField
中用到。之前,在 List 中設置TextField
通常會導致整個視圖階層重新加載。幸好,現在Lists
對Binding
陣列的支援解決了這個問題。
要顯示搜索結果,我們可以使用上文提過的 onSubmit
修飾符,或設置實時過濾的計算屬性 (computed property):
還有一個 Environment 數值 isSearching
,來追踪使用者有沒有在與搜尋視圖互動。你可以使用它在 Overlay 或其他視圖中顯示/隱藏搜尋結果。
你可以試著將 onSubmit
修飾符與嵌套的 SwiftUI List 一起使用,相信會很有趣。
SwiftUI List 的下拉更新 (Pull to refresh)
SwiftUI List 另一個缺少的功能就是下拉更新,之前我們需要使用 UIViewRepresentable
解決方法。在 iOS 15 中,我們可以將它放在帶有 refreshable
修飾符的幾行程式碼中:
你可以獲取 (fetch) 網絡請求,例如在 refreshable 修飾符中使用 async/await
,並顯示結果。
目前,原生 SwiftUI 下拉更新還不適用於 SwiftUI 網格 (Grid) 或滾動視圖 (Scroll View)。但是,它確實為我們帶來了一些客製化的空間。RefreshAction Environment 數值還可以手動觸發刷新 (refresh)。
SwiftUI List 的滑動操作 (Swipe Action)
滑動以執行操作是另一個需要的功能,今年我們終於有這個功能和新的 swipeActions
修飾符。
讓我們來看看全新按鈕樣式的滑動操作:
List {
ForEach (colors, id:\.id){ color in
Text(color.name)
.swipeActions{
Button(role: .destructive){
//do something
}label: {
Label("Delete", systemImage: "xmark.bin")
}
Button{
//do something
}label: {
Label("Pin", systemImage: "pin")
}
}
}
}
在預設情況下,滑動操作會從螢幕的後緣顯示。但是,我們也可以將它配置為從前端顯示:
.swipeActions(edge: .leading)
我們還可以連接多個修飾符,來支援兩側的滑動操作。
在預設情況下,第一個滑動操作會在 full swipe 時觸發。但是我們也可以把 allowedFullSwipe
設置為 false
來更改設置。
目前,滑動操作還未適用於 Binding Array Lists。
更多適用於 Lists、Tabs、Alerts、和 Sheets 的修飾符
今年,SwiftUI List 無疑是改善最多的部分。如果搜尋視圖、下滑刷新、和滑動操作還不夠,還有更多方法可以客製化 List:
- 利用
listRowSeparator
和listSectionSeparator
顯示或隱藏分隔線。 - 利用
listRowSeparatorTint
和listSectionSeparatorTint
分別為每一行和每一節添加顏色。 - 在 macOS 使用
.insetStyle(alternatesRowBackgrounds:)
。 badges
設置在 SwiftUI List 的右側,這也適用於 iOS 15 的 SwiftUI TabViews。
SwiftUI Alert 視圖已經被棄用,取而代之的是新的 .alert
修飾符,它還為 error
對話框帶來變化。
開發者可以選擇 InteractiveDismissDisabled
函數,來防止 Forms 和 Sheets 被關閉。看看以下範例:
Task 修飾符 為 App 提高並行性 (Concurrency)
之前,在為視圖獲取數據時,許多 SwiftUI 開發者都會用到 onAppear
。
在 iOS 15 中,Apple 帶來了全新的 task
修飾符,讓我們可以非同步地加載數據。這個修飾符非常好用,可以在附加的 SwiftUI 視圖被破壞時自動取消 task。
在我們按需要加載更多數據,來創建無限滾動列表時,task
修飾符就可以大派用場了。另外,task
修飾符也適用於處理 NavigationLink
目標視圖的繁重工作(很遺憾這次 NavigationLink
沒有任何更新)。
全新的 SwiftUI Material 結構
Material
結構可以為視圖添加透明度 (translucency) 和虛化 (vibrancy) 效果,來將前景元素與背景混合。我們可以以下方式使用它:
.background(.regularMaterial)
.background(.thinMaterial)
.background(.ultraThinMaterial)
.background(.thickMaterial)
.background(.ultraThicknMaterial)
或者,你可以在 Material 中設置形狀:
基本上,Material 型別就是表示背景型別穿過前景元素的程度。這是為 SwiftUI 視圖帶來模糊效果的一個好方法。
新的 Canvas 和 TimelineView
SwiftUI 兩年來都沒有 drawRect
,終於 Apple 以 Canvas API 形式引入了這個功能。
Canvas API 為繪製 Paths 和 Shapes 提供開箱即用的支援。因此,我們可以在 SwiftUI App 中設置遮罩 (mask),並轉換及應用濾鏡,來創建豐富的圖形。重點是,它是 GPU 加速的。
另外,TimelineView
讓我們可以構建實時更新的動態視圖 (dynamic view)。去年,我們看過 SwiftUI Text 的 Timer Publisher。
但是 TimelineView
將其提升到另一個層次,讓我們可以包裝任何 SwiftUI 視圖,並設置定期或以 TimelineSchedule
在特定時間配置的時間表。TimelineViews 就像你 App 內的 Widget。
在 WWDC 2021,Apple 展示了一系列使用 SwiftUI 構建的股票 iOS App,我肯定 TimelineViews 在部分的 App 中扮演著很重要的角色。
除錯 (Debugging)
SwiftUI 現在為除錯視圖階層 提供內置支援。
在視圖 body 內呼叫以下函式,就可以印出上次重新加載時修改的 SwiftUI 視圖:
let _ = Self._printChanges()
總結
在這篇文章中,我們看了 WWDC 2021 中幾個 SwiftUI 的主要功能,但還有很多功能值得期待,像是 macOS 中 SwiftUI Table
的使用,或是 CoreData
的 SectionedFetchRequest
屬性包裝器,提高 SwiftUI app 的並行性。
本篇文章到此為止,謝謝你的閱讀。