Compose 多平台应用程序中的 iOS 照片库访问问题

问题描述 投票:0回答:1

我正在开发一个 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])
    }
}
android ios kotlin permissions compose-multiplatform
1个回答
0
投票

有一些 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")
}

您可以在此处找到有关此内容的文档。此外,据我所知,使用这些库时不需要任何权限即可访问图库照片。

© www.soinside.com 2019 - 2024. All rights reserved.