菜鸟入门,各位大佬轻喷,如有谬误之处欢迎讨论建议,也欢迎各位道友与我同行
“不积跬步,无以至千里;不积小流,无以成江海”
上文中已经实现将 TODO
项分组,已完成的 todo
和未完成的 todo
理应分开展示。
并且在 todo
项为空的时候进行提示。
并且根据这个分组,我们已经将设置页面做了出来,类似于iOS
原生的设置界面。
但是上文的实现中有一个问题,即两个分组的代码重复了。
所以,本文我们将来进行封装,既然要封装,那么必然会涉及到传参的问题。
本次操作不会对UI和交互发生改变,因此本次没有演示图片,部分的演示放到了部分的讲解中。
观察我们实现的 TodoView.swift
页面,我们可以发现,主要重复的地方在于两个 Section
中。
其实两个 Section
基本上是一致的,只是对数据的过滤方式不一样。
所以我们可以将 Section
封装为一个函数。
同时,我们也能够看到,TodoItem
里面的层级过多,会导致很多个缩进,缩进最里面的代码,已经跑到了很右边去了,这很显然看着很难受。
因此我们可以将 TodoItem
也抽象为一个函数,由 Section
函数进行调用。
这样代码量进一步减少,并且相较之前更加直观、美观。
最终实现代码如下 :
import SwiftUI
struct TodoView: View {
// 省略一堆变量定义。。。
// todo项分组
func todoSectionView(isFinished:Bool = false) -> some View{
return Section(isFinished ? "已完成":"未完成") {
ForEach(todos.todoList.filter{(item) -> Bool in
return item.isFinished == isFinished;
}){ item in
// 这里就直接调用下面封装的 todoitemview 方法了
todoItemView(item: item)
.contentShape(Rectangle())
.onTapGesture {
// todos.toggle(item: item)
showId = item.id;
showDetail = true;
}
// 这个调用将实现横滑删除功能
}.onDelete{ IndexSet in
todos.delete(offsets: IndexSet,isFinished: isFinished)
}
}
}
// todo项,将原来的 Todo 项的内容放到这儿来
func todoItemView(item:TodoItem) -> some View{
return HStack{
VStack{
HStack{
Text("\(item.name)")
Spacer()
}
HStack{
Text("\(item.createdAt)").font(.subheadline)
Spacer()
}
}.foregroundColor(item.isFinished ? .gray : .primary)
Group{
item.isFinished ?
Image(systemName: "circle.fill") :
Image(systemName: "circle")
}.onTapGesture {
todos.toggle(item: item)
}
}
}
var body: some View {
VStack{
// ...省略顶部的输入框部分
// 如果有todo项的时候才显示todo列表,否则提示没有数据
if(todos.todoList.count > 0){
List{
todoSectionView(isFinished: false);
todoSectionView(isFinished: true );
}.animation(.default,value:todos.todoList)
}else{
Text("请添加TODO项").foregroundColor(.gray)
Spacer()
}
}.sheet(isPresented: $showDetail, content: {
Text("String(showId)");
})
}
}
// ... 省略previewView 定义部分
首先,我们的 TodoView.swift
既是页面,同时也可以当做组件,它被 IndexView.swift
所调用。
然后,我们现在从 IndexView.swift
中传入一个title 到 TodoView.swfit
中,作为 section
的前缀名称使用
在IndexView.swift
中应该有一个传入的变量,给 TodoView.swfit
import SwiftUI
struct IndexView: View{
// 。。。省略部分变量定义
// 给一个变量,用于传值给子组件
@State private var test:String = "test";
var body: some View{
// 。。。 省略
TabView {
// 向子组件传参
TodoView(title:test)
.tabItem {
Image(systemName: "list.dash")
Text("TODO")
}.tag(0)
.environmentObject(todos)
SettingView()
.tabItem {
Image(systemName: "gear.circle")
Text("设置")
}.tag(1)
}
.font(.headline)
}
// 。。。省略
}
}
TodoView.swift
中应该有一个变量,接收 IndexView.swfit
传入的变量struct TodoView: View {
// 。。。省略无关变量定义部分
// 接收父组件传入的 title,一定要是个 public,不然外面没法传
@State public var title:String = "test";
// todo项分组
func todoSectionView(isFinished:Bool = false) -> some View{
return Section(isFinished ? title:"未完成") {
// 。。。省略
}
}
// 。。。省略主体部分
子传父时我们可以利用 @Binding
的特性,让子组件对变量的操作可以响应到父组件中
@Binding
import SwiftUI
struct IndexView: View{
// 。。。省略部分变量定义
// 给一个变量,用于传值给子组件
@State private var test:String = "test";
var body: some View{
// 。。。 省略
TabView {
// 向子组件传参
TodoView(title:$test)
.tabItem {
Image(systemName: "list.dash")
Text(test) // 让这个变量显示出来
}
}
.font(.headline)
}
// 。。。省略
}
}
@Binding
参数struct TodoView: View {
// 。。。省略参数定义
// @Binding 也是一个 public,同时不能定义默认值,否则会报错
@Binding public var title:String;
// 。。。省略
var body: some View {
VStack{
HStack{
// 我们将 title 绑定到输入框中以便观察效果
TextField("请输入新的TODO",text:$title).onSubmit {
todos.add(name: newItem)
newItem = ""
}
Button("添加"){
todos.add(name: newItem)
newItem = ""
}
}.padding()
}
// 。。。省略
}
得到以下结果
可以看到,TextField
的绑定值的变化,同时影响了 section
的标题和父组件中TabItem
的标题
@EnvironmentObject
以上我们已经有了父子传递,那么假设我们现在有这么一个需求:
点击 TodoItem
的时候需要弹出一个表单,用来展示 TodoItem
的所有信息,并且组件内所有的数据修改都会影响到点击的哪一条 TodoItem
。
当然,我们可以只用 @Binding
传递,一个参数一个参数地处理,这很显然不是一个很好的处理方式。
最好的办法是让 TodoItem
的表单和外面可以共用一份数据,这样,List
就只需要传一个 id
到表单内部即可,由表单自己去处理。
此时,我们可以借助 @EnvironmentObject
进行传递,顾名思义,这是一个环境对象
,一旦有所引用,大家都是同一份数据模型。
@EnvironmentObject
import SwiftUI
struct IndexView: View{
// 省略。。。
let todos = TodoLists(todoList: [])
var body: some View{
// 省略。。。
VStack{
// 一个简单的tabview,底部导航栏
TabView {
TodoView()
.tabItem {
Image(systemName: "list.dash")
Text("TODO")
}.tag(0)
// 此处将环境对象带上去
.environmentObject(todos)
// 省略。。。
}
.font(.headline)
}
// 省略。。。
}
}
import SwiftUI
struct TodoView: View {
// 使用 @EnvironmentObject 获取即可
@EnvironmentObject var todos:TodoLists;
// 省略。。。
}
本次修改不会对项目的UI和交互等造成任何影响
接下来将在 TodoView
中在点击 TodoItem
时弹出一个表单,并在表单中使用这个环境对象,以及找出要编辑的对象,将数据回传。
这些内容下章再进行讨论。
react
中的渲染逻辑比较类似,可以把某一段view
分离出来。Sheet
等,前文中已有使用,此处不做赘述。