[SwiftUI 100 天] iExpense - part1

貓克杯發表於2020-03-18

iExpense 介紹

我們接下來的兩個專案會把你的 SwiftUI 技術帶向更高的水平,超越基礎。因為我們會探索有多屏介面的 app ,能夠載入和儲存使用者資料,並且 UI 更加靈活。

這頭一個專案叫 iExpense ,它的功能是追蹤花銷,把個人花費和商務花費分隔開。實現這個 app 的過程中我們會學習到:

  • 呈現和消除新的介面
  • 從列表中刪除行
  • 保持和載入使用者資料

等等。

有不少要做的事情,讓我們開始吧:用 Single View App template 建立新 iOS app ,取名“iExpense” 。這是我們的主工程,但開始這個工程之前我們需要先學習一些會用到的新技術。



為什麼 @State 只能在結構體中工作?

用 @ObservedObject 共享 SwiftUI 的狀態

請移步閱讀 

https://juejin.im/post/5e683592e51d4526f16e5d13



譯自 Showing and hiding views

顯示和隱藏檢視

在 SwiftUI 中,顯示檢視有幾種方式,其中最常見的一種是 sheet :它是一種在已有檢視之上呈現的新檢視。在 iOS 上,這種檢視給到我們的是一個像卡片一樣的呈現:當前檢視向後滑出一段距離,新檢視以動畫方式出現在當前檢視的上面。

Sheets 工作方式和 alerts 很像,相似之處在於我們不是通過像 mySheet.present() 這樣的程式碼呈現它。相反,我們定義 sheet 應該顯示的條件。當條件變為 true 或者 false 時,sheet 會相應地呈現或者消失。

讓我們舉例說明。這個例子使用 sheet 展示一個新檢視。首先,我們需要建立一個希望在 sheet 中展示的檢視,像這樣:

struct SecondView: View {
    var body: some View {
        Text("Second View")
    }
}複製程式碼

上面這個檢視沒有什麼特別之處 —— 它並不知道自己會被用作 sheet 顯示,也不需要知道。

接下來我們建立初始檢視,由它來展示第二個檢視。我們儘量簡單實現:

struct ContentView: View { 
    var body: some View {
        Button("Show Sheet") {
            // 顯示 sheet
        }
    }
}複製程式碼

填充註釋部分需要四個步驟,我們逐一拆解。

首先,我們需要某個狀態來追蹤 sheet 是否應該顯示。跟 alerts 一樣,這個狀態可以是一個最簡單的布林型,把以下屬性新增到 ContentView

@State private var showingSheet = false複製程式碼

其次,我們需要在按鈕點選時觸發那個狀態,把 // show the sheet註釋替換成下面的程式碼:

self.showingSheet.toggle()複製程式碼

第三,我們需要把 sheet 附著在檢視層級的某個地方。如果你還記得,我們顯示 alerts 是用 alert(isPresented:) ,用到一個對於狀態的雙向繫結。而我們用在 sheet 上的東西是一樣的 sheet(isPresented:).

sheet() 是一個跟 alert()一樣的 modifier ,所以我們可以把它新增到按鈕:

.sheet(isPresented: $showingSheet) {
    // contents of the sheet
}複製程式碼

第四,我們需要確定 sheet 裡要展示的東西。在例子中,我們已經知道要做什麼:我們要建立並顯示 SecondView 的例項。程式碼上就是 SecondView(),就這樣。

所以,完成後的 ContentView 結構體應該是這樣:

struct ContentView: View {
    @State private var showingSheet = false

    var body: some View {
        Button("Show Sheet") {
            self.showingSheet.toggle()
        }
        .sheet(isPresented: $showingSheet) {
            SecondView()
        }
    }
}複製程式碼

執行程式,點選按鈕,你會看到我們的第二個檢視從底部滑入上,而你可以通過往下拖拽把它關掉。

sheet 可以呈現任何的檢視,所以我們可以調整 SecondView :

struct SecondView: View {
    var name: String

    var body: some View {
        Text("Hello, \(name)!")
    }
}複製程式碼

相應調整例項化的地方:

.sheet(isPresented: $showingSheet) {
    SecondView(name: "@twostraws")
}複製程式碼

現在 sheet 會呈現 “Hello, @twostraws” 。

Swift 在背後做了大量工作:在我們給SecondView 新增了一個名字屬性,Swift 會確保程式碼中所有 SecondView()的例項都變成 SecondView(name: "some name"),然後才能編譯通過。

在我們繼續之前,我還有一個東西要演示,那就是如何關閉檢視。是的,你發現通過通過向下掃的操作可以關掉 sheet ,不過有的時候我們會希望通過程式設計的方式關閉檢視 —— 比如在點選某個按鈕之後關閉某個檢視。

對於這種需求,SwiftUI 給了我們兩個選項。其中簡單一些的選項是引入另一個屬性包裝器 —— 是的,SwiftUI 解決問題的方式經常就是引入一個新的屬性包裝器。

言歸正傳,這個包裝器叫 @Environment,它讓我們可以建立儲存值的屬性,這種屬性可以提供給外部使用。使用者是處於 light mode 還是 dark mode ?使用者是設定的更小的字號還是更大的字號?使用者當前處於什麼時區?所有這些值都來自環境,在我們的案例中,我們將從環境中讀取檢視的 presentation mode

一個檢視的 presentation mode 包含兩部分資料,兩部分都有用:其一是儲存檢視當前是否呈現在螢幕上的屬性,其二是一個可以給我們關閉檢視的方法。

把這個屬性新增到 SecondView,下面的程式碼會建立一個叫presentationMode的屬性,並且繫結到 app 的環境中的 presentation mode 變數:

@Environment(\.presentationMode) var presentationMode複製程式碼

現在把 SecondView中的文字檢視替換成下面的按鈕:

Button("Dismiss") {
    self.presentationMode.wrappedValue.dismiss()
}複製程式碼

這裡面的 wrappedValue 是必須的,因為 presentationMode 實際上是一個由系統自動更新的繫結 —— 我們需要扒開它的外衣,從中查詢實際的 presentation mode 的狀態值,作為關閉檢視的條件。

有了這個按鈕,你會發現現在可以通過按鈕點選來顯示和隱藏 sheet 了。

我的公眾號 這裡有Swift及計算機程式設計的相關文章,以及優秀國外文章翻譯,歡迎關注~

[SwiftUI 100 天] iExpense - part1


相關文章