實戰教程·元宇宙來了,準備好你的電子名片了嗎?(七)
theme: smartblue
前提回顧
不知不覺已經到了第七章了,在過往每一個章節中,我們都增加和完成專案的一些功能,讓Linkwirld這款產品越來越接近完成品。
一款以使用者操作為核心功能的產品,需要包含增刪改查4部分功能,在之前的章節中我們已經完成了檢視、新增、刪除的功能,還缺少對於身份卡進行編輯的功能。編輯操作為使用者對於之前建立的內容的調整或者更新,在ToDo、Note等應用當中很是常見。
那麼在本章中,我們來完成後編輯相關的操作。
功能分析:新增和編輯頁面的區別
很多時候都會有一個疑惑,現在頁面和編輯頁面到底有沒有區別?開發人員究竟只需要維護一個頁面還是要維護兩個頁面?
一般情況下,由於頁面樣式的相似性,新增和編輯操作在開發的角度上是需要做到共用的。但新增和編輯頁面除了資料繫結外沒有太大的樣式區分時,我們建議可以將樣式或者元件抽離出來,然後再單獨應用。
檢視複用:建立單獨的構件
在新增和編輯頁面中,我們會發現有幾部分檢視內容是可以複用的:titleInputView平臺輸入框、platformPicker平臺選擇器、indexURLView連結地址。如下圖所示:
因此對於這三部分,我們可以將其抽離出來搭建單獨的構件,如此便可以在新增頁面和編輯頁面都可以引用。而且如果需要修改樣式,也只需要修改構件的樣式,新增和編輯頁面的樣式就可以統一變化。
我們建立一個新的資料夾,命名為Artifacts,並且建立3個SwiftUI檔案,併為其命名為TitleInputView、PlatformPicker、IndexURLView,如下圖所示:
我們先來到TitleInputView檔案,將原來NewView中的titleInputView檢視程式碼複製過來,如下程式碼所示:
``` import SwiftUI
struct TitleInputView: View { @Binding var title: String
var body: some View {
TextField("請輸入頭銜", text: $title)
.padding()
.background(Color(.systemGray6))
.cornerRadius(8)
.padding(.horizontal)
.disableAutocorrection(true)
.autocapitalization(.none)
}
}
struct TitleInputView_Previews: PreviewProvider { static var previews: some View { TitleInputView(title: .constant("")) } } ```
上述程式碼中,除了TextField輸入框相關程式碼複製過來,我們還需要完善相關的繫結引數。宣告一個用於雙向繫結輸入框的引數title,並且在TitleInputView預覽時給引數賦予預設值。
緊接著來到PlatformPicker檢視,將原來NewView中的platformPicker檢視程式碼複製過來,如下程式碼所示:
``` import SwiftUI
struct PlatformPicker: View {
@Binding var platformIcon: String
@Binding var platformName: String
let platforms = [
("稀土掘金技術社群", "icon_juejin"),
("CSDN部落格", "icon_csdn"),
("阿里雲社群", "icon_aliyun"),
("華為雲社群", "icon_huaweiyun"),
]
var gridItemLayout = [GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible())]
var body: some View {
ScrollView {
LazyVGrid(columns: gridItemLayout, spacing: 10) {
ForEach(0 ..< platforms.count, id: .self) { item in
if platforms[item].0 == platformName {
Image(platforms[item].1)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 48, height: 48)
.clipShape(Circle())
.overlay(
Circle()
.stroke(Color.green, lineWidth: 4)
)
} else {
Image(platforms[item].1)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 48, height: 48)
.clipShape(Circle())
.onTapGesture {
platformIcon = platforms[item].1
platformName = platforms[item].0
}
}
}
}
}
.padding()
.background(Color(.systemGray6))
.cornerRadius(8)
.padding(.horizontal)
.frame(maxHeight: 140)
}
}
struct PlatformPicker_Previews: PreviewProvider { static var previews: some View { PlatformPicker(platformIcon: .constant("icon_juejin"), platformName: .constant("稀土掘金技術社群")) } } ```
上述程式碼中,我們仍舊講需要宣告雙向繫結的變數,如:platformIcon平臺圖示、platformName平臺名稱,值得注意的是,原有我們宣告的selectedItem完全可以換成判斷platformName是否等於點選的名稱,減少一個引數。如下程式碼所示:
platforms[item].0 == platformName
宣告雙向繫結變數還需要在預覽PlatformPicker時增加引數的預設值,如下程式碼所示:
PlatformPicker(platformIcon: .constant("icon_juejin"), platformName: .constant("稀土掘金技術社群"))
下一步到IndexURLView檢視,將原來NewView中的indexURLView檢視程式碼複製過來,如下程式碼所示:
``` import SwiftUI
struct IndexURLView: View { @Binding var indexURL:String
var body: some View {
ZStack(alignment: .topLeading) {
TextEditor(text: $indexURL)
.font(.system(size: 17))
.padding(15)
.disableAutocorrection(true)
.autocapitalization(.none)
if indexURL.isEmpty {
Text("請輸入主頁連結")
.foregroundColor(Color(UIColor.placeholderText))
.padding(20)
}
}
.background(Color(.systemGray6))
.cornerRadius(8)
.padding()
.frame(maxHeight: 240)
}
}
struct IndexURLView_Previews: PreviewProvider { static var previews: some View { IndexURLView(indexURL: .constant("")) } } ```
完成之後,我們就可以回到NewView檢視中,將原來的引數以及titleInputView平臺輸入框檢視、platformPicker平臺選擇器檢視、indexURLView連結地址檢視的程式碼刪掉,如下圖所示:
刪除程式碼後,我們使用單獨搭建的構件來重新搭建樣式,如下程式碼所示:
``` TitleInputView(title: $title)
PlatformPicker(platformIcon: $platformIcon, platformName: $platformName)
IndexURLView(indexURL: $indexURL) ```
如此,NewView檢視在維持原有功能樣式不變的情況下,程式碼量也精簡很多。
介面設計:建立EditView編輯頁面
完成單獨的構件後,我們就可以來完成編輯頁面的設計。建立一個新的SwiftUI檔案,命名為EditView,如下圖所示:
我們先搭建基礎的樣式,示例:頂部導航選單、頁面標題、關閉按鈕等等,如下圖所示:
``` import SwiftUI
struct EditView: View { @Environment(.presentationMode) var presentationMode
var body: some View {
NavigationStack {
Text("Hello, World!")
.navigationBarTitle("編輯身份卡", displayMode: .inline)
.navigationBarItems(trailing: closeBtn())
}
}
// 關閉按鈕
func closeBtn() -> some View {
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
Image(systemName: "xmark.circle.fill")
.font(.system(size: 17))
.foregroundColor(.gray)
}
}
} ```
上述程式碼中,我們基本和NewView頁面的設計一樣,使用NavigationStack搭建頂部導航選單,並使用navigationBarTitle修飾符設定導航標題,使用navigationBarItems修飾符設定關閉頁面按鈕,並通過宣告全域性變數presentationMode和搭建單獨的關閉按鈕closeBtn來實現關閉編輯頁面。
對於編輯儲存按鈕,樣式和操作和NewView頁面可能不一樣,我們也單獨構建按鈕,如下程式碼所示:
// 編輯更新按鈕
func updateBtn() -> some View {
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
Text("確定更新")
.font(.system(size: 17))
.foregroundColor(.white)
.bold()
.padding()
.frame(maxWidth: .infinity)
.background(Color.blue)
.cornerRadius(8)
.padding(.horizontal)
}
}
完成後將編輯按鈕updateBtn新增到EditView編輯頁面檢視中,如下程式碼所示:
VStack(spacing: 15) {
updateBtn()
}
在引用構件之前,我們先來理解下資料關係。
EditView編輯頁面和ContentView主頁的資料關係是,使用者在ContentView主頁點選單張身份卡片,然後開啟EditView編輯頁面,並將ContentView主頁身份卡片的資料傳遞到EditView編輯頁面。
我們可以宣告一個符合Model資料模型的引數,然後無論是TitleInputView、PlatformPicker、IndexURLView的引數都繫結Model資料模型的引數,然後該資料模型雙向繫結到ContentView主頁中,就可以將ContentView主頁的資料傳遞過來,如下程式碼所示:
@State var model: Model
然後呼叫TitleInputView、PlatformPicker、IndexURLView構件,並且繫結model模型的值,如下程式碼所示:
``` VStack(spacing: 15) { TitleInputView(title: $model.title)
PlatformPicker(platformIcon: $model.platformIcon, platformName: $model.platformName)
IndexURLView(indexURL: $model.indexURL)
updateBtn()
Spacer()
} ```
互動動作:開啟EditView編輯頁面
完成後,我們回到ContentView主頁中,我們來實現頁面跳轉的邏輯。首先我們先宣告一個用於頁面跳轉的引數,如下程式碼所示:
@State var showEditView: Bool = false
然後在CardView檢視中增加頁面跳轉的方法,這裡也可以使用Sheet修飾符,如下程式碼所示:
// 開啟編輯彈窗
.sheet(isPresented: self.$showEditView, onDismiss: {self.showEditView = false }) {
EditView(model: self.item ?? Model(platformIcon: "", title: "", platformName: "", indexURL: ""))
}
上述程式碼中,我們使用sheet修飾符使用者開啟模態彈窗,觸發操作繫結宣告好的變數showEditView,開啟的模態彈窗的頁面為EditView編輯頁面。
由於EditView編輯頁面需要傳入引數,這裡我們選擇傳入的對應model的引數為宣告的item,當item不存在時,則預設傳入一個符合Model格式的資料,避免報錯。
對於編輯操作,我們可以給CardView增加多一個指示符,告知使用者這個卡片是可以被編輯的,如下程式碼所示:
Image(systemName: "ellipsis")
.padding()
.foregroundColor(.black)
.gesture(
TapGesture()
.onEnded({
self.showEditView.toggle()
})
)
上述程式碼中,我們增加了一個Image圖片到CardView檢視中,當點選這個Image圖片時,開啟EditView編輯頁面。
考慮到在ContentView我們已經使用NavigationLink頂部導航選單進行跳轉,那麼如果我們給按鈕增加點選事件,會存在兩個點選事件衝突的情況。
我們先註釋掉NavigationLink相關的程式碼,如下圖所示:
完成後,我們點選剛剛建立的編輯按鈕,預覽下效果,如下圖所示:
新增功能:編輯並更新內容
完成樣式後,我們來實現下編輯更新的邏輯,來到ViewModel檢視模型中,我們建立一個編輯並更新的方法,如下圖所示:
// 編輯更新資料項
func editItem(item: Model) {
if let id = models.firstIndex(where: { $0.id == item.id }) {
models[id] = item
}
}
編輯操作很簡單,首先要找到點選編輯的資料項的ID在整個models資料集中的位置,更新後將更新內容賦予當前的ID,便實現了編輯更新操作。
回到EditView檢視中,先引入ViewModel檢視模型,如下程式碼所示:
var viewModel: ViewModel
然後在EditView檢視預覽時,給宣告的viewModel賦予預設值,如下程式碼所示:
EditView(model: Model(platformIcon: "", title: "", platformName: "", indexURL: ""), viewModel: ViewModel())
由於我們在EditView檢視聲明瞭變數viewModel,因此在使用EditView檢視的頁面也要進行引數繫結。
我們來到ContentView檔案,在CardView檢視中,需要補充viewModel相關的引數,如下圖所示:
繫結完成後,我們再回到EditView檢視,在點選updateBtn更新按鈕時,呼叫ViewModel檢視模型中的editItem方法,如下程式碼所示:
self.viewModel.editItem(item: model)
如此,我們便實現了編輯更新的方法。
互動動作:開啟HonePageView頁面
完成打卡編輯彈窗,並實現編輯更新操作後,我們來補充完善下開啟HonePageView頁面的互動。在之前的章節我們使用NavigationLink導航選單跳轉方式實現了頁面跳轉,由於會和我們點選編輯操作相沖突,因此我們註釋了這部分程式碼。
這裡我們再介紹一種彈窗,fullScreenCover全屏覆蓋彈窗,用fullScreenCover也可以實現頁面跳轉的效果。
首先先宣告開啟彈窗的變數,我們在CardView檢視中宣告變數,如下程式碼所示:
@State var showHomePageView: Bool = false
然後實現呼叫fullScreenCover全屏覆蓋彈窗的方法,如下程式碼所示:
//開啟身份卡主頁
.fullScreenCover(isPresented: $showHomePageView, content: {
HomePageView(platformName: platformName, indexURL: indexURL)
.edgesIgnoringSafeArea(.all)
})
上述程式碼中,我們給CardView的主要內容增加了fullScreenCover修飾符,用於開啟全屏覆蓋彈窗。
彈窗開啟觸發動作繫結宣告好的引數showHomePageView,目標地址選擇HomePageView,並且繫結相關的引數,我們希望這個彈窗全屏展示,增加了edgesIgnoringSafeArea忽略安全區域修飾符。
開啟彈窗的操作,我們希望點選身份卡片時開啟,但又不能和編輯按鈕相沖突。
我們可以將平臺圖示、平臺稱號、平臺名稱部分的樣式再使用一個容器包裹起來,點選這個容器開啟HomePageView頁面,這樣就可以做到不和編輯按鈕相沖突了。如下圖所示:
最後我們增加點選事件在這個容器中,當點選時開啟fullScreenCover彈窗,如下程式碼所示:
.gesture(
TapGesture()
.onEnded({
self.showHomePageView.toggle()
})
)
由於fullScreenCover彈窗是由下往上開啟,因此我們可以換一個返回按鈕的樣式,讓操作看起來流暢些,如下圖所示:
專案預覽
完成後,我們在模擬器中預覽下效果,如下圖所示:
專案小結
在本章中,我們實現了EditView頁面的介面設計、頁面跳轉、編輯更新方法,單一個編輯操作就很不容易,哭泣。
但最重要的是學習了結構化程式設計方法,將頁面的元素分塊,然後抽離建立單獨的構件,如此不管在新增頁面還是在編輯頁面,我們都只需要維護一套程式碼,這大大減輕了程式碼量,也使得程式設計更加優雅。
那麼對於一個本地專案來說,linkworld已經完成了檢視、新增、編輯、刪除操作,似乎可以告一段落了。但是,如果一款產品要成功上線AppStore,那麼僅僅在模擬器中使用這些功能是遠遠不夠的,我們還需要進行本地化儲存等功能的開發等等工作。
那麼在下一章中,我們將介紹如果實現本地儲存及遠端資料儲存相關的內容,請保持期待吧~
版權宣告
本文為稀土掘金技術社群首發簽約文章,14天內禁止轉載,14天后未獲授權禁止轉載,侵權必究!
- 實戰教程·元宇宙來了,準備好你的電子名片了嗎?(一)
- 實戰教程·元宇宙來了,準備好你的電子名片了嗎?(八)
- 實戰教程·什麼年代了還在敲傳統木魚?(二)
- 實戰教程·元宇宙來了,準備好你的電子名片了嗎?(七)
- 實戰教程·元宇宙來了,準備好你的電子名片了嗎?(六)
- 實戰教程·元宇宙來了,準備好你的電子名片了嗎?(五)
- 實戰教程·元宇宙來了,準備好你的電子名片了嗎?(四)
- 實戰教程·元宇宙來了,準備好你的電子名片了嗎?(三)
- 實戰教程·元宇宙來了,準備好你的電子名片了嗎?(二)
- 實戰教程·什麼年代了還在敲傳統木魚?(一)
- 技術下午茶:產品經理是如何工作的?如何才算一份好的需求文件?如何設計一個簡單的列表,它應該具備哪些基本功能?
- 釋出&選擇釋出,使用SwiftUI搭建一個新建釋出彈窗(上)
- 釋出&選擇釋出,使用SwiftUI搭建一個新建釋出彈窗(下)
- 使用SwiftUI搭建一個風箏搖擺動畫,實現放風箏的夢想~
- SwiftUI100天:使用SwiftUI搭建一個計時器App
- 實戰程式設計·使用SwiftUI從0到1完成一款iOS筆記App(三)
- 初識MVVM·關於啟動頁、引導頁、登入頁的設計細節和互動邏輯
- 誰說程式設計師不懂浪漫,教你使用SwiftUI搭建一個電子相簿送給她吧~
- 實戰程式設計·刻在男人DNA裡的浪漫,空氣投籃(二)
- 實戰程式設計·使用SwiftUI從0到1完成一款iOS筆記App(四)