Cómo construir programáticamente un clon de Spotify para iOS con AutoLayout

En esta publicación, intentaremos recrear el diseño de la pantalla de inicio de Spotify en Swift mediante programación. ¿Por qué programáticamente? Creo que siempre es bueno saber cómo construir cosas de diferentes maneras, y me gusta escribir código para hacer cosas mediante programación. Estas habilidades son especialmente útiles si trabaja en equipo o utiliza el control de versiones.

Esta es la pantalla de inicio real de la aplicación móvil de Spotify. Entonces, para lograr este tipo de diseño, UICollectionViewusaremos, y podemos usar TabBarControllertambién para crear el navegador de pestañas.

Requisito básico: primero asegúrese de tener Xcode +10 instalado y rápido +4.

Comencemos creando un nuevo proyecto de Xcode usando Xcode:

Y lo primero que debemos hacer ViewController.swiftes cambiar la superclase a en UICollectionViewControllerlugar de   UIViewControllerporque nuestra clase se basará en collectionView.

// // ViewController.swift // spotifyAutoLayout // // Created by admin on 10/31/19. // Copyright © 2019 Said Hayani. All rights reserved. // import UIKit class ViewController: UICollectionViewController { override func viewDidLoad() { super.viewDidLoad() collectionView.backgroundColor = .purple // Do any additional setup after loading the view. } } 

Si intenta ejecutar la aplicación, la compilación fallará. Necesitamos agregar algo de código al AppDelegate.swiftarchivo dentro de la didFinishLaunchingWithOptionsfunción más allá de este fragmento de código antes de la   returndeclaración:

 let layout = UICollectionViewFlowLayout() window = UIWindow() window?.rootViewController = ViewController(collectionViewLayout: layout)

Y el código debería verse así:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. let layout = UICollectionViewFlowLayout() window = UIWindow() window?.rootViewController = ViewController(collectionViewLayout: layout) return true }

Ahora debería poder ejecutar la aplicación y ver el backgroundColorcambio a purple:

El siguiente paso es distribuir el diseño y dividir el espacio equitativamente entre las secciones.

Definamos los métodos de nuestro CollectionView.

Los pasos:

  • Registre una celda reutilizable con identificador único
  • Definir el número de elementos de la sección.
  • Utilice la celda registrada  

Para usar algunos de los CollectionViewmétodos, siempre debemos conformarnos UICollectionViewDelegateFlowLayoutcomo una superclase y obtener el autocompletado de los métodos. Así que comencemos con el registro de CollectionViewCell.

Dentro View.DidLoad()llamamos al collectionView.register()método para registrar la celda reutilizable:

 collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: cellId)

Luego definimos el número de celdas que tendremos dentro del collectionViewusing numberOfItemsInSection. Por ahora solo necesitamos que sean 5 elementos:

 override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return 5 }

El siguiente paso es definir la celda reutilizable cellForItemAtque debe regresar UICollectionViewCelly tener una identificación única llamada cellId. El código se ve así:

 override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) cell.backgroundColor = .red return cell }

El código completo debería verse así:

import UIKit class ViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout { let cellId : String = "cellId" override func viewDidLoad() { super.viewDidLoad() collectionView.backgroundColor = .purple collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: cellId) } override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return 5 } override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) cell.backgroundColor = .red return cell } }

Debería poder ver 5 elementos con fondos rojos en la pantalla:

Agregue un ancho y alto personalizados a las celdas

Ahora debemos colocar las celdas en el orden correcto y darles un widthy height. Cada celda tomará el widthde la pantalla como width.

Tenemos la suerte de tener sizeForItemAtmétodo para que podamos dar a las células una costumbre widthy height. Es un método que debería devolver un CGSizetipo:

 func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { let width = view.frame.width let height = CGFloat(200) return CGSize(width: width, height: height) }

Así que hicimos la   Celltoma widthde la pantalla usando view.frame.widthy un personalizado heightcon es un CGFloattipo.

Ahora puede ver el resultado a continuación en su simulador:

Todo se ve bien hasta ahora. Esta vez, creemos una celda personalizada que se pueda reutilizar. Crea un nuevo archivo Swift llamado CustomCell:

CustomCell.swift debería verse así a continuación:

 import UIKit class CustomCell: UICollectionViewCell { override init(frame: CGRect) { super.init(frame: frame) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } 

Ahora, lo siguiente que tenemos que hacer es modificar dos métodos para admitir la celda reutilizable collectionView.registery cellForItemAt. Primero modifiquemos el método de registro. ReemplazarUICollectionViewCell.selfcon CustomCell:

 collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: cellId) 

