我正在开发一个 Compose 多平台应用程序,该应用程序可以在 Android 上正常运行,但在 iOS 上访问照片库时遇到问题。该应用程序使用 ImagePicker 允许用户选择图像,并实现照片库访问的权限处理。 错误日志显示:
[PAAccessLogger] Failed to log access with error: access=<PATCCAccess 0x280102c40> accessor:<<PAApplication 0x282cac500 identifierType:auditToken identifier:{pid:42414, version:125940}>> identifier:0284C142-B9D2-4134-99B0-337308EC891E kind:intervalEnd timestampAdjustment:0 tccService:kTCCServicePhotos, error=Error Domain=PAErrorDomain Code=10 "Possibly incomplete access interval automatically ended by daemon"
[core] "Error returned from daemon: Error Domain=com.apple.accounts Code=7 "(null)""
此错误表明应用程序处理照片库访问权限的方式或与 iOS 照片库 API 交互的方式可能存在问题。 我已经在我的 Compose Multiplatform 项目中实现了权限请求和图像选取功能,并在 iosMain 源集中针对 iOS 进行了单独的实现。尽管实现了这些,应用程序在尝试访问 iOS 设备上的照片库时仍然遇到错误。 我正在寻找可能导致此 iOS 特定问题的原因以及如何解决该问题,同时在我的 Compose 多平台项目中保持跨平台兼容性。 常见主要:
@Composable
fun PostScreen(onEventSubmitted: () -> Unit) {
val viewModel: PostsViewModel = KoinPlatform.getKoin().get()
val imagePicker: ImagePicker = KoinPlatform.getKoin().get()
val cameraPermissionManager = remember { RequestPhotoLibraryPermission() }
val uiState by viewModel.uiState.collectAsState()
var selectedImage by remember { mutableStateOf<ByteArray?>(null) }
rememberCoroutineScope()
if (uiState.pickImage) {
imagePicker.PickImage { imageBytes ->
viewModel.setEvent(PostsEvents.OnPickImage(false))
selectedImage = imageBytes
}
}
cameraPermissionManager.RequestCameraPermission(
onPermissionGranted = {
AddPostEvent(
selectedImage = selectedImage,
onEventSubmitted = onEventSubmitted,
setEvent = { event ->
viewModel.setEvent(event)
})
},
onPermissionDenied = {
BodyMedium(text = "La fotocamera non è disponibile senza permessi")
})
LaunchedEffect(viewModel) {
viewModel.effect.collect { effect ->
when (effect) {
is PostsUIEffects.ShowSuccess -> {
println("XXXXX success")
}
is PostsUIEffects.ShowErrorMessage -> {
println("XXXXX error: ${effect.message}")
}
}
}
}
}
ios主要: 请求许可
actual class RequestPhotoLibraryPermission {
@Composable
actual fun RequestCameraPermission(
onPermissionGranted: @Composable () -> Unit,
onPermissionDenied: @Composable () -> Unit
) {
var permissionStatus by remember { mutableStateOf(PHAuthorizationStatusNotDetermined) }
LaunchedEffect(Unit) {
permissionStatus = PHPhotoLibrary.authorizationStatus()
if (permissionStatus == PHAuthorizationStatusNotDetermined) {
permissionStatus = requestPhotoLibraryPermission()
}
}
when (permissionStatus) {
PHAuthorizationStatusAuthorized,
PHAuthorizationStatusLimited -> onPermissionGranted()
else -> onPermissionDenied()
}
}
private suspend fun requestPhotoLibraryPermission(): PHAuthorizationStatus =
suspendCancellableCoroutine { continuation ->
PHPhotoLibrary.requestAuthorization { status ->
continuation.resume(status)
}
}
}
ios主图像选择器:
actual class ImagePicker {
private var onImagePickedCallback: ((ByteArray?) -> Unit)? = null
private val pickerController = UIImagePickerController()
init {
pickerController.sourceType = UIImagePickerControllerSourceType.UIImagePickerControllerSourceTypePhotoLibrary
pickerController.setDelegate(object : NSObject(), UIImagePickerControllerDelegateProtocol, UINavigationControllerDelegateProtocol {
override fun imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo: Map<Any?, *>) {
val image = didFinishPickingMediaWithInfo[UIImagePickerControllerOriginalImage] as? UIImage
picker.dismissViewControllerAnimated(true) {
if (image != null) {
val data = UIImageJPEGRepresentation(image, 0.8)
val bytes = data?.toByteArray()
onImagePickedCallback?.invoke(bytes)
} else {
onImagePickedCallback?.invoke(null)
}
onImagePickedCallback = null
}
}
override fun imagePickerControllerDidCancel(picker: UIImagePickerController) {
picker.dismissViewControllerAnimated(true) {
onImagePickedCallback?.invoke(null)
onImagePickedCallback = null
}
}
})
}
@Composable
actual fun PickImage(onImagePicked: (ByteArray?) -> Unit) {
val scope = rememberCoroutineScope()
LaunchedEffect(Unit) {
scope.launch(Dispatchers.Main) {
showImagePicker(onImagePicked)
}
}
}
private suspend fun showImagePicker(onImagePicked: (ByteArray?) -> Unit) = suspendCancellableCoroutine { continuation ->
onImagePickedCallback = { bytes ->
onImagePicked(bytes)
continuation.resume(Unit)
}
UIApplication.sharedApplication.keyWindow?.rootViewController?.let { rootViewController ->
rootViewController.presentViewController(pickerController, animated = true, completion = null)
} ?: run {
onImagePickedCallback?.invoke(null)
continuation.resume(Unit)
}
}
}
@OptIn(ExperimentalForeignApi::class)
fun NSData.toByteArray(): ByteArray = ByteArray(this.length.toInt()).apply {
usePinned {
memcpy(it.addressOf(0), [email protected], [email protected])
}
}
有一些 KMP 库可以帮助您轻松地在所有平台上选择文件和图像:
以下是如何在 Compose 多平台应用程序中使用 FileKit 选择图像:
在你的 build.gradle 中:
// Install
sourceSets {
commonMain.dependencies {
implementation("io.github.vinceglb:filekit-compose:0.8.2")
}
}
在您的撰写文件中:
// FileKit Compose
val launcher = rememberFilePickerLauncher(
type = PickerType.Image,
) { file ->
// Handle the picked file
}
Button(onClick = { launcher.launch() }) {
Text("Pick an image")
}
您可以在此处找到有关此内容的文档。此外,据我所知,使用这些库时不需要任何权限即可访问图库照片。