實戰編程·使用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天后未獲授權禁止轉載,侵權必究!

「其他文章」