實戰程式設計·使用SwiftUI從0到1完成一款iOS筆記App(三)
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]
執行預覽效果如下圖所示:
提示資訊
新建筆記時,當“新建筆記”頁面標題輸入框為空,以及“內容輸入框”為空時,點選“完成”按鈕,應用會建立一條空白的筆記,如下圖所示:
這不是我們想要的結果。
新建筆記頁面標題、內容輸入框都必須輸入內容,方可點選建立一條新筆記,當不滿足此條件時,則需要提示使用者相應的內容,告知使用者需要填寫和輸入。
在移動端常見的方式是使用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
上述程式碼中,我們實現了一個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
首頁,執行預覽效果如下圖所示:
本章小結
在本章中,我們實現了新建筆記的方法,當然也傳達了一個很重要的程式設計理念,也就是結構化程式設計。
無論是View檢視,還是實現某種功能的方法,我們程式設計的方式都是抽離出來單獨構建。這樣程式設計的好處是當我們需要修改某塊內容樣式或者邏輯時,可以快速定位到相關的內容,並且儘量使得檢視、方法鬆散分離,變成一塊一塊的程式碼塊,便於後期擴充。
這也是我喜歡SwiftUI的地方,也是喜歡寫程式碼的原因。
接下來的章節,我們將繼續完成互動邏輯部分,請保持期待吧~
版權宣告
本文為稀土掘金技術社群首發簽約文章,14天內禁止轉載,14天后未獲授權禁止轉載,侵權必究!
- 實戰教程·元宇宙來了,準備好你的電子名片了嗎?(一)
- 實戰教程·元宇宙來了,準備好你的電子名片了嗎?(八)
- 實戰教程·什麼年代了還在敲傳統木魚?(二)
- 實戰教程·元宇宙來了,準備好你的電子名片了嗎?(七)
- 實戰教程·元宇宙來了,準備好你的電子名片了嗎?(六)
- 實戰教程·元宇宙來了,準備好你的電子名片了嗎?(五)
- 實戰教程·元宇宙來了,準備好你的電子名片了嗎?(四)
- 實戰教程·元宇宙來了,準備好你的電子名片了嗎?(三)
- 實戰教程·元宇宙來了,準備好你的電子名片了嗎?(二)
- 實戰教程·什麼年代了還在敲傳統木魚?(一)
- 技術下午茶:產品經理是如何工作的?如何才算一份好的需求文件?如何設計一個簡單的列表,它應該具備哪些基本功能?
- 釋出&選擇釋出,使用SwiftUI搭建一個新建釋出彈窗(上)
- 釋出&選擇釋出,使用SwiftUI搭建一個新建釋出彈窗(下)
- 使用SwiftUI搭建一個風箏搖擺動畫,實現放風箏的夢想~
- SwiftUI100天:使用SwiftUI搭建一個計時器App
- 實戰程式設計·使用SwiftUI從0到1完成一款iOS筆記App(三)
- 初識MVVM·關於啟動頁、引導頁、登入頁的設計細節和互動邏輯
- 誰說程式設計師不懂浪漫,教你使用SwiftUI搭建一個電子相簿送給她吧~
- 實戰程式設計·刻在男人DNA裡的浪漫,空氣投籃(二)
- 實戰程式設計·使用SwiftUI從0到1完成一款iOS筆記App(四)