Building iOS Apps with Compose Multiplatform

3 min read

What I learned

In this post, I'll share the practical aspects of what I learned building iOS apps with Compose Multiplatform.


Understanding the Internals

Project Structure

A typical Compose Multiplatform project structure:

project/ ├── iosApp/ # iOS-specific code (Swift/UIKit wrapper) ├── shared/ # Shared Kotlin code │ ├── commonMain/ # Platform-agnostic code │ ├── androidMain/ # Android-specific implementations │ └── iosMain/ # iOS-specific implementations └── composeApp/ # Shared Compose UI code

From KMP project template

Here's a generated ContentView.swift from project template.

import SwiftUI import Shared struct ComposeView: UIViewControllerRepresentable { func makeUIViewController(context: Context) -> UIViewController { MainViewControllerKt.MainViewController() } func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} } struct ContentView: View { var body: some View { ComposeView() .ignoresSafeArea(.all) } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }

This exact code runs natively on iOS without any modifications.

Layer-by-layer view

iOS App └── SwiftUI └── UIViewControllerRepresentable └── UIKit UIViewController └── Compose Runtime └── Skia / Metal rendering

In a Kotlin Multiplatform project using Compose Multiplatform on iOS, the UI is rendered through a layered architecture rather than directly by SwiftUI or UIKit.

SwiftUI acts only as the entry point of the app, embedding a UIViewController via UIViewControllerRepresentable.

That UIViewController is not responsible for laying out views or drawing UI elements; instead, it serves as a host required by iOS to participate in the app lifecycle.

This makes UIKit the structural container, ensuring compatibility with iOS windowing, lifecycle, and event delivery, while remaining largely unaware of how the UI itself is drawn.

Inside this UIKit container runs the Compose runtime, which owns layout, state, input handling, and rendering. Compose does not use UIKit components like UILabel or UIButton;

Instead, it renders its UI using Skia, a cross-platform graphics engine, which in turn targets Metal for hardware-accelerated drawing on iOS.

Touch and system events flow from UIKit into the Compose runtime, but all visual output and UI logic remain entirely in Kotlin.

As a result, the app achieves a single shared UI codebase across Android and iOS, while still integrating cleanly into the native iOS application stack without relying on a JavaScript bridge or web-based rendering.


The Future of Compose Multiplatform on iOS

Compose Multiplatform for iOS is rapidly maturing. Recent improvements include:

  • Better performance optimizations
  • Improved IDE support
  • Growing ecosystem of libraries
  • Better interop with Swift and SwiftUI

JetBrains is actively investing in the platform, with regular updates and improvements.


Conclusion

Compose Multiplatform offers a compelling approach to iOS development, especially for teams already using Kotlin or building cross-platform apps. While it requires learning Kotlin and the Compose paradigm, the benefits of code sharing, type safety, and modern declarative UI make it a powerful tool in the mobile developer's toolkit.

The key is knowing when to use shared code and when to embrace platform-specific implementations. With the right balance, you can build high-quality iOS apps while maximizing code reuse across platforms.

Thanks for reading. Happy coding!