Aprende a usar la API más moderna de UICollectionView en Swift
Aprende a usar la API más moderna de UICollectionView en Swift

UICollectionView, UICollectionViewCompositionalLayout y Diffable

UICollectionView tiene una nueva API para poder mostrar items en sus celdas. Hoy exploramos la API más moderna de esta vista en UIKit. Veremos paso a paso UICollectionViewCompositionalLayout y UICollectionViewDiffableDataSource

SwiftBeta

Tabla de contenido

Crea UICollectionView con la API más moderna de UIKit

Hoy en SwiftBeta vamos a explorar métodos nuevos de la API del CollectionView en UIKit. Apple, aparte de apostar por SwiftUI sigue mejorando las API del framework UIKit y hoy vamos a ver cómo de una manera muy simple y poco código, podemos implementar un CollectionView representando varios elementos y añadiendo de nuevos.

Creamos el proyecto en Xcode

Lo primero de todo que vamos hacer es crear el proyecto en Xcode. Acuérdate que estamos usando UIKit, es por eso que debes seleccionar Storyboard en Interface.

Una vez hemos creado el proyecto, vamos a crear nuestro UICollectionView:

import UIKit

class ViewController: UIViewController {
    
    lazy var swiftBetaCollectionView: UICollectionView = {
        let configuration = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
        let layout = UICollectionViewCompositionalLayout.list(using: configuration)
        
        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
        collectionView.translatesAutoresizingMaskIntoConstraints = false
        return collectionView
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        swiftBetaCollectionView.backgroundColor = .green
        view.addSubview(swiftBetaCollectionView)
        
        NSLayoutConstraint.activate([
            swiftBetaCollectionView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
            swiftBetaCollectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            swiftBetaCollectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            swiftBetaCollectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
        ])
    }
}
Creamos nuestro ViewController en Swift y UIKit

Vamos a ver paso a paso qué hemos hecho:

  • Nuestro layout es de tipo UICollectionViewCompositionalLayout y para poder instanciarlo necesitamos pasarle una configuración.
  • La configuración que necesitamos para nuestro layout la instanciamos con UICollectionLayoutListConfiguration, aquí seleccionamos la apariencia que queremos que tenga nuestro UICollectionView
  • Una vez hemos creado nuestro layout se los pasamos como parámetro al instanciar el UICollectionView.
  • Añadimos el UICollectionView a la vista del View Controller y añadimos las constraints.

API moderna de UICollectionView, conectamos con el DataSource

Ahora, lo que queremos hacer es mostrar celdas dentro de nuestro UICollectionView. Y para hacerlo debemos crear los datos, para ello vamos a crear una struct y luego crearemos un Array. No nos vamos a complicar y va a ser una simple struct con tres propiedades:

  • id
  • title
  • imageName

Tenemos la siguiente struct, fíjate que conforma el protocolo Hashable, esto es necesario para poder crear el UICollectionView con las APIs más modernas de UIKit:

struct Device: Hashable {
    let id: UUID = UUID()
    let title: String
    let imageName: String
}
Tipo nuevo llamado Device, que conforma el protocolo Hashable

y creamos el siguiente Array que es el que utilizaremos para mostrar dentro del CollectionView, cada elemento del Array será reprensetado en una única celda:

let home = [
    Device(title: "Laptop", imageName: "laptopcomputer"),
    Device(title: "Mac mini", imageName: "macmini"),
    Device(title: "Mac Pro", imageName: "macpro.gen3"),
    Device(title: "Pantallas", imageName: "display.2"),
    Device(title: "Apple TV", imageName: "appletv")
]
Creamos un Array de tipo Device

Una vez tenemos los datos y nuestro collection view, vamos a ver cómo conectamos estos datos para que se representen en él. Para hacerlo vamos a crear una propiedad de tipo UICollectionViewDiffableDataSource<Int, Device>

Y aquí dentro vamos a configurar la celda que queremos mostrar dentro de nuestro UICollectionView:

    lazy var datasource: UICollectionViewDiffableDataSource<Int, Device> = {
        let deviceCell = UICollectionView.CellRegistration<UICollectionViewListCell, Device> { cell, indexPath, model in
            var listContentConfiguration = UIListContentConfiguration.cell()
            listContentConfiguration.text = model.title
            listContentConfiguration.image = UIImage(systemName: model.imageName)
            cell.contentConfiguration = listContentConfiguration
        }
        
 	// TODO:
    }()
