From 521d98905b331edff3a83ecd15b9a3e6da4417eb Mon Sep 17 00:00:00 2001
From: Rick Clephas <rclephas@gmail.com>
Date: Fri, 26 May 2023 19:36:40 +0200
Subject: [PATCH 1/5] Fix iOS build

---
 build.gradle.kts          | 2 +-
 gradle/libs.versions.toml | 8 ++++----
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/build.gradle.kts b/build.gradle.kts
index bbafa528e..a5f0edfcf 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -177,7 +177,7 @@ subprojects {
     if (hasCompose) {
       dependencies {
         add(PLUGIN_CLASSPATH_CONFIGURATION_NAME, libs.androidx.compose.compiler)
-        add(NATIVE_COMPILER_PLUGIN_CLASSPATH_CONFIGURATION_NAME, libs.androidx.compose.compiler)
+//        add(NATIVE_COMPILER_PLUGIN_CLASSPATH_CONFIGURATION_NAME, libs.androidx.compose.compiler)
       }
     }
 
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 6e76788c2..c90fb7d3a 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -11,8 +11,8 @@ coil = "2.4.0"
 compose-animation = "1.4.3"
 # Pre-release versions for testing Kotlin previews can be found here
 # https://androidx.dev/storage/compose-compiler/repository
-compose-compiler = "1.4.7"
-composeCompilerKotlinVersion = "1.8.21"
+compose-compiler = "1.4.6"
+composeCompilerKotlinVersion = "1.8.20"
 compose-foundation = "1.4.3"
 compose-material = "1.4.3"
 compose-material3 = "1.1.0"
@@ -28,10 +28,10 @@ detekt = "1.23.0"
 dokka = "1.8.10"
 eithernet = "1.4.0"
 jdk = "19"
-kotlin = "1.8.21"
+kotlin = "1.8.20"
 kotlinpoet = "1.13.2"
 kotlinx-coroutines = "1.7.1"
-ksp = "1.8.21-1.0.11"
+ksp = "1.8.20-1.0.11"
 ktfmt = "0.44"
 leakcanary = "2.11"
 material = "1.6.1"

From b23b7ef89c5ba6f3406631eb2beb8e42abd4c157 Mon Sep 17 00:00:00 2001
From: Rick Clephas <rclephas@gmail.com>
Date: Fri, 26 May 2023 19:55:39 +0200
Subject: [PATCH 2/5] Add basic SwiftUIPresenter logic

---
 circuit-swiftui/build.gradle.kts              | 23 +++++++++++
 .../slack/circuit/swiftui/SwiftUIPresenter.kt | 26 +++++++++++++
 .../com/slack/circuit/swiftui/molecule.kt     | 34 ++++++++++++++++
 gradle.properties                             |  2 +
 gradle/libs.versions.toml                     |  2 +
 .../apps/Counter.xcodeproj/project.pbxproj    | 28 ++++++++++++-
 .../counter/apps/Counter/ContentView.swift    | 30 +++-----------
 .../counter/apps/Counter/KMMViewModel.swift   | 19 +++++++++
 samples/counter/build.gradle.kts              |  6 ++-
 .../circuit/sample/counter/SwiftSupport.kt    | 39 -------------------
 .../PresenterFactory.kt                       |  8 ++++
 settings.gradle.kts                           |  1 +
 12 files changed, 152 insertions(+), 66 deletions(-)
 create mode 100644 circuit-swiftui/build.gradle.kts
 create mode 100644 circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/SwiftUIPresenter.kt
 create mode 100644 circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/molecule.kt
 create mode 100644 samples/counter/apps/Counter/KMMViewModel.swift
 delete mode 100644 samples/counter/src/commonMain/kotlin/com/slack/circuit/sample/counter/SwiftSupport.kt
 create mode 100644 samples/counter/src/iosMain/kotlin/com.slack.circuit.sample.counter/PresenterFactory.kt

diff --git a/circuit-swiftui/build.gradle.kts b/circuit-swiftui/build.gradle.kts
new file mode 100644
index 000000000..77124d677
--- /dev/null
+++ b/circuit-swiftui/build.gradle.kts
@@ -0,0 +1,23 @@
+// Copyright (C) 2022 Slack Technologies, LLC
+// SPDX-License-Identifier: Apache-2.0
+plugins {
+  kotlin("multiplatform")
+  alias(libs.plugins.compose)
+}
+
+kotlin {
+  // region KMP Targets
+  ios()
+  iosSimulatorArm64()
+  // endregion
+
+  sourceSets {
+    commonMain {
+      dependencies {
+        api(projects.circuitRuntimePresenter)
+        api(libs.kmmviewmodel.core)
+        implementation(libs.molecule.runtime)
+      }
+    }
+  }
+}
diff --git a/circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/SwiftUIPresenter.kt b/circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/SwiftUIPresenter.kt
new file mode 100644
index 000000000..a18b73445
--- /dev/null
+++ b/circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/SwiftUIPresenter.kt
@@ -0,0 +1,26 @@
+package com.slack.circuit.swiftui
+
+import androidx.compose.runtime.Composable
+import app.cash.molecule.RecompositionClock
+import com.rickclephas.kmm.viewmodel.KMMViewModel
+import com.slack.circuit.runtime.CircuitUiState
+import com.slack.circuit.runtime.Navigator
+import com.slack.circuit.runtime.presenter.Presenter
+import com.slack.circuit.runtime.presenter.presenterOf
+
+public class SwiftUIPresenter<UiState: CircuitUiState> internal constructor(
+    private val presenter: Presenter<UiState>
+): KMMViewModel() {
+
+    private val stateFlow = viewModelScope.launchMolecule(RecompositionClock.Immediate) {
+        presenter.present()
+    }
+
+    public val state: UiState get() = stateFlow.value
+}
+
+public fun <UiState : CircuitUiState> swiftUIPresenterOf(
+    body: @Composable (Navigator) -> UiState
+): SwiftUIPresenter<UiState> {
+    return SwiftUIPresenter(presenterOf { body(Navigator.NoOp) })
+}
diff --git a/circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/molecule.kt b/circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/molecule.kt
new file mode 100644
index 000000000..8119108f7
--- /dev/null
+++ b/circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/molecule.kt
@@ -0,0 +1,34 @@
+package com.slack.circuit.swiftui
+
+import androidx.compose.runtime.Composable
+import app.cash.molecule.RecompositionClock
+import app.cash.molecule.launchMolecule
+import com.rickclephas.kmm.viewmodel.MutableStateFlow
+import com.rickclephas.kmm.viewmodel.ViewModelScope
+import com.rickclephas.kmm.viewmodel.coroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+/**
+ * Identical to the molecule implementation, but with a KMM-ViewModel [MutableStateFlow].
+ * https://github.com/cashapp/molecule/blob/c902f7f60022911bf0cc6940cf86f3ff07c76591/molecule-runtime/src/commonMain/kotlin/app/cash/molecule/molecule.kt#L102
+ */
+internal fun <T> ViewModelScope.launchMolecule(
+    clock: RecompositionClock,
+    body: @Composable () -> T,
+): StateFlow<T> {
+    var flow: MutableStateFlow<T>? = null
+    coroutineScope.launchMolecule(
+        clock = clock,
+        emitter = { value ->
+            val outputFlow = flow
+            if (outputFlow != null) {
+                outputFlow.value = value
+            } else {
+                flow = MutableStateFlow(this, value)
+            }
+        },
+        body = body,
+    )
+    return flow!!
+}
diff --git a/gradle.properties b/gradle.properties
index a690f7470..49fd5e1ed 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -42,6 +42,8 @@ kotlin.mpp.stability.nowarn=true
 kotlin.mpp.androidSourceSetLayoutVersion=2
 kotlin.mpp.androidGradlePluginCompatibility.nowarn=true
 
+kotlin.mpp.enableCInteropCommonization=true
+
 # Enable Gradle configuration caching
 # TODO disabled because of compose/kotlin multiplatform
 org.gradle.configuration-cache=false
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index c90fb7d3a..4f2e56e04 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -28,6 +28,7 @@ detekt = "1.23.0"
 dokka = "1.8.10"
 eithernet = "1.4.0"
 jdk = "19"
+kmmviewmodel = "1.0.0-ALPHA-9"
 kotlin = "1.8.20"
 kotlinpoet = "1.13.2"
 kotlinx-coroutines = "1.7.1"
@@ -172,6 +173,7 @@ eithernet = { module = "com.slack.eithernet:eithernet", version.ref = "eithernet
 jline = "org.jline:jline:3.23.0"
 jsoup = "org.jsoup:jsoup:1.16.1"
 junit = "junit:junit:4.13.2"
+kmmviewmodel-core = { module = "com.rickclephas.kmm:kmm-viewmodel-core", version.ref = "kmmviewmodel"}
 kotlinx-immutable = "org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.5"
 kotlinpoet = { module = "com.squareup:kotlinpoet", version.ref = "kotlinpoet"}
 kotlinpoet-ksp = { module = "com.squareup:kotlinpoet-ksp", version.ref = "kotlinpoet"}
diff --git a/samples/counter/apps/Counter.xcodeproj/project.pbxproj b/samples/counter/apps/Counter.xcodeproj/project.pbxproj
index 8d95f2292..74e002759 100644
--- a/samples/counter/apps/Counter.xcodeproj/project.pbxproj
+++ b/samples/counter/apps/Counter.xcodeproj/project.pbxproj
@@ -3,12 +3,14 @@
 	archiveVersion = 1;
 	classes = {
 	};
-	objectVersion = 50;
+	objectVersion = 52;
 	objects = {
 
 /* Begin PBXBuildFile section */
 		058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557BA273AAA24004C7B11 /* Assets.xcassets */; };
 		058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */; };
+		1DE43CE32A210A1400EB0E36 /* KMMViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE43CE22A210A1400EB0E36 /* KMMViewModel.swift */; };
+		1DE43CE52A21281B00EB0E36 /* KMMViewModelState in Frameworks */ = {isa = PBXBuildFile; productRef = 1DE43CE42A21281B00EB0E36 /* KMMViewModelState */; };
 		2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2152FB032600AC8F00CF470E /* iOSApp.swift */; };
 		602DC854301CF43C8E7B0F6D /* Pods_Counter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27D2DFDA1A8E1896075A1701 /* Pods_Counter.framework */; };
 		7555FF83242A565900829871 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* ContentView.swift */; };
@@ -30,6 +32,8 @@
 /* Begin PBXFileReference section */
 		058557BA273AAA24004C7B11 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
 		058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
+		1DE43CE12A2109E500EB0E36 /* KMM-ViewModel */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "KMM-ViewModel"; path = "../../../../../Rick Clephas/KMM-ViewModel"; sourceTree = "<group>"; };
+		1DE43CE22A210A1400EB0E36 /* KMMViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KMMViewModel.swift; sourceTree = "<group>"; };
 		2152FB032600AC8F00CF470E /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = "<group>"; };
 		27D2DFDA1A8E1896075A1701 /* Pods_Counter.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Counter.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		35C96B8C8A190485ECDD3046 /* Pods-Counter.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Counter.debug.xcconfig"; path = "Target Support Files/Pods-Counter/Pods-Counter.debug.xcconfig"; sourceTree = "<group>"; };
@@ -45,6 +49,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				602DC854301CF43C8E7B0F6D /* Pods_Counter.framework in Frameworks */,
+				1DE43CE52A21281B00EB0E36 /* KMMViewModelState in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -68,9 +73,18 @@
 			path = "Preview Content";
 			sourceTree = "<group>";
 		};
+		1DE43CE02A2109E500EB0E36 /* Packages */ = {
+			isa = PBXGroup;
+			children = (
+				1DE43CE12A2109E500EB0E36 /* KMM-ViewModel */,
+			);
+			name = Packages;
+			sourceTree = "<group>";
+		};
 		7555FF72242A565900829871 = {
 			isa = PBXGroup;
 			children = (
+				1DE43CE02A2109E500EB0E36 /* Packages */,
 				7555FF7D242A565900829871 /* Counter */,
 				7555FF7C242A565900829871 /* Products */,
 				7555FFB0242A642200829871 /* Frameworks */,
@@ -94,6 +108,7 @@
 				7555FF8C242A565B00829871 /* Info.plist */,
 				2152FB032600AC8F00CF470E /* iOSApp.swift */,
 				058557D7273AAEEB004C7B11 /* Preview Content */,
+				1DE43CE22A210A1400EB0E36 /* KMMViewModel.swift */,
 			);
 			path = Counter;
 			sourceTree = "<group>";
@@ -125,6 +140,9 @@
 			dependencies = (
 			);
 			name = Counter;
+			packageProductDependencies = (
+				1DE43CE42A21281B00EB0E36 /* KMMViewModelState */,
+			);
 			productName = Counter;
 			productReference = 7555FF7B242A565900829871 /* Counter.app */;
 			productType = "com.apple.product-type.application";
@@ -221,6 +239,7 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				1DE43CE32A210A1400EB0E36 /* KMMViewModel.swift in Sources */,
 				2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */,
 				7555FF83242A565900829871 /* ContentView.swift in Sources */,
 			);
