建立第一個 SwiftUI App:Day 6 – Alert, Navigation

Photo by Eiliv-Sonas Aceron on Unsplash
Photo by Eiliv-Sonas Aceron on Unsplash
上一章都在是講解新增商品頁面的 Layout,本章會談論到新增商品的功能。我們會談論到提示窗 (Alert) 和導覽列 (Navigation Bar) 的使用。另外,我們還會介紹另一個 Property Wrapper 叫 @Environemnt。

上一章都在是講解新增商品頁面的 Layout,本章會談論到新增商品的功能。我們會談論到提示窗 (Alert) 和導覽列 (Navigation Bar) 的使用。另外,我們還會介紹另一個 Property Wrapper 叫 @Environment

Coffee Shop 的完整程式碼可以在 下載。

新增商品功能

下面的程式碼中,我們省略了一些部分,並新增我們這章要教學的。

struct AddProductView: View {
    @State var name: String = ""
    @State var price: String = ""

    @State var isFailed = false
    private var onAddAction: ((Item) -> Void)?

    var body: some View {
        VStack {
            ...
            Button(action: {
                let isSuccessful = self.addProduct()
                self.isFailed = !isSuccessful
            }, label: { Text("Add") })
                ...
                .alert(isPresented: $isFailed) {
                    Alert(title: Text("Input data is incorrect"), dismissButton: .default(Text("OK")))
                }
            ...
        }
    }

    private func addProduct() -> Bool {
        guard !name.isEmpty else {
            return false
        }
        guard let parsedPrice = Double(price) else {
            return false
        }

        let item = Item(name: name, price: parsedPrice)
        onAddAction?(item)
        return true
    }
}

extension AddProductView {
    func onAdd(perform action: ((Item) -> Void)?) -> Self {
        var copy = self
        copy.onAddAction = action
        return copy
    }
}

可以看到 Button 被按下後,馬上就呼叫 addProduct()。它會檢查欄位是否有都有輸入值。沒有的話則回傳 false,有的話則新建 Item。然後,呼叫 onAddAction() 回調 (Callback),並回傳 true

onAddAction Callback 會是由 ProductListView 呼叫 onAdd() 傳過來的。在 onAdd() 中,有先對自己做個備份,再將 action 設給 onAddAction。為何不直接設定呢?而是要先拷貝再設定呢?因為 struct 內的函式是不可以改變自己內部的 Property。將 self 設給 copy 後,copyself 就是不同的實體。在 Swift 中,struct 是傳值而不是傳址。

Alert

isFailed 被宣告為 State。當 addProduct() 回傳 false,就會把 isFailed 設為 true,.alert() 就會被觸發。

Alert 會顯示提示窗。參數 title: 是提示窗的標題,而參數 dismissButton: 是按鈕上的字。所以 Alert 會顯示輸入資料不正確。

提示輸入資料不正確
提示輸入資料不正確

導覽列 (Navigation Bar)

本章至目前為止我們都還單獨在 AddProductView 上開發。現在我們來將 ProductListViewAddProductView 串連起來。讓我們來為 Coffee Shop App 加上導覽列 (Navigation Bar) 的功能吧。

在 SwiftUI 裡加上 Navigation Bar 是非常簡單。我們回到最上層的 View,也就是 ContentView。在 ContentView 中,我們只要用 NavigationViewProductListView 包起來就可以了。因為是加在最上層,所以現在整個 App 都有 Navigation Bar 的功能。

struct ContentView: View {
    var body: some View {
        NavigationView {
            ProductListView()
        }
    }
}

既然已經為 App 加上 Navigation Bar 的功能。那我們來為 ProductListView 加上導覽列標題 (Navigation Bar Title)。在 ProductListView 裡面的最外層的 List 上,我們呼叫 .navigationBarTitle() 就可以了。