Creamos nuestro datasource, primero creamos la representación visual de nuestra celda

Fíjate que hasta aquí nada nuevo, el UIListContentConfiguration lo vimos en el video que explicábamos UITableView y UICollectionView.
Una vez hemos definido nuestra celda, lo segundo que vamos a decirle a nuestro CollectionView es que utilice la nueva celda, la que acabamos de llamar deviceCell para representar la información de nuestro Array home.
La propiedad quedaría de la siguiente manera:

    lazy var datasource: UICollectionViewDiffableDataSource<Int, Device> = {
        let deviceCell = UICollectionView.CellRegistration<UICollectionViewListCell, Device> { cell, indexPath, model in
            var listContentConfiguration = UIListContentConfiguration.cell()
            listContentConfiguration.text = model.title
            listContentConfiguration.image = UIImage(systemName: model.imageName)
            cell.contentConfiguration = listContentConfiguration
        }
        
        let datasource = UICollectionViewDiffableDataSource<Int, Device>(collectionView: swiftBetaCollectionView) { collectionView, indexPath, model in
            return collectionView.dequeueConfiguredReusableCell(using: deviceCell,
                                                                for: indexPath,
                                                                item: model)
        }
            
        return datasource
    }()
En nuestro datasource, usamos el método dequeueConfiguredReusableCell

Creamos el snapshot de nuestro diffable datasource

Fíjate que hasta ahora no hemos conectado aún los datos del Array home, con nuestro CollectionView, y es justo lo que vamos hacer a continuación:

var snapshot = datasource.snapshot() // 1
snapshot.appendSections([0])		 // 2
snapshot.appendItems(home)			 // 3
datasource.apply(snapshot)			 // 4
Creamos un snapshot de nuestro DataSource y le añadimos datos en la primera sección

Vamos paso por paso:

  1. Aquí creamos un snapshot de nuestro datasource
  2. Añadimos una única sección
  3. Añadimos los items
  4. Para poder ver los cambios, aplicamos el nuevo snapshot a nuestro datasource

Si ahora compilamos, vamos a ver qué se muestra en el simulador:

Simulador mostrando nuestro UICollectionView
Simulador mostrando nuestro UICollectionView

En este código podríamos añadir más secciones en nuestro UICollectionView, vamos a crear un nuevo array llamado office:

let office = [
    Device(title: "Laptop", imageName: "laptopcomputer"),
    Device(title: "Mac mini", imageName: "macmini"),
    Device(title: "Mac Pro", imageName: "macpro.gen3"),
]
Creamos más datos para una nueva sección en nuestro UICollectionView

Y ahora modificamos nuestro código anterior (dónde creábamos el snapshot), y en lugar de tener una única sección, vamos a tener dos:

var snapshot = datasource.snapshot()
snapshot.appendSections([0, 1])
snapshot.appendItems(home, toSection: 0)
snapshot.appendItems(office, toSection: 1)
datasource.apply(snapshot)
Añadimos los nuevos datos en la nueva sección

Si ahora compilamos, aparecen dos secciones.

Simulador mostrando información de dos secciones con datos diferentes en nuestro UICollectionView
Simulador mostrando información de dos secciones con datos diferentes en nuestro UICollectionView

Y te preguntarás, cómo podemos añadir más elementos en alguna de estas secciones? Muy fácil, tan solo tenemos que insertar los nuevos elementos en la sección que queramos, de la siguiente manera:

DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
    snapshot.appendItems([.init(title: "New Device", imageName: "appletv")], toSection: 0)
    snapshot.appendItems([.init(title: "New Device 2", imageName: "appletv")], toSection: 0)
    self.datasource.apply(snapshot)
}
Añadimos datos cuando inicializamos la app

Para poder ver el cambio, he añadido un pequeño delay, de esta manera si ejecutamos la app vemos un contenido, y al pasar 2 segundos, se ejecuta el código del DispatchQueue, que lo que hace es añadir 2 nuevos items en la sección 0

Conclusión

Hoy hemos visto otra manera de usar UICollectionView, si comparas el video de UICollectionView, verás que aquí hemos utilizado muchas menos líneas de código, está todo mucho más simplificado y limpio.

Si quieres seguir aprendiendo sobre SwiftUI, Swift, Xcode, o cualquier tema relacionado con el ecosistema Apple