@@ -425,6 +444,13 @@
 			defaultConfigurationName = Release;
 		};
 /* End XCConfigurationList section */
+
+/* Begin XCSwiftPackageProductDependency section */
+		1DE43CE42A21281B00EB0E36 /* KMMViewModelState */ = {
+			isa = XCSwiftPackageProductDependency;
+			productName = KMMViewModelState;
+		};
+/* End XCSwiftPackageProductDependency section */
 	};
 	rootObject = 7555FF73242A565900829871 /* Project object */;
 }
diff --git a/samples/counter/apps/Counter/ContentView.swift b/samples/counter/apps/Counter/ContentView.swift
index cbb90840c..ef263e50c 100644
--- a/samples/counter/apps/Counter/ContentView.swift
+++ b/samples/counter/apps/Counter/ContentView.swift
@@ -1,17 +1,18 @@
 import SwiftUI
 import counter
+import KMMViewModelState
 
 struct ContentView: View {
-  @ObservedObject var presenter = SwiftCounterPresenter()
+  @ObservedViewModelState var state = PresenterFactory.shared.counterPresenter()
 
   var body: some View {
     NavigationView {
       VStack(alignment: .center) {
-        Text("Count \(presenter.state?.count ?? 0)")
+        Text("Count \(state.count)")
           .font(.system(size: 36))
         HStack(spacing: 10) {
           Button(action: {
-            presenter.state?.eventSink(CounterScreenEventDecrement.shared)
+            state.eventSink(CounterScreenEventDecrement.shared)
           }) {
             Text("-")
               .font(.system(size: 36, weight: .black, design: .monospaced))
@@ -20,7 +21,7 @@ struct ContentView: View {
           .foregroundColor(.white)
           .background(Color.blue)
           Button(action: {
-            presenter.state?.eventSink(CounterScreenEventIncrement.shared)
+            state.eventSink(CounterScreenEventIncrement.shared)
           }) {
             Text("+")
               .font(.system(size: 36, weight: .black, design: .monospaced))
@@ -35,27 +36,6 @@ struct ContentView: View {
   }
 }
 
-// TODO we hide all this behind the Circuit UI interface somehow? Then we can pass it state only
-@MainActor
-class SwiftCounterPresenter: BasePresenter<CounterScreenState> {
-  init() {
-    // TODO why can't swift infer these generics?
-    super.init(
-      delegate: SwiftSupportKt.asSwiftPresenter(SwiftSupportKt.doNewCounterPresenter())
-        as! SwiftPresenter<CounterScreenState>)
-  }
-}
-
-class BasePresenter<T: AnyObject>: ObservableObject {
-  @Published var state: T? = nil
-
-  init(delegate: SwiftPresenter<T>) {
-    delegate.subscribe { state in
-      self.state = state
-    }
-  }
-}
-
 struct ContentView_Previews: PreviewProvider {
   static var previews: some View {
     ContentView()
diff --git a/samples/counter/apps/Counter/KMMViewModel.swift b/samples/counter/apps/Counter/KMMViewModel.swift
new file mode 100644
index 000000000..e088156ad
--- /dev/null
+++ b/samples/counter/apps/Counter/KMMViewModel.swift
@@ -0,0 +1,19 @@
+//
+//  KMMViewModel.swift
+//  Counter
+//
+//  Created by Rick Clephas on 26/05/2023.
+//  Copyright © 2023 orgName. All rights reserved.
+//
+
+import KMMViewModelCore
+import KMMViewModelState
+import counter
+
+extension Kmm_viewmodel_coreKMMViewModel: KMMViewModel { }
+
+extension ObservedViewModelState {
+    init(wrappedValue: ViewModel) where ViewModel: Circuit_swiftuiSwiftUIPresenter<State> {
+        self.init(wrappedValue: wrappedValue, \.state)
+    }
+}
diff --git a/samples/counter/build.gradle.kts b/samples/counter/build.gradle.kts
index 356ea5217..2ec765643 100644
--- a/samples/counter/build.gradle.kts
+++ b/samples/counter/build.gradle.kts
@@ -45,7 +45,11 @@ kotlin {
       }
     }
     maybeCreate("commonTest").apply { dependencies { implementation(libs.kotlin.test) } }
-    val iosMain by sourceSets.getting
+    val iosMain by sourceSets.getting {
+      dependencies {
+        implementation(projects.circuitSwiftui)
+      }
+    }
     val iosSimulatorArm64Main by sourceSets.getting
     // Set up dependencies between the source sets
     iosSimulatorArm64Main.dependsOn(iosMain)
diff --git a/samples/counter/src/commonMain/kotlin/com/slack/circuit/sample/counter/SwiftSupport.kt b/samples/counter/src/commonMain/kotlin/com/slack/circuit/sample/counter/SwiftSupport.kt
deleted file mode 100644
index b3103b39e..000000000
--- a/samples/counter/src/commonMain/kotlin/com/slack/circuit/sample/counter/SwiftSupport.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright (C) 2023 Slack Technologies, LLC
-// SPDX-License-Identifier: Apache-2.0
-package com.slack.circuit.sample.counter
-
-import app.cash.molecule.RecompositionClock
-import app.cash.molecule.launchMolecule
-import com.slack.circuit.runtime.CircuitUiState
-import com.slack.circuit.runtime.Navigator
-import com.slack.circuit.runtime.presenter.Presenter
-import com.slack.circuit.runtime.presenter.presenterOf
-import kotlinx.coroutines.DelicateCoroutinesApi
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.IO
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.launch
-
-fun newCounterPresenter() = presenterOf { CounterPresenter(Navigator.NoOp) }
-
-fun <UiState : CircuitUiState> Presenter<UiState>.asSwiftPresenter(): SwiftPresenter<UiState> {
-  return SwiftPresenter(this)
-}
-
-// Adapted from the KotlinConf app
-// https://github.com/JetBrains/kotlinconf-app/blob/642404f3454d384be966c34d6b254b195e8d2892/shared/src/commonMain/kotlin/org/jetbrains/kotlinconf/utils/Coroutines.kt#L6
-// No interface because interfaces don't support generics in Kotlin/Native.
-// TODO let's try to generify this pattern somehow.
-class SwiftPresenter<UiState : CircuitUiState>
-internal constructor(
-  private val delegate: Presenter<UiState>,
-) {
-  // TODO ew globalscope
-  @OptIn(DelicateCoroutinesApi::class)
-  fun subscribe(block: (UiState) -> Unit): Job {
-    return GlobalScope.launch(Dispatchers.IO) {
-      launchMolecule(RecompositionClock.Immediate) { delegate.present() }.collect { block(it) }
-    }
-  }
-}
diff --git a/samples/counter/src/iosMain/kotlin/com.slack.circuit.sample.counter/PresenterFactory.kt b/samples/counter/src/iosMain/kotlin/com.slack.circuit.sample.counter/PresenterFactory.kt
new file mode 100644
index 000000000..c491f5c86
--- /dev/null
+++ b/samples/counter/src/iosMain/kotlin/com.slack.circuit.sample.counter/PresenterFactory.kt
@@ -0,0 +1,8 @@
+package com.slack.circuit.sample.counter
+
+import com.slack.circuit.swiftui.swiftUIPresenterOf
+
+object PresenterFactory {
+    fun counterPresenter() = swiftUIPresenterOf { CounterPresenter(it) }
+    fun primePresenter(number: Int) = swiftUIPresenterOf { PrimePresenter(it, number) }
+}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 7ba18abdb..df25d70f1 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -199,6 +199,7 @@ include(
   ":circuit-runtime",
   ":circuit-runtime-presenter",
   ":circuit-runtime-ui",
+  ":circuit-swiftui",
   ":circuit-test",
   ":samples:counter",
   ":samples:counter:apps",

From fe4228ce95f761094766e4cd649d5101b13b0bb7 Mon Sep 17 00:00:00 2001
From: Rick Clephas <rclephas@gmail.com>
Date: Sat, 27 May 2023 19:57:25 +0200
Subject: [PATCH 3/5] Add SwiftUI navigation support

---
 .gitignore                                    |   4 +-
 Circuit.xcodeproj/project.pbxproj             | 845 ++++++++++++++++++
 CircuitRuntime/CircuitNavigator.swift         |  37 +
 CircuitRuntime/CircuitPresenter.swift         |  13 +
 CircuitRuntimeObjC/CircuitRuntimeObjC.h       |   8 +
 CircuitRuntimeObjC/CircuitRuntimeObjC.m       |   1 +
 CircuitRuntimeObjC/CircuitSwiftUINavigator.h  |  19 +
 CircuitSwiftUI/CircuitNavigationStack.swift   |  32 +
 CircuitSwiftUI/CircuitView.swift              |  33 +
 Package.swift                                 |  41 +
 circuit-swiftui/build.gradle.kts              |  15 +-
 .../slack/circuit/swiftui/SwiftUINavigator.kt |  21 +
 .../slack/circuit/swiftui/SwiftUIPresenter.kt |  12 +-
 .../cinterop/CircuitRuntimeObjC.def           |   3 +
 .../apps/Counter.xcodeproj/project.pbxproj    |  36 +-
 samples/counter/apps/Counter/Circuit.swift    |  22 +
 .../{ContentView.swift => CounterView.swift}  |  15 +-
 .../counter/apps/Counter/KMMViewModel.swift   |  19 -
 samples/counter/apps/Counter/PrimeView.swift  |  35 +
 samples/counter/apps/Counter/iOSApp.swift     |  21 +-
 .../PresenterFactory.kt                       |   8 -
 .../Screens.kt                                |  14 +
 22 files changed, 1198 insertions(+), 56 deletions(-)
 create mode 100644 Circuit.xcodeproj/project.pbxproj
 create mode 100644 CircuitRuntime/CircuitNavigator.swift
 create mode 100644 CircuitRuntime/CircuitPresenter.swift
 create mode 100644 CircuitRuntimeObjC/CircuitRuntimeObjC.h
 create mode 100644 CircuitRuntimeObjC/CircuitRuntimeObjC.m
 create mode 100644 CircuitRuntimeObjC/CircuitSwiftUINavigator.h
 create mode 100644 CircuitSwiftUI/CircuitNavigationStack.swift
 create mode 100644 CircuitSwiftUI/CircuitView.swift
 create mode 100644 Package.swift
 create mode 100644 circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/SwiftUINavigator.kt
 create mode 100644 circuit-swiftui/src/nativeInterop/cinterop/CircuitRuntimeObjC.def
 create mode 100644 samples/counter/apps/Counter/Circuit.swift
 rename samples/counter/apps/Counter/{ContentView.swift => CounterView.swift} (73%)
 delete mode 100644 samples/counter/apps/Counter/KMMViewModel.swift
 create mode 100644 samples/counter/apps/Counter/PrimeView.swift
 delete mode 100644 samples/counter/src/iosMain/kotlin/com.slack.circuit.sample.counter/PresenterFactory.kt
 create mode 100644 samples/counter/src/iosMain/kotlin/com.slack.circuit.sample.counter/Screens.kt

diff --git a/.gitignore b/.gitignore
index 369a65ca5..05aea1981 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,5 +33,7 @@ Podfile.lock
 **/Pods/*
 **/*.xcworkspace/*
 **/*.xcodeproj/*
+xcuserdata/
 !samples/counter/apps/Counter.xcodeproj/project.pbxproj
-*.podspec
\ No newline at end of file
+!Circuit.xcodeproj/project.pbxproj
+*.podspec
diff --git a/Circuit.xcodeproj/project.pbxproj b/Circuit.xcodeproj/project.pbxproj
new file mode 100644
index 000000000..30cf51d21
--- /dev/null
+++ b/Circuit.xcodeproj/project.pbxproj
@@ -0,0 +1,845 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 56;
+	objects = {
+
+/* Begin PBXBuildFile section */
+		1DE43D022A2265EE00EB0E36 /* CircuitRuntimeObjC.h in Headers */ = {isa = PBXBuildFile; fileRef = 1DE43D012A2265EE00EB0E36 /* CircuitRuntimeObjC.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		1DE43D132A22660400EB0E36 /* CircuitRuntime.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DE43CF22A2265C600EB0E36 /* CircuitRuntime.framework */; };
+		1DE43D182A22660B00EB0E36 /* CircuitRuntimeObjC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DE43CFF2A2265EE00EB0E36 /* CircuitRuntimeObjC.framework */; };
+		1DE43D1E2A22669D00EB0E36 /* CircuitRuntimeObjC.m in Sources */ = {isa = PBXBuildFile; fileRef = 1DE43D1D2A22669D00EB0E36 /* CircuitRuntimeObjC.m */; };
+		1DE43D212A22670F00EB0E36 /* CircuitSwiftUINavigator.h in Headers */ = {isa = PBXBuildFile; fileRef = 1DE43D1F2A2266AF00EB0E36 /* CircuitSwiftUINavigator.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		1DE43D232A2267EB00EB0E36 /* CircuitPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE43D222A2267EB00EB0E36 /* CircuitPresenter.swift */; };
+		1DE43D262A22682200EB0E36 /* KMMViewModelCore in Frameworks */ = {isa = PBXBuildFile; productRef = 1DE43D252A22682200EB0E36 /* KMMViewModelCore */; };
+		1DE43D282A226BF200EB0E36 /* CircuitNavigationStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE43D272A226BF200EB0E36 /* CircuitNavigationStack.swift */; };
+		1DE43D2A2A226C3A00EB0E36 /* CircuitNavigator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE43D292A226C3A00EB0E36 /* CircuitNavigator.swift */; };
+		1DE43D2C2A226E2E00EB0E36 /* CircuitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE43D2B2A226E2E00EB0E36 /* CircuitView.swift */; };
+		1DE43D2E2A226E5100EB0E36 /* KMMViewModelSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 1DE43D2D2A226E5100EB0E36 /* KMMViewModelSwiftUI */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+		1DE43D152A22660400EB0E36 /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = 1DE43CE72A22655F00EB0E36 /* Project object */;
+			proxyType = 1;
+			remoteGlobalIDString = 1DE43CF12A2265C600EB0E36;
+			remoteInfo = CircuitRuntime;
+		};
+		1DE43D1A2A22660B00EB0E36 /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = 1DE43CE72A22655F00EB0E36 /* Project object */;
+			proxyType = 1;
+			remoteGlobalIDString = 1DE43CFE2A2265EE00EB0E36;
+			remoteInfo = CircuitRuntimeObjC;
+		};
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXFileReference section */
+		1DE43CF22A2265C600EB0E36 /* CircuitRuntime.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CircuitRuntime.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+		1DE43CFF2A2265EE00EB0E36 /* CircuitRuntimeObjC.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CircuitRuntimeObjC.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+		1DE43D012A2265EE00EB0E36 /* CircuitRuntimeObjC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CircuitRuntimeObjC.h; sourceTree = "<group>"; };
+		1DE43D0B2A2265FD00EB0E36 /* CircuitSwiftUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CircuitSwiftUI.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+		1DE43D1D2A22669D00EB0E36 /* CircuitRuntimeObjC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CircuitRuntimeObjC.m; sourceTree = "<group>"; };
+		1DE43D1F2A2266AF00EB0E36 /* CircuitSwiftUINavigator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CircuitSwiftUINavigator.h; sourceTree = "<group>"; };
+		1DE43D222A2267EB00EB0E36 /* CircuitPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircuitPresenter.swift; sourceTree = "<group>"; };
+		1DE43D272A226BF200EB0E36 /* CircuitNavigationStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircuitNavigationStack.swift; sourceTree = "<group>"; };
+		1DE43D292A226C3A00EB0E36 /* CircuitNavigator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircuitNavigator.swift; sourceTree = "<group>"; };
+		1DE43D2B2A226E2E00EB0E36 /* CircuitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircuitView.swift; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+		1DE43CEF2A2265C600EB0E36 /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				1DE43D262A22682200EB0E36 /* KMMViewModelCore in Frameworks */,
+				1DE43D182A22660B00EB0E36 /* CircuitRuntimeObjC.framework in Frameworks */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		1DE43CFC2A2265EE00EB0E36 /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		1DE43D082A2265FD00EB0E36 /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				1DE43D132A22660400EB0E36 /* CircuitRuntime.framework in Frameworks */,
+				1DE43D2E2A226E5100EB0E36 /* KMMViewModelSwiftUI in Frameworks */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+		1DE43CE62A22655F00EB0E36 = {
+			isa = PBXGroup;
+			children = (
+				1DE43CF42A2265C600EB0E36 /* CircuitRuntime */,
+				1DE43D002A2265EE00EB0E36 /* CircuitRuntimeObjC */,
+				1DE43D0C2A2265FD00EB0E36 /* CircuitSwiftUI */,
+				1DE43CF32A2265C600EB0E36 /* Products */,
+				1DE43D122A22660400EB0E36 /* Frameworks */,
+			);
+			sourceTree = "<group>";
+		};
+		1DE43CF32A2265C600EB0E36 /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				1DE43CF22A2265C600EB0E36 /* CircuitRuntime.framework */,
+				1DE43CFF2A2265EE00EB0E36 /* CircuitRuntimeObjC.framework */,
+				1DE43D0B2A2265FD00EB0E36 /* CircuitSwiftUI.framework */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+		1DE43CF42A2265C600EB0E36 /* CircuitRuntime */ = {
+			isa = PBXGroup;
+			children = (
+				1DE43D222A2267EB00EB0E36 /* CircuitPresenter.swift */,
+				1DE43D292A226C3A00EB0E36 /* CircuitNavigator.swift */,
+			);
+			path = CircuitRuntime;
+			sourceTree = "<group>";
+		};
+		1DE43D002A2265EE00EB0E36 /* CircuitRuntimeObjC */ = {
+			isa = PBXGroup;
+			children = (
+				1DE43D012A2265EE00EB0E36 /* CircuitRuntimeObjC.h */,
+				1DE43D1D2A22669D00EB0E36 /* CircuitRuntimeObjC.m */,
+				1DE43D1F2A2266AF00EB0E36 /* CircuitSwiftUINavigator.h */,
+			);
+			path = CircuitRuntimeObjC;
+			sourceTree = "<group>";
+		};
+		1DE43D0C2A2265FD00EB0E36 /* CircuitSwiftUI */ = {
+			isa = PBXGroup;
+			children = (
+				1DE43D272A226BF200EB0E36 /* CircuitNavigationStack.swift */,
+				1DE43D2B2A226E2E00EB0E36 /* CircuitView.swift */,
+			);
+			path = CircuitSwiftUI;
+			sourceTree = "<group>";
+		};
+		1DE43D122A22660400EB0E36 /* Frameworks */ = {
+			isa = PBXGroup;
+			children = (
+			);
+			name = Frameworks;
+			sourceTree = "<group>";
+		};
+/* End PBXGroup section */
+
+/* Begin PBXHeadersBuildPhase section */
+		1DE43CED2A2265C600EB0E36 /* Headers */ = {
+			isa = PBXHeadersBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		1DE43CFA2A2265EE00EB0E36 /* Headers */ = {
+			isa = PBXHeadersBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				1DE43D212A22670F00EB0E36 /* CircuitSwiftUINavigator.h in Headers */,
+				1DE43D022A2265EE00EB0E36 /* CircuitRuntimeObjC.h in Headers */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		1DE43D062A2265FD00EB0E36 /* Headers */ = {
+			isa = PBXHeadersBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXHeadersBuildPhase section */
+
+/* Begin PBXNativeTarget section */
+		1DE43CF12A2265C600EB0E36 /* CircuitRuntime */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 1DE43CF72A2265C600EB0E36 /* Build configuration list for PBXNativeTarget "CircuitRuntime" */;
+			buildPhases = (
+				1DE43CED2A2265C600EB0E36 /* Headers */,
+				1DE43CEE2A2265C600EB0E36 /* Sources */,
+				1DE43CEF2A2265C600EB0E36 /* Frameworks */,
+				1DE43CF02A2265C600EB0E36 /* Resources */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+				1DE43D1B2A22660B00EB0E36 /* PBXTargetDependency */,
+			);
+			name = CircuitRuntime;
+			packageProductDependencies = (
+				1DE43D252A22682200EB0E36 /* KMMViewModelCore */,
+			);
+			productName = CircuitRuntime;
+			productReference = 1DE43CF22A2265C600EB0E36 /* CircuitRuntime.framework */;
+			productType = "com.apple.product-type.framework";
+		};
+		1DE43CFE2A2265EE00EB0E36 /* CircuitRuntimeObjC */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 1DE43D032A2265EE00EB0E36 /* Build configuration list for PBXNativeTarget "CircuitRuntimeObjC" */;
+			buildPhases = (
+				1DE43CFA2A2265EE00EB0E36 /* Headers */,
+				1DE43CFB2A2265EE00EB0E36 /* Sources */,
+				1DE43CFC2A2265EE00EB0E36 /* Frameworks */,
+				1DE43CFD2A2265EE00EB0E36 /* Resources */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			name = CircuitRuntimeObjC;
+			productName = CircuitRuntimeObjC;
+			productReference = 1DE43CFF2A2265EE00EB0E36 /* CircuitRuntimeObjC.framework */;
+			productType = "com.apple.product-type.framework";
+		};
+		1DE43D0A2A2265FD00EB0E36 /* CircuitSwiftUI */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 1DE43D0F2A2265FD00EB0E36 /* Build configuration list for PBXNativeTarget "CircuitSwiftUI" */;
+			buildPhases = (
+				1DE43D062A2265FD00EB0E36 /* Headers */,
+				1DE43D072A2265FD00EB0E36 /* Sources */,
+				1DE43D082A2265FD00EB0E36 /* Frameworks */,
+				1DE43D092A2265FD00EB0E36 /* Resources */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+				1DE43D162A22660400EB0E36 /* PBXTargetDependency */,
+			);
+			name = CircuitSwiftUI;
+			packageProductDependencies = (
+				1DE43D2D2A226E5100EB0E36 /* KMMViewModelSwiftUI */,
+			);
+			productName = CircuitSwiftUI;
+			productReference = 1DE43D0B2A2265FD00EB0E36 /* CircuitSwiftUI.framework */;
+			productType = "com.apple.product-type.framework";
+		};
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+		1DE43CE72A22655F00EB0E36 /* Project object */ = {
+			isa = PBXProject;
+			attributes = {
+				BuildIndependentTargetsInParallel = 1;
+				LastUpgradeCheck = 1430;
+				TargetAttributes = {
+					1DE43CF12A2265C600EB0E36 = {
+						CreatedOnToolsVersion = 14.3;
+						LastSwiftMigration = 1430;
+					};
+					1DE43CFE2A2265EE00EB0E36 = {
+						CreatedOnToolsVersion = 14.3;
+					};
+					1DE43D0A2A2265FD00EB0E36 = {
+						CreatedOnToolsVersion = 14.3;
+						LastSwiftMigration = 1430;
+					};
+				};
+			};
+			buildConfigurationList = 1DE43CEA2A22655F00EB0E36 /* Build configuration list for PBXProject "Circuit" */;
+			compatibilityVersion = "Xcode 14.0";
+			developmentRegion = en;
+			hasScannedForEncodings = 0;
+			knownRegions = (
+				en,
+				Base,
+			);
+			mainGroup = 1DE43CE62A22655F00EB0E36;
+			packageReferences = (
+				1DE43D242A22682200EB0E36 /* XCRemoteSwiftPackageReference "KMM-ViewModel" */,
+			);
+			productRefGroup = 1DE43CF32A2265C600EB0E36 /* Products */;
+			projectDirPath = "";
+			projectRoot = "";
+			targets = (
+				1DE43CF12A2265C600EB0E36 /* CircuitRuntime */,
+				1DE43CFE2A2265EE00EB0E36 /* CircuitRuntimeObjC */,
+				1DE43D0A2A2265FD00EB0E36 /* CircuitSwiftUI */,
+			);
+		};
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+		1DE43CF02A2265C600EB0E36 /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		1DE43CFD2A2265EE00EB0E36 /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		1DE43D092A2265FD00EB0E36 /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+		1DE43CEE2A2265C600EB0E36 /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				1DE43D232A2267EB00EB0E36 /* CircuitPresenter.swift in Sources */,
+				1DE43D2A2A226C3A00EB0E36 /* CircuitNavigator.swift in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		1DE43CFB2A2265EE00EB0E36 /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				1DE43D1E2A22669D00EB0E36 /* CircuitRuntimeObjC.m in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		1DE43D072A2265FD00EB0E36 /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				1DE43D2C2A226E2E00EB0E36 /* CircuitView.swift in Sources */,
+				1DE43D282A226BF200EB0E36 /* CircuitNavigationStack.swift in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+		1DE43D162A22660400EB0E36 /* PBXTargetDependency */ = {
+			isa = PBXTargetDependency;
+			target = 1DE43CF12A2265C600EB0E36 /* CircuitRuntime */;
+			targetProxy = 1DE43D152A22660400EB0E36 /* PBXContainerItemProxy */;
+		};
+		1DE43D1B2A22660B00EB0E36 /* PBXTargetDependency */ = {
+			isa = PBXTargetDependency;
+			target = 1DE43CFE2A2265EE00EB0E36 /* CircuitRuntimeObjC */;
+			targetProxy = 1DE43D1A2A22660B00EB0E36 /* PBXContainerItemProxy */;
+		};
+/* End PBXTargetDependency section */
+
+/* Begin XCBuildConfiguration section */
+		1DE43CEB2A22655F00EB0E36 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+			};
+			name = Debug;
+		};
+		1DE43CEC2A22655F00EB0E36 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+			};
+			name = Release;
+		};
+		1DE43CF82A2265C600EB0E36 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_ENABLE_OBJC_WEAK = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				CODE_SIGN_STYLE = Automatic;
+				COPY_PHASE_STRIP = NO;
+				CURRENT_PROJECT_VERSION = 1;
+				DEBUG_INFORMATION_FORMAT = dwarf;
+				DEFINES_MODULE = YES;
+				DYLIB_COMPATIBILITY_VERSION = 1;
+				DYLIB_CURRENT_VERSION = 1;
+				DYLIB_INSTALL_NAME_BASE = "@rpath";
+				ENABLE_MODULE_VERIFIER = YES;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				ENABLE_TESTABILITY = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu11;
+				GCC_DYNAMIC_NO_PIC = NO;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_OPTIMIZATION_LEVEL = 0;
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"DEBUG=1",
+					"$(inherited)",
+				);
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				GENERATE_INFOPLIST_FILE = YES;
+				INFOPLIST_KEY_NSHumanReadableCopyright = "";
+				INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+				IPHONEOS_DEPLOYMENT_TARGET = 16.0;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"@executable_path/Frameworks",
+					"@loader_path/Frameworks",
+				);
+				"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = (
+					"@executable_path/../Frameworks",
+					"@loader_path/Frameworks",
+				);
+				MACOSX_DEPLOYMENT_TARGET = 13.1;
+				MARKETING_VERSION = 1.0;
+				MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
+				MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++20";
+				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+				MTL_FAST_MATH = YES;
+				ONLY_ACTIVE_ARCH = YES;
+				PRODUCT_BUNDLE_IDENTIFIER = com.slack.circuit.CircuitRuntime;
+				PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+				SDKROOT = auto;
+				SKIP_INSTALL = YES;
+				SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
+				SUPPORTS_MACCATALYST = NO;
+				SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
+				SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+				SWIFT_EMIT_LOC_STRINGS = YES;
+				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+				SWIFT_VERSION = 5.0;
+				TARGETED_DEVICE_FAMILY = "1,2";
+				VERSIONING_SYSTEM = "apple-generic";
+				VERSION_INFO_PREFIX = "";
+			};
+			name = Debug;
+		};
+		1DE43CF92A2265C600EB0E36 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_ENABLE_OBJC_WEAK = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				CODE_SIGN_STYLE = Automatic;
+				COPY_PHASE_STRIP = NO;
+				CURRENT_PROJECT_VERSION = 1;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				DEFINES_MODULE = YES;
+				DYLIB_COMPATIBILITY_VERSION = 1;
+				DYLIB_CURRENT_VERSION = 1;
+				DYLIB_INSTALL_NAME_BASE = "@rpath";
+				ENABLE_MODULE_VERIFIER = YES;
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu11;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				GENERATE_INFOPLIST_FILE = YES;
+				INFOPLIST_KEY_NSHumanReadableCopyright = "";
+				INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+				IPHONEOS_DEPLOYMENT_TARGET = 16.0;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"@executable_path/Frameworks",
+					"@loader_path/Frameworks",
+				);
+				"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = (
+					"@executable_path/../Frameworks",
+					"@loader_path/Frameworks",
+				);
+				MACOSX_DEPLOYMENT_TARGET = 13.1;
+				MARKETING_VERSION = 1.0;
+				MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
+				MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++20";
+				MTL_ENABLE_DEBUG_INFO = NO;
+				MTL_FAST_MATH = YES;
+				PRODUCT_BUNDLE_IDENTIFIER = com.slack.circuit.CircuitRuntime;
+				PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+				SDKROOT = auto;
+				SKIP_INSTALL = YES;
+				SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
+				SUPPORTS_MACCATALYST = NO;
+				SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
+				SWIFT_COMPILATION_MODE = wholemodule;
+				SWIFT_EMIT_LOC_STRINGS = YES;
+				SWIFT_OPTIMIZATION_LEVEL = "-O";
+				SWIFT_VERSION = 5.0;
+				TARGETED_DEVICE_FAMILY = "1,2";
+				VERSIONING_SYSTEM = "apple-generic";
+				VERSION_INFO_PREFIX = "";
+			};
+			name = Release;
+		};
+		1DE43D042A2265EE00EB0E36 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CODE_SIGN_STYLE = Automatic;
+				CURRENT_PROJECT_VERSION = 1;
+				DEFINES_MODULE = YES;
+				DYLIB_COMPATIBILITY_VERSION = 1;
+				DYLIB_CURRENT_VERSION = 1;
+				DYLIB_INSTALL_NAME_BASE = "@rpath";
+				GENERATE_INFOPLIST_FILE = YES;
+				INFOPLIST_KEY_NSHumanReadableCopyright = "";
+				INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+				IPHONEOS_DEPLOYMENT_TARGET = 16.0;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"@executable_path/Frameworks",
+					"@loader_path/Frameworks",
+				);
+				"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = (
+					"@executable_path/../Frameworks",
+					"@loader_path/Frameworks",
+				);
+				MARKETING_VERSION = 1.0;
+				PRODUCT_BUNDLE_IDENTIFIER = com.slack.circuit.CircuitRuntimeObjC;
+				PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+				SKIP_INSTALL = YES;
+				SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
+				SUPPORTS_MACCATALYST = NO;
+				SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
+				SWIFT_EMIT_LOC_STRINGS = YES;
+				SWIFT_VERSION = 5.0;
+				TARGETED_DEVICE_FAMILY = "1,2";
+			};
+			name = Debug;
+		};
+		1DE43D052A2265EE00EB0E36 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CODE_SIGN_STYLE = Automatic;
+				CURRENT_PROJECT_VERSION = 1;
+				DEFINES_MODULE = YES;
+				DYLIB_COMPATIBILITY_VERSION = 1;
+				DYLIB_CURRENT_VERSION = 1;
+				DYLIB_INSTALL_NAME_BASE = "@rpath";
+				GENERATE_INFOPLIST_FILE = YES;
+				INFOPLIST_KEY_NSHumanReadableCopyright = "";
+				INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+				IPHONEOS_DEPLOYMENT_TARGET = 16.0;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"@executable_path/Frameworks",
+					"@loader_path/Frameworks",
+				);
+				"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = (
+					"@executable_path/../Frameworks",
+					"@loader_path/Frameworks",
+				);
+				MARKETING_VERSION = 1.0;
+				PRODUCT_BUNDLE_IDENTIFIER = com.slack.circuit.CircuitRuntimeObjC;
+				PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+				SKIP_INSTALL = YES;
+				SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
+				SUPPORTS_MACCATALYST = NO;
+				SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
+				SWIFT_EMIT_LOC_STRINGS = YES;
+				SWIFT_VERSION = 5.0;
+				TARGETED_DEVICE_FAMILY = "1,2";
+			};
+			name = Release;
+		};
+		1DE43D102A2265FD00EB0E36 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_ENABLE_OBJC_WEAK = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				CODE_SIGN_STYLE = Automatic;
+				COPY_PHASE_STRIP = NO;
+				CURRENT_PROJECT_VERSION = 1;
+				DEBUG_INFORMATION_FORMAT = dwarf;
+				DEFINES_MODULE = YES;
+				DYLIB_COMPATIBILITY_VERSION = 1;
+				DYLIB_CURRENT_VERSION = 1;
+				DYLIB_INSTALL_NAME_BASE = "@rpath";
+				ENABLE_MODULE_VERIFIER = YES;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				ENABLE_TESTABILITY = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu11;
+				GCC_DYNAMIC_NO_PIC = NO;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_OPTIMIZATION_LEVEL = 0;
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"DEBUG=1",
+					"$(inherited)",
+				);
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				GENERATE_INFOPLIST_FILE = YES;
+				INFOPLIST_KEY_NSHumanReadableCopyright = "";
+				INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+				IPHONEOS_DEPLOYMENT_TARGET = 16.0;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"@executable_path/Frameworks",
+					"@loader_path/Frameworks",
+				);
+				"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = (
+					"@executable_path/../Frameworks",
+					"@loader_path/Frameworks",
+				);
+				MACOSX_DEPLOYMENT_TARGET = 13.1;
+				MARKETING_VERSION = 1.0;
+				MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
+				MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++20";
+				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+				MTL_FAST_MATH = YES;
+				ONLY_ACTIVE_ARCH = YES;
+				PRODUCT_BUNDLE_IDENTIFIER = com.slack.circuit.CircuitSwiftUI;
+				PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+				SDKROOT = auto;
+				SKIP_INSTALL = YES;
+				SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
+				SUPPORTS_MACCATALYST = NO;
+				SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
+				SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+				SWIFT_EMIT_LOC_STRINGS = YES;
+				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+				SWIFT_VERSION = 5.0;
+				TARGETED_DEVICE_FAMILY = "1,2";
+				VERSIONING_SYSTEM = "apple-generic";
+				VERSION_INFO_PREFIX = "";
+			};
+			name = Debug;
+		};
+		1DE43D112A2265FD00EB0E36 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_ENABLE_OBJC_WEAK = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				CODE_SIGN_STYLE = Automatic;
+				COPY_PHASE_STRIP = NO;
+				CURRENT_PROJECT_VERSION = 1;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				DEFINES_MODULE = YES;
+				DYLIB_COMPATIBILITY_VERSION = 1;
+				DYLIB_CURRENT_VERSION = 1;
+				DYLIB_INSTALL_NAME_BASE = "@rpath";
+				ENABLE_MODULE_VERIFIER = YES;
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu11;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				GENERATE_INFOPLIST_FILE = YES;
+				INFOPLIST_KEY_NSHumanReadableCopyright = "";
+				INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+				IPHONEOS_DEPLOYMENT_TARGET = 16.0;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"@executable_path/Frameworks",
+					"@loader_path/Frameworks",
+				);
+				"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = (
+					"@executable_path/../Frameworks",
+					"@loader_path/Frameworks",
+				);
+				MACOSX_DEPLOYMENT_TARGET = 13.1;
+				MARKETING_VERSION = 1.0;
+				MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
+				MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++20";
+				MTL_ENABLE_DEBUG_INFO = NO;
+				MTL_FAST_MATH = YES;
+				PRODUCT_BUNDLE_IDENTIFIER = com.slack.circuit.CircuitSwiftUI;
+				PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+				SDKROOT = auto;
+				SKIP_INSTALL = YES;
+				SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
+				SUPPORTS_MACCATALYST = NO;
+				SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
+				SWIFT_COMPILATION_MODE = wholemodule;
+				SWIFT_EMIT_LOC_STRINGS = YES;
+				SWIFT_OPTIMIZATION_LEVEL = "-O";
+				SWIFT_VERSION = 5.0;
+				TARGETED_DEVICE_FAMILY = "1,2";
+				VERSIONING_SYSTEM = "apple-generic";
+				VERSION_INFO_PREFIX = "";
+			};
+			name = Release;
+		};
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+		1DE43CEA2A22655F00EB0E36 /* Build configuration list for PBXProject "Circuit" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				1DE43CEB2A22655F00EB0E36 /* Debug */,
+				1DE43CEC2A22655F00EB0E36 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		1DE43CF72A2265C600EB0E36 /* Build configuration list for PBXNativeTarget "CircuitRuntime" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				1DE43CF82A2265C600EB0E36 /* Debug */,
+				1DE43CF92A2265C600EB0E36 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		1DE43D032A2265EE00EB0E36 /* Build configuration list for PBXNativeTarget "CircuitRuntimeObjC" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				1DE43D042A2265EE00EB0E36 /* Debug */,
+				1DE43D052A2265EE00EB0E36 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		1DE43D0F2A2265FD00EB0E36 /* Build configuration list for PBXNativeTarget "CircuitSwiftUI" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				1DE43D102A2265FD00EB0E36 /* Debug */,
+				1DE43D112A2265FD00EB0E36 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+/* End XCConfigurationList section */
+
+/* Begin XCRemoteSwiftPackageReference section */
+		1DE43D242A22682200EB0E36 /* XCRemoteSwiftPackageReference "KMM-ViewModel" */ = {
+			isa = XCRemoteSwiftPackageReference;
+			repositoryURL = "https://github.com/rickclephas/KMM-ViewModel/";
+			requirement = {
+				kind = exactVersion;
+				version = "1.0.0-ALPHA-9";
+			};
+		};
+/* End XCRemoteSwiftPackageReference section */
+
+/* Begin XCSwiftPackageProductDependency section */
+		1DE43D252A22682200EB0E36 /* KMMViewModelCore */ = {
+			isa = XCSwiftPackageProductDependency;
+			package = 1DE43D242A22682200EB0E36 /* XCRemoteSwiftPackageReference "KMM-ViewModel" */;
+			productName = KMMViewModelCore;
+		};
+		1DE43D2D2A226E5100EB0E36 /* KMMViewModelSwiftUI */ = {
+			isa = XCSwiftPackageProductDependency;
+			package = 1DE43D242A22682200EB0E36 /* XCRemoteSwiftPackageReference "KMM-ViewModel" */;
+			productName = KMMViewModelSwiftUI;
+		};
+/* End XCSwiftPackageProductDependency section */
+	};
+	rootObject = 1DE43CE72A22655F00EB0E36 /* Project object */;
+}
diff --git a/CircuitRuntime/CircuitNavigator.swift b/CircuitRuntime/CircuitNavigator.swift
new file mode 100644
index 000000000..a9dab5a73
--- /dev/null
+++ b/CircuitRuntime/CircuitNavigator.swift
@@ -0,0 +1,37 @@
+//
+//  CircuitNavigator.swift
+//  CircuitRuntime
+//
+//  Created by Rick Clephas on 27/05/2023.
+//
+
+import Foundation
+import CircuitRuntimeObjC
+
+public class CircuitNavigator: ObservableObject, CircuitSwiftUINavigator {
+    
+    @Published public var root: NSObject
+    @Published public var path: [NSObject]
+    
+    public init(_ root: NSObject, _ path: NSObject...) {
+        self.root = root
+        self.path = path
+    }
+    
+    public func goTo(screen: NSObject) {
+        path.append(screen)
+    }
+    
+    public func pop() -> NSObject? {
+        return path.popLast()
+    }
+    
+    public func resetRoot(newRoot: NSObject) -> [NSObject] {
+        let oldRoot = root
+        var oldPath = path
+        root = newRoot
+        path = []
+        oldPath.insert(oldRoot, at: 0)
+        return oldPath
+    }
+}
diff --git a/CircuitRuntime/CircuitPresenter.swift b/CircuitRuntime/CircuitPresenter.swift
new file mode 100644
index 000000000..4c8554613
--- /dev/null
+++ b/CircuitRuntime/CircuitPresenter.swift
@@ -0,0 +1,13 @@
+//
+//  CircuitPresenter.swift
+//  CircuitRuntime
+//
+//  Created by Rick Clephas on 27/05/2023.
+//
+
+import CircuitRuntimeObjC
+import KMMViewModelCore
+
+public protocol CircuitPresenter: KMMViewModel {
+    var navigator: CircuitSwiftUINavigator? { get set }
+}
diff --git a/CircuitRuntimeObjC/CircuitRuntimeObjC.h b/CircuitRuntimeObjC/CircuitRuntimeObjC.h
new file mode 100644
index 000000000..c831c8ecd
--- /dev/null
+++ b/CircuitRuntimeObjC/CircuitRuntimeObjC.h
@@ -0,0 +1,8 @@
+//
+//  CircuitRuntimeObjC.h
+//  CircuitRuntimeObjC
+//
+//  Created by Rick Clephas on 27/05/2023.
+//
+
+#import "CircuitSwiftUINavigator.h"
diff --git a/CircuitRuntimeObjC/CircuitRuntimeObjC.m b/CircuitRuntimeObjC/CircuitRuntimeObjC.m
new file mode 100644
index 000000000..a21ae655a
--- /dev/null
+++ b/CircuitRuntimeObjC/CircuitRuntimeObjC.m
@@ -0,0 +1 @@
+// We need this empty file, else SPM won't build this
diff --git a/CircuitRuntimeObjC/CircuitSwiftUINavigator.h b/CircuitRuntimeObjC/CircuitSwiftUINavigator.h
new file mode 100644
index 000000000..3f24a2e10
--- /dev/null
+++ b/CircuitRuntimeObjC/CircuitSwiftUINavigator.h
@@ -0,0 +1,19 @@
+//
+//  CircuitSwiftUINavigator.h
+//  Circuit
+//
+//  Created by Rick Clephas on 27/05/2023.
+//
+
+#ifndef CircuitSwiftUINavigator_h
+#define CircuitSwiftUINavigator_h
+
+#import <Foundation/Foundation.h>
+
+@protocol CircuitSwiftUINavigator
+- (void)goToScreen:(NSObject * _Nonnull)screen __attribute__((swift_name("goTo(screen:)")));
+- (NSObject * _Nullable)pop __attribute__((swift_name("pop()")));
+- (NSArray<NSObject *> * _Nonnull)resetRootNewRoot:(NSObject * _Nonnull)newRoot __attribute__((swift_name("resetRoot(newRoot:)")));
+@end
+
+#endif /* CircuitSwiftUINavigator_h */
diff --git a/CircuitSwiftUI/CircuitNavigationStack.swift b/CircuitSwiftUI/CircuitNavigationStack.swift
new file mode 100644
index 000000000..c4b7e6d49
--- /dev/null
+++ b/CircuitSwiftUI/CircuitNavigationStack.swift
@@ -0,0 +1,32 @@
+//
+//  CircuitNavigationStack.swift
+//  CircuitSwiftUI
+//
+//  Created by Rick Clephas on 27/05/2023.
+//
+
+import CircuitRuntime
+import SwiftUI
+
+public struct CircuitNavigationStack<Content: View>: View {
+    
+    @ObservedObject private var navigator: CircuitNavigator
+    private let content: (NSObject) -> Content
+    
+    public init(_ navigator: CircuitNavigator, @ViewBuilder _ content: @escaping (_ screen: NSObject) -> Content) {
+        self._navigator = ObservedObject(wrappedValue: navigator)
+        self.content = content
+    }
+    
+    public var body: some View {
+        NavigationStack(path: $navigator.path) {
+            screen(navigator.root).navigationDestination(for: NSObject.self) { path in
+                screen(path)
+            }
+        }.environmentObject(navigator)
+    }
+    
+    private func screen(_ screen: NSObject) -> some View {
+        content(screen).id(screen)
+    }
+}
diff --git a/CircuitSwiftUI/CircuitView.swift b/CircuitSwiftUI/CircuitView.swift
new file mode 100644
index 000000000..4dc4ef8b3
--- /dev/null
+++ b/CircuitSwiftUI/CircuitView.swift
@@ -0,0 +1,33 @@
+//
+//  CircuitView.swift
+//  CircuitSwiftUI
+//
+//  Created by Rick Clephas on 27/05/2023.
+//
+
+import CircuitRuntime
+import KMMViewModelSwiftUI
+import SwiftUI
+
+public struct CircuitView<Presenter: CircuitPresenter, State: AnyObject, Content: View>: View {
+    
+    @EnvironmentObject private var navigator: CircuitNavigator
+    @StateViewModel private var presenter: Presenter
+    private var stateKeyPath: KeyPath<Presenter, State>
+    private var content: (State) -> Content
+    
+    public init(_ presenter: @autoclosure @escaping () -> Presenter,
+         _ stateKeyPath: KeyPath<Presenter, State>,
+         @ViewBuilder _ content: @escaping (State) -> Content
+    ) {
+        self._presenter = StateViewModel(wrappedValue: presenter())
+        self.stateKeyPath = stateKeyPath
+        self.content = content
+    }
+    
+    public var body: some View {
+        content(presenter[keyPath: stateKeyPath]).onAppear {
+            presenter.navigator = navigator
+        }
+    }
+}
diff --git a/Package.swift b/Package.swift
new file mode 100644
index 000000000..f8e48a0f5
--- /dev/null
+++ b/Package.swift
@@ -0,0 +1,41 @@
+// swift-tools-version:5.7
+import PackageDescription
+
+let package = Package(
+    name: "Circuit",
+    platforms: [.iOS(.v16)],
+    products: [
+        .library(
+            name: "CircuitRuntime",
+            targets: ["CircuitRuntime"]
+        ),
+        .library(
+            name: "CircuitSwiftUI",
+            targets: ["CircuitSwiftUI"]
+        )
+    ],
+    dependencies: [
+        .package(
+            url: "https://github.com/rickclephas/KMM-ViewModel.git",
+            from: "1.0.0-ALPHA-9"
+        )
+    ],
+    targets: [
+        .target(
+            name: "CircuitRuntimeObjC",
+            path: "CircuitRuntimeObjC",
+            publicHeadersPath: "."
+        ),
+        .target(
+            name: "CircuitRuntime",
+            dependencies: [.target(name: "CircuitRuntimeObjC"), .product(name: "KMMViewModelCore", package: "KMM-ViewModel")],
+            path: "CircuitRuntime"
+        ),
+        .target(
+            name: "CircuitSwiftUI",
+            dependencies: [.target(name: "CircuitRuntime"), .product(name: "KMMViewModelSwiftUI", package: "KMM-ViewModel")],
+            path: "CircuitSwiftUI"
+        )
+    ],
+    swiftLanguageVersions: [.v5]
+)
diff --git a/circuit-swiftui/build.gradle.kts b/circuit-swiftui/build.gradle.kts
index 77124d677..c237a93e1 100644
--- a/circuit-swiftui/build.gradle.kts
+++ b/circuit-swiftui/build.gradle.kts
@@ -7,8 +7,9 @@ plugins {
 
 kotlin {
   // region KMP Targets
-  ios()
-  iosSimulatorArm64()
+  val iosArm64 = iosArm64()
+  val iosX64 = iosX64()
+  val iosSimulatorArm64 = iosSimulatorArm64()
   // endregion
 
   sourceSets {
@@ -20,4 +21,14 @@ kotlin {
       }
     }
   }
+
+  listOf(
+    iosArm64, iosX64, iosSimulatorArm64
+  ).forEach {
+    it.compilations.getByName("main") {
+      cinterops.create("CircuitRuntimeObjC") {
+        includeDirs("$projectDir/../CircuitRuntimeObjC")
+      }
+    }
+  }
 }
