SwiftUI 3 新功能一覽:Markdown 支援, AsyncImage, Pull To Refresh 等


本篇原文(標題:What’s New in SwiftUI 3.0?)刊登於作者 Medium,由 Anupam Chugh 所著,並授權翻譯及轉載。

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 上,就會是這樣:

swift 3.0-markdown

如果你想在上面字串中客製化的一系列字元 (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,或是使用新的 AttributeScopeSwiftUIAttributes 來客製化它。

你可以參考 Zheng 的文章,來了解如何在 SwiftUI 為 Attribute String 添加不同的效果。

新的按鈕 Style

SwiftUI 的按鈕 (Button) 也變得更加強大了,現在有新的 role 和樣式 (style) 修飾符 (modifier)。

ButtonRole 讓我們描述按鈕的種類,像是:

  • cancel
  • destructive
  • none
  • some() Publisher

另外,我們現在可以設置 SwiftUI 按鈕的樣式。我們可以利用 buttonStyle 視圖修飾符,來應用 BorderedButtonStyleBorderlessButtonStylePlainButtonStyleDefaultButtonStyle

備註:你也可以利用對應的列舉 (enum) 來取代這些樣式。

至於 BorderedButtons,你可以使用這個新的 BorderedShape,來描述按鈕的邊框為 capsuleroundedRectangle、或 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 數值,這個數值會在 SwiftUI TextField 中用到。之前,在 List 中設置 TextField 通常會導致整個視圖階層重新加載。幸好,現在 ListsBinding 陣列的支援解決了這個問題。

要顯示搜索結果,我們可以使用上文提過的 onSubmit 修飾符,或設置實時過濾的計算屬性 (computed property):

完整程式碼:SwiftUI-List-search-results.swift – GitHub

還有一個 Environment 數值 isSearching,來追踪使用者有沒有在與搜尋視圖互動。你可以使用它在 Overlay 或其他視圖中顯示/隱藏搜尋結果。

你可以試著將 onSubmit 修飾符與嵌套的 SwiftUI List 一起使用,相信會很有趣。

SwiftUI List 的下拉更新 (Pull to refresh)

SwiftUI List 另一個缺少的功能就是下拉更新,之前我們需要使用 UIViewRepresentable 解決方法。在 iOS 15 中,我們可以將它放在帶有 refreshable 修飾符的幾行程式碼中:

完整程式碼:SwiftUI-Pull-To-Refresh.swift – GitHub

你可以獲取 (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:

  • 利用 listRowSeparatorlistSectionSeparator 顯示或隱藏分隔線。
  • 利用 listRowSeparatorTintlistSectionSeparatorTint 分別為每一行和每一節添加顏色。
  • 在 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)<br>.background(.thinMaterial)<br>.background(.ultraThinMaterial)<br>.background(.thickMaterial)<br>.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 的使用,或是 CoreDataSectionedFetchRequest 屬性包裝器,提高 SwiftUI app 的並行性。

本篇文章到此為止,謝謝你的閱讀。

本篇原文(標題:What’s New in SwiftUI 3.0?)刊登於作者 Medium,由 Anupam Chugh 所著,並授權翻譯及轉載。

作者簡介:Anupam Chugh,深入探索 ML 及 AR 的 iOS Developer。喜愛撰寫關於想法、科技、與程式碼的文章。歡迎到我的 Blog 閱讀更多文章,或在 LinkedIn 上關注我。

譯者簡介:Kelly Chan-AppCoda 編輯小姐。


此文章為客座或轉載文章,由作者授權刊登,AppCoda編輯團隊編輯。有關文章詳情,請參考文首或文末的簡介。

blog comments powered by Disqus
Shares
Share This