JetBrains / compose-multiplatform

Compose Multiplatform, a modern UI framework for Kotlin that makes building performant and beautiful user interfaces easy and enjoyable.
https://jetbrains.com/lp/compose-multiplatform
Apache License 2.0
15.98k stars 1.16k forks source link

Disable screen move up in IOS #2595

Closed nekzabirov closed 1 year ago

nekzabirov commented 1 year ago

While the keyboard show my screen move up

How to disable it ?

dima-avdeev-jb commented 1 year ago

This is necessary to correctly display the element in focus. For example, TextField. If the focusable element is in focus and will be covered by the keyboard, then the Compose View will move to keep the element visible.

https://user-images.githubusercontent.com/99798741/211367505-e5d8ed50-d054-4606-82dc-0ef5a302fd03.mp4

For now, this behaviour is not configurable.

dima-avdeev-jb commented 1 year ago

@mykytaknyzevkiy Do you have some bugs with this behaviour? Can you please provide info on why you need another behaviour?

nekzabirov commented 1 year ago

@dima-avdeev-jb. I do like to use native IOS UITextField in my Compose view. cause Compose TextField has a lot of bugs.

I found a solution to my problem.

internal val currentRootViewController by lazy {
    ComposeRootController()
}

internal var onKeyboardOpen: ((Boolean) -> Unit)? = null

fun getRootController() = currentRootViewController

class ComposeRootController internal constructor(): UIViewController(null, null) {

    private val keyboardVisibilityListener = object : NSObject() {
        @Suppress("unused")
        @ObjCAction
        fun keyboardWillShow(arg: NSNotification) {
            val (width, height) = getViewFrameSize()

            view.setClipsToBounds(true)

            composeView.layer.setBounds(
                CGRectMake(
                    x = 0.0,
                    y = 0.0,
                    width = width.toDouble(),
                    height = height.toDouble()
                )
            )

            onKeyboardOpen?.invoke(true)
        }

        @Suppress("unused")
        @ObjCAction
        fun keyboardWillHide(arg: NSNotification) {
            val (width, height) = getViewFrameSize()
            view.layer.setBounds(CGRectMake(0.0, 0.0, width.toDouble(), height.toDouble()))

            onKeyboardOpen?.invoke(false)
        }

        @Suppress("unused")
        @ObjCAction
        fun keyboardDidHide(arg: NSNotification) {
            view.setClipsToBounds(false)

            onKeyboardOpen?.invoke(false)

        }
    }

    private val composeView = Application(title = "Nek") {
        App()
    }.view

    override fun viewDidLoad() {
        super.viewDidLoad()

        this.view.addSubview(composeView)
    }

    private fun getViewFrameSize(): IntSize {
        val (width, height) = view.frame().useContents { this.size.width to this.size.height }
        return IntSize(width.toInt(), height.toInt())
    }

    override fun viewDidAppear(animated: Boolean) {
        super.viewDidAppear(animated)
        NSNotificationCenter.defaultCenter.addObserver(
            observer = keyboardVisibilityListener,
            selector = NSSelectorFromString("keyboardWillShow:"),
            name = UIKeyboardWillShowNotification,
            `object` = null
        )

        NSNotificationCenter.defaultCenter.addObserver(
            observer = keyboardVisibilityListener,
            selector = NSSelectorFromString("keyboardWillHide:"),
            name = UIKeyboardWillHideNotification,
            `object` = null
        )
    }
}
brianguertin commented 1 year ago

The solution provided by @nekzabirov does work. Specifically, after Compose-iOS pans the y upward, we immediately set it back to zero, effectively disabling the built in behavior provide by Compose iOS here:

https://github.com/JetBrains/compose-multiplatform-core/blob/cf7d6a444f4a5dc6379d49de4c8a0867e738a588/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/window/ComposeWindow.uikit.kt#L150

However, sometimes I was seeing a little janky animation from the offset back to 0.0. I was able to fix that by also adding view.layer.removeAllAnimations() when updating the layer bounds.

brianguertin commented 1 year ago

@dima-avdeev-jb FYI, even with this hacky workaround, Compose iOS does still know how to scroll to the correct view to keep it visible! I was pleasantly surprised that it works exactly as expected now.

okushnikov commented 1 month ago

Please check the following ticket on YouTrack for follow-ups to this issue. GitHub issues will be closed in the coming weeks.