diff --git a/circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/SwiftUINavigator.kt b/circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/SwiftUINavigator.kt
new file mode 100644
index 000000000..46f8f32c8
--- /dev/null
+++ b/circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/SwiftUINavigator.kt
@@ -0,0 +1,21 @@
+package com.slack.circuit.swiftui
+
+import com.slack.circuit.runtime.Navigator
+import com.slack.circuit.runtime.Screen
+import com.slack.circuit.swiftui.objc.CircuitSwiftUINavigatorProtocol
+import platform.darwin.NSObject
+
+public class SwiftUINavigator internal constructor(): Navigator {
+
+    internal var navigator: CircuitSwiftUINavigatorProtocol? = null
+
+    private fun requireNavigator(): CircuitSwiftUINavigatorProtocol =
+        navigator ?: throw RuntimeException("SwiftUINavigator hasn't been initialized")
+
+    override fun goTo(screen: Screen): Unit = requireNavigator().goToScreen(screen as NSObject)
+
+    override fun pop(): Screen? = requireNavigator().pop() as Screen?
+
+    override fun resetRoot(newRoot: Screen): List<Screen> =
+        requireNavigator().resetRootNewRoot(newRoot as NSObject).map { it as Screen }
+}
diff --git a/circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/SwiftUIPresenter.kt b/circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/SwiftUIPresenter.kt
index a18b73445..b328433fc 100644
--- a/circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/SwiftUIPresenter.kt
+++ b/circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/SwiftUIPresenter.kt
@@ -7,20 +7,30 @@ import com.slack.circuit.runtime.CircuitUiState
 import com.slack.circuit.runtime.Navigator
 import com.slack.circuit.runtime.presenter.Presenter
 import com.slack.circuit.runtime.presenter.presenterOf
