compose-multiplatform icon indicating copy to clipboard operation
compose-multiplatform copied to clipboard

iOS - Scroll does not work at all when touch starts on a UIKitView inside eg a LazyColumn

Open AttilaBarany opened this issue 1 year ago • 13 comments

Affected platforms

  • iOS

Versions

  • 1.6.0, 1.6.2, 1.6.10-rc01...

To Reproduce Just extend the "uikit-in-compose" example app like this: Screenshot 2024-05-16 at 16 15 51

(adding a big box after the elements inside the column, and make the column scrollable)

and try to scroll by grabbing eg the textfield

You can reproduce this with any UIKitView, eg a simple UIView(), wkwebview etc... Adding eg Androidview webview does allow us to scroll by grabbing it, but UKitView does not bubble up anything, even tried with custom nestedscrollconnection just print out pre- or post scroll, but got not a single trigger. This failure just makes useless the UIKitView, or at least very limited. You can just integrate only in static pages, without any scrolling, otherwise the user will stuck on the page with invisible contents which are below in the column.

AttilaBarany avatar May 16 '24 14:05 AttilaBarany

Funny, I was just about to submit this same issue. It does severely limit the use of UIKitView. Setting interactive = false makes it scrollable, but of course then it isn't interactive.

Here is my minimal reproducer:

@OptIn(ExperimentalForeignApi::class)
fun MainViewController() = ComposeUIViewController {
    Column(modifier = Modifier.verticalScroll(state = rememberScrollState())) {
        UIKitView(
            factory = {
                UIView().apply {
                    backgroundColor = UIColor.redColor
                }
            },
            modifier = Modifier.fillMaxWidth().height(400.dp)
        )
        repeat(100) {
            Text("Item #$it")
        }
    }
}

brewin avatar May 16 '24 17:05 brewin

Hi, it's a known problem, and not exactly a trivial one to fix due to differences in touch processing in Compose and UIKit. We are investigating our options currently.

elijah-semyonov avatar May 17 '24 07:05 elijah-semyonov

Great news, thank you @elijah-semyonov . I am gonna suspend my project until than, and stay with separated UI (I am using a webview in column, which can not be done without UIKitView for iOS).

AttilaBarany avatar May 17 '24 08:05 AttilaBarany

As a workaround, you could present native UIViewController with web view modally on top of compose UI. Use LocalUIViewController.current.

elijah-semyonov avatar May 17 '24 10:05 elijah-semyonov

As a workaround, you could present native UIViewController with web view modally on top of compose UI. Use LocalUIViewController.current.

So how that would could be part of the scrollable content? I dont get it.

AttilaBarany avatar May 17 '24 12:05 AttilaBarany

It won't be. But you don't have to reimplement entire screen natively.

elijah-semyonov avatar May 17 '24 12:05 elijah-semyonov

It won't be. But you don't have to reimplement entire screen natively.

Just the column content, get it. Which is the whole screen (except top and bottom bar).:) Never mind. I started and coded the UI already separately/natively, but wanted to refactor to only maintain one UI instead of two. Took almost 2 weeks, and this was the last piece which is missing, everything else looks working, thats why its so annoying :)) But I might keep going this direction and use this screen only natively as suggested.

Thanks for your help anyway, and I am looking forward to have this fixed. :) (p.s I beleive flutter had the very same issue before)

AttilaBarany avatar May 17 '24 13:05 AttilaBarany

Flutter's UIKitView has a gestureRecognizers property that allows customizing the gestures that are passed to the UIKit view.

https://api.flutter.dev/flutter/widgets/UiKitView/gestureRecognizers.html

For example, with the following setup vertical drags will not be dispatched to the UIKitview as the vertical drag gesture is claimed by the parent [GestureDetector].

GestureDetector(
  onVerticalDragStart: (DragStartDetails details) {},
  child: const UiKitView(
    viewType: 'webview',
  ),
)

brewin avatar May 17 '24 16:05 brewin

Well, we can do that! The problem is, there is no way that is currently apparent to us, to conditionally allow touches to slip to the interop view gesture recognisers (such as compose scroll in a native scroll, for example).

The behavior in your example is possible by the usage of Modifier.draggable and using interactable = false in UIKitView arguments.

elijah-semyonov avatar May 21 '24 07:05 elijah-semyonov

facing same issue

dilip640 avatar May 29 '24 11:05 dilip640

same

vickyleu avatar Jun 17 '24 03:06 vickyleu

@elijah-semyonov could you please expand on your suggestion of "The behavior in your example is possible by the usage of Modifier.draggable and using interactable = false in UIKitView arguments.". I tried a few combinations of setting interactive = false and implementing draggable on the Modifier, without success. With interactive = false, scrolling is fine, but it doesn't register any click events as @brewin has pointed out above.

My use-case is to embed a Native Ad using Google AdMob, where the UIView for the Ad is coming from Swift for iOS. In the ideal case, I want to be able to scroll on top of the Ad, but still be able to click on the Ad.

kavindudiyes avatar Jun 25 '24 08:06 kavindudiyes

My solution is to turn off the touch event of UIView and unify the click penetration through the click event hitTest of compose to the rect of UIView.

For example, my UIView put a webview, and I need to click on the picture inside. I will first register a query of the coordinates of all pictures through js, get a set of rectangular arrays, and confirm that the picture is clicked by matching the array.

vickyleu avatar Jun 28 '24 05:06 vickyleu

Awaiting your feedback (and perhaps future issues) after these changes are released! Track release notes for iOS 🌚

elijah-semyonov avatar Jul 12 '24 10:07 elijah-semyonov

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

