UISheetViewController en iOS 15 para presentar nuestros ViewController como sheets
UISheetViewController en iOS 15 para presentar nuestros ViewController como sheets

Navegación con UIKit: UISheetPresentationController

Desde iOS 15 que podemos usar UISheetPresentationController, en el post de hoy aprendemos a cómo presentar nuestros UIViewController como si fueran sheet. También vamos a aprender a customizarlo con algunos parámetros cómo medium o large.

SwiftBeta

Tabla de contenido

Aprende a presentar ViewController como si fueran sheets

Hoy en SwiftBeta vamos a ver otra manera de navegar a otra pantalla, y para hacerlo vamos a usar una nueva clase que apareció en iOS 15, la clase se llama UISheetPresentationController. En muchas de las apps de iOS se utiliza esta presentación, lo podemos ver por ejemplo en la app de maps. Nosotros hoy vamos a presentar un ViewController utilizando UISheetPresentationController, verás que con muy pocas líneas de código podemos aportar esta presentación.

App maps de iOS donde se ve el funcionamiento de UISheetPresentationController
App maps de iOS donde se ve el funcionamiento de UISheetPresentationController

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 un nuevo ViewController. Lo vamos a llamar SheetViewController, y puedes hacerlo lo más sencillo que quieras. En mi caso voy a añadir un UILabel.

import UIKit

class SheetViewController: UIViewController {
    
    private let swiftBetaLabel: UILabel = {
        let label = UILabel()
        label.text = "¡Suscríbete a SwiftBeta!"
        label.font = .systemFont(ofSize: 32)
        label.textColor = .white
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .purple
        view.addSubview(swiftBetaLabel)
        
        NSLayoutConstraint.activate([
            swiftBetaLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            swiftBetaLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
        ])
    }
}
Creamos un ViewController usando Swift y UIKit

Una vez creado nos vamos a nuestro ViewController, y desde allí mostraremos nuestro SheetViewController. Vamos a crear un UIButton que esté en el centro de la pantalla, y que cada vez que se pulse nos presente el SheetViewController que acabamos de crear.

import UIKit

class ViewController: UIViewController {
    
    private lazy var swiftBetaButton: UIButton = {
        var configuration = UIButton.Configuration.bordered()
        configuration.title = "¡Suscríbete a SwiftBeta! 🥳"
        
        let button = UIButton(type: .system, primaryAction: UIAction(handler: { _ in
            self.presentSheetViewController()
        }))
        button.configuration = configuration
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .orange
        view.addSubview(swiftBetaButton)
    
        NSLayoutConstraint.activate([
            swiftBetaButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            swiftBetaButton.centerYAnchor.constraint(equalTo: view.centerYAnchor),
        ])
    }

    func presentSheetViewController() {
        // TODO
    }
}
Añadimos un UIButton en ViewController para presentar SheetViewController

Hasta aquí nada nuevo, fíjate que nuestro UIButton llama al método presentSheetViewController. Vamos a rellenar el método para que se presente el SheetViewController.

    func presentSheetViewController() {
        let viewControllerToPresent = SheetViewController()
        if let sheet = viewControllerToPresent.sheetPresentationController {
            sheet.detents = [.medium(), .large()]
            sheet.selectedDetentIdentifier = .medium
            sheet.prefersGrabberVisible = true
            sheet.preferredCornerRadius = 20
        }
        present(viewControllerToPresent, animated: true, completion: nil)
    }
Lógica para mostrar nuestro SheetViewController como un sheet

Lo que hemos hecho ha sido modificar la propiedades de sheetPresentationController:

  • Crear una instancia de nuestro SheetViewController
  • Extraemos el valor de sheetPresentationCongtroller en la constante sheet
  • Configuramos nuestro sheet. Aquí podemos dar varios valores: medium y large
  • Presentamos nuestro SheetViewController

Si compilamos nuestro código vemos como al pulsar el UIButton aparece nuestro SheetViewController, con la configuración que hemos especificado en el método presentSheetViewController

Resultado que obtenemos al compilar nuestro código en Xcode
Resultado que obtenemos al compilar nuestro código en Xcode

Antes de acabar, me gustaría comentar un tema interesante, ¿quién se debe encargar de configurar el aspecto de nuestro SheetViewController? En este caso estamos delegando esta responsabilidad en nuestro ViewController, pero podemos mover la lógica dentro del SheetViewController. Así, si otro ViewController quiere mostrar nuestro SheetViewController, no duplicaríamos código en todos aquellos ViewControllers que quieren presentar el SheetViewController. Lo único que tendríamos que hacer es mover parte del código de presentSheetViewController() al viewDidLoad de nuestro SheetViewController, así que nuestro presentSheetViewController quedaría:

    func presentSheetViewController() {
        let viewControllerToPresent = SheetViewController()
        present(viewControllerToPresent, animated: true, completion: nil)
    }
Movemos la lógica de nuestro ViewController al SheetViewController

Y en el viewDidLoad de nuestro SheetViewController, añadimos el siguiente código:

override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .purple
        view.addSubview(swiftBetaLabel)
        
        NSLayoutConstraint.activate([
            swiftBetaLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            swiftBetaLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
        ])
        
        guard let presentationController = presentationController as? UISheetPresentationController else { return }

        presentationController.detents = [.medium(), .large()]
        presentationController.selectedDetentIdentifier = .medium
        presentationController.prefersGrabberVisible = true
        presentationController.preferredCornerRadius = 20
    }
Lógica de cómo debe presentarse nuestro SheetViewController

Mucho mejor. En el canal veremos Coordinator Pattern para delegar aún más esta responsabilidad. Pero primero quería explicaros varias maneras de navegar entre ViewControllers 👍

Conclusión

Hoy hemos aprendido otra manera de presentar view controllers dentro de nuestra jerarquía de vistas. Hemos utilizado el mismo método que vimos de presentar modales, pero en lugar de que nuestro ViewController ocupe toda la pantalla, hemos usado una serie de propiedades para customizarlo, de esta manera nosotros podemos escoger si ocupa toda la pantalla o no.

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