5

I'm trying to use the UiKit API PHPickerViewController using KMM and Compose for iOS.

import androidx.compose.runtime.Composable
import androidx.compose.ui.interop.LocalUIViewController
import platform.PhotosUI.PHPickerConfiguration
import platform.PhotosUI.PHPickerViewController
import platform.PhotosUI.PHPickerViewControllerDelegateProtocol
import platform.darwin.NSObject

@Composable
actual fun pickerController() {
    val uiViewController = LocalUIViewController.current
    val configuration = PHPickerConfiguration()
    val pickerController = PHPickerViewController(configuration)
    val pickerDelegate = object : NSObject(), PHPickerViewControllerDelegateProtocol {
        override fun picker(picker: PHPickerViewController, didFinishPicking: List<*>) {
            println("didFinishPicking: $didFinishPicking")
            picker.dismissViewControllerAnimated(flag = false, completion = {})
            uiViewController.dismissModalViewControllerAnimated(false)
        }
    }

    pickerController.setDelegate(pickerDelegate)
    uiViewController.presentViewController(pickerController, animated = false, completion = null)
}

This displays the image picker:

Unfortunately, when clicking on Cancel, the delegate callback is not called, and I get the following message on the console:

[Picker] PHPickerViewControllerDelegate doesn't respond to picker:didFinishPicking:

Is it possible to implement the callback in Kotlin?
What am I missing?

2
  • there's a chance pickerDelegate is released - setDelegate doesn't increase reference counter of delegate object, in Swift this object would've been released right after exiting pickerController function, not sure how it works with kotlin memory model, but I would've stored it somehow Commented Jun 3, 2023 at 0:43
  • Thank you for the hint @PhilDukhov! I tried to persist the delegate in a global property but unfortunately it doesn't solve the issue... Commented Jun 3, 2023 at 7:19

1 Answer 1

5

Since pickerDelegate is NSObject, it's lifecycle follows ObjC rules, not KMM memory model.

So as soon as the execution leaves composable block, this objects gets released - as setDelegate takes it as weak reference.

You can fix it by storing it using remember.

Also using your function is dangerous because you're gonna call presentViewController on each recomposition - e.g. if some of your reactive data changes on the calling side.

You can update it to return an action that will present it, but store delegate and the action itself using remember:

@Composable
actual fun rememberOpenPickerAction(): () -> Unit {
    val uiViewController = LocalUIViewController.current
    val pickerDelegate = remember {
        object : NSObject(), PHPickerViewControllerDelegateProtocol {
            override fun picker(picker: PHPickerViewController, didFinishPicking: List<*>) {
                println("didFinishPicking: $didFinishPicking")
                picker.dismissViewControllerAnimated(flag = false, completion = {})
            }
        }
    }

    return remember {
        {
            val configuration = PHPickerConfiguration()
            val pickerController = PHPickerViewController(configuration)
            pickerController.setDelegate(pickerDelegate)
            uiViewController.presentViewController(pickerController, animated = true, completion = null)
        }
    }
}

Usage:

Button(onClick = rememberOpenPickerAction()) {

}
Sign up to request clarification or add additional context in comments.

6 Comments

Thanks for the answer Phil. I have a question: after we pick the image we receive - didFinishPicking: List<*> after that we need to cast that to - didFinishPicking.first() as PHPickerResult from there we get the itemProvider, but after that I need to call itemProvider.loadObjectOfClass, but I don't know what to pass... in iOS its itemProvider.loadObjectOfClass(UIImage.self). What is the representation in kotlin? Thanks
@LubomirBabev that's a good question, I have no idea. It feels like Class from objc is not translated correctly, as kotlin expects for NSItemProviderReadingProtocol, but it should expect KClass<NSItemProviderReadingProtocol> or ObjCClass. You should fill an issue on youtrack.jetbrains.com.
Okay but what's the point of using PHPickerViewController if you can't retrieve the image ... ? I have another question, could you please check it: stackoverflow.com/q/77080180/5519005
@LubomirBabev try this
Thank you. That was what I'm looking for. I upvote your answers now. Please check the question in the last comment. :)
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.