+import com.slack.circuit.swiftui.objc.CircuitSwiftUINavigatorProtocol
 
 public class SwiftUIPresenter<UiState: CircuitUiState> internal constructor(
+    private val swiftUINavigator: SwiftUINavigator,
     private val presenter: Presenter<UiState>
 ): KMMViewModel() {
+    public interface Factory<UiState: CircuitUiState> {
+        public fun presenter(): SwiftUIPresenter<UiState>
+    }
 
     private val stateFlow = viewModelScope.launchMolecule(RecompositionClock.Immediate) {
         presenter.present()
     }
 
     public val state: UiState get() = stateFlow.value
+
+    public var navigator: CircuitSwiftUINavigatorProtocol?
+        get() = swiftUINavigator.navigator
+        set(value) { swiftUINavigator.navigator = value }
 }
 
 public fun <UiState : CircuitUiState> swiftUIPresenterOf(
     body: @Composable (Navigator) -> UiState
 ): SwiftUIPresenter<UiState> {
-    return SwiftUIPresenter(presenterOf { body(Navigator.NoOp) })
+    val navigator = SwiftUINavigator()
+    return SwiftUIPresenter(navigator, presenterOf { body(navigator) })
 }
diff --git a/circuit-swiftui/src/nativeInterop/cinterop/CircuitRuntimeObjC.def b/circuit-swiftui/src/nativeInterop/cinterop/CircuitRuntimeObjC.def
new file mode 100644
index 000000000..4cf6588f0
--- /dev/null
+++ b/circuit-swiftui/src/nativeInterop/cinterop/CircuitRuntimeObjC.def
@@ -0,0 +1,3 @@
+language = Objective-C
+package = com.slack.circuit.swiftui.objc
+headers = CircuitSwiftUINavigator.h
diff --git a/samples/counter/apps/Counter.xcodeproj/project.pbxproj b/samples/counter/apps/Counter.xcodeproj/project.pbxproj
index 74e002759..4e49d06df 100644
--- a/samples/counter/apps/Counter.xcodeproj/project.pbxproj
+++ b/samples/counter/apps/Counter.xcodeproj/project.pbxproj
@@ -9,11 +9,12 @@
 /* Begin PBXBuildFile section */
 		058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557BA273AAA24004C7B11 /* Assets.xcassets */; };
 		058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */; };
