Home > Mobile >  Why don't my @State array elements hold their values?
Why don't my @State array elements hold their values?

Time:01-04

I've got an array of Die structs declared as an @State variable in ContentView. However, whenever I modify the values of a given element in the array those new values are immediately lost and the element reverts to it's initial state. Initially I thought this was because I'm assigning the array an initial value in ContentView, but then I also assign a value of 1 to another @State variable, numDice and it maintains it's value once it's been changed.

die.Roll() has no effect beyond changing the die value while the Roll() function is running, but the value is immediately lost upon exiting the function.

ContentView:

import SwiftUI

struct ContentView: View {
    @State private var dice: [Die] = [Die.Example()]
    @State private var numDice: Int = 1

    var body: some View {
        VStack {
            Picker("Number of dice", selection: $numDice) {
                ForEach(1..<5, id: \.self) {
                    Text("\($0) dice")
                }
            }
            .onChange(of: numDice) { _ in
                print(numDice)
                dice.removeAll()

                for _ in 1...numDice {
                    dice.append(Die.Example())
                }
            }

            Button("Roll") {
                for var die in dice {
                    die.Roll()
                }
            }

            HStack {
                ForEach(dice) { die in
                    DieView(dieValue: die.Value)
                    List(dice) { die in
                        ForEach(die.ValueHistory, id: \.self) { v in
                            Text("\(v)")
                        }
                    }
                }
            }
        }
    }
}

enum DieNumber: Int {
    case One = 1, Two, Three, Four, Five, Six
}

struct DieView: View {
    let dieValue: Int
    var body: some View {
        HStack {
            ZStack{
                Rectangle()
                    .size(width: DieSizes.Width, height: DieSizes.Width)
                    .stroke(.black, lineWidth: DieSizes.StrokeWidth)
                    .foregroundColor(.white)

                NumberView(number: DieNumber(rawValue: dieValue)!)
            }
        }
    }
}

struct NumberView: View {
    var number: DieNumber

    var body: some View {
        switch number {
        case .One:
            OneView()
        case .Two:
            TwoView()
        case .Three:
            ThreeView()
        case .Four:
            FourView()
        case .Five:
            FiveView()
        case .Six:
            SixView()
        }
    }
}

struct OneView: View {
    var body: some View {
        DotView()
            .offset(x: DieSizes.DotPositions.OneDot.Ax, y: DieSizes.DotPositions.OneDot.Ay)
    }
}

struct TwoView: View {
    var body: some View {
        DotView()
            .offset(x: DieSizes.DotPositions.TwoDot.Ax, y: DieSizes.DotPositions.TwoDot.Ay)
        DotView()
            .offset(x: DieSizes.DotPositions.TwoDot.Bx, y: DieSizes.DotPositions.TwoDot.By)
    }
}

struct ThreeView: View {
    var body: some View {
        DotView()
            .offset(x: DieSizes.DotPositions.ThreeDot.Ax, y: DieSizes.DotPositions.ThreeDot.Ay)
        DotView()
            .offset(x: DieSizes.DotPositions.ThreeDot.Bx, y: DieSizes.DotPositions.ThreeDot.By)
        DotView()
            .offset(x: DieSizes.DotPositions.ThreeDot.Cx, y: DieSizes.DotPositions.ThreeDot.Cy)
    }
}

struct FourView: View {
    var body: some View {
        DotView()
            .offset(x: DieSizes.DotPositions.FourDot.Ax, y: DieSizes.DotPositions.FourDot.Ay)
        DotView()
            .offset(x: DieSizes.DotPositions.FourDot.Bx, y: DieSizes.DotPositions.FourDot.By)
        DotView()
            .offset(x: DieSizes.DotPositions.FourDot.Cx, y: DieSizes.DotPositions.FourDot.Cy)
        DotView()
            .offset(x: DieSizes.DotPositions.FourDot.Dx, y: DieSizes.DotPositions.FourDot.Dy)
    }
}

struct FiveView: View {
    var body: some View {
        DotView()
            .offset(x: DieSizes.DotPositions.FiveDot.Ax, y: DieSizes.DotPositions.FiveDot.Ay)
        DotView()
            .offset(x: DieSizes.DotPositions.FiveDot.Bx, y: DieSizes.DotPositions.FiveDot.By)
        DotView()
            .offset(x: DieSizes.DotPositions.FiveDot.Cx, y: DieSizes.DotPositions.FiveDot.Cy)
        DotView()
            .offset(x: DieSizes.DotPositions.FiveDot.Dx, y: DieSizes.DotPositions.FiveDot.Dy)
        DotView()
            .offset(x: DieSizes.DotPositions.FiveDot.Ex, y: DieSizes.DotPositions.FiveDot.Ey)
    }
}

