Hey all, I've found a way to do progress tracking ...
# compose-ios
l
Hey all, I've found a way to do progress tracking to integrate PredictiveBackHandler for iOS, source in the thread:
Step 1) Wrap the MainViewController generated from Compose in a swift code that allows us to track back navigation progress:
Copy code
class CustomViewController: UIViewController {
    
    var backGestureProvider : BackGestureProvider?

    override func viewDidLoad() {
        super.viewDidLoad()

        // Initialize the edge pan gesture recognizer
        let edgePanGestureRecognizer = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(handleEdgePan(_:)))
        edgePanGestureRecognizer.edges = .left  // Set the edge to left
        
        // Add the gesture recognizer to the view
        self.view.addGestureRecognizer(edgePanGestureRecognizer)
        
        // Add the nested view controller
        addNestedViewController()
    }

    @objc func handleEdgePan(_ gestureRecognizer: UIScreenEdgePanGestureRecognizer) {
        let translation = gestureRecognizer.translation(in: view)

        switch gestureRecognizer.state {
        case .began:
            // Gesture began, start tracking
            print("Gesture began")

        case .changed:
            // Gesture changed, update tracking
            let progress = translation.x / view.bounds.width
            print("Gesture changed, progress: \(progress)")
            // You can update the UI based on progress if needed

        case .ended:
            // Gesture ended, determine whether to complete the navigation or not
            let velocity = gestureRecognizer.velocity(in: view)
            if translation.x > view.bounds.width / 2 || velocity.x > 1000 {
                // Complete the navigation
                print("Gesture ended, completing navigation")
                backGestureProvider?.onSwipeBack()
            } else {
                // Cancel the navigation
                print("Gesture ended, cancelling navigation")
                // Optionally, add logic to animate the UI back to the original state
            }

        case .cancelled, .failed:
            // Gesture cancelled or failed, reset the UI
            print("Gesture cancelled or failed")

        default:
            break
        }
    }

    func addNestedViewController() {
        let nestedViewController = MainKt.MainViewController(backGestureProvider: provideSwipeBack(_:))
        
        // Add the nested view controller as a child
        addChild(nestedViewController)
        
        // Add the nested view controller's view to the view hierarchy
        view.addSubview(nestedViewController.view)
        
        // Set up constraints or frame for the nested view
        nestedViewController.view.frame = view.bounds
        nestedViewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        
        // Notify the nested view controller that it has been moved to a parent
        nestedViewController.didMove(toParent: self)
    }
    
    func provideSwipeBack(_ provider: BackGestureProvider) -> Void {
        backGestureProvider = provider
    }
}
Step 2) You may noticed that the constructor of the
MainKt.MainViewController
contains the backGestureProvider. This is an interface that is implemented on Kotlin side which just passes a reference to a function Swift side can call (look for
backGestureProvider?.onSwipeBack()
in step 1. Right now it's set up to only trigger on swipe completion but can be modified to actually provide the percentage (check the comments in the code)
Step 3) Pass the backGestureProvider as such from Kotlin side:
Copy code
fun MainViewController(backGestureProvider: (BackGestureProvider) -> Unit): UIViewController = ComposeUIViewController() {
    App(backGestureProvider)
}
Step 4) In the
App
function (which is the actual compose root), send it to the appropriate
NavHost
where you can do something like this (I have created a wrapper for it called AppNavigation):
Copy code
@Composable
fun AppNavigation(backGestureProvider: (BackGestureProvider) -> Unit, modifier: Modifier = Modifier, navController: NavHostController = rememberNavController()) {
    LaunchedEffect(Unit) {
        backGestureProvider(object : BackGestureProvider {
            override fun onSwipeBack() {
                navController.navigateUp()
            }
        })
    }
}
And that's it, this way we can have a proper predictive back. Unfortunately, I was not able to find repo where I can contribute to the navigation, so maybe someone like @Ivan Matkov or @Elijah Semyonov can help here please?
p
https://github.com/JetBrains/compose-multiplatform-core/tree/jb-main/navigation, navigation-common and navigation-compose are the 2 folders in which the code is located (maybe theres more but these 2 are main ones)
👍 2
l
Thanks! I'll try to make a PR then
👀 1
s
Btw for 4), it's probably not what you want when you call
navController.navigateUp()
when you do the back gesture, but
popBackstack
instead for normal back gestures.
l
Hmm good point, I'll test that out
i
Hey @Lukas Anda, just reviewed your PR - thanks for your time. Unfortunately, this approach won't work - we're redirecting to original Google binaries on Android, so we cannot change common public API and android implementation without collaborating with Google. We're already in process of discussion how this feature/common APIs should look like, but the decision isn't here yet.
l
Ah that's a shame. But thanks for the heads-up. I needed to change the api since it handles an edge-case which Android does not have.
m
@Lukas Anda, please file an issue in the Activity library’s issue tracker to request that our system APIs be made Multiplatform. Also, please include details about your use case and what you think the API should look like (you can link your PR above too as an example). https://issuetracker.google.com/issues/new?component=527362&template=1189829 Thank you!
👍 1
l