okushnikov avatar Jul 14 '24 14:07 okushnikov

@elijah-semyonov I have imported 1.7.0-dev1727, But I do not experience any difference. The same lazycolumn which holds a wkwebview or a single uiview, does not scroll if the drag starts on the UIKitview (Modify.draggable also not triggered). But it does scroll with Androidview.

AttilaBarany avatar Jul 15 '24 10:07 AttilaBarany

I also see no difference when running 1.7.0-dev1727. In my case it's lazy column with UIKitView that has UITextField inside of it.

paskowski avatar Jul 15 '24 10:07 paskowski

@AttilaBarany @paskowski You need to move a finger over the screen fast to recognise a scroll

elijah-semyonov avatar Jul 15 '24 10:07 elijah-semyonov

@AttilaBarany @paskowski You need to move a finger over the screen fast to recognise a scroll

Nothing happens. Slow, fast, long, short, delay...

Screenshot 2024-07-15 at 14 18 04

AttilaBarany avatar Jul 15 '24 12:07 AttilaBarany

I don’t think 1.7.0-dev1727 includes the changes. The next dev release will.

brewin avatar Jul 15 '24 12:07 brewin

I don’t think 1.7.0-dev1727 includes the changes. The next dev release will.

I got it from his commit linked in this issue above. https://github.com/JetBrains/compose-multiplatform-core/commit/174766b50bf9fda2aa04a4a276026f6ee6bd9d5f

Screenshot 2024-07-15 at 14 45 49

AttilaBarany avatar Jul 15 '24 12:07 AttilaBarany

@AttilaBarany Please provide a full repro project, I can't reproduce your behavior from a snippet:

val factory = remember {
    {
        UIView().apply {
            backgroundColor = UIColor.blueColor
            userInteractionEnabled = true
        }
    }
}

LazyColumn(modifier = Modifier.fillMaxSize()) {
    item {
        UIKitView(
            modifier = Modifier.fillMaxWidth().height(300.dp),
            factory = factory
        )
    }

    item {
        Box(Modifier.fillMaxWidth().height(1500.dp).background(Color.Red))
    }
}

https://github.com/user-attachments/assets/4a2702cd-2683-4e37-87a0-a5cd3e2e1bf2

elijah-semyonov avatar Jul 15 '24 13:07 elijah-semyonov

The logic is identical to UIScrollView now - if there is fast initial movement, gesture is recognised as scroll and dispatched accordingly. If there is no movement and hitTest is an interop view, it intercepts the touches and no scrolling will ever happen until touches sequence ends (all fingers are up)

elijah-semyonov avatar Jul 15 '24 14:07 elijah-semyonov

I agree with https://github.com/JetBrains/compose-multiplatform/issues/4818#issuecomment-2228420632 that https://github.com/JetBrains/compose-multiplatform/releases/tag/v1.7.0-dev1727 does not have the changes that were supposed to fix this issue.

paskowski avatar Jul 15 '24 15:07 paskowski

@elijah-semyonov The video looks very promising, thank you for testing and showing me the error maybe is in my setup!!! I had a mistake providing the library version. I have changed the lib to 1.7.0-dev1727, but the gradle was set to use the compose.plugin which had an earlier version set. After fixed (set the plugin version to 1.7.0-dev1727), or I thought I fixed, gradle does not want to sync: Could not find org.jetbrains.compose.runtime:runtime:1.7.0-dev1727. Required by: project :shared

AttilaBarany avatar Jul 15 '24 16:07 AttilaBarany

@elijah-semyonov I got the versions sorted, I set 1.7.0-alpha01 whis is the latest, and the scroll still does not work. So @paskowski and @brewin are correct. The fix is still not included.

Screenshot 2024-07-15 at 20 37 04

AttilaBarany avatar Jul 15 '24 18:07 AttilaBarany

That's super awkward, CI shows that it was included in 1727. Anyway, let's w8 for other dev build and see if it flies.

elijah-semyonov avatar Jul 16 '24 07:07 elijah-semyonov

I got the versions sorted, I set 1.7.0-alpha01 whis is the latest,

1.7.0-dev1727 is newer than 1.7.0-alpha01. 1.7.0-alpha01 does not contain @elijah-semyonov 's fix, but dev1727 does. devXXXX is regular builds from master that are not uploaded to maven central (available only from dev one), so sorting is not really correct here. We might improve naming in the future, but now it is what it is.

MatkovIvan avatar Jul 16 '24 08:07 MatkovIvan

I can confirm that both 1.7.0-dev1727 and 1.7.0-dev1731 work correctly. I think I set an incorrect dependency's version yesterday, apologies for that. Thank you for fixing this, it's a real game changer @elijah-semyonov 🎉 .

paskowski avatar Jul 16 '24 17:07 paskowski

This broke one of the things that used to work fine. I am wrapping an (native iOS) eBook reader (which is basically a UIViewController) which internally responds to gestures, especially tap gestures. I am wrapping that UIViewController in Compose:

@OptIn(ExperimentalForeignApi::class)
@Composable
fun ReadiumViewport(c: UIViewController) {
    UIKitViewController(
            factory = { obtainReadiumController() },
            modifier = Modifier.fillMaxSize()
     )
}

It used to scroll, tap, pinch in/out fine, but now it's not responding to touches at all. It seems like Compose is preventing all gestures from propagating to the UIViewController itself. I am not sure why this is happening because I am not setting any external Compose tap listeners. All the gesture listening is happening internally inside the UIViewController itself.

yuroyami avatar Jul 31 '24 19:07 yuroyami