-		1DE43CE32A210A1400EB0E36 /* KMMViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE43CE22A210A1400EB0E36 /* KMMViewModel.swift */; };
-		1DE43CE52A21281B00EB0E36 /* KMMViewModelState in Frameworks */ = {isa = PBXBuildFile; productRef = 1DE43CE42A21281B00EB0E36 /* KMMViewModelState */; };
+		1DC5EA872A22732C00037003 /* CircuitSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 1DC5EA862A22732C00037003 /* CircuitSwiftUI */; };
+		1DC5EA892A22785A00037003 /* PrimeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DC5EA882A22785A00037003 /* PrimeView.swift */; };
+		1DE43CE32A210A1400EB0E36 /* Circuit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE43CE22A210A1400EB0E36 /* Circuit.swift */; };
 		2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2152FB032600AC8F00CF470E /* iOSApp.swift */; };
 		602DC854301CF43C8E7B0F6D /* Pods_Counter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27D2DFDA1A8E1896075A1701 /* Pods_Counter.framework */; };
-		7555FF83242A565900829871 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* ContentView.swift */; };
+		7555FF83242A565900829871 /* CounterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* CounterView.swift */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXCopyFilesBuildPhase section */
@@ -32,13 +33,14 @@
 /* Begin PBXFileReference section */
 		058557BA273AAA24004C7B11 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
 		058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
