This document is also available in English: FAQ.md
Preguntas y respuestas comunes sobre ImagePickerKMP.
ImagePickerKMP es una librería moderna y multiplataforma para selección de imágenes en Kotlin Multiplatform (KMP) que proporciona integración de cámara para Android e iOS.
Características clave:
-
Integración de cámara multiplataforma
-
Manejo inteligente de permisos
-
Componentes de UI personalizables
-
Captura de fotos de alta calidad
-
Manejo de errores completo
- Android: API 21+ (Android 5.0+)
- iOS: iOS 12.0+
- Kotlin Multiplatform: Soporte completo
Android:
- SDK mínimo: API 21
- Kotlin: 1.8+
- Compose Multiplatform: 1.4+
iOS:
- iOS: 12.0+
- Xcode: 14+
- Kotlin Multiplatform: 1.8+
Sí, ImagePickerKMP es open-source y gratuita bajo la licencia MIT. Puedes usarla en proyectos personales y comerciales.
Ventajas:
-
Multiplataforma con un solo código base
-
UI moderna con Compose Multiplatform
-
Manejo inteligente de permisos
-
Componentes personalizables
-
Desarrollo y soporte activo
Comparado con alternativas:
- Más moderna que CameraX (solo Android)
- Más integrada que UIImagePickerController (solo iOS)
- Mejor manejo de permisos que la mayoría
- Ventaja multiplataforma sobre soluciones específicas
Agrega la dependencia en tu build.gradle.kts:
dependencies {
implementation("io.github.ismoy:imagepickerkmp:1.0.22")
}Android (AndroidManifest.xml):
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" android:required="true" />iOS (Info.plist):
<key>NSCameraUsageDescription</key>
<string>Esta app necesita acceso a la cámara para capturar fotos</string>Para uso básico, no se requiere configuración adicional. La librería maneja la mayoría de la configuración automáticamente.
Para funciones avanzadas, podrías necesitar:
-
Configurar temas personalizados
-
Añadir diálogos de permisos personalizados
-
Configurar preferencias de captura de fotos
-
Agrega a tu proyecto iOS:
# Podfile target 'YourApp' do use_frameworks! pod 'ImagePickerKMP', :path => '../ruta/a/tu/libreria' end
-
Ejecuta pod install:
pod install
-
Importa en tu código iOS:
import ImagePickerKMP
@Composable
fun MyImagePicker() {
var showImagePicker by remember { mutableStateOf(false) }
if (showImagePicker) {
ImagePickerLauncher(
context = LocalContext.current,
onPhotoCaptured = { result ->
println("Photo captured: ${result.uri}")
showImagePicker = false
},
onError = { exception ->
println("Error: ${exception.message}")
showImagePicker = false
}
)
}
Button(onClick = { showImagePicker = true }) {
Text("Take Photo")
}
}La librería maneja los permisos automáticamente, pero puedes personalizar el comportamiento:
@Composable
fun CustomPermissionHandler() {
RequestCameraPermission(
titleDialogConfig = "Permiso de cámara requerido",
descriptionDialogConfig = "Por favor habilita el acceso a la cámara",
btnDialogConfig = "Abrir ajustes",
onPermissionPermanentlyDenied = {
// Manejar denegación permanente
},
onResult = { granted ->
// Manejar resultado de permiso
}
)
}@Composable
fun CustomImagePicker() {
var showPicker by remember { mutableStateOf(false) }
if (showPicker) {
ImagePickerLauncher(
context = LocalContext.current,
config = ImagePickerConfig(
onPhotoCaptured = { result -> showPicker = false },
onError = { exception -> showPicker = false },
cameraCaptureConfig = CameraCaptureConfig(
preference = CapturePhotoPreference.HIGH_QUALITY,
permissionAndConfirmationConfig = PermissionAndConfirmationConfig(
customPermissionHandler = { config ->
// Manejo personalizado de permisos
},
customConfirmationView = { result, onConfirm, onRetry ->
// Vista de confirmación personalizada
}
)
)
)
)
}
Button(onClick = { showPicker = true }) {
Text("Tomar foto")
}
}@Composable
fun HighQualityImagePicker() {
ImagePickerLauncher(
context = LocalContext.current,
onPhotoCaptured = { result ->
// Manejar foto de alta calidad
},
onError = { exception ->
// Manejar errores
},
preference = CapturePhotoPreference.HIGH_QUALITY
)
}La librería proporciona un callback onDismiss que se activa cuando el usuario cancela o cierra el selector sin seleccionar nada. Esto es esencial para resetear el estado de tu UI.
@Composable
fun MiSelectorImagen() {
var mostrarSelector by remember { mutableStateOf(false) }
if (mostrarSelector) {
ImagePickerLauncher(
config = ImagePickerConfig(
onPhotoCaptured = { resultado ->
println("Foto capturada: ${resultado.uri}")
mostrarSelector = false
},
onError = { excepcion ->
println("Error: ${excepcion.message}")
mostrarSelector = false
},
onDismiss = {
println("Usuario canceló o cerró el selector")
mostrarSelector = false // Resetear estado cuando el usuario no selecciona nada
}
)
)
}
Button(onClick = { mostrarSelector = true }) {
Text("Tomar Foto")
}
}@Composable
fun MiSelectorGaleria() {
var mostrarGaleria by remember { mutableStateOf(false) }
if (mostrarGaleria) {
GalleryPickerLauncher(
onPhotosSelected = { resultados ->
println("Seleccionadas ${resultados.size} imágenes")
mostrarGaleria = false
},
onError = { excepcion ->
println("Error: ${excepcion.message}")
mostrarGaleria = false
},
onDismiss = {
println("Usuario canceló la selección de galería")
mostrarGaleria = false // Resetear estado cuando el usuario no selecciona nada
},
allowMultiple = true
)
}
Button(onClick = { mostrarGaleria = true }) {
Text("Seleccionar de la Galería")
}
}Cuándo se activa onDismiss:
- Android: Usuario cancela el diálogo de selección o la interfaz de cámara
- iOS: Usuario toca "Cancelar" en el diálogo o la interfaz de cámara
- iOS: Usuario cancela la solicitud de permisos de cámara
- iOS: Usuario cancela la interfaz de cámara (toca "Cancel" o "X")
Similitudes:
- Misma interfaz de API
- Mismo manejo de permisos
- Mismo manejo de errores
- Mismas opciones de personalización
Diferencias:
- Android usa CameraX, iOS usa AVFoundation
- El flujo de permisos es ligeramente diferente (iOS muestra ajustes tras la primera denegación)
- Parámetro context (Android lo requiere, iOS usa null)
- Algunas optimizaciones específicas de plataforma
@Composable
fun PlatformSpecificImagePicker() {
ImagePickerLauncher(
context = LocalContext.current, // null para iOS
onPhotoCaptured = { result ->
// Manejo agnóstico de plataforma
},
onError = { exception ->
when (exception) {
is CameraPermissionException -> {
// Manejar errores de permisos
}
is PhotoCaptureException -> {
// Manejar errores de captura
}
else -> {
// Manejar otros errores
}
}
}
)
}Las características específicas de iOS son gestionadas internamente por la librería. No necesitas escribir código específico para la mayoría de los casos.
Para funciones avanzadas de iOS:
// Configuración específica de iOS
@Composable
fun IOSImagePicker() {
ImagePickerLauncher(
context = null, // iOS no necesita context
onPhotoCaptured = { result ->
// Manejo específico de iOS
},
onError = { exception ->
// Manejo de errores específico de iOS
}
)
}- Permisos: Asegúrate de que el permiso de cámara esté concedido
- Hardware: Verifica que el dispositivo tenga cámara
- Contexto: Asegúrate de pasar el context correcto (Android)
- Ciclo de vida: Verifica el estado del componente
- Dependencias: Revisa que todas las dependencias estén agregadas
- Revisa el manifest: Asegúrate de declarar el permiso de cámara
- Revisa Info.plist: Asegúrate de tener NSCameraUsageDescription (iOS)
- Revisa la implementación: Usa RequestCameraPermission
- Revisa la plataforma: Verifica la configuración específica
- Hardware: El dispositivo puede no tener cámara
- Permisos: El permiso puede estar denegado
- Cámara en uso: Otra app puede estar usando la cámara
- Simulador: La cámara no está disponible en simulador (usa un dispositivo)
- Memoria: Usa compresión de imagen para fotos grandes
- Ciclo de vida: Maneja correctamente el ciclo de vida
- Contexto: Verifica la validez del context
- Manejo de excepciones: Añade manejo de errores adecuado
@Composable
fun RobustImagePicker() {
ImagePickerLauncher(
context = LocalContext.current,
onPhotoCaptured = { result ->
try {
// Procesar foto de forma segura
processPhoto(result)
} catch (e: Exception) {
// Manejar errores de procesamiento
showError("Error al procesar la foto: ${e.message}")
}
},
onError = { exception ->
// Manejar errores de captura
showError("Error de cámara: ${exception.message}")
}
)
}// Depurar estado de permisos
fun debugPermissions(context: Context) {
val hasPermission = ContextCompat.checkSelfPermission(
context,
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED
println("Permiso de cámara concedido: $hasPermission")
}La compilación de iOS falla con error de enlazador: _OBJC_CLASS_$_CLLocation o CoreLocation.framework no encontrado
Síntomas:
Could not find or use auto-linked framework '_LocationEssentials': framework '_LocationEssentials' not found
ld: Undefined symbols:
_OBJC_CLASS_$_CLLocation, referenced from:
in ComposeApp[...](libImagePickerKMP:library-cache.a.o)
linker command failed with exit code 1Android y JVM Desktop funcionan correctamente, pero la compilación de iOS falla durante la fase de enlazado.
Causa:
ImagePickerKMP usa CoreLocation internamente (p. ej. para metadatos de ubicación al capturar imágenes). En algunas configuraciones de Xcode / KMP el framework no se enlaza automáticamente, por lo que el linker no puede resolver los símbolos de CLLocation.
Solución:
Añade CoreLocation.framework manualmente en las Build Phases de tu proyecto Xcode:
- Abre tu proyecto iOS (
.xcworkspaceo.xcodeproj) en Xcode. - Selecciona el target de tu app en el navegador de proyecto.
- Ve a Build Phases → Link Binary With Libraries.
- Haz clic en + y busca CoreLocation.
- Selecciona
CoreLocation.frameworky haz clic en Add. - Limpia el build folder (Product → Clean Build Folder, o
⇧⌘K) y vuelve a compilar.
✅ No se requieren cambios en el código — es únicamente un paso de configuración a nivel de proyecto.
Entorno reportado: Xcode 15.2 · iOS 15 · Kotlin 2.2.x · ImagePickerKMP 1.0.35
- Usa URIs en vez de Bitmaps:
@Composable
fun MemoryEfficientImagePicker() {
var imageUri by remember { mutableStateOf<Uri?>(null) }
ImagePickerLauncher(
context = LocalContext.current,
onPhotoCaptured = { result ->
// Guarda URI en vez de Bitmap
imageUri = result.uri
}
)
// Carga la imagen solo cuando sea necesario
imageUri?.let { uri ->
AsyncImage(
model = uri,
contentDescription = "Captured photo"
)
}
}- Usa compresión de imagen:
@Composable
fun CompressedImagePicker() {
ImagePickerLauncher(
context = LocalContext.current,
onPhotoCaptured = { result ->
// Comprime la imagen antes de procesar
val compressedImage = compressImage(result.uri, 80)
}
)
}- Usa preferencia FAST:
ImagePickerLauncher(
context = LocalContext.current,
preference = CapturePhotoPreference.FAST
)- Preinicializa la cámara:
// Preinicializa la cámara en background
LaunchedEffect(Unit) {
initializeCamera()
}@Composable
fun LargePhotoHandler() {
ImagePickerLauncher(
context = LocalContext.current,
onPhotoCaptured = { result ->
lifecycleScope.launch(Dispatchers.IO) {
// Procesa la foto grande en background
val processedImage = processLargeImage(result.uri)
withContext(Dispatchers.Main) {
// Actualiza la UI con la imagen procesada
}
}
}
)
}@Composable
fun CustomPermissionDialog(
title: String,
description: String,
onConfirm: () -> Unit,
onDismiss: () -> Unit
) {
AlertDialog(
onDismissRequest = onDismiss,
title = { Text(title) },
text = { Text(description) },
confirmButton = {
Button(onClick = onConfirm) {
Text("Grant Permission")
}
},
dismissButton = {
TextButton(onClick = onDismiss) {
Text("Cancel")
}
}
)
}@Composable
fun CustomConfirmationView(
result: PhotoResult,
onConfirm: () -> Unit,
onRetry: () -> Unit
) {
Column(
modifier = Modifier.fillMaxSize()
) {
// Vista previa de la foto
AsyncImage(
model = result.uri,
contentDescription = "Captured photo",
modifier = Modifier.weight(1f)
)
// Botones de acción
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
Button(onClick = onRetry) {
Text("Retry")
}
Button(onClick = onConfirm) {
Text("Use Photo")
}
}
}
}@Composable
fun ThemedImagePicker() {
val customTheme = remember {
MaterialTheme.colors.copy(
primary = Color(0xFF1976D2),
secondary = Color(0xFF42A5F5)
)
}
MaterialTheme(colors = customTheme) {
ImagePickerLauncher(
context = LocalContext.current,
onPhotoCaptured = { result ->
// Manejar captura de foto
},
onError = { exception ->
// Manejar errores
}
)
}
}Problema: Las fotos capturadas con la cámara frontal aparecen con orientación incorrecta (espejadas o rotadas).
Causa: Las cámaras frontales de Android tienen una orientación diferente a las traseras. La imagen se captura con una orientación que no es natural para el usuario.
Solución: La librería ahora incluye corrección automática de orientación para fotos de cámara frontal. El sistema:
- Detecta automáticamente si la foto fue tomada con cámara frontal
- Aplica corrección de espejo solo cuando es necesario
- Mantiene la calidad de la imagen original
- Es eficiente - solo procesa cuando realmente necesita corrección
Ejemplo de uso:
ImagePickerLauncher(
context = LocalContext.current,
onPhotoCaptured = { result ->
// La imagen ya viene corregida automáticamente
// No necesitas hacer nada adicional
}
)Nota: Esta corrección se aplica automáticamente y es transparente para el desarrollador. No necesitas configurar nada adicional.
La corrección incluye:
- Lectura de metadatos EXIF: Se lee la orientación original de la imagen
- Aplicación de rotación: Se corrige la rotación basada en los metadatos
- Corrección de espejo: Solo para cámara frontal, se aplica un espejo horizontal
- Optimización: Solo se procesa si realmente es necesario
No, la corrección está optimizada para:
- Procesamiento solo cuando es necesario: Si no hay corrección requerida, se devuelve la imagen original
- Gestión eficiente de memoria: Los bitmaps se reciclan correctamente
- Procesamiento asíncrono: No bloquea la UI
Actualmente la corrección es automática y no se puede desactivar, ya que mejora significativamente la experiencia del usuario. Si necesitas el comportamiento original, puedes procesar la imagen manualmente después de recibirla.
- Documentación: README.es.md
- Referencia de API: API_REFERENCE.es.md
- Ejemplos: EXAMPLES.es.md
- GitHub Issues: GitHub Issues
- Discusiones: GitHub Discussions
- Email: belizairesmoy72@gmail.com
- Busca issues existentes: Verifica si ya fue reportado
- Crea un nuevo issue: Usa la plantilla de bug report
- Proporciona detalles: Incluye pasos para reproducir, info de entorno, logs
- Da seguimiento: Responde a preguntas de los maintainers
- Busca issues existentes: Verifica si ya fue solicitada
- Crea una solicitud: Usa la plantilla de feature request
- Proporciona detalles: Incluye caso de uso, implementación propuesta
- Discute: Participa en la comunidad
- Haz fork del repositorio
- Crea una rama de feature
- Haz tus cambios
- Añade tests
- Envía un pull request
Consulta CONTRIBUTING.es.md para guías detalladas.
¿Aún tienes preguntas? No dudes en preguntar en nuestras GitHub Discussions o contáctanos directamente.