Os presentamos Photos Framework, una librería que nos ofrece Apple que nos permitirá trabajar con la biblioteca de fotos y videos fácilmente.
Introducción
Si antes de iOS 8 habéis intentado hacer uso de las fotos que el usuario guarda en su galería, os habréis dado cuenta de que es un auténtico parto, no solo por la poca funcionalidad que podemos ejercer sobre dichas fotografías, sino también por lo críptico que era la API.
Ahora con Photos Framework se acabaron nuestro problemas, un framework destinado a que podamos trabajar fácilmente con las fotos del usuario que tiene nuestra app instalada.
La funcionalidad no se va a limitar a guardar fotos en el carrete, sino que vamos a poder recuperar las fotos existentes, las colecciones, los momentos, las favoritas del usuario, y mucho más…
Photos Framework es uno de los dos frameworks, junto a Photos UI, que componen la suite PhotosKit.
Las principales clases
Para trabajar con Photos Framework vamos a trabajar principalmente con las siguientes clases:
PHAsset
: representa un único elemento multimedia, que podrá ser tanto una fotografía como un video. Entre sus propiedades contiene si es una foto, un video, si esta oculto (funcionalidad de iOS 8), si esta marcada como favorito por el usuario, y por supuesto todos sus metatarso.PHAssetCollection
: representa una lista ordenada de PHAsset.PHCollectionList
: representa una lista ordenada de PHAssetCollection o incluso otros PHCollectionList.
Todos estos objetos, también llamados entidades foto, son de solo lectura.
Para hacer cualquier tipo de modificación sobre una entidad foto, se debe crear una petición de modificación y persistir los cambios contra un objeto PHPhotoLibrary
, que representa la biblioteca compartida del usuario. Por como esta pensado el framework, es muy sencillo trabajar con múltiples hilos o múltiples apps contra un mismo asset o colección de assets.
Recuperando assets
Para recuperar de la biblioteca del usuario cualquiera de las entidades que acabamos de ver, basta con utilizar cualquier de la variedad de métodos de clase correspondientes a dicha entidad.
A continuación os mostramos algunos sencillos ejemplos:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// Recuperar los albumes inteligentes PHAssetCollection.fetchAssetCollectionsWithType( PHAssetCollectionType.SmartAlbum, subtype: PHAssetCollectionSubtype.Any, options: nil) // Recuperar los albumes normales PHAssetCollection.fetchAssetCollectionsWithType( PHAssetCollectionType.Album, subtype: PHAssetCollectionSubtype.Any, options: nil) // Recuperar los momentos PHAssetCollection.fetchAssetCollectionsWithType( PHAssetCollectionType.Moment, subtype: PHAssetCollectionSubtype.Any, options: nil) // Recuperar todas las imágenes del carrete PHAsset.fetchAssetsWithMediaType(.Image, options: nil) |
Cada consulta que realizamos a la biblioteca del usuario puede además ir acompañada de un objeto de tipo PHFetchOptions
. Este objeto nos va a permitir indicar qué tipo de objetos queremos recuperar, el orden en que serán recuperados, y como nos notificará la app Fotos ante cualquier cambio en los resultados. Veréis muchas cosas en común en el uso de PHFetchOptions
con respecto al uso de predicados en Core Data.
Las consultamos que realizamos con este tipo de clases siempre devuelven un tipo PHFetchResult
. Esta clase tiene un manejo muy similar al de un NSArray, por lo que podemos iterar sus elementos, acceder a uno concreto por índice, o hacer un count del número total de registros. Es importante saber que la carga de estos elementos es de tipo lazy, es decir, que no se recuperando todos los elementos en el momento de la llamada, sino que dicha carga se hacer por bloques a criterio del framework
. Es por esto último, que si depuramos nuestro código es posible que al llegar a la linea de la consulta no veamos resultados dentro del depurador.
Recuperando la información binaria del asset
Cuando ya hemos dado con el asset que nos interesa y queremos recuperar la información multimedia, es decir la foto o video en si mismo, deberemos hacer uso de una nueva clase Singleton: PHImageManager
. Esta clase se encargará de recuperar nuestros contenidos de forma asíncrona y con los mecanismos de caché que sean necesarios.
1 2 3 4 5 6 7 8 |
self.imageManager?.requestImageForAsset(imageAsset!, targetSize: CGSize(width: 320, height: 320), contentMode: .AspectFill, options: nil) { image, info in self.photoImageView.image = image } |
Observando cambios en los assets
Dado que la biblioteca multimedia del usuario es algo dinámico, podrían producirse cambios en los assets que la componen, y nuestra app deberá estar al corriente de los mismos.
Para ello, utilizando la clase PHPhotoLibrary
podremos suscribirnos a dichos cambios para poder actuar sobre nuestra app siempre que sea necesario debido al cambio.
Los cambios que se producen en los assets van inmersos dentro de un objeto de tipo PHChange
, que contiene la versión previa y posterior para cada uno de los datos modificados para dicho asset.
Cacheo de imágenes y miniaturas
Por si todo esto fuera poco, además el Photo Framework nos hace que podamos olvidarnos de los mecanismos de caché, ya que hace todo el trabajo sucio por nosotros.
Cuando recuperamos un archivo multimedia utilizando la clase PHImageManager
, podemos especificar el tamaño en que deseamos recuperar dicho elemento, y la clase se encargará de devolvernos un elemento cacheado o generarlo para nosotros en dicho momento.
Estas funcionalidades están presentes tanto para el uso de fotografías como de videos.
La privacidad es lo primero
Para poder interactuar con la biblioteca multimedia del usuario, debemos pedir permiso, y para ello deberemos hacer lo siguiente:
1 2 3 4 5 6 7 8 9 10 11 |
PHPhotoLibrary.requestAuthorization { status in dispatch_async(dispatch_get_main_queue()) { if (status == PHAuthorizationStatus.Authorized) { // Lógica cuando tengamos permisos } else { // Lógica cuando no tengamos permisos } } } |
Una app para mostrar las fotos favoritas del usuario
Vamos a crear una sencilla app utilizando la plantilla Single View Application, y vamos a susituir el View Controller que viene por defecto por un Table View Controller desde el Storyboard universal de nuestra app.
A continuación dentro de nuestro Storyboard vamos a añadir un UIImageView
a nuestra celda prototipo, y creando una clase PhotoCell.swift, subclase de UITableViewCell
, crearemos un sencillo IBOutlet
con nuestra UIImageView
, para poder manipularla desde el código.
A continuación vamos a crear un método, que sera el que recupere de la biblioteca el album de favoritos del usuario:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
func recuperarColecciones() { // Cargamos el album de favoritos del usuario var albumes = PHAssetCollection.fetchAssetCollectionsWithType(PHAssetCollectionType.SmartAlbum, subtype: PHAssetCollectionSubtype.SmartAlbumFavorites, options: nil) // Cogemos el único album devuelto (dado que es Favorites) var album : PHAssetCollection = albumes.lastObject as PHAssetCollection // Cargamos los assets del album var assets = PHAsset.fetchAssetsInAssetCollection(album, options: nil) elementos = assets tableView.reloadData() } |
Ahora, vamos a crear en nuestro View Controller un par de propiedades que nos van a ser de utilidad:
1 2 3 4 5 6 7 |
// Objeto PHFetchResult para el datasource var elementos : PHFetchResult! // Image Manager var imageManager = PHImageManager.defaultManager() |
Hecho esto, vamos a crearnos en nuestra Table View un UIRefreshControl
que invoque al método que hemos creado anteriormente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
override func viewDidLoad() { super.viewDidLoad() self.title = "Photos Sample" // Creamos el refresh control y le asociamos un método de nuestra clase self.refreshControl = UIRefreshControl() self.refreshControl?.attributedTitle = NSAttributedString(string: "Recargar favoritas") self.refreshControl?.addTarget(self, action: "refrescar:", forControlEvents: UIControlEvents.ValueChanged) // Solicitamos autorización para acceder a la biblioteca del usuario PHPhotoLibrary.requestAuthorization { status in dispatch_async(dispatch_get_main_queue()) { if (status == PHAuthorizationStatus.Authorized) { self.recuperarColecciones() } else { var alert = UIAlertController(title: "Error", message: "No tenemos acceso a las Photos", preferredStyle: UIAlertControllerStyle.Alert) self.presentViewController(alert, animated: true, completion: nil) } } } } func refrescar(sender:AnyObject) { self.recuperarColecciones() self.refreshControl?.endRefreshing() } |
Como se puede ver, ademas de asignar a un método de nuestra clase el UIRefreshControl
, estamos solicitando al usuario acceso a la biblioteca como hemos visto anteriormente, y en caso afirmativo, estamos invocando al método que recupera los assets marcados como favoritos por el usuario.
Ya solo nos queda implementar los métodos correspondientes al Table View y su datasource:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return elementos?.count ?? 0 } override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier("CellIdentifier", forIndexPath: indexPath) as PhotoCell var asset : PHAsset = elementos[indexPath.row] as PHAsset // Recuperamos el binario del asset self.imageManager.requestImageForAsset(asset, targetSize: CGSizeMake(584, 292), contentMode: PHImageContentMode.AspectFit, options: nil, resultHandler: { (image, info) -> Void in var imagen : UIImage = image as UIImage cell.imagen.image = imagen }) return cell } |
Si todo ha ido bien, y ejecutamos nuestra app, veremos como todas aquellas fotografías o videos marcados por nuestra parte como favoritos previamente, se visualizan automáticamente dentro de nuestro Table View.
Si lo deseais podéis descargar el proyecto del ejemplo en la siguiente dirección:
http://www.migueldiazrubio.com/wp-content/uploads/2014/10/PhotosSample.zip
Conclusión
Como habréis visto con Photos Framework es muy sencillo hacer consultas sobre la biblioteca del usuario, recuperar los archivos multimedia que lo componen.
Aunque no lo hemos visto en el artículo, también es posible suscribirnos ante cambios en los elementos que componen la biblioteca del usuario, y también podemos generar nuevos elementos dentro de la misma.
Os recomiendo investigar en la documentación de Apple para sacarle todo el provecho posible a este nuevo framework.
Swift
Si os hacéis un lío con el código Swift de este artículo, es porque aún no conocéis nuestro Curso Swift en español. Podéis aprovecharos de un descuento promocional del 10% en la siguiente dirección:
https://www.udemy.com/swift-en-espanol/?couponCode=MIGUELDIAZRUBIO10
¡Disfrutarlo y nos vemos en el siguiente artículo!
Muy buen post, muy bien explicado todo, ademas de ser un tema que no se encuentra muy facil en internet.
Tengo una duda y no se si me la puedas resolver, dentro de mi app primero recupero las fotos y despues las voy clasificando por asi decir, quisiera saber como puedo borrar una foto de un asset o assetCollection aun no me queda claro cual jajaj, pero borrar la foto solamente dentro del asset de la app, no borrar del dispositivo iOS, y asi como borro la foto de ese asset, añadirlo a otro que al inicio seria uno vacio.
No se si me doy a explicar, pero es como si fueran Arrays y tuviera dos Array, uno vacio y el otro lleno de datos, y seria pasar por ejemplo :
ArraySinDatos.append(ArrayConDatos[0])
asi, pero con las fotos, pasar una foto al asset nuevo, y luego eliminar ese del asset original, asi:
ArrayConDatos.removeAtIndex(0)
Se que no se puede asi tal cual porque no son arreglos, pero queria demostrarlo en arreglos para darme a entender mejor, espero si me haya explicado bien.
Gracias por este post! 🙂
Hola,
en este post hablas acerca de como hacer para que nos cargue las fotos favoritas del usuario. Me gustaría hacer que en lugar de cargar las favoritas del usuario, cargase las que el usuario seleccione cuando pulse un botón en la app y que aunque las borre de la galería no se borren de la app a no ser que así lo haga mediante un botón de la app.
¿Como se haría esto?
Un saludo,
Ruben Fernandez
Hola Ruben,
Tendrías que en lugar de utilizar «PHAssetCollectionSubtype.SmartAlbumFavorites» utilizar aquella opción que mejor te encaje para ver las fotos del usuario. Una vez hecho esto tendrías que ir haciendo que cada vez que pinche en una foto y le diga «guardar» o lo que quieras hacer, se haga una copia de la información binaria de la imagen dentro del sandbox de tu app.
Esto es solo una breve indicación.
Te recomiendo utilizar nuestro foro para este tipo de preguntas, así todos pueden aportar y leer los comentarios fácilmente. http://foro.migueldiazrubio.com
Un abrazo y gracias por comentar!