Reputation: 61
After tapping buttons I need to refresh view with new random number of buttons:
@State private var random = arc4random_uniform(5)
ForEach(0..<random) {index in Button(Text("\(random)")){
self.random = arc4random_uniform(5) }
It updates title of buttons but amount of them stays the same
Upvotes: 1
Views: 459
Reputation: 345
The following is the same as what you wrote above, but just a bit cleaner:
@State private var randomNumber = Int.random(in: 0..<5)
var body: some View {
ForEach(0..<randomNumber, id: \.self) { _ in
Button(action: {
self.randomNumber = Int.random(in: 0..<5)
}) {
Text("\(self.randomNumber)")
}
}
}
When clicking the button (or buttons), the following error is printed in the console:
ForEach<Range<Int>, Int, Button<Text>> count (2) != its initial count (1).
ForEach(:content:)should only be used for *constant* data. Instead conform data to
Identifiableor use
ForEach(:id:content:)and provide an explicit
id!
What that means is that for non-constant (changing) data either an id should be provided when initialising ForEach
or the given data should conform to Identifiable. By doing so, SwiftUI will be able to distinguish new data from old one.
The correct code would look like this (also, I've changed the lowerBound to 1, so that there is always one button):
@State private var randomNumber = Int.random(in: 1..<5)
var body: some View {
ForEach(0..<randomNumber, id: \.self) { _ in
Button(action: {
self.randomNumber = Int.random(in: 1..<5)
}) {
Text("\(self.randomNumber)")
}
}
}
Or if you want something more compact:
@State private var random = Int.random(in: 1..<5)
var body: some View {
ForEach(0..<random, id: \.self) { _ in
Button("\(self.random)") {
self.random = Int.random(in: 1..<5)
}
}
}
Upvotes: 0
Reputation: 7072
I'd suggest separating out your model and your view. That's a more SwiftUI way of doing this.
class Model : ObservableObject {
@Published var buttonLabels = ["Button: 1"]
func generate() {
let buttonCount = Int.random(in: 2...5)
var newLabels = [String]()
for index in 1...buttonCount {
newLabels.append("Button: \(index)")
}
buttonLabels = newLabels
}
}
struct ContentView: View {
@ObservedObject private var model = Model()
var body: some View {
return VStack() {
ForEach(model.buttonLabels, id: \.self) { label in
Button(action: { self.model.generate() }) { Text(label) }
}
}
}
}
Upvotes: 0
Reputation: 257493
Here is possible solutions (tested & works with Xcode 11.4):
struct TestRandomButtons: View {
@State private var random = Array(repeating: 1, count: Int.random(in: 1...5))
var body: some View {
VStack {
ForEach(random, id: \.self) {_ in
Button("\(self.random.count)") {
self.random = Array(repeating: 1, count: Int.random(in: 1...5))
}
}
}
}
}
Upvotes: 2