UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: Why don't my array elements hold values?

Forums > 100 Days of SwiftUI

I've got an array of Die structs declared as a @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 elements 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()
    }
}

2      

@Legion is dying to know the answer:

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.

I think you forgot an early lesson in the 100 Days of SwiftUI course. When you pass a Struct to a function or a view, you are only sending a COPY of the struct.

struct ContentView: View {
    @State private var dice: [Die] = [Die.Example()]  // <-- you may have 3 Die structs in this array.
    [... snip ...]

In this snip, dice is a collection of Die structs.

Here, you try to roll your collection of Die.

Button("Roll") {
        for var die in dice {   // <-- dice is a collection of 3 Die from above snip.

           // This is a COPY of the die that's in the View's collection.
           // This die copy exists for a fleeting second to run this code.
           // Then it... wait for it...... waaaaaaait for it..... then it dies!
            die.Roll()
        }
    }

You make a copy of a single dice. You roll that copy. The value in the array does not get updated, because you rolled a copy of it.

Unrequested Feedback

Consider changing your view's data model to be a Class so that you pass a reference to Die between views, buttons, etc. This is the "view model" part of MVVM.

Next: Time to upgrade! See -> Renaming ContentView
Please leave a comment!

Also: It's <-- Not possessive. It's means it is, or it has.

Wrong syntax: ...the elements reverts to it's initial state...
Also wrong: ...numDice and it maintains it's value once it's been changed.

2      

@Obelix You had way too much fun writing that answer.

Thanks!

3      

BUILD THE ULTIMATE PORTFOLIO APP Most Swift tutorials help you solve one specific problem, but in my Ultimate Portfolio App series I show you how to get all the best practices into a single app: architecture, testing, performance, accessibility, localization, project organization, and so much more, all while building a SwiftUI app that works on iOS, macOS and watchOS.

Get it on Hacking with Swift+

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.