Tema Sysoev
Tema Sysoev

Reputation: 61

Is it possible to create different number of buttons each time in SwiftUI

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

Answers (3)

Filip Sakel
Filip Sakel

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 toIdentifiableor useForEach(:id:content:)and provide an explicitid!

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

Obliquely
Obliquely

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

Asperi
Asperi

Reputation: 257493

Here is possible solutions (tested & works with Xcode 11.4):

demo

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

Related Questions