實戰程式設計·使用SwiftUI從0到1完成一款iOS筆記App(三)

語言: CN / TW / HK

theme: smartblue

前提回顧

在上兩章節中,我們完成了念頭筆記首頁新建筆記頁面的頁面構建,以及兩個頁面之間的簡單互動。單獨從前端角度來看,靜態專案已經搭建完畢,接下來我們要進入到更加深層次一點點的學習。

在本章中,我們將實現繼續實現念頭筆記的新建一條筆記的互動。

新建筆記

我們先來完成新建筆記的操作,在之前的章節裡我們建立了一個類NoteItem來作為Model資料用來遍歷列表資料,而新建筆記的方法也很簡單,我們只需要在NoteItem類陣列中插入一條資料,並且插入的資料資訊是我們標題輸入框以及內容輸入框輸入的資料就行了。

首先需要在NewNoteView檢視中,建立用於雙向繫結的NoteItem類陣列,如下程式碼所示:

@Binding var noteItems: [NoteItem]

因為使用@Binding進行雙向繫結,在NewNoteView檢視預覽時需要加一個預設值,如下程式碼所示:

NewNoteView(title: "", content: "", showNewNoteView: .constant(true), noteItems: .constant([]))

報錯是因為我們在ContentView首頁檢視中通過模態彈窗跳轉到NewNoteView新建筆記檢視,而NewNoteView剛剛使用@Binding繫結的引數在ContentView首頁檢視缺少關聯。

很多情況都是這樣,只要使用了@Binding綁定了引數,就必須在其他與該頁面關聯的頁面雙向繫結,也是挺煩的。

我們回到ContentView首頁檢視,做一下雙向繫結,如下圖所示:

新建筆記方法

新建筆記操作可以和建立View檢視的方式一樣,先建立一個方法,然後在需要的地方呼叫這個方法,新建筆記的方法如下程式碼所示:

``` // MARK: 新建筆記方法

func addNote(writeTime:String,title:String,content:String) { let note = NoteItem(writeTime: writeTime, title:title,content:content) noteItems.append(note) } ```

上述程式碼中,我們建立了一個新建筆記的方法addNote,傳入三個String型別的引數:writeTime錄入時間、title標題、content內容。

然後將傳入的引數的值賦予NoteItem模型類中,並賦值給note常量。

宣告note常量是常用的程式設計方法,當代碼太長的時候就會抽離出來賦值,後面再使用。

最後我們noteItems陣列使用append將note的內容插入到noteItems陣列陣列中。

使用addNote方法時,只需要在執行動作時呼叫並且賦值就行了。在NewNoteView新建筆記檢視中,點選“完成”按鈕時,建立將會建立一條新筆記,我們在點選“完成”按鈕時呼叫addNote方法。如下程式碼所示:

addNote(writeTime: "", title: title, content: content)

上述程式碼中,我們在saveBtnView“完成按鈕”檢視點選時呼叫addNote方法,並且將3個引數賦值,但我們看到writeTime引數是String字串型別,我們賦值了空值。

獲得時間方法

writeTime引數是錄入時間,我們需要在新建筆記時需要獲得當前系統的時間,並存起來。這裡需要注意2點,一是時間需要精確到“”,二是writeTime引數是String字串型別,而時間常常是Date型別,還有可能做格式轉換。

我們依舊可以建立一個方法來獲得當前的系統時間,如下程式碼所示:

``` // MARK: 獲取當前系統時間

func getCurrentTime() -> String { let dateformatter = DateFormatter() dateformatter.dateFormat = "YYYY.MM.dd" return dateformatter.string(from: Date()) } ```

上述程式碼中,我們建立一個獲得當前時間的方法getCurrentTime,獲得時間後返回 一個String型別的返回值。

使用DateFormatter格式化方法,時間格式為“YYYY.MM.dd”,也就是XXXX年XX月XX日的展示格式,最後return當前時間戳。

我們將getCurrentTime方法賦予addNote新增筆記的方法中的writeTime引數,當我們迫不及待地回到ContentView首頁檢視想嘗試使用時,發現並沒有實現新建筆記操作.

這是因為什麼呢?

我們看看原來NoteListView筆記列表檢視的方法,發現這裡使用@State聲明瞭NoteItem模型類陣列,是的,這導致了在NewNoteView新建筆記檢視的新建筆記引數沒有能回傳回來

是的,這裡埋了一個坑。

@Binding屬性包裝器

這裡科普下@State@Binding的用法,@State屬性包裝器常常用於宣告變數的字首,使用@State屬性包裝器宣告的變數,可以儲存當前引數的狀態或者值,而@Binding屬性包裝器常常在其他頁面反向繫結@State屬性包裝器宣告的變數。

比如,在首頁宣告的開啟彈窗的引數showNewNoteView,在首頁切換showNewNoteView狀態用於開啟彈窗,因此需要儲存其狀態