在 Navigation Bar 的左右兩邊,呼叫 .navigationBarItems() 可以加上按鈕。trailing: 表示是要加在右邊。按鈕上的 UI 是 Image(systemName: "plus"),這是個+號。iOS 有提供一些系統預設的 Image。用這些 Image 的名稱就可以取用。可以在這邊找到這些預先安裝的 Image 列表。

然後我們希望點擊這個+號後,可以導覽到 AddProductView。要實現這功能,我們只要將這個+號用 NavigationLink 包起來即可。參數 destination: 是指要導覽的目的地,所以這裡填入 AddProductView

還記得上面我們有替 AddProductView 加上 onAdd() Callback 嗎?這是當新增商品時,AddProductView 將新增的商品,透過 onAdd() 傳回來給 ProductListView。然後 ProductListView 就可以將新增的商品加入到商品列表中。

struct ProductListView: View {
    var body: some View {
        List {
            ...
        }
            .navigationBarTitle("Product List")
            .navigationBarItems(trailing: NavigationLink(destination: AddProductView().onAdd(perform: onAdd)) {
                Image(systemName: "plus")
            })
    }

    private func onAdd(_ item: Item) {
        products.append(item)
    }
}

現在你可以從 ProductListView 導覽到 AddProductView。但是還沒有辦法返回。讓我們回到 AddProductView

首先我們先為 AddProductView 加上 Navigation Bar Title。

Property presentationMode 被宣告為 Environment。@Environment 是讓我們可以存取一些預先定義好的 View’s Environment 變數。\.presentationMode 就是其中一個預先定義好的 Key。我們只要在 Button 的動作裡面,呼叫 presentationMode.dismiss() 就可以導覽回上一頁。

struct AddProductView: View {
    @Environment(\.presentationMode) var presentationMode
    ...
    var body: some View {
        VStack {
            ...
            Button(action: {
                let isSuccessful = self.addProduct()
                self.isFailed = !isSuccessful
                if isSuccessful {
                    self.presentationMode.wrappedValue.dismiss()
                }
            }, label: { Text("Add") })
                ...
            ...
        }
            .navigationBarTitle("Add a product")
    }
}

新增商品功能終於完成了!

新增商品功能
新增商品功能

結語

本章節介紹了 Navigation Bar 的使用。SwiftUI 使得 Navigation Bar 非常易於使用。至目前為止,我們已經介紹了很多 UI 元件的使用,而且它們的使用方式都非常的相似。如果你有熟悉這些使用方式的話,之後當你遇到新的 UI 時,一定會可以很容易的就上手。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

You May Also Like
Photo by Alex Alvarez on Unsplash
Read More

Dispatch Queue 教學

GCD 提供有效率的並行處理,讓我們不需要直接管理多執行緒。它的 Dispatch Queues 可以循序地(serially)或是並行地(concurrently)執行任務。我們只需要將要並行的程式當作任務提交到 dispatch queues 就可以了。
Read More
Photo by Florinel Gorgan on Unsplash
Read More

如何製作一個 XCFramework

XCFramework 讓你可以將 iPhone、iPhone 模擬器等多的不同平台的二進位碼打包到一個可發佈的 .xcframework 檔。你只需要為你的 Framework 產生出一個 .xcframework 檔,就可以支援多種平台。
Read More
Photo by Fabian Gieske on Unsplash
Read More

SwiftUI @State & @Binding 教學

SwiftUI 推出了兩個 Property Wrapper – @State and @Binding。利用它們可以達到變數的 Two-way Binding 功能。也就是當變數的值改變時,它會重新被顯示。本章藉由製作一個 Custom View 來展示如何使用 @State 和 @Binding。
Read More
Photo by Svitlana on Unsplash
Read More

iOS:禁止螢幕截圖

基於一些理由,我們可能會想要禁止使用者對我們的 app 做螢幕截圖。然而,iOS 並沒有提供這樣的功能。所幸,我們可以利用 UITextField 來達到此效果。
Read More