From 65263979176051f0657118576f9ba57528204b60 Mon Sep 17 00:00:00 2001
From: "dima.avdeev" <dima.avdeev@jetbrains.com>
Date: Mon, 22 Jan 2024 17:24:55 +0300
Subject: [PATCH] fix UIKitView z-order (#965)

Fix Z-order issue with UIKitView
https://github.com/JetBrains/compose-multiplatform/issues/4004
---
 .../src/uikitMain/kotlin/UIKitViewOrder.kt    | 52 +++++++++++++++++++
 .../androidx/compose/mpp/demo/main.uikit.kt   |  2 +
 ...ikit.kt => LocalInteropContainer.uikit.kt} |  2 +-
 .../compose/ui/interop/UIKitView.uikit.kt     |  7 ++-
 .../ui/scene/ComposeSceneMediator.uikit.kt    |  8 +++
 .../ui/window/ComposeContainer.uikit.kt       |  2 -
 6 files changed, 66 insertions(+), 7 deletions(-)
 create mode 100644 compose/mpp/demo/src/uikitMain/kotlin/UIKitViewOrder.kt
 rename compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/interop/{LocalLayerContainer.uikit.kt => LocalInteropContainer.uikit.kt} (91%)

diff --git a/compose/mpp/demo/src/uikitMain/kotlin/UIKitViewOrder.kt b/compose/mpp/demo/src/uikitMain/kotlin/UIKitViewOrder.kt
new file mode 100644
index 0000000000000..81c5c24386010
--- /dev/null
+++ b/compose/mpp/demo/src/uikitMain/kotlin/UIKitViewOrder.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.size
+import androidx.compose.mpp.demo.Screen
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.interop.UIKitView
+import androidx.compose.ui.unit.dp
+import platform.UIKit.UIColor
+import platform.UIKit.UIView
+
+/**
+ * Issue https://github.com/JetBrains/compose-multiplatform/issues/4004
+ */
+val UIKitViewOrder = Screen.Example("UIKitViewOrder") {
+    Box(modifier = Modifier.fillMaxSize().background(Color.Yellow)) {
+        UIKitView(
+            factory = {
+                UIView().apply {
+                    backgroundColor = UIColor.blueColor
+                }
+            },
+            modifier = Modifier.size(100.dp),
+        )
+        UIKitView(
+            factory = {
+                UIView().apply {
+                    backgroundColor = UIColor.redColor
+                }
+            },
+            modifier = Modifier.size(100.dp).offset(50.dp, 50.dp),
+        )
+    }
+}
diff --git a/compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/main.uikit.kt b/compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/main.uikit.kt
index 6b8f507a5dc13..1e99262590969 100644
--- a/compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/main.uikit.kt
+++ b/compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/main.uikit.kt
@@ -3,6 +3,7 @@ package androidx.compose.mpp.demo
 
 import NativeModalWithNaviationExample
 import SwiftUIInteropExample
+import UIKitViewOrder
 import androidx.compose.runtime.*
 import androidx.compose.ui.main.defaultUIKitMain
 import androidx.compose.ui.window.ComposeUIViewController
