Yes, still figuring out a more elegant solution, but basically if you store your "TCA state" on a property @State
inside a children of the view hierarchy (and not on the first level), then you can use it for constructing independent stores when instantiating windows
.
import SwiftUI
import ComposableArchitecture
@main
struct TCAMultiWindowApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct AppState: Identifiable, Equatable {
let id: UUID //Only used for demo purposes
var color: Color = .clear
}
enum AppAction: Equatable {
case changeColor(Color)
}
struct AppEnvironment {}
let appReducer = Reducer<AppState, AppAction, AppEnvironment> { (state, action, _) in
switch action {
case let .changeColor(color):
state.color = color
return .none
}
}
struct ContentView: View {
// This is what will allow an independent state for each window
@State var appState: AppState? = nil
var body: some View {
if let state = appState {
let store = Store(
initialState: state,
reducer: appReducer,
environment: AppEnvironment()
)
WithViewStore(store) { viewStore in
VStack {
Text(state.id.description).background(viewStore.color)
Button("Change Color") {
viewStore.send(.changeColor(.pink))
}
}
.padding()
}
}
else {
Spacer()
.onAppear {
appState = AppState(id: UUID())
}
}
}
}
Tried also combined with mathieutozer idea (using IdentifiedArray
and ForEachStore
) and also works with the same @State
on children technic and even if it gives you centralised control on the windows, in my experience its rather easy to enter on recursion.
Note that I've mostly focused on making this work on macOS, so I didn't tested properly yet on iOS.
The key reference for me has being this mention on the WindowGroup documentation:
Every window created from the group maintains independent state. For example, for each new window created from the group the system allocates new storage for any
State
orStateObject
variables instantiated by the scene’s view hierarchy.