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?
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()) {
}