在SwiftUI中更改@State变量不会更新视图

24

我有一个如下的View(去除了不相关的部分):

struct Chart : View {
    var xValues: [String]
    var yValues: [Double]
    @State private var showXValues: Bool = false

    var body = some View {
        ...
        if showXValues {
            ...
        } else {
            ...
        }
        ...
    }
}

然后我想添加一种从外部修改这个值的方式,所以我添加了一个函数:

func showXValues(show: Bool) -> Chart {
    self.showXValues = show
    return self
}

所以我像这样从外部构建图表视图:

Chart(xValues: ["a", "b", "c"], yValues: [1, 2, 3])
    .showXValues(true)

但它的行为就像值仍然是假的一样。我做错了什么?我认为更新一个@State变量应该更新视图。总的来说,我对Swift比较新,对SwiftUI更是如此,我是否遗漏了某种特殊技术应该在这里使用?


改变另一个视图中的值的方法之一是使用 @Binding。 - Nalov
请访问 https://dev59.com/QFMI5IYBdhLWcg3wPpAa。 - BollMose
3个回答

10

如评论所述,@Binding是解决问题的方法。

这里是一个最简示例,展示了如何使用你的代码实现这个概念:

struct Chart : View {
    var xValues: [String]
    var yValues: [Double]
    @Binding var showXValues: Bool

    var body: some View {
        if self.showXValues {
            return Text("Showing X Values")
        } else {
            return Text("Hiding X Values")
        }
    }
}

struct ContentView: View {
    @State var showXValues: Bool = false

    var body: some View {
        VStack {
            Chart(xValues: ["a", "b", "c"], yValues: [1, 2, 3], showXValues: self.$showXValues)
            Button(action: {
                self.showXValues.toggle()
            }, label: {
                if self.showXValues {
                    Text("Hide X Values")
                }else {
                    Text("Show X Values")
                }
            })
        }
    }
}

3
但是我有20个可选参数,比如showXValues,我不想每次都修改它们,我想要像构建器模式一样的东西 - 就像我的示例函数所展示的那样。我不想编写一个20个参数的初始化程序。例如,用户可以告诉我他们是否想要显示X值,但默认情况下我会将它们隐藏。 - iSpain17
1
你可以将所有这些变量打包在一个@ObservedObject中吗?并给它们一个标准值@iSpain17。 - Unpunny
11
我觉得这种方法并不令人满意。只有 Chart 应该关心 showXValues,而不是 ContentView。我不想把视图的状态定义向上推到它的父级。 - Chuck Batson
1
即使我不认为这是正确的方法,如果一个子组件没有更新值,那么父组件就不应该将其作为绑定传递。SwiftUI会在您在父组件中更新状态后负责重新渲染并将更新后的值传递给子组件。 - Jay Mehta

3

不需要创建func。我只需要不将属性标记为私有属性,而是给它们一个初始值,这样它们就会在构造函数中变成可选的。因此,用户可以选择指定它们或者不关心它们。像这样:

var showXLabels: Bool = false

这样构造函数要么是 Chart(xLabels:yLabels) 要么是 Chart(xLabels:yLabels:showXLabels)

问题与@State无关。

编辑,几年后

实际上,对于二进制选项(如显示/隐藏内容)解决此类问题的方法更简洁。对于超过两种可能的配置,仍然需要不同的参数。

关键在于OptionSet才是用于这种情况的工具,可以在Apple框架(如Foundation等)中找到。

所以,我们只需添加一个Chart.Options结构即可:

extension Chart {
    struct Options: OptionSet {
        let rawValue: Int

        static var showXValueLabels = Self(rawValue: 1 << 0)
        static var showYValueLabels = Self(rawValue: 1 << 1)
        [... add more options if you want]
    }
}

然后,视图本身保持更紧凑,因为它只有一个用于二进制设置的变量:

struct Chart: View {
    var xValues: [String]
    var yValues: [Int]
    var options: Chart.Options = []

    var body: some View {
        VStack {
            Text("A chart")

            if options.contains(.showXValueLabels) {
                [... whatever xValue label specific view]
            }

            if options.contains(.showYValueLabels) {
                [... whatever yValue label specific view]
            }
        }
    }
}

你可以像这样构建一个Chart

Chart(xValues: ["a", "b", "c"],
      yValues: [1, 2, 3],
      options: [.showXValueLabels, .showYValueLabels])

或者

Chart(xValues: ["a", "b", "c"],
      yValues: [1, 2, 3],
      options: [.showXValueLabels])

或者直接使用默认选项:

Chart(xValues: ["a", "b", "c"],
      yValues: [1, 2, 3])

8
这个回答与你的原问题无关。StackOverflow也在这里帮助其他可能会遇到你的问题的人,你不应该接受那些没有回答你问题的答案。 - Tiebe Groosman
我的问题可以有两种理解方式:1. 分配一个绑定,动态地允许显示/隐藏xValues - 这个答案是来自Unpunny的答案。或者,2. 指定一个始终保持不变的设置,我从未打算更改 - 我只是SwiftUI的初学者,并认为我始终需要在init()之后添加@State到我想要更改的值中,通过构建器模式。这是不正确的,我甚至不需要构建器模式。两个答案都有赞,所以它们显然都与原始问题有很大关系。 - iSpain17

1
func showXValues(show: Bool) -> Chart {
    var copy = self
    copy._showXValues = .init(wrappedValue: show)
    return copy
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接