英文:
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<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()
}
}
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 // <-- 这里
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) // <-- 这里
}
.disabled(button.status == .b)
}
}
}
}
EDIT-1:
@State var status
被声明在自定义的 struct MyButton
中(不在 View 内),@State
仅用于在视图中使用,这是不应在此处使用的原因。
"为什么需要将双向绑定传递给 action?"
因为这是您声明的 let action: (Binding<Status>)->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 // <-- 这里
Button("按钮 \(button.id.uuidString)") {
button.status = .b // <-- 这里
}
.buttonStyle(.bordered)
.disabled(button.status == .b)
.foregroundColor(button.status == .b ? .red : .blue) // <-- 用于测试
}
}
}
}
英文:
@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 // <-- here
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 // <-- here
Button("button x") {
button.action($button.status) // <-- 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<Status>)->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 // <-- here
Button("button \(button.id.uuidString)") {
button.status = .b // <-- here
}
.buttonStyle(.bordered)
.disabled(button.status == .b)
.foregroundColor(button.status == .b ? .red : .blue) // <-- for testing
}
}
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论