@@ -25,6 +26,7 @@ fun IosDemo(arg: String, makeHostingController: ((Int) -> UIViewController)? = n
             extraScreens = listOf(
                 IosBugs,
                 NativeModalWithNaviationExample,
+                UIKitViewOrder,
             ) + listOf(makeHostingController).mapNotNull {
                 it?.let {
                     SwiftUIInteropExample(it)
diff --git a/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/interop/LocalLayerContainer.uikit.kt b/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/interop/LocalInteropContainer.uikit.kt
similarity index 91%
rename from compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/interop/LocalLayerContainer.uikit.kt
rename to compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/interop/LocalInteropContainer.uikit.kt
index aef9b7417a233..839d7841b4cef 100644
--- a/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/interop/LocalLayerContainer.uikit.kt
+++ b/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/interop/LocalInteropContainer.uikit.kt
@@ -19,6 +19,6 @@ package androidx.compose.ui.interop
 import androidx.compose.runtime.staticCompositionLocalOf
 import platform.UIKit.UIView
 
-internal val LocalLayerContainer = staticCompositionLocalOf<UIView> {
+internal val LocalInteropContainer = staticCompositionLocalOf<UIView> {
     error("CompositionLocal LayerContainer not provided")
 }
diff --git a/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/interop/UIKitView.uikit.kt b/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/interop/UIKitView.uikit.kt
index 04f465aa113c5..72379907ee8a6 100644
--- a/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/interop/UIKitView.uikit.kt
+++ b/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/interop/UIKitView.uikit.kt
@@ -27,7 +27,6 @@ import androidx.compose.runtime.setValue
 import androidx.compose.runtime.snapshots.SnapshotStateObserver
 import androidx.compose.ui.*
 import androidx.compose.ui.draw.drawBehind
-import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.BlendMode
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.input.pointer.InteropViewCatchPointerModifier
@@ -83,7 +82,7 @@ fun <T : UIView> UIKitView(
 ) {
     // TODO: adapt UIKitView to reuse inside LazyColumn like in AndroidView:
     //  https://developer.android.com/reference/kotlin/androidx/compose/ui/viewinterop/package-summary#AndroidView(kotlin.Function1,kotlin.Function1,androidx.compose.ui.Modifier,kotlin.Function1,kotlin.Function1)
-    val rootView = LocalLayerContainer.current
+    val rootView = LocalInteropContainer.current
     val embeddedInteropComponent = remember {
         EmbeddedInteropView(
             rootView = rootView,
@@ -179,7 +178,7 @@ fun <T : UIViewController> UIKitViewController(
 ) {
     // TODO: adapt UIKitViewController to reuse inside LazyColumn like in AndroidView:
     //  https://developer.android.com/reference/kotlin/androidx/compose/ui/viewinterop/package-summary#AndroidView(kotlin.Function1,kotlin.Function1,androidx.compose.ui.Modifier,kotlin.Function1,kotlin.Function1)
-    val rootView = LocalLayerContainer.current
+    val rootView = LocalInteropContainer.current
     val rootViewController = LocalUIViewController.current
     val embeddedInteropComponent = remember {
         EmbeddedInteropViewController(
@@ -296,7 +295,7 @@ private abstract class EmbeddedInteropComponent<T : Any>(
         wrappingView = UIView().apply {
             addSubview(view)
         }
-        rootView.insertSubview(wrappingView, 0)
+        rootView.addSubview(wrappingView)
     }
 
     protected fun removeViewFromHierarchy(view: UIView) {
diff --git a/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/scene/ComposeSceneMediator.uikit.kt b/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/scene/ComposeSceneMediator.uikit.kt
index b6dc4999ffac1..333ab21995498 100644
--- a/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/scene/ComposeSceneMediator.uikit.kt
+++ b/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/scene/ComposeSceneMediator.uikit.kt
@@ -28,6 +28,7 @@ import androidx.compose.ui.input.pointer.HistoricalChange
 import androidx.compose.ui.input.pointer.PointerEventType
 import androidx.compose.ui.input.pointer.PointerId
 import androidx.compose.ui.input.pointer.PointerType
+import androidx.compose.ui.interop.LocalInteropContainer
 import androidx.compose.ui.interop.LocalUIKitInteropContext
 import androidx.compose.ui.interop.UIKitInteropContext
 import androidx.compose.ui.interop.UIKitInteropTransaction
@@ -146,6 +147,11 @@ internal class ComposeSceneMediator(
         renderingUIViewFactory(renderDelegate)
     }
 
+    /**
+     * Container for UIKitView and UIKitViewController
+     */
+    private val interopViewContainer = UIView()
+
     private val interactionView by lazy {
         InteractionUIView(
             keyboardEventHandler = keyboardEventHandler,
@@ -331,6 +337,7 @@ internal class ComposeSceneMediator(
             this.onAttachedToWindow?.invoke()
             focusStack?.pushAndFocus(interactionView)
         }
+        container.addSubview(interopViewContainer)
         container.addSubview(interactionView)
         interactionView.translatesAutoresizingMaskIntoConstraints = false
         NSLayoutConstraint.activateConstraints(
@@ -382,6 +389,7 @@ internal class ComposeSceneMediator(
             LocalKeyboardOverlapHeight provides keyboardOverlapHeightState.value,
             LocalSafeArea provides safeAreaState.value,
             LocalLayoutMargins provides layoutMarginsState.value,
+            LocalInteropContainer provides interopViewContainer,
             content = content
         )
 
diff --git a/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/window/ComposeContainer.uikit.kt b/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/window/ComposeContainer.uikit.kt
index 54c252f3bf50a..db7f0ff1d0e81 100644
--- a/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/window/ComposeContainer.uikit.kt
+++ b/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/window/ComposeContainer.uikit.kt
@@ -25,7 +25,6 @@ import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.ui.LocalSystemTheme
 import androidx.compose.ui.SystemTheme
-import androidx.compose.ui.interop.LocalLayerContainer
 import androidx.compose.ui.interop.LocalUIViewController
 import androidx.compose.ui.platform.PlatformContext
 import androidx.compose.ui.platform.WindowInfoImpl
@@ -419,7 +418,6 @@ internal fun ProvideContainerCompositionLocals(
 ) = with(composeContainer) {
     CompositionLocalProvider(
         LocalUIViewController provides this,
-        LocalLayerContainer provides view,
         LocalInterfaceOrientation provides interfaceOrientationState.value,
         LocalSystemTheme provides systemThemeState.value,
         content = content