compose-multiplatform
compose-multiplatform copied to clipboard
No way to create a UIKitView with transparent background
Describe the bug
I want to place UIKIt view with transparent background on top of my Compose content, but this line cuts underlying Compose content and exposes the white root view
Versions
- Compose Multiplatform version*: 1.4.0
This is a limitation of the current implementation. We will look in the future, if it is possible to fully support transparency.
I add enhancement
, and keep bug
until we add a check into code:
require(!background.isSpecified || background.alpha == 1.0) {
"Transparent background isn't supported at the moment. Follow https://github.com/JetBrains/compose-multiplatform/issues/3154"
}
Hope you find a way some day!
Is there a workaround for this?
This is really a blocking issue as I cannot use UIKitView with UIImageView to display png
icons.
@alexeiartsimovich Are you sure you need to use a uiimageview for that? What about compose image/icon?
@alexzhirkevich I need to display a local png
icon on iOS. Can compose image/icon do this? I didn't find an example
Like from resources or from file system? There are many libraries for common resources. Official one is used in template. You can also display image from file system by decoding byte array to skia image
Also if you want to display iOS system icons in compose you can use this trick (there is also an example of decoding UIImage to compose bitmap)
Wow I didn't know we can draw vectors on iOS. Thanks!
This can be any image (png, jpeg). Not only xml
Hi @dima-avdeev-jb , I am also facing same thing. is there any workaround until this issue resolved?
@JamshedAlamQaderi Here's a temporary workaround
The main difference from original source is:
The last compose version is used
@rustamsmax thanks for the workaround. I'm gonna try it
@rustamsmax Doing this UiKitView still adds a flicker to the view before setting the background color The flicker is still white. This is how I am doing it.
var animation by remember { mutableStateOf<CompatibleAnimation?>(null) }
LaunchedEffect(Unit) {
animation = CompatibleAnimation(
name = animationRes.fileName,
subdirectory = null,
animationRes.bundle
)
}
when (val value = animation) {
null -> {}
else -> {
UIKitView(
modifier = modifier,
factory = {
UIView().apply {
backgroundColor = UIColor.clearColor
opaque = true
setClipsToBounds(true)
}
},
background = MaterialTheme.colorScheme.surface,
update = {
val view = CompatibleAnimationView()
view.translatesAutoresizingMaskIntoConstraints = false
it.addSubview(view)
NSLayoutConstraint.activateConstraints(
listOf(
view.widthAnchor.constraintEqualToAnchor(it.widthAnchor),
view.heightAnchor.constraintEqualToAnchor(it.heightAnchor)
)
)
view.setAnimationSpeed(speed.toDouble())
view.setCompatibleAnimation(value)
view.setLoopAnimationCount(if (isInfinite) -1.0 else 1.0)
view.setContentMode(UIViewContentMode.UIViewContentModeScaleAspectFit)
view.playWithCompletion { completed ->
if (completed) onComplete?.invoke()
}
}
)
}
}
Hi @rustamsmax , it's not working.
@LaatonWalaBhoot
I couldn't replicate reported behavior using the snippet below. Could you please make a minimal reproducible one which I can investigate?
val UIKitRenderSync = Screen.Example("UIKitRenderSync") {
var text by remember { mutableStateOf("Type something") }
var showUIViews by remember { mutableStateOf(true) }
LazyColumn(Modifier.fillMaxSize().background(Color.Red)) {
item {
Button(onClick = {
showUIViews = !showUIViews
}) {
Text("Click")
}
}
items(100) { index ->
when (index % 4) {
0 -> Text("material.Text $index", Modifier.fillMaxSize().height(40.dp))
1 -> {
if (showUIViews) {
UIKitView(
factory = {
val label = UILabel(frame = CGRectZero.readValue())
label.text = "UILabel $index"
label.textColor = UIColor.blackColor
label.backgroundColor = UIColor.clearColor
label
},
background = Color.Green,
modifier = Modifier.fillMaxWidth().height(40.dp)
)
}
}
2 -> TextField(text, onValueChange = { text = it }, Modifier.fillMaxWidth())
else -> {
if (showUIViews) {
ComposeUITextField(text, onValueChange = { text = it }, Modifier.fillMaxWidth().height(40.dp))
}
}
}
}
}
}
/**
* Compose wrapper for native UITextField.
* @param value the input text to be shown in the text field.
* @param onValueChange the callback that is triggered when the input service updates the text. An
* updated text comes as a parameter of the callback
* @param modifier a [Modifier] for this text field. Size should be specified in modifier.
*/
@Composable
private fun ComposeUITextField(value: String, onValueChange: (String) -> Unit, modifier: Modifier) {
val latestOnValueChanged by rememberUpdatedState(onValueChange)
UIKitView(
factory = {
val textField = object : UITextField(CGRectMake(0.0, 0.0, 0.0, 0.0)) {
@ObjCAction
fun editingChanged() {
latestOnValueChanged(text ?: "")
}
}
textField.addTarget(
target = textField,
action = NSSelectorFromString(textField::editingChanged.name),
forControlEvents = UIControlEventEditingChanged
)
textField
},
modifier = modifier,
update = { textField ->
textField.text = value
}
)
}
https://github.com/JetBrains/compose-multiplatform/assets/4167681/05153560-5866-4d92-957c-db4347c4e7ec
@elijah-semyonov could you change the background color to Color.Transparent
and check if the LazyColumn
background color Red
is visible or not?
It won't be visible, because currently interop views are drawn behind the compose view through the transparent holes.
@elijah-semyonov isn't it possible to stack it to top off all compose views by anyway?
In absolute terms - yes, it is possible and we will probably do it that way in the future. It will require adapting all modifiers that affect masking (like round corners) for native views.
@elijah-semyonov Hi, do u have approximate date or release when it can be on release? Thx
@BoykoDmytro We currently don't work on this specific issue and it's not planned yet.
@elijah-semyonov Is there any particular reason why this issue will not worked on for the next release? Supporting dark mode is impossible with this because of white blinking which seemingly presents as a UI glitch Especially when using animations
@LaatonWalaBhoot
We actually did some work in this direction. Can you check if ComposeUIViewControllerConfiguration.opaque
is sufficient for your case?
@elijah-semyonov this is available with what version of CMP?
@LaatonWalaBhoot It should be present in 1.6.0
@elijah-semyonov is there any workaround to avoid transparent background for UIKitView and use compose theme color?
Here is a code:
fun SomeDialog() {
Dialog {
Scaffold(containerColor = MaterialTheme.colorScheme.background) {
Surface(color = MaterialTheme.colorScheme.surface) {
SomeTextField()
}
}
}
}
@Composable
expect fun SomeTextField()
@OptIn(ExperimentalForeignApi::class, BetaInteropApi::class)
@Composable
actual fun SomeTextField() {
var value by remember { mutableStateOf("") }
UIKitView(
factory = {
val textField = object : UITextField(
CGRectMake(0.0, 0.0, 0.0, 0.0)
) {
@ObjCAction
fun editingChanged() {
value = text ?: ""
}
}
textField.addTarget(
target = textField,
action = NSSelectorFromString(textField::editingChanged.name),
forControlEvents = UIControlEventEditingChanged
)
// textField.backgroundColor = whiteColor // I don't want to hardcode color
textField
},
modifier = Modifier.background(Color.White).padding(16.dp),
update = { textField -> textField.text = value },
onRelease = { textField ->
textField.removeTarget(
target = textField,
action = NSSelectorFromString(textField::editingChanged.name),
forControlEvents = UIControlEventEditingChanged
)
}
)
}
It's been so long. but still facing this issue. Please somehow help me. Background shows white if backgroundColor set to transparent
UIKitView(
factory = {
UIView().apply {
backgroundColor = UIColor.clearColor
opaque = false
clipsToBounds = true
}
},
update = {
it.backgroundColor = UIColor.clearColor
it.opaque = false
it.clipsToBounds = true
},
modifier = Modifier.size(250.dp).border(1.dp, Color.Transparent).padding(50.dp),
)
@JamshedAlamQaderi This worked for me. Make sure you are on latest compose version
UIKitView(
modifier = modifier,
factory = {
UIView().apply {
backgroundColor = UIColor.clearColor
opaque = false
setClipsToBounds(true)
}
},
background = Color.Transparent,
update = {
val view = CompatibleAnimationView()
view.translatesAutoresizingMaskIntoConstraints = false
it.addSubview(view)
it.opaque = true
NSLayoutConstraint.activateConstraints(
listOf(
view.widthAnchor.constraintEqualToAnchor(it.widthAnchor),
view.heightAnchor.constraintEqualToAnchor(it.heightAnchor)
)
)
view.setAnimationSpeed(speed.toDouble())
view.setCompatibleAnimation(animation)
view.setLoopAnimationCount(if (isInfinite) -1.0 else 1.0)
view.setContentMode(contentMode)
view.playWithCompletion { completed ->
if (completed) onComplete?.invoke()
}
}
)
@LaatonWalaBhoot thank you for the workaround. I'm gonna give it a try with your example. Compose: 1.6.10
Hi @LaatonWalaBhoot , could you tell me what is the package of CompatibleAnimationView()
?