-		1DE43CE12A2109E500EB0E36 /* KMM-ViewModel */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "KMM-ViewModel"; path = "../../../../../Rick Clephas/KMM-ViewModel"; sourceTree = "<group>"; };
-		1DE43CE22A210A1400EB0E36 /* KMMViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KMMViewModel.swift; sourceTree = "<group>"; };
+		1DC5EA882A22785A00037003 /* PrimeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrimeView.swift; sourceTree = "<group>"; };
+		1DE43CE22A210A1400EB0E36 /* Circuit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Circuit.swift; sourceTree = "<group>"; };
+		1DE43D322A2272A300EB0E36 /* circuit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = circuit; path = ../../..; sourceTree = "<group>"; };
 		2152FB032600AC8F00CF470E /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = "<group>"; };
 		27D2DFDA1A8E1896075A1701 /* Pods_Counter.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Counter.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		35C96B8C8A190485ECDD3046 /* Pods-Counter.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Counter.debug.xcconfig"; path = "Target Support Files/Pods-Counter/Pods-Counter.debug.xcconfig"; sourceTree = "<group>"; };
 		7555FF7B242A565900829871 /* Counter.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Counter.app; sourceTree = BUILT_PRODUCTS_DIR; };
-		7555FF82242A565900829871 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
+		7555FF82242A565900829871 /* CounterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CounterView.swift; sourceTree = "<group>"; };
 		7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		E90321D9A8A0D137411EFA43 /* Pods-Counter.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Counter.release.xcconfig"; path = "Target Support Files/Pods-Counter/Pods-Counter.release.xcconfig"; sourceTree = "<group>"; };
 /* End PBXFileReference section */
@@ -48,8 +50,8 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				1DC5EA872A22732C00037003 /* CircuitSwiftUI in Frameworks */,
 				602DC854301CF43C8E7B0F6D /* Pods_Counter.framework in Frameworks */,
-				1DE43CE52A21281B00EB0E36 /* KMMViewModelState in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -76,7 +78,7 @@
 		1DE43CE02A2109E500EB0E36 /* Packages */ = {
 			isa = PBXGroup;
 			children = (
-				1DE43CE12A2109E500EB0E36 /* KMM-ViewModel */,
+				1DE43D322A2272A300EB0E36 /* circuit */,
 			);
 			name = Packages;
 			sourceTree = "<group>";
@@ -104,11 +106,12 @@
 			isa = PBXGroup;
 			children = (
 				058557BA273AAA24004C7B11 /* Assets.xcassets */,
-				7555FF82242A565900829871 /* ContentView.swift */,
+				7555FF82242A565900829871 /* CounterView.swift */,
 				7555FF8C242A565B00829871 /* Info.plist */,
 				2152FB032600AC8F00CF470E /* iOSApp.swift */,
 				058557D7273AAEEB004C7B11 /* Preview Content */,
-				1DE43CE22A210A1400EB0E36 /* KMMViewModel.swift */,
+				1DE43CE22A210A1400EB0E36 /* Circuit.swift */,
+				1DC5EA882A22785A00037003 /* PrimeView.swift */,
 			);
 			path = Counter;
 			sourceTree = "<group>";
@@ -141,7 +144,7 @@
 			);
 			name = Counter;
 			packageProductDependencies = (
-				1DE43CE42A21281B00EB0E36 /* KMMViewModelState */,
+				1DC5EA862A22732C00037003 /* CircuitSwiftUI */,
 			);
 			productName = Counter;
 			productReference = 7555FF7B242A565900829871 /* Counter.app */;
@@ -239,9 +242,10 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				1DE43CE32A210A1400EB0E36 /* KMMViewModel.swift in Sources */,
+				1DE43CE32A210A1400EB0E36 /* Circuit.swift in Sources */,
 				2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */,
-				7555FF83242A565900829871 /* ContentView.swift in Sources */,
+				1DC5EA892A22785A00037003 /* PrimeView.swift in Sources */,
+				7555FF83242A565900829871 /* CounterView.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -377,6 +381,7 @@
 					"$(SRCROOT)/../build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)",
 				);
 				INFOPLIST_FILE = Counter/Info.plist;
+				IPHONEOS_DEPLOYMENT_TARGET = 16.0;
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",
 					"@executable_path/Frameworks",
@@ -406,6 +411,7 @@
 					"$(SRCROOT)/../build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)",
 				);
 				INFOPLIST_FILE = Counter/Info.plist;
+				IPHONEOS_DEPLOYMENT_TARGET = 16.0;
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",
 					"@executable_path/Frameworks",
@@ -446,9 +452,9 @@
 /* End XCConfigurationList section */
 
 /* Begin XCSwiftPackageProductDependency section */
-		1DE43CE42A21281B00EB0E36 /* KMMViewModelState */ = {
+		1DC5EA862A22732C00037003 /* CircuitSwiftUI */ = {
 			isa = XCSwiftPackageProductDependency;
-			productName = KMMViewModelState;
+			productName = CircuitSwiftUI;
 		};
 /* End XCSwiftPackageProductDependency section */
 	};
diff --git a/samples/counter/apps/Counter/Circuit.swift b/samples/counter/apps/Counter/Circuit.swift
new file mode 100644
index 000000000..6b6e9d858
--- /dev/null
+++ b/samples/counter/apps/Counter/Circuit.swift
@@ -0,0 +1,22 @@
+//
+//  Circuit.swift
+//  Counter
+//
+//  Created by Rick Clephas on 26/05/2023.
+//  Copyright © 2023 orgName. All rights reserved.
+//
+
+import counter
+import SwiftUI
+import CircuitRuntime
+import CircuitSwiftUI
+
+extension Circuit_swiftuiSwiftUIPresenter: CircuitPresenter { }
+
+extension CircuitView {
+    init(_ presenter: @autoclosure @escaping () -> Presenter,
+         @ViewBuilder _ content: @escaping (State) -> Content
+    ) where Presenter: Circuit_swiftuiSwiftUIPresenter<State> {
+        self.init(presenter(), \.state, content)
+    }
+}
diff --git a/samples/counter/apps/Counter/ContentView.swift b/samples/counter/apps/Counter/CounterView.swift
similarity index 73%
rename from samples/counter/apps/Counter/ContentView.swift
rename to samples/counter/apps/Counter/CounterView.swift
index ef263e50c..3657f6ec7 100644
--- a/samples/counter/apps/Counter/ContentView.swift
+++ b/samples/counter/apps/Counter/CounterView.swift
@@ -1,12 +1,11 @@
 import SwiftUI
 import counter
-import KMMViewModelState
 
-struct ContentView: View {
-  @ObservedViewModelState var state = PresenterFactory.shared.counterPresenter()
+struct CounterView: View {
+    
+  var state: CounterScreenState
 
   var body: some View {
-    NavigationView {
       VStack(alignment: .center) {
         Text("Count \(state.count)")
           .font(.system(size: 36))
@@ -30,14 +29,16 @@ struct ContentView: View {
           .foregroundColor(.white)
           .background(Color.blue)
         }
+        Button("Prime?") {
+          state.eventSink(CounterScreenEventGoTo(screen: IosPrimeScreen(number: state.count)))
+        }.padding()
       }
       .navigationBarTitle("Counter")
-    }
   }
 }
 
-struct ContentView_Previews: PreviewProvider {
+struct CounterView_Previews: PreviewProvider {
   static var previews: some View {
-    ContentView()
+      CounterView(state: CounterScreenState(count: 0, eventSink: { _ in }))
   }
 }
diff --git a/samples/counter/apps/Counter/KMMViewModel.swift b/samples/counter/apps/Counter/KMMViewModel.swift
deleted file mode 100644
index e088156ad..000000000
--- a/samples/counter/apps/Counter/KMMViewModel.swift
+++ /dev/null
@@ -1,19 +0,0 @@
-//
-//  KMMViewModel.swift
-//  Counter
-//
-//  Created by Rick Clephas on 26/05/2023.
-//  Copyright © 2023 orgName. All rights reserved.
-//
-
-import KMMViewModelCore
-import KMMViewModelState
-import counter
-
-extension Kmm_viewmodel_coreKMMViewModel: KMMViewModel { }
-
-extension ObservedViewModelState {
-    init(wrappedValue: ViewModel) where ViewModel: Circuit_swiftuiSwiftUIPresenter<State> {
-        self.init(wrappedValue: wrappedValue, \.state)
-    }
-}
diff --git a/samples/counter/apps/Counter/PrimeView.swift b/samples/counter/apps/Counter/PrimeView.swift
new file mode 100644
index 000000000..283a4e31c
--- /dev/null
+++ b/samples/counter/apps/Counter/PrimeView.swift
@@ -0,0 +1,35 @@
+//
+//  PrimeView.swift
+//  Counter
+//
+//  Created by Rick Clephas on 27/05/2023.
+//  Copyright © 2023 orgName. All rights reserved.
+//
+
+import SwiftUI
+import counter
+
+struct PrimeView: View {
+    
+    var state: PrimeScreenState
+    
+    var body: some View {
+        Text("\(state.number)")
+          .font(.system(size: 36))
+          .padding()
+        if (state.isPrime) {
+            Text("\(state.number) is a prime number!")
+        } else {
+            Text("\(state.number) is not a prime number.")
+        }
+        Button("Back") {
+            state.eventSink(PrimeScreenEventPop())
+        }.padding()
+    }
+}
+
+struct PrimeView_Previews: PreviewProvider {
+    static var previews: some View {
+        PrimeView(state: PrimeScreenState(number: 0, isPrime: false, eventSink: { _ in }))
+    }
+}
diff --git a/samples/counter/apps/Counter/iOSApp.swift b/samples/counter/apps/Counter/iOSApp.swift
index 0648e8602..ddfb5d8b0 100644
--- a/samples/counter/apps/Counter/iOSApp.swift
+++ b/samples/counter/apps/Counter/iOSApp.swift
@@ -1,10 +1,25 @@
 import SwiftUI
+import counter
+import CircuitRuntime
+import CircuitSwiftUI
 
 @main
 struct iOSApp: App {
+    
+    @StateObject private var navigator = CircuitNavigator(IosCounterScreen.shared)
+    
 	var body: some Scene {
 		WindowGroup {
-			ContentView()
-		}
+            CircuitNavigationStack(navigator) { screen in
+                switch screen {
+                case let screen as IosCounterScreen:
+                    CircuitView(screen.presenter(), CounterView.init)
+                case let screen as IosPrimeScreen:
+                    CircuitView(screen.presenter(), PrimeView.init)
+                default:
+                    fatalError("Unsupported screen: \(screen)")
+                }
+            }
+        }
 	}
-}
\ No newline at end of file
+}
diff --git a/samples/counter/src/iosMain/kotlin/com.slack.circuit.sample.counter/PresenterFactory.kt b/samples/counter/src/iosMain/kotlin/com.slack.circuit.sample.counter/PresenterFactory.kt
deleted file mode 100644
index c491f5c86..000000000
--- a/samples/counter/src/iosMain/kotlin/com.slack.circuit.sample.counter/PresenterFactory.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.slack.circuit.sample.counter
-
-import com.slack.circuit.swiftui.swiftUIPresenterOf
-
-object PresenterFactory {
-    fun counterPresenter() = swiftUIPresenterOf { CounterPresenter(it) }
-    fun primePresenter(number: Int) = swiftUIPresenterOf { PrimePresenter(it, number) }
-}
diff --git a/samples/counter/src/iosMain/kotlin/com.slack.circuit.sample.counter/Screens.kt b/samples/counter/src/iosMain/kotlin/com.slack.circuit.sample.counter/Screens.kt
new file mode 100644
index 000000000..203787478
--- /dev/null
+++ b/samples/counter/src/iosMain/kotlin/com.slack.circuit.sample.counter/Screens.kt
@@ -0,0 +1,14 @@
+package com.slack.circuit.sample.counter
+
+import com.slack.circuit.swiftui.SwiftUIPresenter
+import com.slack.circuit.swiftui.swiftUIPresenterOf
+
+object IosCounterScreen: CounterScreen, SwiftUIPresenter.Factory<CounterScreen.State> {
+    override fun presenter() = swiftUIPresenterOf { CounterPresenter(it) }
+}
+
+data class IosPrimeScreen(
+    override val number: Int
+): PrimeScreen, SwiftUIPresenter.Factory<PrimeScreen.State> {
+    override fun presenter() = swiftUIPresenterOf { PrimePresenter(it, number) }
+}

