KSP library and Gradle plugin for generating ComposeUIViewController
and UIViewControllerRepresentable
files when using Compose Multiplatform for iOS.
Version | Kotlin | KSP | Compose Multiplatform | Xcode |
---|---|---|---|---|
2.3.0-dev-5897 | 2.0.2 | 1.9.0-rc01 | 26.0.0 | |
2.2.20-1.9.0-rc01 |
2.2.20 | 2.0.2 | 1.9.0-rc01 | 26.0.0 |
Tip
For Swift Export support, until the official release of Kotlin 2.3.0, use 2.3.0-dev-*
.
Don't forget to change embedAndSignAppleFrameworkForXcode
to embedSwiftExportForXcode
in your project.pbxproj
, and delete the Derived Data
(recommended when switching between modes)
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.
Note
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.
Configure the plugins
block with the following three plugins. Once added, you can use the ComposeUiViewController
block to set up the plugin’s configuration.
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"
}
With this setup, all necessary configurations are automatically applied. You only need to adjust the ComposeUiViewController
block to match your
project settings (e.g. iosAppName
and targetName
). If you wish to change the default values, you can configure its parameters:
Parameters available
iosAppFolderName
name of the folder containing the iosApp in the root's project tree;iosAppName
name of the iOS project (name.xcodeproj
);targetName
name of the iOS project's target;exportFolderName
name of the destination folder inside iOS project (iosAppFolderName
) where theUIViewControllerRepresentable
files will be copied to whenautoExport
istrue
;autoExport
enables auto export generated files to Xcode project. If set tofalse
, you will find the generated files under/build/generated/ksp/
;
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.
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 Shared
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 Shared
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)
}
}
Tip
The @ComposeUIViewController
has a frameworkBaseName
parameter to manually set the framework name. This parameter will only be used if detection fails within the Processor.
After a successful build the UIViewControllerRepresentable
files are included and referenced in the xcodeproj
ready to be used:
import SwiftUI
import Shared
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 KMP 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] GradientScreenUIViewControllerRepresentable created!
> Task :CopyFilesToXcode
> Copying files to iosApp/Representables/
> 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.