Open alexzhirkevich opened 1 year ago
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
}
)
}
@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()
?
@LaatonWalaBhoot I tried your solution but it doesn't work for me?
@LaatonWalaBhoot neither work for me too
i found a solutions
fun UIView.parent(count:Int):UIView?{
if(count<=0)return null
var c = count-1
var v:UIView? = this.superview
while ((c-- >0 && v!=null)){
v =v.superview
}
return v
}
update = { view ->
val parent = view.parent(4) ?: return@UIKitView
if(parent.tag?.toInt()!=10086){
parent.opaque = true
parent.backgroundColor = UIColor.clearColor
parent.findViewController()?. apply controller@ {
coroutineScope.launch {
var notStop=true
while (notStop){
withContext(uoocDispatchers.io){
delay(100)
withContext(uoocDispatchers.main){
if(this@controller.isViewLoaded()){
notStop=false
this@controller.view.opaque = true
this@controller.view.backgroundColor = UIColor.clearColor
this@controller.view.setTag(10086)
}
}
}
}
}
}
}
}
view.parent(4) is ComposeContainer UIController root view, normally is black systemBackgroundColor
Printing description of $13:
<ComposeContainer: 0x1087ec740>
Printing description of $14:
<UIView_IgnoreSafeArea: 0x1087ea340; frame = (0 0; 390 844); clipsToBounds = YES; opaque = NO; autoresize = W+H; backgroundColor = <UIDynamicSystemColor: 0x3012faf80; name = systemBackgroundColor>; layer = <CALayer: 0x30079ae40>>
so you just need change UIView_IgnoreSafeArea backgroundColor
@vickyleu can you show the full UIKitView code please
@vickyleu can you show the full UIKitView code please
fun UIView.parent(count:Int):UIView?{
if(count<=0)return null
var c = count-1
var v:UIView? = this.superview
while ((c-- >0 && v!=null)){
v =v.superview
}
return v
}
fun UIView.findViewController(): UIViewController? {
var nextResponder: UIResponder? = this
while (nextResponder != null) {
if (nextResponder is UIViewController) {
return nextResponder
}
nextResponder = nextResponder.nextResponder
}
return null
}
fun UIView.removeControllerColor(scope: CoroutineScope){
if(tag.toInt()!=10086){
opaque = true
backgroundColor = UIColor.clearColor
findViewController()?. apply controller@ {
scope.launch {
var notStop=true
while (notStop){
withContext(uoocDispatchers.io){
delay(100)
withContext(uoocDispatchers.main){
if(this@controller.isViewLoaded()){
notStop=false
this@controller.view.opaque = true
this@controller.view.backgroundColor = UIColor.clearColor
this@controller.view.setTag(10086)
}
}
}
}
}
}
}
}
UIKitView(
factory = {
webview
},
interactive = true,
background = Color.Transparent,
modifier = Modifier.fillMaxWidth().height(height),
update = { view ->
val parent = view.parent(4) ?: return@UIKitView
parent.removeControllerColor(coroutineScope)
})
It still shows a white background, am I missing something?
@OptIn(ExperimentalForeignApi::class)
@Composable
fun TestAnimation(
modifier: Modifier,
) {
val scope = rememberCoroutineScope()
when (composition as? CompatibleAnimationView) {
null -> {}
else -> {
UIKitView(
modifier = modifier,
factory = {
val view = UIView()
view
},
background = Color.Transparent,
update = { view ->
val parent = view.parent(4)
parent?.removeControllerColor(scope)
}
)
}
}
}
fun UIView.parent(count:Int):UIView?{
if(count<=0)return null
var c = count-1
var v:UIView? = this.superview
while ((c-- >0 && v!=null)){
v =v.superview
}
return v
}
fun UIView.findViewController(): UIViewController? {
var nextResponder: UIResponder? = this
while (nextResponder != null) {
if (nextResponder is UIViewController) {
return nextResponder
}
nextResponder = nextResponder.nextResponder
}
return null
}
fun UIView.removeControllerColor(scope: CoroutineScope) {
if (tag.toInt() != 10086) {
opaque = true
backgroundColor = UIColor.clearColor
findViewController()?.apply controller@{
scope.launch {
var notStop = true
while (notStop) {
withContext(Dispatchers.IO) {
delay(100)
withContext(Dispatchers.Main) {
if (this@controller.isViewLoaded()) {
notStop = false
this@controller.view.opaque = true
this@controller.view.backgroundColor = UIColor.clearColor
this@controller.view.setTag(10086)
}
}
}
}
}
}
}
}
It still shows a white background, am I missing something?
@OptIn(ExperimentalForeignApi::class) @Composable fun TestAnimation( modifier: Modifier, ) { val scope = rememberCoroutineScope() when (composition as? CompatibleAnimationView) { null -> {} else -> { UIKitView( modifier = modifier, factory = { val view = UIView() view }, background = Color.Transparent, update = { view -> val parent = view.parent(4) parent?.removeControllerColor(scope) } ) } } } fun UIView.parent(count:Int):UIView?{ if(count<=0)return null var c = count-1 var v:UIView? = this.superview while ((c-- >0 && v!=null)){ v =v.superview } return v } fun UIView.findViewController(): UIViewController? { var nextResponder: UIResponder? = this while (nextResponder != null) { if (nextResponder is UIViewController) { return nextResponder } nextResponder = nextResponder.nextResponder } return null } fun UIView.removeControllerColor(scope: CoroutineScope) { if (tag.toInt() != 10086) { opaque = true backgroundColor = UIColor.clearColor findViewController()?.apply controller@{ scope.launch { var notStop = true while (notStop) { withContext(Dispatchers.IO) { delay(100) withContext(Dispatchers.Main) { if (this@controller.isViewLoaded()) { notStop = false this@controller.view.opaque = true this@controller.view.backgroundColor = UIColor.clearColor this@controller.view.setTag(10086) } } } } } } } }
your UIView background not set
I've set the background color but it's still not going away
@OptIn(ExperimentalComposeApi::class)
fun MainViewController() = ComposeUIViewController() {
val modifier: Modifier = Modifier
Box(
modifier = modifier.fillMaxSize().background(Color.Red),
contentAlignment = Alignment.Center
){
Box(
modifier = modifier
.size(200.dp)
){
TestAnimation(
modifier = modifier
)
}
}
}
@OptIn(ExperimentalForeignApi::class)
@Composable
fun TestAnimation(
modifier: Modifier,
) {
val scope = rememberCoroutineScope()
UIKitView(
modifier = modifier.fillMaxSize(),
factory = {
UIView().apply {
backgroundColor = UIColor.clearColor
opaque = false
}
},
background = Color.Transparent,
update = { view ->
val parent = view.parent(4)
parent?.removeControllerColor(scope)
}
)
}
fun UIView.parent(count:Int):UIView?{
if(count<=0)return null
var c = count-1
var v:UIView? = this.superview
while ((c-- >0 && v!=null)){
v =v.superview
}
return v
}
fun UIView.findViewController(): UIViewController? {
var nextResponder: UIResponder? = this
while (nextResponder != null) {
if (nextResponder is UIViewController) {
return nextResponder
}
nextResponder = nextResponder.nextResponder
}
return null
}
fun UIView.removeControllerColor(scope: CoroutineScope) {
if (tag.toInt() != 10086) {
opaque = true
backgroundColor = UIColor.clearColor
findViewController()?.apply controller@{
scope.launch {
var notStop = true
while (notStop) {
withContext(Dispatchers.IO) {
delay(100)
withContext(Dispatchers.Main) {
if (this@controller.isViewLoaded()) {
notStop = false
this@controller.view.opaque = true
this@controller.view.backgroundColor = UIColor.clearColor
this@controller.view.setTag(10086)
}
}
}
}
}
}
}
}
@ismai117 maybe the factory method of UIKitView is recreat, now add Observer for CMPInteropWrappingView's backgroundColor
update = { view ->
val parent = view.parent(4) ?: return@UIKitView
view.removeInteropWrappingViewColor(coroutineScope)
parent.removeControllerColor(coroutineScope)
}
/**
* UIKitView factory maybe reattach to window,Observe backgroundColor change
*/
class InteropWrappingViewWatching(val view:UIView) : NSObject(), ObserverProtocol {
override fun observeValueForKeyPath(
keyPath: String?,
ofObject: Any?,
change: Map<Any?, *>?,
context: COpaquePointer?
) {
if(keyPath=="backgroundColor"){
if(view.backgroundColor!=UIColor.clearColor){
view.opaque = true
view.backgroundColor = UIColor.clearColor
view.setTag(10088)
view.removeObserver(this,"backgroundColor")
}
}
}
}
fun UIView.removeInteropWrappingViewColor(scope: CoroutineScope){
if(tag.toInt()!=10087 && tag.toInt()!=10088){
opaque = true
backgroundColor = UIColor.clearColor
val parent = superview?:return
val obs= InteropWrappingViewWatching(parent)
findViewController()?. apply controller@ {
scope.launch {
var notStop=true
while (notStop){
withContext(uoocDispatchers.io){
delay(100)
withContext(uoocDispatchers.main){
if(this@controller.isViewLoaded()){
notStop=false
parent.apply {
opaque = true
backgroundColor = UIColor.clearColor
setTag(10087)
addObserver(obs, forKeyPath = "backgroundColor", options = NSKeyValueObservingOptionNew, context = null)
}
}
}
}
}
}
}
}
}
observer.def
language = Objective-C
---
#import <Foundation/Foundation.h>
@protocol Observer
@required
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey, id> *)change
context:(void *)context;
@end;
targets.withType<KotlinNativeTarget> {
val path = projectDir.resolve("src/nativeInterop/cinterop/observer")
binaries.all {
linkerOpts("-F $path")
linkerOpts("-ObjC")
}
compilations.getByName("main") {
cinterops.create("observer") {
compilerOpts("-F $path")
}
}
}
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