我们至上而下构建页面,首先是搜索栏。从设计稿中,我们可以知道搜索栏由一个搜索图标、搜索输入框、清除按钮组成。由于会使用输入框TextField
,因此需要提前声明绑定的变量,如下代码所示:
@State var searchText = ""
// MARK: 搜索 func searchBarView() -> some View { TextField("搜索内容", text: $searchText) .padding(7) .padding(.horizontal, 25) .background(Color(.systemGray6)) .cornerRadius(8) .overlay( HStack { Image(systemName: "magnifyingglass") .foregroundColor(.gray) .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) .padding(.leading, 8) // 编辑时显示清除按钮 if searchText != "" { Button(action: { self.searchText = "" }) { Image(systemName: "multiply.circle.fill") .foregroundColor(.gray) .padding(.trailing, 8) } } } ) .padding(.horizontal, 10) }
上述代码中,我们创建了一个新的搜索栏视图searchBarView
。
搜索栏使用TextField输入框组件,内容text
部分绑定声明好的变量searchText
。
样式部分使用padding修饰符撑开一段距离,使用background修饰符设置背景填充颜色为灰色,使用cornerRadius修饰符设置视图的圆角度数。
输入框右边有一个“搜索
”的图标,这里使用overlay
修饰符在输入框层叠一个“搜索”图标和一个“清除”图标按钮。
其中“清除”图标的交互逻辑是,判断输入框内输入的文字searchText
是否为空,如果不为空,则展示“清除”按钮,当点击“清除”按钮的时候清空searchText
内容。
运行预览效果如下图所示:
接下来是列表部分,依旧拆解下列表的元素,列表由记录时间、笔记标题、笔记内容、更多按钮组成,如下图所示:
拆解好元素后,由于列表不是固定写好的内容,而是由用户编辑输入的内容,因此我们需要构建数据模型。
在Xcode视图窗口右键,选择New File
,创建一个新的Swift
文件,名称为Model.swift
。如下图所示:
创建完成后,录入以下代码:
import SwiftUI class NoteItem: ObservableObject, Identifiable { var id = UUID() @Published var writeTime: String = "" @Published var title: String = "" @Published var content: String = "" // 实例化 init(writeTime: String, title: String, content: String) { self.writeTime = writeTime self.title = title self.content = content } }
上述代码中,我们创建了一个类NoteItem
,遵循ObservableObject
可被观察对象协议和Identifiable
可被识别协议。
在NoteItem类里面有三个参数:writeTime
录入时间、title
标题、content
内容。并且在ObservableObject协议需要使用@Published
定义,这样才能在参数改变的时候检测到变化。由于使用Identifiable可被识别协议,因此需要声明一个id
为UUID()
。
定义好Model数据模型后,回到ContentView
文件,我们来创建列表视图,如下代码所示:
// MARK: 列表内容 struct NoteListRow: View { @ObservedObject var noteItem: NoteItem var body: some View { HStack { VStack(alignment: .leading, spacing: 10) { Text(noteItem.writeTime) .font(.system(size: 14)) .foregroundColor(.gray) Text(noteItem.title) .font(.system(size: 17)) .foregroundColor(.black) Text(noteItem.content) .font(.system(size: 14)) .foregroundColor(.gray) .lineLimit(1) .multilineTextAlignment(.leading) } Spacer() Button(action: { }) { Image(systemName: "ellipsis") .foregroundColor(.gray) .font(.system(size: 23)) } } } }
上述代码中,我们先创建了一个单条列表视图NoteListRow
,使用@ObservedObject
引用监听实例对象的类NoteItem
。
在构建列表视图的样式上,和基础构建视图的方式一致,只是原本固定录入的参数,变成了来自于NoteItem类的参数,示例:标题
,使用noteItem.title
。
三个文本使用VStack纵向布局容器,设置左对齐以及间距为10。最后使用HStack横向布局容器包裹三个文本和“更多”按钮。由于noteItem.content内容文字可能很长,我们只需要一行,因此可以使用lineLimit
限制长度为1行省略。
如此,便构建完成了单条笔记的样式。
然后我们基于单条笔记的样式构建列表视图,如下代码所示:
// MARK: 列表 struct NoteListView: View { @State var noteItems: [NoteItem] = [NoteItem(writeTime: "2022.09.17", title: "第一条笔记", content: "快来使用念头笔记记录生活吧~快来使用念头笔记记录生活吧~")] var body: some View { List { ForEach(noteItems) { noteItem in NoteListRow(noteItem: noteItem) } } .listStyle(InsetListStyle()) } }
上述代码中,我们创建了一个NoteListView
列表视图,使用@State
声明一个数组noteItems
,并赋予noteItems来源于NoteItem数组类并赋予内容。
在主体body
部分,使用List
列表组件和ForEach
循环遍历noteItems
数组的数据,并传递参数给NoteListRow
。
List列表样式部分,由于SwiftUI默认样式是圆角矩形分组的方式,这边还需要设置List列表样式为InsetListStyle
。
我们在ContentView的body中使用搜索栏视图和列表视图,如下代码所示:
NavigationView { ZStack { VStack { searchBarView() NoteListView() } newBtnView() }.navigationBarTitle("念头笔记", displayMode: .inline) }
运行预览效果如下图所示:
首页-页面判断
上述编程过程中,我们完成了缺省页和列表页,它们之间的交互逻辑是:当笔记列表中没有笔记时,App将展示缺省页,当存在笔记时,展示列表页。
可以先引入NoteItem
数组类,如下代码所示:
@State var noteItems: [NoteItem] = [NoteItem(writeTime: "2022.09.17", title: "第一条笔记", content: "快来使用念头笔记记录生活吧~快来使用念头笔记记录生活吧~")] 然后我们就可以根据noteItems数组的数量作为判断条件,如下代码所示: scss 复制代码 NavigationView { ZStack { if noteItems.count == 0 { noDataView() } else { VStack { searchBarView() NoteListView() } } newBtnView() }.navigationBarTitle("念头笔记", displayMode: .inline) }
上述代码中,当noteItems
数组中的数量为0
时,则展示noDataView
缺省页,否则则展示搜索栏+笔记列表
组成的列表页。
运行预览效果如下图所示:
本章小结
由于项目较长,这里将分成几个章节完成,请按耐住性子一步一步完成。
在本章中,我们从产品规划开始,通过需求分析、产品设计、UI设计、实战编程等阶段来从0到1完成一款iOS笔记App,其中涉及到各个阶段不同职业的工作细节,希望能给大家对一款App全生命周期过程有一个大概的认识。
快来动手试试吧~