0

I'm making this app that rolls random dice. I have a custom object that defines one dice object view. This object has two functions that start and stop an animation on the view. When calling this regularly, they work, but the issue is that when I add the view to an array and then use ForEach to put them in the parent view and call the animateDice() function, the dice are still on screen, but they are not animated as they should be.

Here is the dice object:

struct DiceView : View, Identifiable {
let id = UUID()

let images: [UIImage] = [UIImage(named: "dice_1")!, UIImage(named: "dice_2")!, UIImage(named: "dice_3")!, UIImage(named: "dice_4")!, UIImage(named: "dice_5")!, UIImage(named: "dice_6")!]
    
    @State private var currentImageIndex = 0
    @State private var timer: Timer?

var body : some View{
    VStack{
        Image(uiImage: images[currentImageIndex])
            .font(.system(size: 30))
            .frame(width: 40, height: 40, alignment: .center)
    }
}

func animateDice(){
    timer = Timer.scheduledTimer(withTimeInterval: 0.06, repeats: true) { _ in
        currentImageIndex = (currentImageIndex + 1) % images.count
    }
}

func stopAnimatingDice(){
    timer?.invalidate()
    timer = nil
}
}

The animation just cycles through all of the dice images and is supposed to stop randomly.

Here is the code for my main activity (for lack of a better term):

struct ContentView: View {
@State var diceToRoll : String = "5"
@State var diceArray : [DiceView] = []

let uiNavAppearance = UINavigationBarAppearance()
let appearance: UITabBarAppearance = UITabBarAppearance()

var body: some View {
    NavigationView{
        ZStack {
            VStack{
                HStack{
                    Spacer()
                    Text("Dice type --->")
                        .padding(EdgeInsets(top: 20, leading: 0, bottom: 0, trailing: 20))
                    Image("dice_1")
                        .resizable()
                        .frame(width: 35, height: 35)
                        .padding(EdgeInsets(top: 20, leading: -15, bottom: 0, trailing: 20))
                        .foregroundColor(.red)
                        .onTapGesture {
                            // TODO: Change the dice type
                        }
                }
                Spacer()
            }
            VStack{
                Text("Random Dice Roller")
                    .foregroundColor(.teal)
                    .font(.system(size: 35))
                    .bold()
                
                // TODO: Dice get loaded here
                LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible())], spacing: 20){
                    if(Int(diceToRoll) ?? 0 != 0){
                        // The dice from the array are stored here
                        ForEach(diceArray) { die in
                            die
                        }
                    }
                }
                .frame(alignment: .center)
                .padding(EdgeInsets(top: 0, leading: 25, bottom: 30, trailing: 25))
                
                HStack{
                    TextField("Amount of dice", text: $diceToRoll)
                        .textFieldStyle(.roundedBorder)
                        .padding(EdgeInsets(top: 0, leading: 20, bottom: 0, trailing: 10))
                        .keyboardType(.numberPad)
                        .onChange(of: diceToRoll){ _ in
                            if(Int(diceToRoll) ?? 0 != 0){
                                storeDice()
                            }
                        }
                    
                    Button("Roll Dice"){
                        rollDice()
                    }
                    .padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 20))
                    .buttonStyle(.borderedProminent)
                    .tint(.teal)
                }
            }
        }
        .navigationTitle("Random Dice Roller")
        .navigationBarTitleDisplayMode(.inline)
    }
}


// This is used to add the dice views to an array to be used in the 'ForEach' object
private func storeDice(){
    diceArray = []
    
    for _ in 1...Int(diceToRoll)!{
        diceArray.append(DiceView())
    }
}

private func rollDice(){
    // MARK: NOT WORKING - Start the animation for every dice
    for i in 0...diceArray.count - 1{
        diceArray[i].animateDice()
    }
}
}

So, in the storeDice() function, the dice are set to the array. I just want to point out that this works correctly as the dice are present in the array ans are properly placed on-screen with the ForEach object. The rollDice() is where the problem lies: When calling the animateDice() function on each individual view in thee array, somehow the UI is not re-rendered with the changed attributes of the dice array. I still want to be able to use this array because I want to alter each dice individually as they are generating random numbers.

ToxicFlame427
  • 355
  • 2
  • 13
  • Don't hold SwiftUI views in variables and try to reuse them. Views are lightweight, ephemeral structs that SwiftUI instantiates as required. You should change the value that is bound to a dice view (with animation) and let SwiftUI take care of the view creation – Paulw11 May 05 '23 at 18:20

0 Answers0