Reputation: 3063
I have a following View (took out irrelevant parts):
struct Chart : View {
var xValues: [String]
var yValues: [Double]
@State private var showXValues: Bool = false
var body = some View {
...
if showXValues {
...
} else {
...
}
...
}
}
then I wanted to add a way to modify this value from outside, so I added a function:
func showXValues(show: Bool) -> Chart {
self.showXValues = show
return self
}
so I build the Chart view from the outside like this:
Chart(xValues: ["a", "b", "c"], yValues: [1, 2, 3])
.showXValues(true)
but it works as if the value was still false. What am I doing wrong? I thought updating an @State variable should update the view. I am pretty new to Swift in general, more so to SwiftUI, am I missing some kind of special technique that should be used here?
Upvotes: 28
Views: 43266
Reputation: 223
Modify @State property ouside of your view will not update the displaying UI.
There's quite a few approches to achieve this optional chain like pattern.
struct Chart: View {
let param: Param
init(param: Param) {...}
var body: some View {
...
.id(param)
}
}
extension Chart {
func update(value: Value) -> Self {
param.value = value
return self
}
}
ref: https://github.com/onevcat/Kingfisher See how they do about KFOptionSetter
struct Chart: View {
let value: Value
init(value: Value) {...}
var body: some View {
...
}
}
extension Chart {
func update(value: Value) -> Self {
return Chart(value: value)
}
}
If your view request seveal params, you can always provide convenient methods.
Upvotes: 0
Reputation: 621
func showXValues(show: Bool) -> Chart {
var copy = self
copy._showXValues = .init(wrappedValue: show)
return copy
}
Upvotes: 2
Reputation: 3063
There is no need to create func
-s. All I have to do is not mark the properties as private but give them an initial value, so they're gonna become optional in the constructor. So user can either specify them, or not care. Like this:
var showXLabels: Bool = false
This way the constructor is either Chart(xLabels:yLabels)
or Chart(xLabels:yLabels:showXLabels)
.
Question had nothing to do with @State.
Edit, a few years later
Actually, there is an even cleaner way to solve this for binary options, like show/hide stuff. For more than 2-way configurations, distinct arguments are still the way.
The point is that OptionSet
s are intended for exactly this use case, and they can be found throughout Apple frameworks like Foundation etc.
So, we are just gonna add a Chart.Options
struct:
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]
}
}
Then, the View itself remains more compact, because it will only have a single variable for binary settings:
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]
}
}
}
}
And you can construct a Chart
like this:
Chart(xValues: ["a", "b", "c"],
yValues: [1, 2, 3],
options: [.showXValueLabels, .showYValueLabels])
or
Chart(xValues: ["a", "b", "c"],
yValues: [1, 2, 3],
options: [.showXValueLabels])
or just use the default options:
Chart(xValues: ["a", "b", "c"],
yValues: [1, 2, 3])
Upvotes: 3
Reputation: 172
As in the comments mentioned, @Binding
is the way to go.
Here is a minimal example that shows the concept with your code:
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")
}
})
}
}
}
Upvotes: 10