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

Can't figure out why I cannot update a value

Forums > SwiftUI

I recently starteded working on an app for creating characters in a ttrpg. In the initial view the idea is that a characters abilities are all rolled together. After they have been rolled, they may reroll individual abilities if the scores are too low.

Here is an ability:

class Strength: Ability {
    let id: UUID
    let name: String
    var shortName: String
    var value: UInt

    init(value: UInt) {
        self.id = UUID()
        self.name = "Strength"
        self.shortName = "str"
        self.value = value
    }

    func getModifiers() -> [Modifier] {
        switch value {
        case 3:
            return [Modifier(name: "Melee", modifier: "-3"), Modifier(name: "Open Doors", modifier: "1-in-6")]
        case 4...5:
            return [Modifier(name: "Melee", modifier: "-2"), Modifier(name: "Open Doors", modifier: "1-in-6")]
        case 6...8:
            return [Modifier(name: "Melee", modifier: "-1"), Modifier(name: "Open Doors", modifier: "1-in-6")]
        case 9...12:
            return [Modifier(name: "Melee", modifier: "None"), Modifier(name: "Open Doors", modifier: "2-in-6")]
        case 13...15:
            return [Modifier(name: "Melee", modifier: "+1"), Modifier(name: "Open Doors", modifier: "3-in-6")]
        case 16...17:
            return [Modifier(name: "Melee", modifier: "+2"), Modifier(name: "Open Doors", modifier: "4-in-6")]
        case 18...:
            return [Modifier(name: "Melee", modifier: "+3"), Modifier(name: "Open Doors", modifier: "5-in-6")]
        default:
            return []
        }
    }

    func rerollAbility() {
        self.value = UInt.random(in: 3...18);
    }
}

The problem I am having is in the card subview I created, I am getting the error: "Cannot use mutating member on immutable value: 'self' is immutable". I've tried chaning ability to a constant, and that leads to a similar issue.

Here is the card:

struct AbilityCard: View {
    var ability: Ability
    @Binding var rerollDisabled: Bool

    var body: some View {
        VStack {
            HStack {
                Text(ability.name)
                    .font(.title2)
                    .fontWeight(.bold)
                Spacer()
                Text(String(ability.value))
                    .font(.title3)
            }
            .padding(.bottom, -1)
            Divider()
            HStack {
                Button(action: {
                    ability.rerollAbility()
                    print("Reroll Clicked!")
                }) {
                    Label("Reroll", systemImage: "die.face.6.fill")
                        .font(.title3)
                }
                .disabled(rerollDisabled)
                Spacer()
                Button(action: {
                    print("Info clicked!")
                }) {
                    Text("Modifiers")
                        .font(.body)
                }
                .padding(.trailing, 5)
            }
        }
        .padding(30.0)
        .overlay(
            RoundedRectangle(cornerRadius: 8.0)
                .strokeBorder(Color.gray.opacity(0.25), lineWidth: 1)
                .padding()
        )
    }
}

struct AbilityCard_Previews: PreviewProvider {
    static var previews: some View {
        AbilityCard(ability: Strength(value: 18), rerollDisabled: .constant(true))
    }
}

What am I missing here? Aren't classes supposed to be mutable?

2      

Sorry about that... protocol is:

protocol Ability {
    var id: UUID { get }
    var name: String { get }
    var shortName: String { get set }
    var value: UInt { get set }

    func getModifiers() -> [Modifier]
    func rerollAbility()
}

2      

Lol, yeah, by rolling abilities together... it is meant to simulate rolling dice to generate character stats for a role-playing game.

2      

I mucked around and got a card view to work. But removed the Ability protocol, which probably is useful for your other cards. Wasn't sure about using UInt? Is there a lesson here for us? Why UInt over a vanilla Int?

Also implemented ObservableObject protocol so changes made to value variable (please pick a more descriptive name!) will update other parts of the user interface.

Also implemented the array of modifiers to be a computed property based on value. When value changes, so does the array of modifiers. The function version feels like a heavy handed solution. For me, a computed property seems more elegant.

// Run this in playgrounds to test assumptions and have fun breaking the code!
import SwiftUI
import PlaygroundSupport

struct AbilityCard: View {
    @ObservedObject var ability: Strength // Any changes to the class may require updating the UI
    @State private var playerCanReroll = true  // This is a toggle for testing.

    var body: some View {
        VStack {
            Toggle("Reroll", isOn: $playerCanReroll) // Just for testing.
            HStack {
                Text(ability.name)
                    .font(.title2).fontWeight(.bold)
                Spacer()
                Text(String(ability.value)).font(.title3)
            }
            .padding(.bottom)
            Divider()
            HStack {
                Button(action: {
                    withAnimation { ability.reroll() }
                }) {
                    Label("Reroll", systemImage: "die.face.6.fill").font(.title3)
                }
                .disabled(!playerCanReroll)
            }
            Divider()
            VStack(alignment: .leading){
                ForEach(ability.allModifiers, id:\.self.name ) { modifier in
                    HStack{
                        Text( "> \(modifier.name):")
                        Text( "\(modifier.modifier)")
                    }
                }
            }
        }
        .frame(width: 200, height: 250)
        .padding(30.0)
        .overlay(
            RoundedRectangle(cornerRadius: 8.0)
                .strokeBorder(Color.indigo.opacity(0.85), lineWidth: 5) // bigger for my eyes to see
                .padding()
        )
    }
}

struct Modifier {
    var name:     String
    var modifier: String
}

class Strength: ObservableObject {
    let id:               UUID
    let name:             String
    var shortName:        String
    @Published var value: Int  // If this number changes, recalculate this card
    var allModifiers: [Modifier] {
        switch value {
        case 3:
            return [Modifier(name: "Melee", modifier: "-3"), Modifier(name: "Open Doors", modifier: "1-in-6")]
        case 4...5:
            return [Modifier(name: "Melee", modifier: "-2"), Modifier(name: "Open Doors", modifier: "1-in-6")]
        case 6...8:
            return [Modifier(name: "Melee", modifier: "-1"), Modifier(name: "Open Doors", modifier: "1-in-6")]
        case 9...12:
            return [Modifier(name: "Melee", modifier: "None"), Modifier(name: "Open Doors", modifier: "2-in-6")]
        case 13...15:
            return [Modifier(name: "Melee", modifier: "+1"), Modifier(name: "Open Doors", modifier: "3-in-6")]
        case 16...17:
            return [Modifier(name: "Melee", modifier: "+2"), Modifier(name: "Open Doors", modifier: "4-in-6")]
        case 18...:
            return [Modifier(name: "Melee", modifier: "+3"), Modifier(name: "Open Doors", modifier: "5-in-6")]
        default:
            return []
        }
    }

    func reroll() { value = Int.random(in: 3...18) } // Int is fine. But you're using UInt? Please explain.

    init(value: Int) {
        self.id        = UUID()
        self.name      = "Strength"
        self.shortName = "str"
        self.value     = value
    }
}

PlaygroundPage.current.setLiveView(AbilityCard(ability: Strength(value: 18) ))  // <- Run this line in Playgrounds.

2      

I went with UInt as a (probably needless) optimization since I know the value will never be negative, it should always be between 3 and 18 (representing a score that is 3 6-six sided die added together). I'll play with what you suggested and see where I land. Appreciate you taking the time to look over it!

2      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

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.