Skip to content

Instantly share code, notes, and snippets.

@sidepelican
Created October 6, 2025 00:13
Show Gist options
  • Save sidepelican/ecf1dd24fa6fdb25f97d6e30febc5dad to your computer and use it in GitHub Desktop.
Save sidepelican/ecf1dd24fa6fdb25f97d6e30febc5dad to your computer and use it in GitHub Desktop.
@MainActor protocol ComposableViewModel: DynamicProperty {
associatedtype Body: View
@ViewBuilder @MainActor func build(content: NonVisibleView) -> Self.Body
}
struct NonVisibleView: View {
var body: some View {
EmptyView()
}
}
extension View {
func compose(_ viewModel: some ComposableViewModel) -> some View {
return self.background {
viewModel.build(content: NonVisibleView())
}
}
}
struct FooViewModel: DynamicProperty, ComposableViewModel {
@Environment(\.colorScheme) var colorScheme
@Observable final class UIState {
var value: Int
init(value: Int) {
self.value = value
}
}
@State var state: UIState
init(value: Int) {
self.state = State(value: value)
}
func foo() {
state.value += 1
print("colorScheme", colorScheme)
}
func build(content: NonVisibleView) -> some View {
content
.onAppear {
print("onAppear")
}
}
}
struct ContentView: View {
var viewModel: FooViewModel
init(value: Int) {
self.viewModel = FooViewModel(value: value)
}
var body: some View {
VStack {
Text("value: \(viewModel.state.value)")
Button("Tap!!") {
viewModel.foo()
}
}
.compose(viewModel)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment