实战编程·使用SwiftUI从0到1完成一款iOS笔记App(五)
前提回顾
上一章节发布之后,有不少开发的童鞋评论:
本来好好的,经过上一章节的调整后就各个页面开始报错了?
这很正常,刚开始学习SwiftUI
的时候,有时候改了一个参数
,或者少了一个花括号
,愣是找不到哪里写错了。
后面写多了就基本知道哪里需要调整,而且遇到Bug
不可怕,就怕的是明明没有报错,而且是跟着项目教程来的,项目运行后就是没效果,也不知道哪里出错了。
找不到自己的薄弱点,这才是最可怕的。
因此不用太过于担心,本章我们将继续基于完成的Model
、ViewModel
,来完成View
相关的内容。
实战编程-首页
单条笔记
我们回到View文件夹下的ContentView视图,先从NoteListRow
单条笔记视图开始调整。
单条笔记涵盖哪些交互逻辑?一个是点击单条笔记的时候打开编辑笔记弹窗,二是点击笔记右侧的“更多”按钮,唤起二次确认弹窗,并可进行操作删除。如下图所示:
在上一章搭建ViewModel功能的时候,我们说到对单条笔记进行操作需要获得笔记的ID
,然后基于单条数据的ID进行操作。那么这里需要先获得笔记的ID,如下代码所示:
kotlin
复制代码
// 引用viewModel @EnvironmentObject var viewModel: ViewModel // 获得项目唯一ID var itemId: UUID // 从模型类中找ID var item: NoteModel? { return viewModel.getItemById(itemId: itemId) }
上述代码中,我们先使用@EnvironmentObject
全局环境变量引入ViewModel
类,并赋值给viewModel。
@EnvironmentObject是一个动态视图属性,为了无论任何时候可绑定对象发生改变时停用当前视图的属性。
紧接着,声明一个变量itemId
,遵循UUID
格式,作为要使用到的ID。之前我们使用@ObservedObject获得NoteItem模型类,这里我们使用ViewModel就可以弃用原来的这块内容了,直接声明一个变量item,并通过调用viewModel中的getItemById
方法获得对应的笔记ID。
在获得笔记ID后,系统可能无法返回相关的数据内容,也就是参数为空的情况导致系统报错,因此我们使用“?”,当返回的参数值为空的时候,就可以使用默认值填充,避免系统奔溃。
说回正题,由于我们使用item
替换了原来的noteItem,在下面视图对应的参数也需要调整,如下代码所示:
var body: some View { HStack { HStack { VStack(alignment: .leading, spacing: 10) { Text(item?.writeTime ?? "") .font(.system(size: 14)) .foregroundColor(.gray) Text(item?.title ?? "") .font(.system(size: 17)) .foregroundColor(.black) Text(item?.content ?? "") .font(.system(size: 14)) .foregroundColor(.gray) .lineLimit(1) .multilineTextAlignment(.leading) } } Spacer() // 更多操作 Button(action: { }) { Image(systemName: "ellipsis") .foregroundColor(.gray) .font(.system(size: 23)) } } }
上述代码中,替换单条笔记绑定的参数,使用item替换noteItem,替换如下:
- noteItem.writeTime 替换为
item?.writeTime ?? ""
- noteItem.title 替换为
item?.title ?? ""
- noteItem.content 替换为
item?.content ?? ""
替换后,我们来实现两个基本功能,一个是点击笔记的时候,打开编辑笔记弹窗,如下代码所示:
var body: some View { HStack { HStack { VStack(alignment: .leading, spacing: 10) { Text(item?.writeTime ?? "") .font(.system(size: 14)) .foregroundColor(.gray) Text(item?.title ?? "") .font(.system(size: 17)) .foregroundColor(.black) Text(item?.content ?? "") .font(.system(size: 14)) .foregroundColor(.gray) .lineLimit(1) .multilineTextAlignment(.leading) } } //点击编辑 .onTapGesture { self.viewModel.isAdd = false self.viewModel.showEditNoteView = true } Spacer() // 更多操作 Button(action: { viewModel.showActionSheet = true }) { Image(systemName: "ellipsis") .foregroundColor(.gray) .font(.system(size: 23)) } } }
上述代码中,我们在笔记内容的HStack横向容器中增加了onTapGesture
,当点击笔记内容的时候,说明我们需要编辑笔记,这里需要更新isAdd是否新增笔记状态为false,然后更新showEditNoteView
打开编辑笔记弹窗的触发条件为true。
当用户点击“更多”操作时,更新showActionSheet
打开二次确认弹窗的触发条件为true。
然后我们完成打开编辑弹窗和打开删除的二次确认弹窗的操作,如下代码所示:
// 编辑笔记 .sheet(isPresented: $viewModel.showEditNoteView) { //编辑笔记弹窗 } // 删除笔记 .actionSheet(isPresented: self.$viewModel.showActionSheet) { ActionSheet( title: Text("你确定要删除此项吗?"), message: nil, buttons: [ .destructive(Text("删除"), action: { self.viewModel.deleteItem(itemId: itemId) }), .cancel(Text("取消")), ]) }
上述代码中,我们使用sheet
方法,绑定showEditNoteView
打开编辑弹窗的触发参数,编辑笔记页面后面我们会和新建笔记页面功用,这里还没有修改,就先放着。
删除笔记的方法我们使用actionSheet
弹窗,绑定showActionSheet
打开删除二次确认弹窗触发参数,在ActionSheet
弹窗内,我们设置好标题,以及删除按钮的操作,当点击删除的时候,调用viewModel中的deleteItem
方法,指定单条笔记的itemId
找到对应的笔记进行删除。
完成后,我们单条笔记部分,除了打开编辑弹窗,其他内容已经修改完成。
笔记列表
回到ContentView
视图,我们修改了单条笔记的内容,因此笔记列表noteListView
视图也需要调整,首先引入ViewModel,如下代码所示:
// 引用viewModel @EnvironmentObject var viewModel: ViewModel
紧接着,我们换一种方法实现笔记列表,如下代码所示:
// MARK: 笔记列表 func noteListView() -> some View { List { ForEach(viewModel.noteModels) { noteItem in NoteListRow(itemId: noteItem.id) } } .listStyle(InsetListStyle()) }
上述代码中,我们换成了使用func
方式声明视图,这是另一种创建视图的方法,这样创建视图的好处是,我们需要声明的参数可以放在ContentView视图中,就不需要在每一个视图中声明。
笔记列表唯一的改动就是NoteListRow
单条笔记遍历循环的时候,数组来源于viewModel中的noteModels
,然后NoteListRow中的ID为noteItem中的ID
。