属性包装器@State and @binding 在 SwiftUI中的使用

452 阅读2分钟

在SwiftUI中,@State@Binding是用于管理视图状态和数据流的重要属性包装器。

@State

@State是一个属性包装器,用于在视图中管理可变的局部状态。通过将属性标记为@State,您可以使该属性与视图的生命周期相关联,并在属性值更改时自动更新视图。当使用@State标记属性时,SwiftUI会负责在视图层次结构发生更改时自动刷新相关部分的视图。

下图为一个简单的例子,点击按钮来更改背景色和按钮的文字。

image.png

左侧为初始化状态,右侧是已点击状态

struct StateAndBindingSample: View {
    
    @State var backgroundColor: Color = .mint
    @State var title: String = "未点击"
    
    var body: some View {
        ZStack {
            backgroundColor
                .edgesIgnoringSafeArea(.all)
            
            Button {
                backgroundColor = Color.yellow
                title = "已完成"
            } label: {
                Text(title)
                    .font(.subheadline)
                    .fontWeight(.semibold)
                    .foregroundColor(Color.white)
                    .padding()
                    .padding(.horizontal)
                    .background(Color.black)
                    .cornerRadius(10)
            }
        }
    }
}

这是一个简单的例子,那么在实际开发中,往往会有很多代码。此时我们不可能还是写在一个方法里面,我们会把功能按照模块划分,多处使用的代码会封装在一个方法里面,多处使用的UI会做成组件等。

我们现在的需求还是点击按钮更换父类的背景色,我们现在把这个代码重构一下,把button的代码部分提取成一个类来管理

image.png

我们先把option键按住,点击鼠标Button,此时会出现一个Extract Subview,点击后buttong部分的代码就会被提取成一个组件,我们命名为ButtonView。

image.png

但是你会发现,此时有问题了。因为backgroundColor和title属性在父类里面,而现在自定义的ButtonView不能访问。

另外title可以从父类移动到自定义的ButtonView,因为它只是在ButtonView里面使用title,并未和外界有关联。有关联的部分是当我们点击Button来改变父类的背景色。 那么我们改如何来实现了?此时我们就要使用 @binding来完成这个任务

我们把title移入到ButtonView里面

struct StateAndBindingSample: View {
    
    @State var backgroundColor: Color = .mint
    
    var body: some View {
        ZStack {
            backgroundColor
                .edgesIgnoringSafeArea(.all)
            
            ButtonView()
        }
    }
}

struct StateAndBindingSample_Previews: PreviewProvider {
    static var previews: some View {
        StateAndBindingSample()
    }
}

struct ButtonView: View {
    @State var title: String = "未点击"
    
    var body: some View {
        Button {
            backgroundColor = Color.yellow
            title = "已完成"
        } label: {
            Text(title)
                .font(.subheadline)
                .fontWeight(.semibold)
                .foregroundColor(Color.white)
                .padding()
                .padding(.horizontal)
                .background(Color.black)
                .cornerRadius(10)
        }
    }
}

@Binding

@Binding用于在不同视图之间创建双向绑定,使它们共享和同步相同的数据。通过将属性标记为@Binding,您可以传递数据的引用,并确保对该数据的更改在源视图和目标视图之间保持同步

struct StateAndBindingSample: View {
    
    @State var backgroundColor: Color = .mint
    
    var body: some View {
        ZStack {
            backgroundColor
                .edgesIgnoringSafeArea(.all)
            
            ButtonView(backgroundColor: $backgroundColor)
        }
    }
}

struct StateAndBindingSample_Previews: PreviewProvider {
    static var previews: some View {
        StateAndBindingSample()
    }
}

struct ButtonView: View {
    @Binding var backgroundColor: Color
    @State var title: String = "未点击"
    
    var body: some View {
        Button {
            backgroundColor = Color.yellow
            title = "已完成"
        } label: {
            Text(title)
                .font(.subheadline)
                .fontWeight(.semibold)
                .foregroundColor(Color.white)
                .padding()
                .padding(.horizontal)
                .background(Color.black)
                .cornerRadius(10)
        }
    }
}

在上述示例中, StateAndBindingSample 拥有一个 backgroundColor@State 属性,用于控制是否显示 StateAndBindingSample背景色,通过接受 backgroundColor@Binding 属性,与 StateAndBindingSamplebackgroundColor 进行双向绑定。当 ButtonView 中的按钮被点击时,backgroundColor 的值会更新,并将 更改传递回StateAndBindingSample,从而更新视图状态。

需要注意的是,我们在 父类往子类传递变量进行绑定时,需要使用$,美元符号来修饰,要进行双向绑定的变量。如果只是正常传递数据,那么就不需要$符号