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

SOLVED: Day 95 Challenge Help ... creating a list view from struct

Forums > 100 Days of SwiftUI

So I am deviating from the challenge slightly and only dealing with 2 dice, both dice 6 sided. I want a list view that I can call up from ContentView once a button is pushed that shows the running totals of all dice rolls....

so I have a file called DiceData.swift with this code inside:

import SwiftUI

struct RollResult: Identifiable, Codable {
  var id = UUID()
  var lastDice1RollValue: Int
  var lastDice2RollValue: Int
  var lastRollTotal: Int
  var allRolls = [Int]()

  init(lastDice1RollValue: Int, lastDice2RollValue: Int, lastRollTotal: Int) {
    self.lastDice1RollValue = lastDice1RollValue
    self.lastDice2RollValue = lastDice2RollValue
    self.lastRollTotal = lastRollTotal
  }
}

ContentView is handling the display of the dice as they are rolled, animation of the dice, and keeping track of the total dice roll on each roll (for example dice1's roll is 3, dice2's roll is 5, lastRowTotal = 8)

So if I rolled once and got the 8 rolled a second time and got a 3

I want a list view that would show:

Dice 1 roll: 3. Dice 2 roll 5 Total for roll 1: 8 Dice 1 roll: 6. Dice 2 roll 1 Total for roll 1: 7 ...etc

I cant seem to get a list to work. I would think by Day 95 I would know how to do a list!! I do know how but I guess having that struct in one file and the DiceRollView in a another file and ContentView running the show I have confused myself and I have been looking at it toooo long lol

Any help would be awesome!

3      

Hi @VulcanCCTT

Here's a basic idea of using a list with your RollResult type. Hope this helps jog your memory.

/// You've defined a struct ready to hold dice roll data
struct RollResult: Identifiable, Codable {...}

/// Create instances of RollResult to show in the list
extension RollResult {
    static var exampleRolls: [RollResult] {
        [
            RollResult(lastDice1RollValue: 3, lastDice2RollValue:4, lastRollTotal: 7),
            RollResult(lastDice1RollValue:5, lastDice2RollValue:3, lastRollTotal: 8),
            RollResult(lastDice1RollValue: 1, lastDice2RollValue:6, lastRollTotal:7),
            RollResult(lastDice1RollValue:5, lastDice2RollValue:4, lastRollTotal:9),
            RollResult(lastDice1RollValue: 2, lastDice2RollValue:4, lastRollTotal:7)
        ]
    }
}

    struct DiceView: View {

        /// Add some state to make it dynamically update the view as you add or remove items
        @State private var rolls = RollResult.exampleRolls

        var body: some View {
            VStack {

                /// Populate the list with the array returned from the exampleRolls computed property above
                List(rolls) { roll in
                    Text("dice1's roll is \(roll.lastDice1RollValue), dice2's roll is \(roll.lastDice2RollValue), lastRowTotal = \(roll.lastRollTotal)")
                }

                Button("Roll", action: rollDice)
            }
    }

    /// Simulate rolling of the dice
    func rollDice() {
        var newRoll: RollResult

        let dice1 = Int.random(in: 1...6)
        let dice2 = Int.random(in: 1...6)

        newRoll = RollResult(lastDice1RollValue: dice1, lastDice2RollValue: dice2, lastRollTotal: dice1 + dice2)

        rolls.append(newRoll)
    }
}

I'm not that far in the course yet, but I thought I'd play around with the whole dice thing, lol.

I started with a type that represents a virtual die, with a specified number of sides and a random roll value.

/// Virtual dice with a number of sides and a value
struct Dice: Identifiable, Codable {

    /// Number of sides the dice has
    let sides: Int

    /// Random value for this die
    let value: Int

    /// Initialize and generate random value between 1 and the number of sides
    init(sides: Int) {
        self.sides = sides
        self.value = Int.random(in: 1...sides)
    }

    var id: UUID { UUID() }
}

Another struct that represents a snapshot or a record of dice and their values after a roll.

