访问 State 在不安装在 View 上时的值

huangapple go评论66阅读模式
英文:

Accessing State's value outside of being installed on a View

问题

在下面的代码中,在编译时,编译器会在以下警告位置引发警告:

在未安装在视图上的情况下访问 State 的值。这将导致初始值的常量绑定,不会更新。

import SwiftUI

struct MyView: View {

    enum Status {
        case a, b
    }

    struct MyButton: Identifiable {
        let id = UUID()
        @State var status: Status = .a
        let action: (Binding<Status>)->Void
    }

    @State var buttons: [MyButton] = [
        MyView.MyButton(action: { status in
            status.wrappedValue = .b
        })
    ]
    
    var body: some View {
        VStack {
            ForEach(buttons) { button in
                Button("x") {
                    button.action(button.$status) // #1
                }
                .disabled(button.status == .b)
            }
        }
    }
}

struct MyView_Previews: PreviewProvider {
    static var previews: some View {
        MyView()
    }
}

在运行代码时,按钮确实在点击后不会被禁用。似乎这是上述错误所描述的情况。

关于此错误,StackOverflow 上有现有的问题和答案,例如:

但它们似乎都在代码中使用类。鉴于上述代码仅使用结构体,是否有办法在不添加类和 @ObservableObject 的情况下修复它?代码的基本概念不应改变,即在按钮操作内部应该能够设置按钮的禁用状态。

英文:

At line #1 in the code below the compiler throws this warning at build time:

> Accessing State's value outside of being installed on a View. This will result in a constant Binding of the initial value and will not update.

import SwiftUI

struct MyView: View {

    enum Status {
        case a, b
    }

    struct MyButton: Identifiable {
        let id = UUID()
        @State var status: Status = .a
        let action: (Binding&lt;Status&gt;)-&gt;Void
    }

    @State var buttons: [MyButton] = [
        MyView.MyButton(action: { status in
            status.wrappedValue = .b
        })
    ]
    
    var body: some View {
        VStack {
            ForEach(buttons) { button in
                Button(&quot;x&quot;) {
                    button.action(button.$status) // #1
                }
                .disabled(button.status == .b)
            }
        }
    }
}

struct MyView_Previews: PreviewProvider {
    static var previews: some View {
        MyView()
    }
}

When running the code, the button indeed does not become disabled after tapping on it. It seems that's what the error above describes.

There are existing questions and answers on SO regarding this error, like:

But they all seem to use classes in the code in question. Given that the code above uses only structs, is there a way to fix it without adding a class and @ObservableObject? The basic concept of the code should not change, i.e. from within the button action it should be possible to set the disabled state of the button.

答案1

得分: 1

@State 应该仅在视图内使用/声明。您可以尝试以下方法,在点击按钮后禁用按钮而无需添加类。

struct MyView: View {
    
    enum Status {
        case a, b
    }
    
    struct MyButton: Identifiable {
        let id = UUID()
        var status: Status = .a  // &lt;-- 这里
        let action: (Binding&lt;Status&gt;)->Void
    }
    
    @State var buttons: [MyButton] = [
        MyView.MyButton(action: { status in
            status.wrappedValue = .b
        })
    ]
    
    var body: some View {
        VStack {
            ForEach($buttons) { $button in  // &lt;-- 这里
                Button("按钮 x") {
                    button.action($button.status) // &lt;-- 这里
                }
                .disabled(button.status == .b)
            }
        }
    }
}

EDIT-1:

@State var status 被声明在自定义的 struct MyButton 中(不在 View 内),@State 仅用于在视图中使用,这是不应在此处使用的原因。

"为什么需要将双向绑定传递给 action?"
因为这是您声明的 let action: (Binding&lt;Status&gt;)->Void,这是您所需的。您可以轻松地去掉 let action: ...,以使所有这些工作,参见下面的代码示例:

struct MyView: View {
    
    enum Status {
        case a, b
    }
    
    struct MyButton: Identifiable {
        let id = UUID()
        var status: Status = .a
    }
    
    @State var buttons: [MyButton] = [MyView.MyButton(), MyView.MyButton()]
    
    var body: some View {
        VStack {
            ForEach($buttons) { $button in  // &lt;-- 这里
                Button("按钮 \(button.id.uuidString)") {
                    button.status = .b // &lt;-- 这里
                }
                .buttonStyle(.bordered)
                .disabled(button.status == .b)
                .foregroundColor(button.status == .b ? .red : .blue) // &lt;-- 用于测试
            }
        }
    }
}
英文:

@State should only be used/declared inside a view. You could try this approach
to disable the Button after tapping it and without adding a class.

struct MyView: View {

    enum Status {
        case a, b
    }

    struct MyButton: Identifiable {
        let id = UUID()
        var status: Status = .a  // &lt;-- here
        let action: (Binding&lt;Status&gt;)-&gt;Void
    }

    @State var buttons: [MyButton] = [
        MyView.MyButton(action: { status in
            status.wrappedValue = .b
        })
    ]
    
    var body: some View {
        VStack {
            ForEach($buttons) { $button in  // &lt;-- here
                Button(&quot;button x&quot;) {
                    button.action($button.status) // &lt;-- here
                }
                .disabled(button.status == .b)
            }
        }
    }
}

EDIT-1:

@State var status is declared inside a custom struct MyButton (not inside a View), @State is
only for use in Views, that is the reason for not using it there.

... why do we need to pass the 2-way binding to action?
because that is what you said you wanted, by declaring let action: (Binding&lt;Status&gt;)-&gt;Void.

You could easily do without the let action: ..., to make all this work, see the code example below:

struct MyView: View {

    enum Status {
        case a, b
    }

    struct MyButton: Identifiable {
        let id = UUID()
        var status: Status = .a
    }
    
    @State var buttons: [MyButton] = [MyView.MyButton(), MyView.MyButton()]
    
    var body: some View {
        VStack {
            ForEach($buttons) { $button in  // &lt;-- here
                Button(&quot;button \(button.id.uuidString)&quot;) {
                    button.status = .b // &lt;-- here
                }
                .buttonStyle(.bordered)
                .disabled(button.status == .b)
                .foregroundColor(button.status == .b ? .red : .blue) // &lt;-- for testing
            }
        }
    }
}

huangapple
  • 本文由 发表于 2023年3月23日 09:54:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/75818675.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定