From 87b8dde12f217d4bc14b826446ef674bcbd1cdc2 Mon Sep 17 00:00:00 2001
From: Rick Clephas <rclephas@gmail.com>
Date: Fri, 8 Sep 2023 19:29:07 +0200
Subject: [PATCH 4/5] Remove KMM-ViewModel dependency and reduce boilerplate

---
 Circuit.xcodeproj/project.pbxproj             | 35 ++------------
 CircuitRuntime/CircuitPresenter.swift         |  6 ++-
 CircuitSwiftUI/CircuitView.swift              | 15 +++---
 CircuitSwiftUI/ObservablePresenter.swift      | 47 +++++++++++++++++++
 circuit-swiftui/build.gradle.kts              |  1 -
 .../slack/circuit/swiftui/SwiftUINavigator.kt |  2 +-
 .../slack/circuit/swiftui/SwiftUIPresenter.kt | 41 +++++++++++++---
 .../swiftui/SwiftUIPresenterProtocol.kt       | 10 ++++
 .../com/slack/circuit/swiftui/molecule.kt     | 34 --------------
 gradle/libs.versions.toml                     |  2 -
 samples/counter/apps/Counter/Circuit.swift    | 12 +----
 11 files changed, 108 insertions(+), 97 deletions(-)
 create mode 100644 CircuitSwiftUI/ObservablePresenter.swift
 create mode 100644 circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/SwiftUIPresenterProtocol.kt
 delete mode 100644 circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/molecule.kt

diff --git a/Circuit.xcodeproj/project.pbxproj b/Circuit.xcodeproj/project.pbxproj
index 30cf51d21..cee95a29d 100644
--- a/Circuit.xcodeproj/project.pbxproj
+++ b/Circuit.xcodeproj/project.pbxproj
@@ -7,17 +7,16 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
+		1D0026E32AA8F5BE0072496E /* ObservablePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0026E22AA8F5BE0072496E /* ObservablePresenter.swift */; };
 		1DE43D022A2265EE00EB0E36 /* CircuitRuntimeObjC.h in Headers */ = {isa = PBXBuildFile; fileRef = 1DE43D012A2265EE00EB0E36 /* CircuitRuntimeObjC.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		1DE43D132A22660400EB0E36 /* CircuitRuntime.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DE43CF22A2265C600EB0E36 /* CircuitRuntime.framework */; };
 		1DE43D182A22660B00EB0E36 /* CircuitRuntimeObjC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DE43CFF2A2265EE00EB0E36 /* CircuitRuntimeObjC.framework */; };
 		1DE43D1E2A22669D00EB0E36 /* CircuitRuntimeObjC.m in Sources */ = {isa = PBXBuildFile; fileRef = 1DE43D1D2A22669D00EB0E36 /* CircuitRuntimeObjC.m */; };
 		1DE43D212A22670F00EB0E36 /* CircuitSwiftUINavigator.h in Headers */ = {isa = PBXBuildFile; fileRef = 1DE43D1F2A2266AF00EB0E36 /* CircuitSwiftUINavigator.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		1DE43D232A2267EB00EB0E36 /* CircuitPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE43D222A2267EB00EB0E36 /* CircuitPresenter.swift */; };
-		1DE43D262A22682200EB0E36 /* KMMViewModelCore in Frameworks */ = {isa = PBXBuildFile; productRef = 1DE43D252A22682200EB0E36 /* KMMViewModelCore */; };
 		1DE43D282A226BF200EB0E36 /* CircuitNavigationStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE43D272A226BF200EB0E36 /* CircuitNavigationStack.swift */; };
 		1DE43D2A2A226C3A00EB0E36 /* CircuitNavigator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE43D292A226C3A00EB0E36 /* CircuitNavigator.swift */; };
 		1DE43D2C2A226E2E00EB0E36 /* CircuitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE43D2B2A226E2E00EB0E36 /* CircuitView.swift */; };
-		1DE43D2E2A226E5100EB0E36 /* KMMViewModelSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 1DE43D2D2A226E5100EB0E36 /* KMMViewModelSwiftUI */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -38,6 +37,7 @@
 /* End PBXContainerItemProxy section */
 
 /* Begin PBXFileReference section */
+		1D0026E22AA8F5BE0072496E /* ObservablePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservablePresenter.swift; sourceTree = "<group>"; };
 		1DE43CF22A2265C600EB0E36 /* CircuitRuntime.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CircuitRuntime.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		1DE43CFF2A2265EE00EB0E36 /* CircuitRuntimeObjC.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CircuitRuntimeObjC.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		1DE43D012A2265EE00EB0E36 /* CircuitRuntimeObjC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CircuitRuntimeObjC.h; sourceTree = "<group>"; };
@@ -55,7 +55,6 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				1DE43D262A22682200EB0E36 /* KMMViewModelCore in Frameworks */,
 				1DE43D182A22660B00EB0E36 /* CircuitRuntimeObjC.framework in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
@@ -72,7 +71,6 @@
 			buildActionMask = 2147483647;
 			files = (
 				1DE43D132A22660400EB0E36 /* CircuitRuntime.framework in Frameworks */,
-				1DE43D2E2A226E5100EB0E36 /* KMMViewModelSwiftUI in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -124,6 +122,7 @@
 			children = (
 				1DE43D272A226BF200EB0E36 /* CircuitNavigationStack.swift */,
 				1DE43D2B2A226E2E00EB0E36 /* CircuitView.swift */,
+				1D0026E22AA8F5BE0072496E /* ObservablePresenter.swift */,
 			);
 			path = CircuitSwiftUI;
 			sourceTree = "<group>";
@@ -180,7 +179,6 @@
 			);
 			name = CircuitRuntime;
 			packageProductDependencies = (
-				1DE43D252A22682200EB0E36 /* KMMViewModelCore */,
 			);
 			productName = CircuitRuntime;
 			productReference = 1DE43CF22A2265C600EB0E36 /* CircuitRuntime.framework */;
@@ -220,7 +218,6 @@
 			);
 			name = CircuitSwiftUI;
 			packageProductDependencies = (
-				1DE43D2D2A226E5100EB0E36 /* KMMViewModelSwiftUI */,
 			);
 			productName = CircuitSwiftUI;
 			productReference = 1DE43D0B2A2265FD00EB0E36 /* CircuitSwiftUI.framework */;
@@ -258,7 +255,6 @@
 			);
 			mainGroup = 1DE43CE62A22655F00EB0E36;
 			packageReferences = (
-				1DE43D242A22682200EB0E36 /* XCRemoteSwiftPackageReference "KMM-ViewModel" */,
 			);
 			productRefGroup = 1DE43CF32A2265C600EB0E36 /* Products */;
 			projectDirPath = "";
@@ -318,6 +314,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				1DE43D2C2A226E2E00EB0E36 /* CircuitView.swift in Sources */,
+				1D0026E32AA8F5BE0072496E /* ObservablePresenter.swift in Sources */,
 				1DE43D282A226BF200EB0E36 /* CircuitNavigationStack.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
@@ -816,30 +813,6 @@
 			defaultConfigurationName = Release;
 		};
 /* End XCConfigurationList section */
-
-/* Begin XCRemoteSwiftPackageReference section */
-		1DE43D242A22682200EB0E36 /* XCRemoteSwiftPackageReference "KMM-ViewModel" */ = {
-			isa = XCRemoteSwiftPackageReference;
-			repositoryURL = "https://github.com/rickclephas/KMM-ViewModel/";
-			requirement = {
-				kind = exactVersion;
-				version = "1.0.0-ALPHA-9";
-			};
-		};
-/* End XCRemoteSwiftPackageReference section */
-
-/* Begin XCSwiftPackageProductDependency section */
-		1DE43D252A22682200EB0E36 /* KMMViewModelCore */ = {
-			isa = XCSwiftPackageProductDependency;
-			package = 1DE43D242A22682200EB0E36 /* XCRemoteSwiftPackageReference "KMM-ViewModel" */;
-			productName = KMMViewModelCore;
-		};
-		1DE43D2D2A226E5100EB0E36 /* KMMViewModelSwiftUI */ = {
-			isa = XCSwiftPackageProductDependency;
-			package = 1DE43D242A22682200EB0E36 /* XCRemoteSwiftPackageReference "KMM-ViewModel" */;
-			productName = KMMViewModelSwiftUI;
-		};
-/* End XCSwiftPackageProductDependency section */
 	};
 	rootObject = 1DE43CE72A22655F00EB0E36 /* Project object */;
 }
diff --git a/CircuitRuntime/CircuitPresenter.swift b/CircuitRuntime/CircuitPresenter.swift
index 4c8554613..232cb6646 100644
--- a/CircuitRuntime/CircuitPresenter.swift
+++ b/CircuitRuntime/CircuitPresenter.swift
@@ -6,8 +6,10 @@
 //
 
 import CircuitRuntimeObjC
-import KMMViewModelCore
 