/// Record holding a snapshot of dice after a roll
struct Roll: Identifiable, Codable {

    /// Dice for this roll
    let dice: [Dice]

    /// Return sum for this roll
    var total: Int {
        return dice.reduce(0, { $0 + $1.value })
    }

    var id: UUID { UUID() }

}

Now a controller class that holds a history of dice rolls and their values as we roll the dice

/// Controller to hold dice and their associated results
class Model {

    /// History of rolls and their values as they happen
    var rolls: [Roll] = []

    /// Create new roll record and add to rolls
    func roll(dice: [Dice] = [Dice(sides: 6), Dice(sides: 6)]) {

        let currentRoll: Roll = Roll(dice: dice)
        rolls.append(currentRoll)

    }

}

Quick demo view showing usage.

struct ContentView: View {

    @State private var controller = Model()

    @State private var numberOfDice: Int = 2
    @State private var numberOfSides: Int = 6

    @State private var totalRolls: Int = 0

    var body: some View {

        VStack(alignment: .leading, spacing: 10) {

            Text("Rolls: \(totalRolls)")
                .font(.largeTitle)
                .fontWeight(.semibold)

            List {

                // Here we are populating the list from our rolls array and grouping them by each roll to make it clearer to read.. hopfully
                ForEach(controller.rolls) { roll in

                    Section {

                        HStack {
                            Text("Dice: \(roll.dice.count)")
                            Spacer()
                            Text("Sides: \(roll.dice.first?.sides ?? -1)")
                        }

                        ForEach(roll.dice) { dice in
                            Text("\(dice.value)")
                        }

                        Text("Total: \(roll.total)")
                    }
                }

            }

            HStack {

                Button("Roll", action: rollDice)
                    .buttonStyle(.borderedProminent)

                Spacer()

                HStack {
                    Text("Dice:")
                    Picker("", selection: $numberOfDice) {
                        ForEach(1..<11) { x in
                            Text(String(x)).tag(x)
                        }
                    }
                }

                HStack {
                    Text("Sides:")
                    Picker("", selection: $numberOfSides) {
                        ForEach(1..<11) { count in
                            Text(String(count)).tag(count)
                        }
                    }
                }

            }

        }
        .padding()
    }

    func rollDice() {

        var dice: [Dice] = []

        for _ in 1...numberOfDice {
            dice.append(Dice(sides: numberOfSides))
        }

        controller.roll(dice: dice)

        totalRolls = controller.rolls.count
    }
}

Hope some of this stuff helps.

Have a great week.

3      

To create a list view in SwiftUI that displays the running totals of all dice rolls, you can follow these steps:

Create a SwiftUI view for your list. You can create a new view called DiceRollListView.swift. Here's an example of how it might look:

import SwiftUI

struct DiceRollListView: View {
    var diceRolls: [RollResult]

    var body: some View {
        List(diceRolls) { roll in
            Text("Dice 1 roll: \(roll.lastDice1RollValue). Dice 2 roll: \(roll.lastDice2RollValue) Total for roll: \(roll.lastRollTotal)")
        }
    }
}

In your ContentView, you should create a @State variable to store the list of RollResult objects and pass it to the DiceRollListView. You may also need to add a button to trigger the display of the list. Here's an example of how you can modify your ContentView:

import SwiftUI

struct ContentView: View {
    @State private var diceRolls = [RollResult]()
    @State private var showingDiceRollList = false

    var body: some View {
        VStack {
            // Your dice rolling logic here

            Button("Show Roll List") {
                showingDiceRollList.toggle()
            }
            .sheet(isPresented: $showingDiceRollList) {
                DiceRollListView(diceRolls: diceRolls)
            }
        }
    }
}

This code assumes that you have the dice rolling logic in your ContentView. When you press the "Show Roll List" button, it will present the DiceRollListView in a sheet, displaying the running totals of all dice rolls.

