菜鸟入门,各位大佬轻喷,如有谬误之处欢迎讨论建议,也欢迎各位道友与我同行
“不积跬步,无以至千里;不积小流,无以成江海”
继续上文中已经实现将TODO项分组,已完成的todo和未完成的todo理应分开展示。
并且在todo项为空的时候进行提示。
并且根据这个分组,我们已经将设置页面做了出来,类似于iOS原生的设置界面。
但是上文的实现中有一个问题,即两个分组的代码重复了。
所以,本文我们将进行封装,既然要封装,那么必然会涉及到传参的问题。
本次操作不会对UI和交互发生改变,因此本次没有演示图片,部分的演示放到了部分的讲解中。
最简单的封装:函数观察我们实现的TodoView.swift页面,我们可以发现,主要重复的地方在于两个Section中。
其实两个Section基本上是一致的,只是对数据的过滤方式不一样。
所以我们可以将Section封装为一个函数。
同时,我们也能够看到,TodoItem里面的层级过多,会导致很多个缩进,缩进最里面的代码,已经跑到了很右边去了,这很显然看着很难受。
因此我们可以将TodoItem也抽象为一个函数,由Section函数进行调用。
这样代码量进一步减少,并且相较之前更加直观、美观。
最终实现代码如下:
importSwiftUIstructTodoView:View{//省略一堆变量定义。。。//todo项分组functodoSectionView(isFinished:Bool=false)-someView{returnSection(isFinished?"已完成":"未完成"){ForEach(todos.todoList.filter{(item)-Boolinreturnitem.isFinished==isFinished;}){itemin//这里就直接调用下面封装的todoitemview方法了todoItemView(item:item).contentShape(Rectangle()).onTapGesture{//todos.toggle(item:item)showId=item.id;showDetail=true;}//这个调用将实现横滑删除功能}.onDelete{IndexSetintodos.delete(offsets:IndexSet,isFinished:isFinished)}}}//todo项,将原来的Todo项的内容放到这儿来functodoItemView(item:TodoItem)-someView{returnHStack{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)}}}varbody:someView{VStack{//...省略顶部的输入框部分//如果有todo项的时候才显示todo列表,否则提示没有数据if(todos.todoList.count0){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
importSwiftUIstructIndexView:View{//。。。省略部分变量定义//给一个变量,用于传值给子组件
Stateprivatevartest:String="test";varbody:someView{//。。。省略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传入的变量
structTodoView:View{//。。。省略无关变量定义部分//接收父组件传入的title,一定要是个public,不然外面没法传
Statepublicvartitle:String="test";//todo项分组functodoSectionView(isFinished:Bool=false)-someView{returnSection(isFinished?title:"未完成"){//。。。省略}}//。。。省略主体部分最终效果如下:
组件传参:子传父子传父时我们可以利用
Binding的特性,让子组件对变量的操作可以响应到父组件中第一步:父组件传入一个
BindingimportSwiftUIstructIndexView:View{//。。。省略部分变量定义//给一个变量,用于传值给子组件
Stateprivatevartest:String="test";varbody:someView{//。。。省略TabView{//向子组件传参TodoView(title:$test).tabItem{Image(systemName:"list.dash")Text(test)//让这个变量显示出来}}.font(.headline)}//。。。省略}}第二步:子组件中接收
Binding参数structTodoView:View{//。。。省略参数定义//
Binding也是一个public,同时不能定义默认值,否则会报错Bindingpublicvartitle:String;//。。。省略varbody:someView{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进行传递,顾名思义,这是一个环境对象,一旦有所引用,大家都是同一份数据模型。第一步:定义
EnvironmentObjectimportSwiftUIstructIndexView:View{//省略。。。lettodos=TodoLists(todoList:[])varbody:someView{//省略。。。VStack{//一个简单的tabview,底部导航栏TabView{TodoView().tabItem{Image(systemName:"list.dash")Text("TODO")}.tag(0)//此处将环境对象带上去.environmentObject(todos)//省略。。。}.font(.headline)}//省略。。。}}
第二步:子组件中获取
importSwiftUIstructTodoView:View{//使用
EnvironmentObject获取即可EnvironmentObjectvartodos:TodoLists;//省略。。。}本次修改不会对项目的UI和交互等造成任何影响
接下来将在TodoView中在点击TodoItem时弹出一个表单,并在表单中使用这个环境对象,以及找出要编辑的对象,将数据回传。
这些内容下章再进行讨论。
总结函数式的组件片段封装与react中的渲染逻辑比较类似,可以把某一段view分离出来。
既然是函数式的封装,那么参数的传递自然遵从函数的参数传递方法。
暂时没有考虑事件的传递,既然一切都可以是数据,那么完全可以把事件视作一个数据的变化,有数据的子父级影响和全局影响我觉得大部分的场景已经足够了。
EnvironmentObject还有很多其他的用法,例如关闭Sheet等,前文中已有使用,此处不做赘述。