Swift–Kotlin Interoperability in Compose for iOS
This article demonstrates how to bridge Swift and Kotlin in a Compose‑Multiplatform iOS app by using Swift view modifiers to detect orientation changes and call Kotlin functions, while exposing Kotlin callbacks that Swift registers to change device orientation, enabling seamless two‑way platform‑specific logic integration.
Preface
Similar to Android's Compose, iOS Compose also supports nesting Compose UI with SwiftUI or UIKit. However, unlike Android which uses Kotlin, iOS development uses Swift or Objective‑C. Some platform‑specific logic must be implemented in native iOS code and called from Kotlin.
This article demonstrates, with a real project, how to achieve business‑logic interoperation between Swift and Kotlin in Compose for iOS.
Swift Calls Kotlin
In the github.com/equationl/calculator-Compose-MultiPlatform project we need to listen for screen‑rotation events and switch the keyboard type accordingly. Because rotation listening is iOS‑specific, it must be implemented in Swift and then invoke Kotlin code.
First, add a view modifier in ContentView.swift that subscribes to UIDevice.orientationDidChangeNotification :
struct DetectOrientation: ViewModifier {
func body(content: Content) -> some View {
content
.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
// trigger screen orientation change event
}
}
}
extension View {
func detectOrientation() -> some View {
modifier(DetectOrientation())
}
}Apply the modifier to the Compose view:
struct ContentView: View {
var body: some View {
VStack {
ComposeView()
.ignoresSafeArea(.keyboard) // Compose has its own keyboard handler
}.detectOrientation()
}
}When the device orientation changes, the Swift code calls the Kotlin function Main_iosKt.onScreenChange(orientation: Int) :
struct DetectOrientation: ViewModifier {
func body(content: Content) -> some View {
content
.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
if UIDevice.current.orientation.isLandscape {
Main_iosKt.onScreenChange(orientation: 1)
} else {
Main_iosKt.onScreenChange(orientation: 0)
}
}
}
}The corresponding Kotlin function resides in shared/iosMain/main.ios.kt :
/**
* @param orientation 0 portrait, 1 landscape
*/
fun onScreenChange(orientation: Int) {
if (orientation == 0) {
homeChannel?.trySend(
HomeAction.OnScreenOrientationChange(changeToType = KeyboardTypeStandard)
)
} else {
homeChannel?.trySend(
HomeAction.OnScreenOrientationChange(changeToType = KeyboardTypeProgrammer)
)
}
}Kotlin Calls Swift
Most business logic can be written in Kotlin, but some platform‑specific APIs (e.g., Bluetooth, screen rotation) require Swift. Kotlin Multiplatform already wraps many iOS APIs, such as CoreBluetooth:
import platform.CoreBluetooth.CBCentralManager
import platform.CoreBluetooth.CBManagerAuthorizationAllowedAlways
import platform.CoreBluetooth.CBManagerAuthorizationDenied
import platform.CoreBluetooth.CBManagerAuthorizationNotDetermined
import platform.CoreBluetooth.CBManagerAuthorizationRestricted
internal class BluetoothPermissionDelegate : PermissionDelegate {
override fun getPermissionState(): PermissionState {
return when (CBCentralManager.authorization) {
CBManagerAuthorizationNotDetermined -> { /* not granted */ }
CBManagerAuthorizationAllowedAlways, CBManagerAuthorizationRestricted -> { /* granted */ }
CBManagerAuthorizationDenied -> { /* denied */ }
else -> { /* other */ }
}
}
override suspend fun providePermission() {
CBCentralManager().authorization()
}
override fun openSettingPage() {
// open settings
}
}For functionality not wrapped (e.g., forcing screen orientation), we implement a Swift helper:
func changeOrientation(to orientation: UIInterfaceOrientation) {
if #available(iOS 16.0, *) {
let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene
if orientation.isPortrait {
windowScene?.requestGeometryUpdate(.iOS(interfaceOrientations: .portrait))
} else {
windowScene?.requestGeometryUpdate(.iOS(interfaceOrientations: .landscape))
}
} else {
UIDevice.current.setValue(orientation.rawValue, forKey: "orientation")
}
}To let Kotlin invoke this Swift function, we expose a callback from Kotlin that Swift can set:
var changeScreenOrientationFunc: ((to: Int) -> Unit)? = null
fun changeScreenOrientation(callBack: (to: Int) -> Unit) {
changeScreenOrientationFunc = callBack
}Swift registers the callback during initialization:
Main_iosKt.changeScreenOrientation { kotlinInt ->
if (kotlinInt == 0) {
changeOrientation(to: UIInterfaceOrientation.portrait)
} else {
changeOrientation(to: UIInterfaceOrientation.landscapeLeft)
}
}Summary
The article shows how to bridge Swift and Kotlin in a Compose‑Multiplatform iOS project: Swift listens to device rotation, forwards the event to Kotlin, Kotlin updates the UI state, and Kotlin can also request Swift to change the device orientation via a callback mechanism. Full source code is available at github.com/equationl/calculator-Compose-MultiPlatform .
Sohu Tech Products
A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.