Gipfeli
Gipfeli

Reputation: 319

Create Platform specific Views in KMM Compose Multiplatform

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?

Upvotes: 3

Views: 1546

Answers (1)

Phil Dukhov
Phil Dukhov

Reputation: 88082

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 = {
        ...
    })
}

Upvotes: 3

Related Questions