From 521d98905b331edff3a83ecd15b9a3e6da4417eb Mon Sep 17 00:00:00 2001 From: Rick Clephas 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 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 internal constructor( + private val presenter: Presenter +): KMMViewModel() { + + private val stateFlow = viewModelScope.launchMolecule(RecompositionClock.Immediate) { + presenter.present() + } + + public val state: UiState get() = stateFlow.value +} + +public fun swiftUIPresenterOf( + body: @Composable (Navigator) -> UiState +): SwiftUIPresenter { + 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 ViewModelScope.launchMolecule( + clock: RecompositionClock, + body: @Composable () -> T, +): StateFlow { + var flow: MutableStateFlow? = 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 = ""; }; 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 1DE43CE12A2109E500EB0E36 /* KMM-ViewModel */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "KMM-ViewModel"; path = "../../../../../Rick Clephas/KMM-ViewModel"; sourceTree = ""; }; + 1DE43CE22A210A1400EB0E36 /* KMMViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KMMViewModel.swift; sourceTree = ""; }; 2152FB032600AC8F00CF470E /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = ""; }; 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 = ""; }; @@ -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 = ""; }; + 1DE43CE02A2109E500EB0E36 /* Packages */ = { + isa = PBXGroup; + children = ( + 1DE43CE12A2109E500EB0E36 /* KMM-ViewModel */, + ); + name = Packages; + sourceTree = ""; + }; 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 = ""; @@ -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 { - init() { - // TODO why can't swift infer these generics? - super.init( - delegate: SwiftSupportKt.asSwiftPresenter(SwiftSupportKt.doNewCounterPresenter()) - as! SwiftPresenter) - } -} - -class BasePresenter: ObservableObject { - @Published var state: T? = nil - - init(delegate: SwiftPresenter) { - 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 { + 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 Presenter.asSwiftPresenter(): SwiftPresenter { - 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 -internal constructor( - private val delegate: Presenter, -) { - // 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 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 = ""; }; + 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 = ""; }; + 1DE43D1F2A2266AF00EB0E36 /* CircuitSwiftUINavigator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CircuitSwiftUINavigator.h; sourceTree = ""; }; + 1DE43D222A2267EB00EB0E36 /* CircuitPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircuitPresenter.swift; sourceTree = ""; }; + 1DE43D272A226BF200EB0E36 /* CircuitNavigationStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircuitNavigationStack.swift; sourceTree = ""; }; + 1DE43D292A226C3A00EB0E36 /* CircuitNavigator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircuitNavigator.swift; sourceTree = ""; }; + 1DE43D2B2A226E2E00EB0E36 /* CircuitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircuitView.swift; sourceTree = ""; }; +/* 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 = ""; + }; + 1DE43CF32A2265C600EB0E36 /* Products */ = { + isa = PBXGroup; + children = ( + 1DE43CF22A2265C600EB0E36 /* CircuitRuntime.framework */, + 1DE43CFF2A2265EE00EB0E36 /* CircuitRuntimeObjC.framework */, + 1DE43D0B2A2265FD00EB0E36 /* CircuitSwiftUI.framework */, + ); + name = Products; + sourceTree = ""; + }; + 1DE43CF42A2265C600EB0E36 /* CircuitRuntime */ = { + isa = PBXGroup; + children = ( + 1DE43D222A2267EB00EB0E36 /* CircuitPresenter.swift */, + 1DE43D292A226C3A00EB0E36 /* CircuitNavigator.swift */, + ); + path = CircuitRuntime; + sourceTree = ""; + }; + 1DE43D002A2265EE00EB0E36 /* CircuitRuntimeObjC */ = { + isa = PBXGroup; + children = ( + 1DE43D012A2265EE00EB0E36 /* CircuitRuntimeObjC.h */, + 1DE43D1D2A22669D00EB0E36 /* CircuitRuntimeObjC.m */, + 1DE43D1F2A2266AF00EB0E36 /* CircuitSwiftUINavigator.h */, + ); + path = CircuitRuntimeObjC; + sourceTree = ""; + }; + 1DE43D0C2A2265FD00EB0E36 /* CircuitSwiftUI */ = { + isa = PBXGroup; + children = ( + 1DE43D272A226BF200EB0E36 /* CircuitNavigationStack.swift */, + 1DE43D2B2A226E2E00EB0E36 /* CircuitView.swift */, + ); + path = CircuitSwiftUI; + sourceTree = ""; + }; + 1DE43D122A22660400EB0E36 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* 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 + +@protocol CircuitSwiftUINavigator +- (void)goToScreen:(NSObject * _Nonnull)screen __attribute__((swift_name("goTo(screen:)"))); +- (NSObject * _Nullable)pop __attribute__((swift_name("pop()"))); +- (NSArray * _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: 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: View { + + @EnvironmentObject private var navigator: CircuitNavigator + @StateViewModel private var presenter: Presenter + private var stateKeyPath: KeyPath + private var content: (State) -> Content + + public init(_ presenter: @autoclosure @escaping () -> Presenter, + _ stateKeyPath: KeyPath, + @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 = + 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 internal constructor( + private val swiftUINavigator: SwiftUINavigator, private val presenter: Presenter ): KMMViewModel() { + public interface Factory { + public fun presenter(): SwiftUIPresenter + } 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 swiftUIPresenterOf( body: @Composable (Navigator) -> UiState ): SwiftUIPresenter { - 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 = ""; }; 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; - 1DE43CE12A2109E500EB0E36 /* KMM-ViewModel */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "KMM-ViewModel"; path = "../../../../../Rick Clephas/KMM-ViewModel"; sourceTree = ""; }; - 1DE43CE22A210A1400EB0E36 /* KMMViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KMMViewModel.swift; sourceTree = ""; }; + 1DC5EA882A22785A00037003 /* PrimeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrimeView.swift; sourceTree = ""; }; + 1DE43CE22A210A1400EB0E36 /* Circuit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Circuit.swift; sourceTree = ""; }; + 1DE43D322A2272A300EB0E36 /* circuit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = circuit; path = ../../..; sourceTree = ""; }; 2152FB032600AC8F00CF470E /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = ""; }; 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 = ""; }; 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 = ""; }; + 7555FF82242A565900829871 /* CounterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CounterView.swift; sourceTree = ""; }; 7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 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 = ""; }; /* 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 = ""; @@ -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 = ""; @@ -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 { + 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 { - 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 { + override fun presenter() = swiftUIPresenterOf { CounterPresenter(it) } +} + +data class IosPrimeScreen( + override val number: Int +): PrimeScreen, SwiftUIPresenter.Factory { + override fun presenter() = swiftUIPresenterOf { PrimePresenter(it, number) } +} From 87b8dde12f217d4bc14b826446ef674bcbd1cdc2 Mon Sep 17 00:00:00 2001 From: Rick Clephas 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 = ""; }; 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 = ""; }; @@ -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 = ""; @@ -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: View { +public struct CircuitView: View { @EnvironmentObject private var navigator: CircuitNavigator - @StateViewModel private var presenter: Presenter + @StateObject private var observableObject: ObservablePresenter private var stateKeyPath: KeyPath private var content: (State) -> Content public init(_ presenter: @autoclosure @escaping () -> Presenter, - _ stateKeyPath: KeyPath, - @ViewBuilder _ content: @escaping (State) -> Content + @ViewBuilder _ content: @escaping (State) -> Content, + _ stateKeyPath: KeyPath = \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: 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 { + weak var observablePresenter: ObservablePresenter? + init(_ observablePresenter: ObservablePresenter) { + self.observablePresenter = observablePresenter + } +} + +internal func observablePresenter(for presenter: Presenter) -> ObservablePresenter { + if let object = objc_getAssociatedObject(presenter, &observablePresenterKey) { + guard let observablePresenter = (object as! WeakObservablePresenter).observablePresenter else { + fatalError("ObservablePresenter has been deallocated") + } + return observablePresenter + } else { + let observablePresenter = ObservablePresenter(presenter) + let object = WeakObservablePresenter(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 internal constructor( private val swiftUINavigator: SwiftUINavigator, private val presenter: Presenter -): KMMViewModel() { +): SwiftUIPresenterProtocol() { public interface Factory { public fun presenter(): SwiftUIPresenter } - 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 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 ViewModelScope.launchMolecule( - clock: RecompositionClock, - body: @Composable () -> T, -): StateFlow { - var flow: MutableStateFlow? = 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 { - self.init(presenter(), \.state, content) - } -} +extension Circuit_swiftuiSwiftUIPresenterProtocol: CircuitPresenter { } From 499eaf09c7c40f614d6a815a250fc24db27a334c Mon Sep 17 00:00:00 2001 From: Rick Clephas 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")