I have been using <this great plugin > And it work...
# touchlab-tools
z
I have been using this great plugin And it works great, but it seems that it only responds to touch gestures and not scroll/swipe/ fling gestures. I replaced the
ShowNativeText
view in the example project with a simple scrollable list, but it does not respond to the scroll gesture on the simulator:
Copy code
struct SimpleTextView : View {
    @ObservedObject var observable: ComposeApp.ShowNativeTextObservable
    
    let items = Array(1...100).map { "Item \($0)" }

        var body: some View {
            NavigationStack {
                List(items, id: \.self) { item in
                    Text(item)
                }
                .navigationTitle("Scrollable List")
            }
        }
}
Is there a known issue with gestures or am i missing some bindings?
👀 1
h
I never use this plugin, so my response might be completely off the mark (sorry), but recently, on a
UIKitView
, the following API (
UIKitInteropProperties
) has been introduced to fix a similar issue in Compose :
Copy code
UIKitView(
    factory = { myView()},
    modifier = modifier,
    properties = UIKitInteropProperties(interactionMode = UIKitInteropInteractionMode.NonCooperative),
)
By default, it is set to
Cooperative
. Maybe there is a similar configuration to do with the bridge ?
z
After reviewing the docs here I found this: To solve this, Compose Multiplatform implements behavior inspired by
UIScrollView
. When the touch is first detected, there is a short delay (150 ms) that lets the app decide whether to make the container aware of it:
And realized that i have to click, hold 150ms, than it works....
Also, using the UIKitInteropInteractionMode allows you to disable touches completly, not take over the 150ms stragtegy
k
on a
UIKitView
, the following API (
UIKitInteropProperties
) has been introduced to fix a similar issue in Compose
The bridge calls
UIKitView
under the hood, and looking at the generated code, the version it calls is deprecated and should be updated to the one you posted. What the bridge does is let you write this in Kotlin
Copy code
@ExpectSwiftView(
    type = ViewType.UIView
)
@Composable
expect fun MapViewWithUiView(
    modifier: Modifier = Modifier,
    coordinate: MapCoordinates,
    title: String,
)
The in iOS (if using UIKit) you can write something like this:
Copy code
class NativeMapUIKitView: UIView, MapViewWithUiViewDelegate {
    private var mapView: MKMapView!
    private var annotation = MKPointAnnotation()

    init(title: String, coordinate: MapCoordinates, frame: CGRect = .zero) {
        super.init(frame: frame)
        setupMapView(title: title, coordinate: coordinate) // Does iOS Stuff
    }
    
    // iOS Code
    
    // MARK: - MapViewWithUiViewDelegate
    func updateCoordinate(coordinate: MapCoordinates) {
        // Triggered from Composable
    }

    func updateTitle(title: String) {
        // Triggered from Composable
    }
}
The arguments to the Composable, not including
Modifier
, get wired into
update*
calls when values change. Not sure why I put the UIKit example first, but SwiftUI is similar:
Copy code
@ExpectSwiftView(
    type = ViewType.SwiftUI
)
@Composable
expect fun MapViewWithSwiftUI(
    modifier: Modifier = Modifier,
    coordinate: MapCoordinates,
    title: String,
    callback: (String) -> Unit
)
Then in Swift:
Copy code
class SwiftUINativeViewFactory : NativeViewFactory {
    // Etc
    
    // This function implements one of the functions from the generated NativeViewFactory
    func createMapViewWithSwiftUI(observable: ComposeApp.MapViewWithSwiftUIObservable) -> AnyView {
        return AnyView(NativeMapViewBindingSwiftUI(observable: observable))
    }

    // Etc
}

struct NativeMapViewBindingSwiftUI : View {
    @ObservedObject var observable: MapViewWithSwiftUIObservable
    
    var body: some View {
        NativeMapView(title: observable.title, coordinate: observable.coordinate)
    }
}
The class
MapViewWithSwiftUIObservable
is generated and has the params from the Composable (again, minus
Modifier
). The idea is that everything you'd need to do in between to get updates from the Composable into a form that is usable from Swift is generated, so you (mostly) just need to implement the iOS UI code.
Kotlin names can be different when you get to Swift, but the bridge runs within SKIE, which knows what the compiler will call them, as well as what the name of the whole package is, so it can generate the Swift-side import statement.
Currently, this works for a project you're building, but ideally this can work for libraries. As in somebody would write a CMP map library, and the Swift side that needed the names and imports would resolve correctly. However, it is complicated by a few things. Most obvious would be the inflexibility of the KMP linker for native dependencies. Lots of manual cinterop, or you need to use CocoaPods. No import-side SPM. That kind of thing.
z
Thank you for the detailed response! 🙏 So if i understand correctly, future versions of the plugin would allow passing the
cooperative
or
non-cooperative
parameter to the
NativeViewFactory
function?
s
We do have a way that you can replace the Interoperability function being used for your Composable function. I have not documented it yet. You can see this sample right here: https://github.com/touchlab/compose-swift-bridge/blob/main/sample/composeApp/src/commonMain/kotlin/ui/MapView.kt#L42-L51 https://github.com/touchlab/compose-swift-bridge/blob/main/sample/composeApp/src/iosMain/kotlin/sample/CustomizedInterop.kt In practice, this annotation allows you to provide a custom interop function that requires to have the same signature as the sample, a function with these two parameters with the same name:
Copy code
modifier: Modifier,
factory: () -> UIViewController
👀 1
z
I dont see any way to edit the
DefaultDelayMillis
as the constructor only takes a true/false which will lead to Cooperative/NonCoopertive modes So the basic problem of the 150 millis delay still exists for me. Is there any way to resolve this?