Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Touches forwarding to underlying interop views on iOS #1426

Merged
merged 32 commits into from
Jul 9, 2024

Conversation

elijah-semyonov
Copy link

@elijah-semyonov elijah-semyonov commented Jul 2, 2024

First approach to allow touches to be processed by both Compose and nested interop views.
Other UX improvements will follow.

  1. actual typealias InteropView = UIView
  2. Change ComposeScene API to allow hit testing specific InteropView.
  3. Make InteractionUIView a container view of InteropContainer to enforce it being in the same responder chain as interop views.
  4. Remove InteractionUIView.Delegate and replace it with callbacks to avoid by lazy reentry in ComposeSceneMediator, make InteractionUIView construction eager.
  5. Use event.allTouches instead of event.touchesForView to avoid receiving an empty list in case InteractionUIView is part of responder chain but not the hit test result itself.
  6. Remove KeyboardEventHandler.uikit.kt to avoid by lazy reentry, stick everything in a single lambda.

Fixes some of the cases from the domain of JetBrains/compose-multiplatform#4818

Testing

Demo/IosBugs/UIKitRenderSync now properly registers touches to allow LazyColumn scrolling and native view reaction.

Release Notes

iOS - Fixes

  • Touches inside interop views are not exclusive to them and are processed on Compose side as well.

Multiple Platforms - Breaking changes

  • ComposeScene fun hitTestInteropView(position: Offset): Boolean changed to fun hitTestInteropView(position: Offset): InteropView?

@elijah-semyonov elijah-semyonov self-assigned this Jul 2, 2024
@elijah-semyonov elijah-semyonov changed the title Touches forwarding to underlying interop views Touches forwarding to underlying interop views on iOS Jul 2, 2024
@elijah-semyonov elijah-semyonov marked this pull request as ready for review July 2, 2024 10:30
Copy link
Member

@MatkovIvan MatkovIvan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍 Just a few docs nitpicks

*/
@InternalComposeUiApi
fun Modifier.interopView(view: InteropView): Modifier =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name is too generic. I assume it will show up in suggestions for unrelated cases

Copy link
Author

@elijah-semyonov elijah-semyonov Jul 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any suggestions? I guess it's exactly what it is, an InteropView association for a given element.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

modifier is no a VIEW. So at least something like interopViewAnchor.

Wait. We don't need this function in public at all - let's just keep a node. (InteropViewAnchorModifierNode)

private var updateTouchesCount: (count: Int) -> Unit,
private var hitTestInteropView: (point: CValue<CGPoint>, event: UIEvent?) -> InteropView?,
private var onTouchesEvent: (view: UIView, event: UIEvent, phase: CupertinoTouchesPhase) -> Unit,
private var onTouchesCountChange: (count: Int) -> Unit,
private var inBounds: (CValue<CGPoint>) -> Boolean,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would consider some simplification of the API here:

  • Move all calls to delegate-like interface.
  • Omit inBounds, and move hitTest logic out to the class to its owner.

Copy link
Author

@elijah-semyonov elijah-semyonov Jul 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed delegate specifically to avoid instantiation of objects that capture this.
Due to heavy coupling of dependencies in ComposeSceneMediator it's already a problem to figure out what's the order of lazy instantiations and I've tried this approach before I efficiently just made this. Delegates implementation required wrapping coupled objects access into lambda to avoid lazy-reentry, which just makes things way too complicated. But I agree with the second point, we can make those things easier. Let's do it in a separate PR?

@@ -87,7 +87,7 @@ internal class IntermediateTextInputUIView(
hideEditMenu()
}
}
var keyboardEventHandler: KeyboardEventHandler? = null
var onPresses: (Set<*>) -> Unit = NoOpOnPresses
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are presses can be done from another source, different from the attached keyboard?
I would keep keyboard naming part in API to have more context when reading the code.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, hardware keyboard too, afaik.
Anyway, a good point, will add it.

@elijah-semyonov elijah-semyonov marked this pull request as draft July 3, 2024 09:48
@elijah-semyonov elijah-semyonov marked this pull request as ready for review July 3, 2024 09:49
@elijah-semyonov elijah-semyonov marked this pull request as draft July 3, 2024 10:02
@elijah-semyonov
Copy link
Author

I implied, that simple touches forwarding is needed, but some views like MKMapView and UITextField use internal gesture recognisers, so we have to use one too.

@elijah-semyonov
Copy link
Author

Screen.Recording.2024-07-03.at.12.04.39.mov

Not ideal, but it works for now

@elijah-semyonov elijah-semyonov marked this pull request as ready for review July 3, 2024 10:05
Copy link
Member

@MatkovIvan MatkovIvan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, let's just fine tune the naming

*/
@InternalComposeUiApi
fun Modifier.interopView(view: InteropView): Modifier =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

modifier is no a VIEW. So at least something like interopViewAnchor.

Wait. We don't need this function in public at all - let's just keep a node. (InteropViewAnchorModifierNode)

@@ -18,6 +18,7 @@ package androidx.compose.ui.viewinterop

import androidx.compose.runtime.ComposeNodeLifecycleCallback

// TODO: implement this for iOS
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a link to this task

@elijah-semyonov elijah-semyonov force-pushed the es/gesture-recognizer-touch-forwarding branch from 3dab05d to 66f99e1 Compare July 9, 2024 09:00
@elijah-semyonov elijah-semyonov force-pushed the es/gesture-recognizer-touch-forwarding branch from 28dca12 to c324419 Compare July 9, 2024 09:58
@elijah-semyonov elijah-semyonov merged commit a78dd41 into jb-main Jul 9, 2024
6 checks passed
@elijah-semyonov elijah-semyonov deleted the es/gesture-recognizer-touch-forwarding branch July 9, 2024 11:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
3 participants