实战编程-新建笔记页
标题&内容输入
首先还是需要引用ViewModel,才能使用里面声明好的参数。如下代码所示:
// 引用viewModel @EnvironmentObject var viewModel: ViewModel
引用viewModel后,其他声明的参数都可以删掉了。当我们在首页笔记列表点击单条笔记时,会打开新建笔记弹窗,并把内容传递过来,因此我们需要声明模型类参数,如下代码所示:
// 引用viewModel @EnvironmentObject var viewModel: ViewModel // 声明参数 @State var noteModel: NoteModel // 关闭弹窗 @Environment(.presentationMode) var presentationMode
声明好需要的参数后,我们来到标题输入框和内容输入框的部分,这是需要绑定的参数就不是之前声明的参数了。当我们是新建笔记的时候,标题和内容应该是为空,也就是绑定在viewModel
声明的title
、content
,而如果是编辑笔记,则我们需要绑定的是来自于点击的单条笔记的内容。如下代码所示:
// MARK: 标题输入框 func titleView() -> some View { TextField("请输入标题", text: viewModel.isAdd ? $viewModel.title : $noteModel.title) .padding() } // MARK: 内容输入框 func contentView() -> some View { ZStack(alignment: .topLeading) { TextEditor(text: viewModel.isAdd ? $viewModel.content : $noteModel.content) .font(.system(size: 17)) .padding() if viewModel.isAdd ? (viewModel.content.isEmpty) : (noteModel.content.isEmpty) { Text("请输入内容") .foregroundColor(Color(UIColor.placeholderText)) .padding(20) } } }
完成按钮
完成按钮这块,回忆下我们前几章学习的内容,它应该包含几块内容:
一是判断条件,当我们标题或者内容输入为空的时候,应该提示输入。
二是点击完成操作的时候,也需要判断当前是新增操作还是编辑操作,如果是新增操作,则插入一条新笔记,如果是编辑操作,则需要更新笔记的内容。
我们修改代码如下所示:
// MARK: 完成按钮 func saveBtnView() -> some View { Button(action: { //判断当前是新增还是编辑 if viewModel.isAdd { //判断标题是否为空 if viewModel.isTextEmpty(text: viewModel.title) { viewModel.showToast = true viewModel.showToastMessage = "请输入标题" } //判断内容是否为空 else if viewModel.isTextEmpty(text: viewModel.content) { viewModel.showToast = true viewModel.showToastMessage = "请输入内容" } //校验通过 else { // 新增一条笔记 self.viewModel.addItem(writeTime: viewModel.getCurrentTime(), title: viewModel.title, content: viewModel.content) //关闭弹窗 self.presentationMode.wrappedValue.dismiss() } } else { //判断标题是否为空 if viewModel.isTextEmpty(text: noteModel.title) { viewModel.showToast = true viewModel.showToastMessage = "标题不能为空" } //判断内容是否为空 else if viewModel.isTextEmpty(text: noteModel.content) { viewModel.showToast = true viewModel.showToastMessage = "内容不能为空" } //校验通过 else { // 保存一条新笔记 self.viewModel.editItem(item: noteModel) //关闭弹窗 self.presentationMode.wrappedValue.dismiss() } } }) { Text("完成") .font(.system(size: 17)) } }
代码好像很多的样子,其实不然,逻辑很简单。
当点击“完成”按钮时,首先需要isAdd
状态判断当前是新增还是删除。
如果是新增,则判断viewModel中的输入的标题title和内容content是否为空
,如果为空,则更改showToast
打开提示信息,已经更新showToastMessage
提示信息的内容。如果不为空时,则调用addItem
方法新增一条笔记。
如果点击“完成”按钮时的操作为编辑操作,则和上面一样的判断,只是判断为空的参数变成了来源于noteModel
的标题title
和内容content
,当为空判断通过后,则调用editItem
编辑方法更新笔记内容。
最后都是调用presentationMode.wrappedValue.dismiss
关闭弹窗,当然直接点击关闭按钮时也可以调用这个方法关闭弹窗。
主视图
最后来到新建笔记的body
部分,修改部分就只有标题和toast
绑定的参数,如下代码所示:
var body: some View { NavigationView { VStack { Divider() titleView() Divider() contentView() } .navigationBarTitle(viewModel.isAdd ? "新建笔记" : "编辑笔记", displayMode: .inline) .navigationBarItems(leading: closeBtnView(), trailing: saveBtnView()) .toast(present: $viewModel.showToast, message: $viewModel.showToastMessage) } }
由于新建笔记页面使用了ViewModel
和声明了noteModel
模型类,因此我们如果需要预览该页面,则需要在预览的代码中设置默认值,如下代码所示:
struct NewNoteView_Previews: PreviewProvider { static var previews: some View { NewNoteView(noteModel: NoteModel(writeTime: "", title: "", content: "")).environmentObject(ViewModel()) } }
最后,新建笔记页面修改好后,需要回到ContentView首页,我们打开弹窗的路径还没有配置呢。
在新建笔记时,跳转的页面时NewNoteView,如下代码所示:
// 新增笔记 .sheet(isPresented: $viewModel.showNewNoteView) { NewNoteView(noteModel: NoteModel(writeTime: "", title: "", content: "")) }
在编辑笔记时,跳转的页面也是NewNoteView,如下代码所示:
// 编辑笔记 .sheet(isPresented: $viewModel.showEditNoteView) { NewNoteView(noteModel: self.item ?? NoteModel(writeTime: "", title: "", content: "")) }
项目预览
完成后,运行预览效果如下图所示:
本章小结
恭喜你,完成了使用SwiftUI
从0到1完成一款笔记APP的全部内容!
在整个项目过程中,我们首先学习如何完成一个个单独的视图,再将一块块的代码组合成一个页面,最后再基础页面和交互的基础上使用Model-View-ViewModel
的方式进行开发调整,最终完成整个项目。
回顾第一个项目的整个过程,我们会发现我们构建视图的方式都是自上而下构建,而实现交互功能、逻辑是自下而上搭建。这便是专栏开始之初提到的编程逻辑:
自顶向下逐步求精的模块化设计思想、面向对象的方法自底而上进行开发思想。
编程技巧固然重要,但更重要的是思维方式,方法很容易学会,但观念和习惯就没有那么容易改变了。
编程本就是一条没有那么有趣的路,不妨沉下心来,写好每一段代码,写好每一块业务。
看着最终成功运行的项目,感受着心底的喜悦喷涌而出~
接下来,我们将继续实现和完成其他项目,请保持期待吧~