在本章中,你将学会如何使用State状态和Binding绑定,监听属性值的变化,和根据Binding绑定关系,改变一个属性值的同时影响另一个属性值的变化。
举个例子:
我们在听音乐或者看视频时,点击“播放”按钮,“播放”按钮会变成“暂停”按钮,同时视频开始播放。
这就用到了@State属性包装器和@Binding属性包装器。
我们尝试完成下面的设计稿。
首先,先创建一个新的项目,命名为SwiftUIState。
在ContentView.swift文件中,我们先创建一个简单的按钮。
关于按钮的编程可以参考之前的文章。
//创建按钮 Button(action: { }) { Image(systemName: "circle") .font(.system(size:150)) .foregroundColor(Color(red: 170/255, green: 170/255, blue: 170/255)) }
在这里我们使用了系统自带的图标、大小、RGB颜色。
好了,我们的到一个按钮的状态,那么我们希望接下来怎么操作?
点击按钮(这个图片),切换状态变成“选中”的状态。
也就是说,现在这个按钮有2个状态,一个状态是“未选中”,是灰色的圆形,另一个状态是“选中”,是填充了绿色的选中图形。
那么,我们可以定义一个初始状态叫做“isSelected”,它的状态是“false”。
@State var isSelected = false 复制代码
在这里,我们定义了一个叫做“isSelected”的Bool变量,它的值是“否”。
当我们点击按钮的时候,我们可以切换这个Bool变量变成“是”。
这样我们就可以通过点击按钮,实现状态来回切换了。
我们来试试。
科普一个知识点。
定义的变量都需要放在结构体下面,也就是Struct ContentView: View下面,而要在body上面。
这样我们才能在body中使用定义好的变量。
下一步,我们回到设计稿中。
当我们是“是”的状态时,图片变成了“选中”,而背景颜色也变成了绿色。
这里,我们用到了状态判断语句。
示例:
Image(systemName: isSelected ? "checkmark.circle.fill" :"circle") 复制代码
我们预设了一个图片,它是systemName系统图片名称,它的取值通过判断取值,也就是isSelected。
这里的格式是:【?XX : XX】
isSelected ? "" :"" 复制代码
简单来说,就是“如果isSelected为正确,那么它是什么,反之是什么”
【?】后面跟随Bool值为true的取值,【:】后面跟随Bool值为false的取值。
回归这句代码,也就是如果isSelected为true,那么它的图片是”checkmark.circle.fill”,如果是false,它的图片是”circle”。
Image(systemName: isSelected ? "checkmark.circle.fill" :"circle") 复制代码
同理,我们完善下背景颜色的判断。
Button(action: { }) { Image(systemName: isSelected ? "checkmark.circle.fill" :"circle") .font(.system(size:150)) .foregroundColor(isSelected ? Color(red: 112/255, green: 182/255, blue: 3/255) : Color(red: 170/255, green: 170/255, blue: 170/255) ) }
接下来,我们需要在按钮操作加上执行动作。
当我们每次点击按钮的时候,isSelected的状态都需要切换。
我们可以用到下面的代码:
self.isSelected.toggle() 复制代码
即点击的时候,isSelected的状态切换。
toggle()是切换状态。
struct ContentView: View { //定义变量 @State var isSelected = false var body: some View { Button(action: { self.isSelected.toggle() }) { Image(systemName: isSelected ? "checkmark.circle.fill" :"circle") .font(.system(size:150)) .foregroundColor(isSelected ? Color(red: 112/255, green: 182/255, blue: 3/255) : Color(red: 170/255, green: 170/255, blue: 170/255) ) } } }
恭喜你,我们完成了@State属性包装器的学习!
下面我们学习下Binding的应用。
在我们实际编程中,会存在状态共享的场景,以上面的“单选按钮”为例,我们可以创建一个状态描述。
比如,在按钮下面加一个状态文字。
我们使用垂直排布的结构,将一个Text和按钮进行上下排布。
下一步,我们把Text抽离出一个子视图,具体做法可以查看之前的文章。
然后记得修改子视图的名称。
我们这里修改为titleView,当然你可以使用任何你想要的名称。
//文字 struct titleView: View { var body: some View { Text("未开启") .fontWeight(.bold) .font(.system(size: 17)) .padding() } }
之后,我们希望完成一个交互,当我们按钮为勾选时,title文字变为“已开启”。
而按钮为关闭状态时,title文字为“未开启”。
我们尝试用isSlected状态关联Text的文字,这和我们的按钮状态一样。
此处用到了下面的代码:
isSelected ? "" :""
但这个时候我们会遇到一个报错信息,点开后的内容如上图所示。
不要着急,遇到报错信息千万不要着急和烦躁,写代码这是正常的。
我们看一下里面的内容,上面写的“找不到isSelected”。
这是因为titleView是一个视图,而这个视图里面没有定义”isSelected”,isSelected只在ContentView里面。
因此,这里引入了Binding绑定的概念,Binding共享了State定义的状态,State状态改变时Binding绑定的参数会一起改变。
struct titleView: View { //绑定状态 @Binding var isSelected: Bool var body: some View { Text(isSelected ? "已开启" :"未开启") .fontWeight(.bold) .font(.system(size: 17)) .padding() } }
使用@Binding,我们只需要填写与需要关联的状态一样的参数,并注明它的参数类型。
状态参数为isSelected,它的类型为Bool。
这时候,我们子视图就不报错了。
但我们主视图报错了,这是因为在titleView引用了一个Bool类型的参数,但在ContentView视图中,不知道它的值是哪里来的。
我们点击红点,点击Fix。
代码告诉我们,在titleView视图中,有一个Binding绑定的参数需要做关联。
这正好对应着ContentView的isSelected。
我们可以用$进行绑定,代码如下:
titleView(isSelected: $isSelected) 复制代码
这样表明了我们titleView里面的isSelected参数,是关联我们ContentView里面的isSelected参数。
当我们主视图的isSelected发生改变时,共享状态给子视图。
这样我们完成了基础的状态共享啦,当我们按钮为开启状态时,文字为“已开启”,当按钮为关闭状态时,文字为“未开启”。
完整代码如下
import SwiftUI struct ContentView: View { // 定义变量 @State var isSelected = false var body: some View { VStack { Button(action: { self.isSelected.toggle() }) { Image(systemName: isSelected ? "checkmark.circle.fill" : "circle") .font(.system(size: 150)) .foregroundColor(isSelected ? Color(red: 112 / 255, green: 182 / 255, blue: 3 / 255) : Color(red: 170 / 255, green: 170 / 255, blue: 170 / 255)) } //文字 titleView(isSelected: $isSelected) } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } struct titleView: View { //绑定状态 @Binding var isSelected: Bool var body: some View { Text(isSelected ? "已开启" :"未开启") .fontWeight(.bold) .font(.system(size: 17)) .padding() } }
在实际开发过程中,会遇到很多像这样的情况。
在第一个页面更改一个状态,进入第二个页面时,对应的状态也要同步成一个状态。
这时候,就可以在第一个页面用@State创建一个变量,然后在第二个页面用@Binding建立关联。
不过要记得要在第一个页面用$进行绑定关联才能使用。
如果说一个变量在所有页面都用到了,一层层关联岂不是很麻烦?
没事,在往后的学习中,我们会学习到全局关联的知识点。
慢慢来,走好每一步。