-public protocol CircuitPresenter: KMMViewModel {
+public protocol CircuitPresenter: AnyObject {
+    var state: Any { get }
     var navigator: CircuitSwiftUINavigator? { get set }
+    func setStateWillChangeListener(listener: @escaping () -> Void)
+    func cancel()
 }
diff --git a/CircuitSwiftUI/CircuitView.swift b/CircuitSwiftUI/CircuitView.swift
index 4dc4ef8b3..ffb5625e1 100644
--- a/CircuitSwiftUI/CircuitView.swift
+++ b/CircuitSwiftUI/CircuitView.swift
@@ -6,28 +6,27 @@
 //
 
 import CircuitRuntime
-import KMMViewModelSwiftUI
 import SwiftUI
 
-public struct CircuitView<Presenter: CircuitPresenter, State: AnyObject, Content: View>: View {
+public struct CircuitView<Presenter: CircuitPresenter, State: Any, Content: View>: View {
     
     @EnvironmentObject private var navigator: CircuitNavigator
-    @StateViewModel private var presenter: Presenter
+    @StateObject private var observableObject: ObservablePresenter<Presenter>
     private var stateKeyPath: KeyPath<Presenter, State>
     private var content: (State) -> Content
     
     public init(_ presenter: @autoclosure @escaping () -> Presenter,
-         _ stateKeyPath: KeyPath<Presenter, State>,
-         @ViewBuilder _ content: @escaping (State) -> Content
+                @ViewBuilder _ content: @escaping (State) -> Content,
+                _ stateKeyPath: KeyPath<Presenter, State> = \Presenter.state
     ) {
-        self._presenter = StateViewModel(wrappedValue: presenter())
+        self._observableObject = StateObject(wrappedValue: observablePresenter(for: presenter()))
         self.stateKeyPath = stateKeyPath
         self.content = content
     }
     
     public var body: some View {
-        content(presenter[keyPath: stateKeyPath]).onAppear {
-            presenter.navigator = navigator
+        content(observableObject.presenter[keyPath: stateKeyPath]).onAppear {
+            observableObject.presenter.navigator = navigator
         }
     }
 }
diff --git a/CircuitSwiftUI/ObservablePresenter.swift b/CircuitSwiftUI/ObservablePresenter.swift
new file mode 100644
index 000000000..70a3e5330
--- /dev/null
+++ b/CircuitSwiftUI/ObservablePresenter.swift
@@ -0,0 +1,47 @@
+//
+//  ObservablePresenter.swift
+//  CircuitSwiftUI
+//
+//  Created by Rick Clephas on 06/09/2023.
+//
+
+import Foundation
+import CircuitRuntime
+
+internal class ObservablePresenter<Presenter: CircuitPresenter>: ObservableObject {
+    let presenter: Presenter
+    
+    init(_ presenter: Presenter) {
+        self.presenter = presenter
+        presenter.setStateWillChangeListener { [weak self] in
+            self?.objectWillChange.send()
+        }
+    }
+    
+    deinit {
+        presenter.cancel()
+    }
+}
+
+private var observablePresenterKey = "observablePresenter"
+
+private class WeakObservablePresenter<Presenter: CircuitPresenter> {
+    weak var observablePresenter: ObservablePresenter<Presenter>?
+    init(_ observablePresenter: ObservablePresenter<Presenter>) {
+        self.observablePresenter = observablePresenter
+    }
+}
+
+internal func observablePresenter<Presenter: CircuitPresenter>(for presenter: Presenter) -> ObservablePresenter<Presenter> {
+    if let object = objc_getAssociatedObject(presenter, &observablePresenterKey) {
+        guard let observablePresenter = (object as! WeakObservablePresenter<Presenter>).observablePresenter else {
+            fatalError("ObservablePresenter has been deallocated")
+        }
+        return observablePresenter
+    } else {
+        let observablePresenter = ObservablePresenter(presenter)
+        let object = WeakObservablePresenter<Presenter>(observablePresenter)
+        objc_setAssociatedObject(presenter, &observablePresenterKey, object, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
+        return observablePresenter
+    }
+}
diff --git a/circuit-swiftui/build.gradle.kts b/circuit-swiftui/build.gradle.kts
index c237a93e1..ba365ebab 100644
--- a/circuit-swiftui/build.gradle.kts
+++ b/circuit-swiftui/build.gradle.kts
@@ -16,7 +16,6 @@ kotlin {
     commonMain {
       dependencies {
         api(projects.circuitRuntimePresenter)
-        api(libs.kmmviewmodel.core)
         implementation(libs.molecule.runtime)
       }
     }
diff --git a/circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/SwiftUINavigator.kt b/circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/SwiftUINavigator.kt
index 46f8f32c8..0c5af6d21 100644
--- a/circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/SwiftUINavigator.kt
+++ b/circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/SwiftUINavigator.kt
@@ -5,7 +5,7 @@ import com.slack.circuit.runtime.Screen
 import com.slack.circuit.swiftui.objc.CircuitSwiftUINavigatorProtocol
 import platform.darwin.NSObject
 
-public class SwiftUINavigator internal constructor(): Navigator {
+internal class SwiftUINavigator internal constructor(): Navigator {
 
     internal var navigator: CircuitSwiftUINavigatorProtocol? = null
 
diff --git a/circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/SwiftUIPresenter.kt b/circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/SwiftUIPresenter.kt
index b328433fc..cb1e85de4 100644
--- a/circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/SwiftUIPresenter.kt
+++ b/circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/SwiftUIPresenter.kt
@@ -2,30 +2,57 @@ package com.slack.circuit.swiftui
 
 import androidx.compose.runtime.Composable
 import app.cash.molecule.RecompositionClock
-import com.rickclephas.kmm.viewmodel.KMMViewModel
+import app.cash.molecule.launchMolecule
 import com.slack.circuit.runtime.CircuitUiState
 import com.slack.circuit.runtime.Navigator
 import com.slack.circuit.runtime.presenter.Presenter
 import com.slack.circuit.runtime.presenter.presenterOf
 import com.slack.circuit.swiftui.objc.CircuitSwiftUINavigatorProtocol
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.cancel
 
 public class SwiftUIPresenter<UiState: CircuitUiState> internal constructor(
     private val swiftUINavigator: SwiftUINavigator,
     private val presenter: Presenter<UiState>
-): KMMViewModel() {
+): SwiftUIPresenterProtocol() {
     public interface Factory<UiState: CircuitUiState> {
         public fun presenter(): SwiftUIPresenter<UiState>
     }
 
-    private val stateFlow = viewModelScope.launchMolecule(RecompositionClock.Immediate) {
-        presenter.present()
-    }
+    private val coroutineScope = CoroutineScope(Dispatchers.Main)
 
-    public val state: UiState get() = stateFlow.value
+    public override lateinit var state: UiState
+        private set
 
-    public var navigator: CircuitSwiftUINavigatorProtocol?
+    public override var navigator: CircuitSwiftUINavigatorProtocol?
         get() = swiftUINavigator.navigator
         set(value) { swiftUINavigator.navigator = value }
+
+    private var stateWillChangeListener: (() -> Unit)? = null
+
+    public override fun setStateWillChangeListener(listener: () -> Unit) {
+        if (this.stateWillChangeListener != null) {
+            throw IllegalStateException("SwiftUIPresenter can't be wrapped more than once")
+        }
+        stateWillChangeListener = listener
+    }
+
+    public override fun cancel() {
+        coroutineScope.cancel()
+    }
+
+    init {
+        coroutineScope.launchMolecule(
+            clock = RecompositionClock.Immediate,
+            emitter = { value ->
+                stateWillChangeListener?.invoke()
+                state = value
+            }
+        ) {
+            presenter.present()
+        }
+    }
 }
 
 public fun <UiState : CircuitUiState> swiftUIPresenterOf(
diff --git a/circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/SwiftUIPresenterProtocol.kt b/circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/SwiftUIPresenterProtocol.kt
new file mode 100644
index 000000000..3a994e0b9
--- /dev/null
+++ b/circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/SwiftUIPresenterProtocol.kt
@@ -0,0 +1,10 @@
+package com.slack.circuit.swiftui
+
+import com.slack.circuit.swiftui.objc.CircuitSwiftUINavigatorProtocol
+
+public sealed class SwiftUIPresenterProtocol {
+    public abstract val state: Any
+    public abstract var navigator: CircuitSwiftUINavigatorProtocol?
+    public abstract fun setStateWillChangeListener(listener: () -> Unit)
+    public abstract fun cancel()
+}
diff --git a/circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/molecule.kt b/circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/molecule.kt
deleted file mode 100644
index 8119108f7..000000000
--- a/circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/molecule.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-package com.slack.circuit.swiftui
-
-import androidx.compose.runtime.Composable
-import app.cash.molecule.RecompositionClock
-import app.cash.molecule.launchMolecule
-import com.rickclephas.kmm.viewmodel.MutableStateFlow
-import com.rickclephas.kmm.viewmodel.ViewModelScope
-import com.rickclephas.kmm.viewmodel.coroutineScope
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-
-/**
- * Identical to the molecule implementation, but with a KMM-ViewModel [MutableStateFlow].
- * https://github.com/cashapp/molecule/blob/c902f7f60022911bf0cc6940cf86f3ff07c76591/molecule-runtime/src/commonMain/kotlin/app/cash/molecule/molecule.kt#L102
- */
-internal fun <T> ViewModelScope.launchMolecule(
-    clock: RecompositionClock,
-    body: @Composable () -> T,
-): StateFlow<T> {
-    var flow: MutableStateFlow<T>? = null
-    coroutineScope.launchMolecule(
-        clock = clock,
-        emitter = { value ->
-            val outputFlow = flow
-            if (outputFlow != null) {
-                outputFlow.value = value
-            } else {
-                flow = MutableStateFlow(this, value)
-            }
-        },
-        body = body,
-    )
-    return flow!!
-}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index a6cc1252a..395275906 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -28,7 +28,6 @@ detekt = "1.23.0"
 dokka = "1.8.10"
 eithernet = "1.4.0"
 jdk = "19"
-kmmviewmodel = "1.0.0-ALPHA-9"
 kotlin = "1.8.20"
 kotlinpoet = "1.13.2"
 kotlinx-coroutines = "1.7.1"
@@ -173,7 +172,6 @@ eithernet = { module = "com.slack.eithernet:eithernet", version.ref = "eithernet
 jline = "org.jline:jline:3.23.0"
 jsoup = "org.jsoup:jsoup:1.16.1"
 junit = "junit:junit:4.13.2"
-kmmviewmodel-core = { module = "com.rickclephas.kmm:kmm-viewmodel-core", version.ref = "kmmviewmodel"}
 kotlinx-immutable = "org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.5"
 kotlinpoet = { module = "com.squareup:kotlinpoet", version.ref = "kotlinpoet"}
 kotlinpoet-ksp = { module = "com.squareup:kotlinpoet-ksp", version.ref = "kotlinpoet"}
diff --git a/samples/counter/apps/Counter/Circuit.swift b/samples/counter/apps/Counter/Circuit.swift
index 6b6e9d858..04e71b224 100644
--- a/samples/counter/apps/Counter/Circuit.swift
+++ b/samples/counter/apps/Counter/Circuit.swift
@@ -7,16 +7,6 @@
 //
 
 import counter
-import SwiftUI
 import CircuitRuntime
-import CircuitSwiftUI
 
-extension Circuit_swiftuiSwiftUIPresenter: CircuitPresenter { }
-
-extension CircuitView {
-    init(_ presenter: @autoclosure @escaping () -> Presenter,
-         @ViewBuilder _ content: @escaping (State) -> Content
-    ) where Presenter: Circuit_swiftuiSwiftUIPresenter<State> {
-        self.init(presenter(), \.state, content)
-    }
-}
+extension Circuit_swiftuiSwiftUIPresenterProtocol: CircuitPresenter { }

From 499eaf09c7c40f614d6a815a250fc24db27a334c Mon Sep 17 00:00:00 2001
From: Rick Clephas <rclephas@gmail.com>
Date: Fri, 8 Sep 2023 19:31:24 +0200
Subject: [PATCH 5/5] Cleanup

---
 Package.swift                                          | 10 ++--------
 .../com/slack/circuit/swiftui/SwiftUINavigator.kt      |  4 ++--
 2 files changed, 4 insertions(+), 10 deletions(-)

diff --git a/Package.swift b/Package.swift
index f8e48a0f5..719e8dc34 100644
--- a/Package.swift
+++ b/Package.swift
@@ -14,12 +14,6 @@ let package = Package(
             targets: ["CircuitSwiftUI"]
         )
     ],
-    dependencies: [
-        .package(
-            url: "https://github.com/rickclephas/KMM-ViewModel.git",
-            from: "1.0.0-ALPHA-9"
-        )
-    ],
     targets: [
         .target(
             name: "CircuitRuntimeObjC",
@@ -28,12 +22,12 @@ let package = Package(
         ),
         .target(
             name: "CircuitRuntime",
-            dependencies: [.target(name: "CircuitRuntimeObjC"), .product(name: "KMMViewModelCore", package: "KMM-ViewModel")],
+            dependencies: [.target(name: "CircuitRuntimeObjC")],
             path: "CircuitRuntime"
         ),
         .target(
             name: "CircuitSwiftUI",
-            dependencies: [.target(name: "CircuitRuntime"), .product(name: "KMMViewModelSwiftUI", package: "KMM-ViewModel")],
+            dependencies: [.target(name: "CircuitRuntime")],
             path: "CircuitSwiftUI"
         )
     ],
diff --git a/circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/SwiftUINavigator.kt b/circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/SwiftUINavigator.kt
index 0c5af6d21..2489e5e22 100644
--- a/circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/SwiftUINavigator.kt
+++ b/circuit-swiftui/src/commonMain/kotlin/com/slack/circuit/swiftui/SwiftUINavigator.kt
@@ -5,9 +5,9 @@ import com.slack.circuit.runtime.Screen
 import com.slack.circuit.swiftui.objc.CircuitSwiftUINavigatorProtocol
 import platform.darwin.NSObject
 
-internal class SwiftUINavigator internal constructor(): Navigator {
+internal class SwiftUINavigator: Navigator {
 
-    internal var navigator: CircuitSwiftUINavigatorProtocol? = null
+    var navigator: CircuitSwiftUINavigatorProtocol? = null
 
     private fun requireNavigator(): CircuitSwiftUINavigatorProtocol =
         navigator ?: throw RuntimeException("SwiftUINavigator hasn't been initialized")