A continuación, necesitamos lanzar cellForItemAtpara conformarnos CustomCellcomo se muestra a continuación:

 let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! CustomCell

Si ejecuta la aplicación, probablemente no notará ningún cambio, así que asigne a CustomCell un color de fondo backgroundColor = .yellow. No se olvide de quitar la línea cell.backgroundColor = .reden cellForItemAt. ¿Debería ver que el color de fondo cambia a amarillo?

Ahora es el momento de poner un poco de sal en CutomCell: D

Si observa la pantalla de inicio de Spotify, cada sección que es una CustomCellen nuestro ejemplo contiene un título de sección, subcélulas y es horizontal:

Agregar un título de sección

Let's add a title label to the cell. Create the titleLabel element inside the CutomCell class:

let titleLabel: UILabel = { let lb = UILabel() lb.text = "Section Title" lb.font = UIFont.boldSystemFont(ofSize: 14) lb.font = UIFont.boldSystemFont(ofSize: 14) return lb }()

Then add the element to the view inside init() block:

addSubview(titleLabel)

If you run the app you won't see any changes, and that's because we didn't put any constraint to the element yet. So let's add some constraints – add this property              lb.translatesAutoresizingMaskIntoConstraints = falsetotitleLabel to be able to apply constraints to the element:

After we add titleLabel to the view, we define the constraints:

 addSubview(titleLabel) titleLabel.topAnchor.constraint(equalTo: topAnchor, constant: 8).isActive = truetitleLabel.leftAnchor.constraint(equalTo: leftAnchor,constant: 8 ).isActive = true

Always make sure to add .isActive = true property – without it the constraint won't work!

Before we move on to the next part, let's first change the background color of the screen to black and also remove the yellow color for the cells:

Now comes the big part: putting sub cells into each cell. To achieve that we are going to add a CollectionView inside CustomCell.

To add a CollectionView inside UICollectionViewCell we need to add  properties UICollectionViewDelegate, UICollectionViewDelegateFlowLayout, and UICollectionViewDataSource as superClass to CustomCell.

Let's create the collectionView element as any simple view:

 let collectionView : UICollectionView = { // init the layout let layout = UICollectionViewFlowLayout() // set the direction to be horizontal layout.scrollDirection = .horizontal // the instance of collectionView let cv = UICollectionView(frame: .zero, collectionViewLayout: layout) // Activate constaints cv.translatesAutoresizingMaskIntoConstraints = false return cv }()

Notice that we add layout to the collectionView as layer in the initializer as we did the first time with the viewController.swift. Here we also specify the direction of the FlowLayout to be .horizontal.

Let's add the collectionView element to the view as subView.

We gonna make a function that do that for us to make the code a little bit cleaner.

 fileprivate func setupSubCells(){ // add collectionView to the view addSubview(collectionView) collectionView.dataSource = self collectionView.delegate = self // setup constrainst // make it fit all the space of the CustomCell collectionView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor).isActive = true collectionView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true collectionView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true collectionView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true } 

Make sure to set delegate to self for the collectionView and the dataSource as well:

 collectionView.dataSource = self

  collectionView.delegate = self

Then call the function within init block.

Xcode will display some errors if you trying to build the app because we are not conforming to UICollectionViewDelegate and UICollectionViewDelegateFlowLayout protocols. To fix that we need first to register the sub cell as a reusable cell.

Create a variable at the top of the class and give it a name of cellId so we can use it when we need the cell identifier:

let cellId : String = "subCellID"

collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: cellId)

Now we're missing two more methods to make the errors go away: numberOfItemsInSection that define the number of cells in the section and cellForItemAt that define the reusable cell. These methods are necessary for  collectionView to work properly:

 // number of cells func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return 4 } // reusable Cell func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) cell.backgroundColor = .yellow return cell }

The results should look like this:

As you can see, the collectionView are in purple as background and sub cells are yellow.

The last things we can do before ending this article is make subCells have the height of the section and as width. Again we are using sizeForItemAt to define the height and the width of the cell .

 func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { let width = frame.height let height = frame.height return CGSize(width: width, height: height) }

And here we are ?:

NICE! I'm gonna stop at this point so this post isn't too long. I'll make a second part where we are going to add some mocked pictures and fill it with some data.

Full source code ? here

Please please if you have any additions, questions, or corrections, post it in the comments below ? or hit me up on Twitter.

Subscribe to my email list to be notified when the second part of this tutorial is published