KSP library for generating ComposeUIViewController
and UIViewControllerRepresentable
implementations when using Compose Multiplatform for iOS.
Version | Kotlin | KSP | Compose Multiplatform | Xcode |
---|---|---|---|---|
2.1.0 | 1.0.29 | 1.7.1 | 16.2.0 |
The suffix -BETA
will be added to reflect JetBrain's Compose Multiplatform iOS stability level, until it becomes STABLE
.
As the project expands, the codebase required naturally grows, which can quickly become cumbersome and susceptible to errors. To mitigate this challenge, this library leverages Kotlin Symbol Processing to automatically generate the necessary Kotlin and Swift code for you.
It can be used for simple and advanced use cases.
@Composable
UI state is managed inside the common code from the KMP module.
@Composable
UI state is managed by the iOS app.
Kotlin Multiplatform and Compose Multiplatform are built upon the philosophy of incremental adoption and sharing only what you require. Consequently, the support for this specific use-case - in my opinion - is of paramount importance, especially in its capacity to entice iOS developers to embrace Compose Multiplatform.
Tip
This library takes care of the heavy lifting for you, but if you're interested in understanding how it works, the detailed approach is explained here: Compose Multiplatform — Managing UI State on iOS.
By using the Gradle plugin all configurations will be applied automatically. If you wish to change the default values, you can configure its parameters using the available extension.
plugins {
id("org.jetbrains.kotlin.multiplatform")
id("com.google.devtools.ksp")
id("io.github.guilhe.kmp.plugin-composeuiviewcontroller") version "$LASTEST_VERSION"
}
ComposeUiViewController {
iosAppName = "Gradient"
targetName = "Gradient"
}
Inside iosMain
we can take advantage of two annotations:
@ComposeUIViewController
:
To annotate the @Composable
as a desired ComposeUIViewController
to be used by the iOS app.
@ComposeUIViewControllerState
:
To annotate the parameter as the composable state variable (for advanced use cases).
Important
Only 0 or 1 @ComposeUIViewControllerState
and an arbitrary number of parameter types (excluding @Composable
) are allowed in @ComposeUIViewController
functions.
The @ComposeUIViewController
includes a frameworkBaseName
parameter, allowing you to specify a framework name manually. While the plugin typically attempts to retrieve this name automatically, you can use this parameter to enforce a specific name if the automatic retrieval fails.
For more information consult the ProcessorTest.kt file from kmp-composeuiviewcontroller-ksp
.
Simple
//iosMain
@ComposeUIViewController
@Composable
internal fun ComposeSimpleView() { }
will produce a ComposeSimpleViewUIViewController
:
object ComposeSimpleViewUIViewController {
fun make(): UIViewController {
return ComposeUIViewController {
ComposeSimpleView()
}
}
}
and also a ComposeSimpleViewRepresentable
:
import SwiftUI
import SharedUI
public struct ComposeSimpleViewRepresentable: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIViewController {
ComposeSimpleViewUIViewController().make()
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
//unused
}
}
Advanced
//iosMain
data class ViewState(val isLoading: Boolean)
@ComposeUIViewController
@Composable
internal fun ComposeAdvancedView(@ComposeUIViewControllerState viewState: ViewState, callback: () -> Unit) { }
will produce a ComposeAdvancedViewUIViewController
:
object ComposeAdvancedViewUIViewController {
private val viewState = mutableStateOf<ViewState?>(null)
fun make(callback: () -> Unit): UIViewController {
return ComposeUIViewController {
viewState.value?.let { ComposeAdvancedView(it, callback) }
}
}
fun update(viewState: ViewState) {
this.viewState.value = uiState
}
}
and also a ComposeAdvancedViewRepresentable
:
import SwiftUI
import SharedUI
public struct ComposeAdvancedViewRepresentable: UIViewControllerRepresentable {
@Binding var viewState: ViewState
let callback: () -> Void
func makeUIViewController(context: Context) -> UIViewController {
ComposeAdvancedViewUIViewController().make(callback: callback)
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
ComposeAdvancedViewUIViewController().update(viewState: viewState)
}
}
After a successful build the UIViewControllerRepresentable
files are included and referenced in the xcodeproj
ready to be used:
import SwiftUI
import SharedUI
struct SomeView: View {
@State private var state: ViewState = ViewState(isLoading: false)
var body: some View {
VStack {
ComposeSimpleViewRepresentable()
ComposeAdvancedViewRepresentable(viewState: $state, callback: {})
}
}
}
Important
Avoid deleting iosApp/Representables
without first using Xcode to Remove references
.
For a working sample open iosApp/Gradient.xcodeproj
in Xcode and run standard configuration or use KMM plugin for Android Studio and choose iosApp
in run configurations.
> Task :shared:kspKotlinIosSimulatorArm64
note: [ksp] loaded provider(s): [com.github.guilhe.kmp.composeuiviewcontroller.ksp.ProcessorProvider]
note: [ksp] GradientScreenUIViewController created!
note: [ksp] GradientScreenRepresentable created!
> Task :CopyFilesToXcode
> Copying files to iosApp/SharedRepresentables/
> Checking for new references to be added to xcodeproj
> GradientScreenUIViewControllerRepresentable.swift added!
> Done
You can also find other working samples in:
Copyright (c) 2023-present GuilhE
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.