So I have racked my brain out with this app for almost a week now, trying to do this is in a way where I can have reusable components and have state variables that I can call from those reusable components..
Finally I gave up and said fine.. I will just hardcode views for every icon on the screen.
I have a bunch of icons that when you click them, they go dark and lose color, click again and they come back.
No matter how I implement this, the behavior is the same: Clicking the icons appears to work visually. Resetting with cmd-R menu item, doesn't work.
Now I've actually printed out the values of each boss's isDead state variables in appState and they're false before I do the reset, regardless of what their local isDead state variable is in the View. I click on a boss icon, and it goes gray, and the isDead property that's supposed to be bound to the property of the @Observable AppState class toggles correctly, but doesn't connect to the AppState Observable class.
The state vars are also false after the reset, which is what they should be .. but I don't know if it actually sets them if that actually works.. or if it's just because they were false before, and clicking the icons doesn't change the global state.
Also keep in mind this code produces no errors. I know some things like the Views in ContentView being called without parenthesis is a bit weird.. Not really sure why that works but it works, and if it ain't broke .... one thing at a time :)
Heres all the relevant code:
Bosses.swift
import SwiftUI
struct BossBody: View {
let iconName: String
@Binding var isDead: Bool
var appState = AppState()
var body: some View {
Image(isDead ? "dead" + iconName : iconName)
.resizable()
.frame(width: 65, height: 65)
.gesture(
TapGesture()
.onEnded {
$isDead.wrappedValue.toggle()
print(isDead)
}
)
.modifier(AppearanceModifier(type: .boss, isActive: isDead))
}
}
struct BossRidley: View {
let name: String = "ridley"
@State var appState = AppState()
var body: some View {
BossBody(iconName: name, isDead: $appState.ridleyDead)
}
}
struct BossPhantoon: View {
let name: String = "phantoon"
@State private var appState = AppState()
var body: some View {
BossBody(iconName: name, isDead: $appState.phantoonDead)
}
}
struct BossKraid: View {
let name: String = "kraid"
@State private var appState = AppState()
var body: some View {
BossBody(iconName: name, isDead: $appState.kraidDead)
}
}
struct BossDraygon: View {
let name: String = "draygon"
@State private var appState = AppState()
var body: some View {
BossBody(iconName: name, isDead: $appState.draygonDead)
}
}
So I did make it a LITTLE reusable.. I made the BossBody View so that I just didn't have that code repeating coz that just seemed not right.
Here's the relevant portion of State.swift, the rest of the file is just all the other icon states.. I haven't done them yet other than type the states in because I wanted to try with the bosses first since there's only 4.
import SwiftUI
@Observable
class AppState {
// Bosses
var ridleyDead: Bool = false
var phantoonDead: Bool = false
var kraidDead: Bool = false
var draygonDead: Bool = false
Relevant portion of ContentView.swift:
import SwiftUI
struct ContentView: View {
// Application State
var body: some View {
ZStack {
Color.black.edgesIgnoringSafeArea(.all)
HStack(spacing: 20) {
bosses
itemGrid
gameOptions
}
}
.padding(0)
}
}
private var bosses: some View {
VStack(spacing: 30) {
BossRidley()
BossPhantoon()
BossKraid()
BossDraygon()
}
.padding(0)
}
And finally, the main App swift file where the reset function is written:
import SwiftUI
@main
struct SimpleTrackerApp: App {
@State var appState = AppState()
func resetTracker() {
print(appState.ridleyDead)
print(appState.phantoonDead)
print(appState.kraidDead)
print(appState.draygonDead)
appState.ridleyDead = false
appState.phantoonDead = false
appState.kraidDead = false
appState.draygonDead = false
print(appState.ridleyDead)
print(appState.phantoonDead)
print(appState.kraidDead)
print(appState.draygonDead)
}
var body: some Scene {
WindowGroup {
ContentView()
}
.commands {
CommandGroup(replacing: .newItem) {
Button("Reset Tracker") {
resetTracker()
}
.keyboardShortcut("R", modifiers: [.command])
}
}
}
}
Every time you call AppState()
you create a new instance, one does not know anything about the other.
You can pass a shared state into the environment in SimpleTrackerApp
ContentView()
.environment(appState)
Then replace all the other
@State var appState = AppState()
and
var appState = AppState()
With
@Environment(AppState.self) private var appState
Something to know that when you need a Binding
for a value type property you can add
var body: some View {
@Bindable var appState = appState
to the body
and then access the property with
$appState.someProperty
https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app