Create Platform specific Views in KMM Compose Multiplatform

149 views Asked by At

I'm very new to Kotlin and Compose Multiplatform but I wonder if there is a way to create Platform specific Views between iOS and Android.

I've managed to make a Platform specific Button for Android but the one for iOS is not visible. The application does run on both platforms without errors.

Usage in App.kt in commonMain:

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp


@Composable
fun App() {
    var counter by remember { mutableStateOf(0) }

    MaterialTheme {
        Column(
            verticalArrangement = Arrangement.Center,
            horizontalAlignment = Alignment.CenterHorizontally,
            modifier = Modifier
                .fillMaxSize()
        ) {
            Column(
                verticalArrangement = Arrangement.spacedBy(16.dp),
            ) {
                Text("Counter: $counter")
                PlatformButton.createButton("Click to increase Counter") {
                    counter++
                }
            }
        }
    }
}

My expect class in commonMain:

import androidx.compose.runtime.Composable

expect class PlatformButton {
    companion object {
        @Composable
        fun createButton(label: String, onClick: () -> Unit): Any
    }
}

My actual class in androidMain:

import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable

actual class PlatformButton {
    actual companion object {
        @Composable
        actual fun createButton(label: String, onClick: () -> Unit): Any {
            return Button(onClick) {
                Text(label)
            }
        }
    }
}

My actual class in iosMain:

import androidx.compose.runtime.Composable
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.ObjCAction
import platform.UIKit.UIButton
import platform.UIKit.UIButtonTypeSystem
import platform.UIKit.UIControlEventTouchUpInside
import platform.darwin.NSObject
import platform.darwin.sel_registerName

actual class PlatformButton {
    actual companion object {
        @OptIn(ExperimentalForeignApi::class)
        @Composable
        actual fun createButton(label: String, onClick: () -> Unit): Any {
            val uiButton = UIButton.buttonWithType(UIButtonTypeSystem)
            uiButton.setTitle(label, forState = 0u)

            val block = object : NSObject() {
                @ObjCAction
                fun buttonClicked() {
                    onClick()
                }
            }

            uiButton.addTarget(block, action = sel_registerName("buttonClicked"), forControlEvents = UIControlEventTouchUpInside)

            return uiButton
        }
    }
}

I've searched for documentations but couldn't find any. I also don't really like the use of UIKit in Kotlin. Isn't there maybe another way of using SwiftUI-Code and somehow create a Composable View out of it with a bridge? What I'm also wondering: Is there a specific return type for such a use case instead of Any?

1

There are 1 answers

2
Phil Dukhov On BEST ANSWER

On iOS, you create UIButton and expect compose to draw it, but it doesn't know how to render this view.

To bridge UIKit view into Compose, you need UIKitView:

UIKitView(factory = {
    val uiButton = ...
    uiButton
})

Also, you don't need a class to define expect compose function, you can declare it directly - it'll make your compose code look much more natural:

@Composable
expect PlatformButton(...)
@Composable
actual PlatformButton(...) {
    UIKitView(factory = {
        ...
    })
}