struct SixView: View {
    var body: some View {
        DotView()
            .offset(x: DieSizes.DotPositions.SixDot.Ax, y: DieSizes.DotPositions.SixDot.Ay)
        DotView()
            .offset(x: DieSizes.DotPositions.SixDot.Bx, y: DieSizes.DotPositions.SixDot.By)
        DotView()
            .offset(x: DieSizes.DotPositions.SixDot.Cx, y: DieSizes.DotPositions.SixDot.Cy)
        DotView()
            .offset(x: DieSizes.DotPositions.SixDot.Dx, y: DieSizes.DotPositions.SixDot.Dy)
        DotView()
            .offset(x: DieSizes.DotPositions.SixDot.Ex, y: DieSizes.DotPositions.SixDot.Ey)
        DotView()
            .offset(x: DieSizes.DotPositions.SixDot.Fx, y: DieSizes.DotPositions.SixDot.Fy)
    }
}

struct DotView: View {
    var body: some View {
        Circle()
            .size(width: DieSizes.DotSize, height: DieSizes.DotSize)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

DieSizes:

import Foundation

struct DieSizes {
    static let Width: CGFloat = 100
    static let StrokeWidth: CGFloat = 5
    static var DotSize: CGFloat { get { return StrokeWidth * 3.0 } }

    struct DotPositions {
        struct OneDot {
            static var Ax: CGFloat { get { return (Width - (2.5 * StrokeWidth)) / 2.0 } }
            static var Ay: CGFloat { get { return Ax } }
        }

        struct TwoDot {
            static var Ax: CGFloat { get { return 2.0 * StrokeWidth } }
            static var Ay: CGFloat { get { return Ax } }
            static var Bx: CGFloat { get { return Width - (5.0 * StrokeWidth) } }
            static var By: CGFloat { get { return Bx } }
        }

        struct ThreeDot {
            static var Ax: CGFloat { get { return 2.0 * StrokeWidth } }
            static var Ay: CGFloat { get { return Ax } }
            static var Bx: CGFloat { get { return (Width - (2.5 * StrokeWidth)) / 2.0 } }
            static var By: CGFloat { get { return Bx } }
            static var Cx: CGFloat { get { return Width - (5.0 * StrokeWidth) } }
            static var Cy: CGFloat { get { return Cx } }
        }

        struct FourDot {
            static var Ax: CGFloat { get { return 2.0 * StrokeWidth } }
            static var Ay: CGFloat { get { return Ax } }
            static var Bx: CGFloat { get { return Ax } }
            static var By: CGFloat { get { return Width - 2.5 * Ax } }
            static var Cx: CGFloat { get { return By } }
            static var Cy: CGFloat { get { return Ay } }
            static var Dx: CGFloat { get { return Cx } }
            static var Dy: CGFloat { get { return By } }
        }

        struct FiveDot {
            static var Ax: CGFloat { get { return 2.0 * StrokeWidth } }
            static var Ay: CGFloat { get { return Ax } }
            static var Bx: CGFloat { get { return Ax } }
            static var By: CGFloat { get { return Width - 2.5 * Ax } }
            static var Cx: CGFloat { get { return By } }
            static var Cy: CGFloat { get { return Ay } }
            static var Dx: CGFloat { get { return Cx } }
            static var Dy: CGFloat { get { return By } }
            static var Ex: CGFloat { get { return (Width - (2.5 * StrokeWidth)) / 2.0 } }
            static var Ey: CGFloat { get { return Ex } }
        }

        struct SixDot {
            static var Ax: CGFloat { get { return 2.0 * StrokeWidth } }
            static var Ay: CGFloat { get { return Ax } }
            static var Bx: CGFloat { get { return Ax } }
            static var By: CGFloat { get { return (Width - (2.5 * StrokeWidth)) / 2.0 } }
            static var Cx: CGFloat { get { return Ax } }
            static var Cy: CGFloat { get { return Width - 2.5 * Ax } }
            static var Dx: CGFloat { get { return Cy } }
            static var Dy: CGFloat { get { return Ay } }
            static var Ex: CGFloat { get { return Dx } }
            static var Ey: CGFloat { get { return By } }
            static var Fx: CGFloat { get { return Dx } }
            static var Fy: CGFloat { get { return Cy } }
        }
    }
}

Die:

import Foundation

struct Die: Identifiable {
    let id = UUID()
    private(set) var Value: Int = 1
    private(set) var ValueHistory: [Int] = [Int]()
    private var hasBeenRolled = false

    mutating func Roll() {
        print("Has been rolled: \(hasBeenRolled)")
        print("Old value: \(Value)")
        print("Historical values pre-update: \(ValueHistory)")

        if hasBeenRolled {
            ValueHistory.append(Value)
            print("Appended value")
        }

        Value = Int.random(in: 1..<7)
        hasBeenRolled = true
        print("New value: \(Value)")
        print("Historical values: \(ValueHistory)")

    }

    mutating func ResetDie() {
        Value = 1
        ValueHistory = [Int]()
        hasBeenRolled = false
    }

    static func Example() -> Die {
        return Die()
    }
}

CodePudding user response:

In your for loop when calling Roll() you get a copy of the object from the array that you then update but the original object in the array remains unchanged.

One way to solve this is to access the object in the array using an index when calling Roll()

Button("Roll") {
    for index in dice.indices {
        dice[index].Roll()
    }
}
  • Related