KMP shares business logic; Compose Multiplatform goes further and shares the UI itself, rendering the same @Composable tree through Skia on both Android and iOS instead of stopping at the ViewModel layer.
#The old way
A task checklist screen was written twice: LazyColumn and Checkbox on Android, List and Toggle on iOS — same layout, same interaction, same list of items, implemented in two declarative UI frameworks that don't share a line of code.
#The new way
data class Task(val id: String, val title: String, val done: Boolean)
@Composable
fun TaskListScreen(
tasks: List<Task>,
onToggle: (Task) -> Unit,
modifier: Modifier = Modifier
) {
LazyColumn(modifier = modifier.fillMaxSize()) {
items(tasks, key = { it.id }) { task ->
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Checkbox(checked = task.done, onCheckedChange = { onToggle(task) })
Text(text = task.title, modifier = Modifier.padding(start = 8.dp))
}
}
}
}
fun MainViewController(tasks: List<Task>, onToggle: (Task) -> Unit) =
ComposeUIViewController { TaskListScreen(tasks = tasks, onToggle = onToggle) }
TaskListScreen compiles for both androidMain and iosMain unchanged; ComposeUIViewController wraps it as a UIViewController that a SwiftUI UIViewControllerRepresentable or a plain UIKit push can host.
#Why it matters
- One layout, one set of interaction states, one set of UI bugs to fix instead of two
- Design changes (spacing, typography, a new state) ship to both platforms in a single PR
- Still interoperates with native navigation — a Compose screen can sit inside an otherwise-native iOS nav stack
#Gotcha
Compose Multiplatform on iOS renders through Skia inside a hosted UIViewController, not through UIKit or SwiftUI's layout system — it doesn't automatically pick up SwiftUI's safe-area or keyboard-avoidance behavior. A TaskListScreen embedded next to native screens can render content under the notch or behind the keyboard unless you thread WindowInsets through it explicitly, something a pure SwiftUI screen gets for free. Screens dominated by platform-native transitions, widgets, or heavy accessibility customization are often still better left native.