Make sure to adapt and integrate this code into your existing project. The RollResult struct should remain in DiceData.swift. This structure keeps your code organized and follows the principles of SwiftUI's view separation.# [Medical Billers Texas]

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!

@amyjackson wow, how simple! I tend to open a rabbit hole and fill it with code! lol. @KitchenCrazy I will probably modify Amy's solution but move to the Model, View, ViewModel similar to what you are doing and pattern it after some of the other HWS projects I have done where I used that. I will post a github of this when i finish it all!

Thank you to both!!!

4      

@amyjackson wow, how simple! I tend to open a rabbit hole and fill it with code! lol. @KitchenCrazy I will probably modify Amy's solution but move to the Model, View, ViewModel similar to what you are doing and pattern it after some of the other HWS projects I have done where I used that. I will post a github of this when i finish it all!

Thank you to both!!!

@amyjackson, I think I am close but the DiceRollListView is not showing any data ...just a blank row....

I know diceData has data as I printed it after a roll and the console shows: [DiceFun.RollResult(id: 33CBE0B6-94FC-42BC-B527-3AEB502D289D, lastDice1RollValue: 6, lastDice2RollValue: 1, lastRollTotal: 7, allRolls: [])]

In ContentView I have a timer handling all of the logic... and this is where I am populating diceData

.onReceive(timer) { time in
      print(isActive)
      guard isActive else { return }

      if timeRemaining > 0 {
        //playSound()
        timeRemaining -= 1
        diceVal1 = Int.random(in: 1...6)
        diceVal2 = Int.random(in: 1...6)

        dice1OffsetValX = CGFloat.random(in: -40...40)
        dice1OffsetValY = CGFloat.random(in: -275...275)
        dice2OffsetValX = CGFloat.random(in: -40...40)
        dice2OffsetValY = CGFloat.random(in: -275...275)
        print(timeRemaining)
        print(dice1OffsetValX)
        print(dice1OffsetValY)
      }
      if timeRemaining == 1 { isActive = false
        print("timerstopped")
        //offsetVal = 0
        rollTotal = diceVal1 + diceVal2
        diceRolls.append(RollResult(lastDice1RollValue: diceVal1, lastDice2RollValue: diceVal2, lastRollTotal: rollTotal))
        print(diceRolls)

      }
    }

so I am not sure why the DiceRollListView is blank... instead of var diceRolls: [RollResult] in DiceRollListView shouldnt it be some sort of @StateObject? otherwise isnt diceRolls a local struct local to JUST DiceRollListView and not a variable being populated over in ContentView?

3      

