I am dynamically/programatically creating windows like this:
func createWelcomeWindow() {
let w = WelcomeScreenView().frame(width: 400, height: 600)
let hostingController = NSHostingController(rootView: w)
appState.welcomeWindow = NSWindow(contentViewController: hostingController)
appState.welcomeWindow?.title = "Welcome to Last Warning"
appState.welcomeWindow?.collectionBehavior = [.moveToActiveSpace, .fullScreenAuxiliary]
appState.welcomeWindow?.makeKeyAndOrderFront(nil)
}
The appState is being created so:
class AppState: ObservableObject {
@Published var warningWindow: NSWindow?
@Published var welcomeWindow: NSWindow?
}
@main
struct LastWarningApp: App {
@ObservedObject var appState = AppState()
I am confused about state management in swift.
I tried, mainly out of curiousity, changing for
@main
struct LastWarningApp: App {
@State var welcomeWindow: NSWindow?
And then doing:
welcomeWindow = NSWindow(contentViewController: hostingController)
welcomeWindow?.title = "Welcome to Last Warning"
welcomeWindow?.collectionBehavior = [.moveToActiveSpace, .fullScreenAuxiliary]
welcomeWindow?.makeKeyAndOrderFront(nil)
But this doesn't create the window, I am wondering why this is, as far as I've understood asking chatGPT, @State seems to be for managing the state of the views and is not suitable for this, I've gone through as much github repos as possible trying to see best practice and I couldn't find clear ways to go about this.
One thing I've noticed, but I am not sure about it, is taht welcome window seems to not be set when I debug it.
So is this a reasonable approach? And why doesn't @State work in this particular case?
It also confuses me that I can do:
@State var appState = AppState()
And behaviour stays the same.
@State
works as a source of truth and redraws the body
when the “value” changes.
There isn’t anything wrong with
@State var appState = AppState()
You just have to be aware that changing a property of a reference type does not trigger a redraw because the value isn’t changing. <<<<< Very important
ObservableObject
need a series of things to work.
@Published
emitting when the value changes > ObservableObject
synthesizing the emission > an “Object” property wrapper to invalidate the View
.
Now the “gap”, reference types that aren’t ObservableObject
or Obervable
, These can be stored with @State
but won’t redraw the View
because there is no mechanism.
You may need storage within SwiftUI for these because View
is a value type and can be recreated at any time by SwiftUI. You will end up with multiple instances if you don’t use it.
There is a difference with the “Object” property wrappers. StateObject
is for initializing and ObservedObject
is for passing around.
@ObservedObject var appState = AppState()
Is incorrect…..
You should use
@StateObject var appState = AppState()
The main difference behind this is having access to the secret storage SwiftUI has.
In iOS 13 “the right” way to initialize an ObservableObject
was with State
but we couldn’t observe it until the next view where we could use ObservedObject
. StateObject
was introduced in iOS 14.
Now this is the basics of the SwiftUI property wrappers but I think your biggest issue is that you are mixing SwiftUI and AppKit SwiftUI has a very different way of managing windows.
https://developer.apple.com/videos/play/wwdc2022/10061/
https://developer.apple.com/documentation/swiftui/bringing_multiple_windows_to_your_swiftui_app
After you design the windows in your App
.
@main
struct BookClub: App {
@StateObject private var store = ReadingListStore()
var body: some Scene {
WindowGroup {
ReadingListViewer(store: store)
}
Window("Activity", id: "activity") {
ReadingActivity(store: store)
}
}
}
You present them something like
struct OpenWindowButton: View {
@Environment(\.openWindow) private var openWindow
var body: some View {
Button("Open Activity Window") {
openWindow(id: "activity")
}
}
}