而在新建筆記頁面需要關閉彈窗,而開啟和關閉彈窗的引數在首頁中,於是需要使用@Binding屬性包裝器進行兩個頁面之間的雙向繫結。

於是使用@Binding宣告的showNewNoteView引數在新建筆記切換其狀態,在首頁中@State宣告繫結的同一個引數就可以實時監聽更新其狀態,就實現了關閉彈窗的互動。

好了,迴歸主題,我們這裡需要修改的將NoteListView筆記列表檢視的NoteItem模型類陣列使用@Binding進行雙向繫結,並在ContentView首頁檢視中進行繫結,如下程式碼所示:

@Binding var noteItems: [NoteItem]

執行預覽效果如下圖所示:

QQ20220920-211222-HD.gif

提示資訊

新建筆記時,當“新建筆記”頁面標題輸入框為空,以及“內容輸入框”為空時,點選“完成”按鈕,應用會建立一條空白的筆記,如下圖所示:

這不是我們想要的結果。

新建筆記頁面標題、內容輸入框都必須輸入內容,方可點選建立一條新筆記,當不滿足此條件時,則需要提示使用者相應的內容,告知使用者需要填寫和輸入。

在移動端常見的方式是使用Toast冒泡提示,我們可以使用ViewModifier協議建立一個Toast檢視,首先建立一個新的Swift檔案,命名為ToastView

然後錄入下面的程式碼:

``` import SwiftUI

struct ToastViewModifier: ViewModifier { @Binding var present: Bool @Binding var message: String var alignment: Alignment = .center

func body(content: Content) -> some View {
    ZStack {
        content
            .zIndex(0)
        VStack {
            Text(message)
                .padding(Edge.Set.horizontal, 20)
                .padding(Edge.Set.vertical, 10)
                .multilineTextAlignment(.center)
                .foregroundColor(.white)
                .background(Color.black.opacity(0.7))
                .cornerRadius(5)
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: alignment)
        .background(Color.gray.opacity(0.1))
        .opacity(present ? 1 : 0)
        .zIndex(1)
        .onChange(of: present) { value in
            if value {
                // 延遲2秒消失
                DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
                    present.toggle()
                }
            }
        }
    }
}

}

extension View { func toast(present: Binding, message: Binding, alignment: Alignment = .center) -> some View { modifier(ToastViewModifier(present: present, message: message, alignment: alignment)) } } ```

上述程式碼中,我們實現了一個Toast冒泡提示的樣式,詳細說明見【Swift實用小冊19:ExtenExtension擴充套件的使用】。

回到NewNoteView新建筆記檢視,我們先宣告Toast需要的引數,如下程式碼所示:

@State var showToast = false @State var showToastMessage: String = ""

上述程式碼中,showToast引數觸發是否展示Toast,showToastMessage引數則顯示Toast的內容。呼叫Toast的方法如下程式碼所示:

.toast(present: $showToast, message: $showToastMessage, alignment: .center)

回到邏輯部分,我們需要判斷輸入的標題title、內容content是否為空,若為空,則展示相應的內容,我們可以寫一個方法判讀輸入內容是否為空,如下程式碼所示:

``` // MARK: 判斷輸入是否為空

func isNull(text: String) -> Bool { if text == "" { return true } return false } ```

上述程式碼中,我們建立了一個判斷輸入的文字是否為空的方法isNull,傳入一個String型別的文字,通過判斷是否為空,對應返回Bool

我們在點選“完成”按鈕時,呼叫判斷方法,如下程式碼所示:

``` if isNull(text: title) { self.showToastMessage = "請輸入標題" self.showToast = true

} else if isNull(text: content) { self.showToastMessage = "請輸入內容" self.showToast = true } else { addNote(writeTime: getCurrentTime(), title: title, content: content) self.showNewNoteView = false } ```

本章專案預覽

完成後,我們回到ContentView首頁,執行預覽效果如下圖所示:

QQ20220920-231325-HD.gif

本章小結

在本章中,我們實現了新建筆記的方法,當然也傳達了一個很重要的程式設計理念,也就是結構化程式設計

無論是View檢視,還是實現某種功能的方法,我們程式設計的方式都是抽離出來單獨構建。這樣程式設計的好處是當我們需要修改某塊內容樣式或者邏輯時,可以快速定位到相關的內容,並且儘量使得檢視、方法鬆散分離,變成一塊一塊的程式碼塊,便於後期擴充

這也是我喜歡SwiftUI的地方,也是喜歡寫程式碼的原因。

接下來的章節,我們將繼續完成互動邏輯部分,請保持期待吧~

版權宣告

本文為稀土掘金技術社群首發簽約文章,14天內禁止轉載,14天后未獲授權禁止轉載,侵權必究!

「其他文章」