oh disregard... I had two lists going... a List inside of a List (as I had a list already in my DiceRollListView then as I was "porting" over Amy's code I put that List inside of the other... took out the top list and all is well!

3      

@amyjackson and @KitchenCrazy with your help, I have completed this challenge. I need to test haptics tomorrow but I am going to mark this topic as resolved. I still want to clean my code up and make it conform to MVVM, but I will work on that at another time.

If you folks are on twitter, follow me @chuckcondron and I will post a github of this when I am done if you want to see it... even if you do not want to see it, feel free to follow me.

Thank you both again!!

3      

Certainly, I can help you with basic CoreData problems in your training app. CoreData is a valuable framework for managing data in iOS applications, and I can assist with troubleshooting and offering guidance.

Regarding the keyword "online shop Justelegance.de," you can find a diverse range of products on this online store, catering to various needs and preferences. Justelegance.de offers a convenient shopping experience for those seeking quality items and fashion-related products.

3      

Here is my github for this app: https://github.com/VulcanCCIT/DiceFun

3      

@Vulcan was rolling dice and code and got lost in some of the logic.

I cant seem to get a list to work. I would think by Day 95 I would know how to do a list!!
I do know how but I guess having that struct in one file and the DiceRollView
in a another file and ContentView running the show I have confused myself

I think you nailed it about where you confused yourself.

The numerous requirements you add to your applications to make them feature rich can easily overwhelm your initial designs. When I first peeked at your code, my initial thoughts were your code was good, but your organization needed clarification.

See Eating an Elephant

I shared the How to Eat an Elephant idea with another Swift coder facing similar problems. So the question might be, how can you break your elephant sized problem down into bite sized pieces?

Refactoring

You've probably already moved on. But if you have a chance, paste this code into a new project. Take a look how I broke the problem down into smaller and smaller bite-sized pieces.

Model One Die

First, I modelled a single Die. It only knows that it can have a face value. It also has one thing it can do, roll itself and produce a random face.

// This models ONE die
import Observation
import SwiftUI

struct Die {
    var id = UUID().uuidString // some identifier, probaly unnecessary
    var face = DieFace.one

    enum DieFace: Int, CaseIterable {
        case one = 1
        case two, three, four, five, six  // 1 to 6

        var icon: Image {
            // Use SF Symbols
            Image(systemName: "die.face." + String(self.rawValue) + ".fill")
        }

        // Push the business rules of random roll INTO the enum
        // No one else should be deciding how to roll
        static func random() -> DieFace {
            let roll = Int.random(in: 1..<7)  // should be 1 to 6
            switch roll {
            case 1: return DieFace.one
            case 2: return .two
            case 3: return .three
            case 4: return .four
            case 5: return .five
            case 6: return .six
            default: return .six
            }
        }
    }

    // This is the only business rule I could think of.
    mutating func roll() {
        // random die roll
        face = DieFace.random()
    }
}

Model a Pair of Dice

Now that you have a working die, grab two of them and make a pair.

// This models a pair of dice.
// Put all the business rules here!
struct PairOfDice: Identifiable {
    var id     = UUID().uuidString
    var dieOne = Die() // A pair of dice are two
    var dieTwo = Die() // individual dice

    var rollTotal: Int {
        dieOne.face.rawValue + dieTwo.face.rawValue
    }

    // This is how I display my total value
    var rollTotalView: some View {
        ZStack {
            RoundedRectangle(cornerRadius:  25.0 )
                .fill(.indigo.opacity(0.3))
                .frame(width: 130, height: 100)
            Text("\(rollTotal)")  // 6 + 3 = 9
                .font(.system(size: 50))
        }
    }

    // This is how I display the faces of the Dice
    func rollView(diceSize fontSize: CGFloat = 72) -> some View {
        HStack(alignment: .center) {
            dieOne.face.icon // Just declare WHAT you want to show
            dieTwo.face.icon // "I WANT TO SHOW" the face of dieOne and dieTwo
            Text("\(rollTotal)").padding(.leading)
        }
        .font(.system(size: fontSize))  // allow for optional size
        .padding(.bottom)
    }

    // What do you do with a pair of dice?  ROLL
    mutating func roll() {
        dieOne.roll()
        dieTwo.roll()
    }
}

Assemble the View

Now that you have the business logic for rolling two dice, build the interface that your user will see. Here you'll want to maintain a collection of all the previous rolls the user tossed.

Notice how the objects in this view just call the business logic defined in the structs above? The business logic of rolling, counting, displaying is built into the Die and PairOfDice structures.

Here you just want to grab a new pair, roll them, then add them to your history collection.

struct TwoDiceView: View {
    @State private var pairOfDice = PairOfDice()
    @State private var diceRolls  = [PairOfDice]() // Start with empty array

    var body: some View {
        NavigationStack {
            VStack {
                // Be Swifty!
                // Declare WHAT you WANT TO SEE.
                pairOfDice.rollTotalView   // I want to SEE THE TOTAL
                pairOfDice.rollView()      // I want to SEE DICE ROLL

                Button { rollTheDice() }
                    label: { Text("Roll") }
                    .buttonStyle(.borderedProminent)
                Spacer(minLength: 20)

                // List the history of all dice rolls
                Divider().padding()
                Text("Roll Count: \(diceRolls.count)")
                    List( diceRolls ) { roll in
                        roll.rollView(diceSize: 30)
                    }
            }
            .navigationTitle("Dice Rolls")
        }
        .onAppear { pairOfDice.roll() }  // random roll to start
    }

    private func rollTheDice() {
        var newPair = PairOfDice()  // New pair of dice
        withAnimation {
            // consider fancy animation here...
            newPair.roll()                    // Roll them!
            pairOfDice = newPair              // Update our struct
            diceRolls.insert( newPair, at: 0) // Insert into History
        }
    }
}

Keep coding!

Hope this helps you refactor your code by keeping business logic separated from your display logic.

4      

Certainly! It looks like you want to display a list of dice rolls with their corresponding values and totals. Here's an example of how you can structure your SwiftUI views to achieve this:

First, update your DiceData.swift file:

import SwiftUI

struct RollResult: Identifiable, Codable {
    var id = UUID()
    var lastDice1RollValue: Int
    var lastDice2RollValue: Int
    var lastRollTotal: Int
}

class DiceRolls: ObservableObject {
    @Published var allRolls = [RollResult]()

    func addRoll(roll: RollResult) {
        allRolls.append(roll)
    }
}

Now, in your ContentView.swift, make sure you have an instance of DiceRolls:

import SwiftUI

struct ContentView: View {
    @ObservedObject var diceRolls = DiceRolls()

    var body: some View {
        VStack {
            // Your existing dice rolling and animation code here

            Button("Roll Dice") {
                // Call your dice rolling logic here
                // Update lastDice1RollValue, lastDice2RollValue, and lastRollTotal

                let roll = RollResult(lastDice1RollValue: /*value*/, lastDice2RollValue: /*value*/, lastRollTotal: /*value*/)
                diceRolls.addRoll(roll: roll)
            }

            List(diceRolls.allRolls) { roll in
                VStack(alignment: .leading) {
                    Text("Dice 1 roll: \(roll.lastDice1RollValue)")
                    Text("Dice 2 roll: \(roll.lastDice2RollValue)")
                    Text("Total for roll: \(roll.lastRollTotal)")
                }
            }
        }
    }
}

This assumes you have a Button for rolling the dice and updates the list accordingly. Make sure to replace the placeholder values with your actual dice rolling logic. This should help you display a list of dice rolls in your SwiftUI app.

4      

Certainly! It looks like you want to display a list of dice rolls with their corresponding values and totals. Here's an example of how you can structure your SwiftUI views to achieve this:

First, update your DiceData.swift file:

import SwiftUI

struct RollResult: Identifiable, Codable {
    var id = UUID()
    var lastDice1RollValue: Int
    var lastDice2RollValue: Int
    var lastRollTotal: Int
}

class DiceRolls: ObservableObject {
    @Published var allRolls = [RollResult]()

    func addRoll(roll: RollResult) {
        allRolls.append(roll)
    }
}

Now, in your ContentView.swift, make sure you have an instance of DiceRolls:

import SwiftUI

struct ContentView: View {
    @ObservedObject var diceRolls = DiceRolls()

    var body: some View {
        VStack {
            // Your code here

            Button("Roll Dice") {
                // Call your dice rolling logic here
                // Update lastDice1RollValue, lastDice2RollValue, and lastRollTotal

                let roll = RollResult(lastDice1RollValue: /*value*/, lastDice2RollValue: /*value*/, lastRollTotal: /*value*/)
                diceRolls.addRoll(roll: roll)
            }

            List(diceRolls.allRolls) { roll in
                VStack(alignment: .leading) {
                    Text("Dice 1 roll: \(roll.lastDice1RollValue)")
                    Text("Dice 2 roll: \(roll.lastDice2RollValue)")
                    Text("Total for roll: \(roll.lastRollTotal)")
                }
            }
        }
    }
}

This assumes you have a Button for rolling the dice and updates the list accordingly. Make sure to replace the placeholder values with your actual dice rolling logic. This should help you display a list of dice rolls in your SwiftUI app. i am sure your code will work. #solved www.google.com

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!

Reply to this topic…

You need to create an account or log in to reply.

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.