@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<Bool>, message: Binding<String>, alignment: Alignment = .center) -> some View { modifier(ToastViewModifier(present: present, message: message, alignment: alignment)) } }
上述代码中,我们实现了一个Toast冒泡提示的样式,详细说明见【Swift实用小册19:ExtenExtension扩展的使用】。
回到NewNoteView
新建笔记视图,我们先声明Toast需要的参数,如下代码所示:
less
复制代码
@State var showToast = false @State var showToastMessage: String = ""
上述代码中,showToast
参数触发是否展示Toast,showToastMessage
参数则显示Toast的内容。调用Toast的方法如下代码所示:
php
复制代码
.toast(present: $showToast, message: $showToastMessage, alignment: .center)
回到逻辑部分,我们需要判断输入的标题title、内容content是否为空,若为空,则展示相应的内容,我们可以写一个方法判读输入内容是否为空,如下代码所示:
arduino
复制代码
// 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的地方,也是喜欢写代码的原因。
接下来的章节,我们将继续完成交互逻辑部分,请保持期待吧~