
Cómo crear un módulo con Swift Package Manager y Github en Español
Crea módulos con Swift Package Manager en Xcode de una manera rápida y fácil. Swift Package Manager nos permite crear módulos y así agrupar código que está relacionado. Normalmente estos módulos tienen el código, assets, tests, etc. Al tener el código en módulos es mucho más limpio.
Tabla de contenido

Hoy en SwiftBeta vamos a aprender a crear módulos con Swift Package Manager. Pero ¿por qué es importante crear módulos? Cuando tienes una app en la que no solo empieza a crecer el código sino que también los developers que trabajan en ella, lo mejor que se puede hacer es crear módulos. La finalidad de los módulos es extraer código que está relacionado, hay distintas maneras de agrupar este código una podría ser una feature. Es decir, tener un módulo del Chat, Wallet, DesignSystem, etc.
Normalmente dentro de las empresas, cada feature es asignada a un equipo y ese equipo es el encargado de iterarla, arreglar bugs, añadir los tests, etc.
Si tenemos esta clara separación de features/responsabilidad es más fácil que:
- No hayan conflictos, es decir, no tendremos a varios developers tocando el mismo código.
- Tiempos de compilación más rápidos. Puedo probar mis cambios en mi módulo en lugar de compilar la app principal.
- Mejor categorización de bugs.
- Evitamos duplicar código.
- Podemos reusar módulos en otras apps.
Hoy lo que vamos a ver es a crear una app muy sencilla simulando un login (con su formulario, imagen, button, etc), no haremos ninguna petición HTTP, el propósito del video es crear un módulo en Swift Package Manager.
Una vez creado el login extraeremos esta pantalla a un módulo, lo llamaremos el módulo de Login y lo que haremos será importar el módulo de Login a nuestra app. Ese login lo podremos reutilizar en varias apps.
Desde Swift 5.3 podemos añadir a nuestros módulos de SPM imágenes, sonidos, etc. Al crear el paquete SPM generará el Bundle con el contenido que hayas añadido. Pudiéndolo usar sin ningún tipo de problemas. Antes de la versión 5.3 era imposible añadir recursos.
Creamos nuestro Login
Vamos a crear la siguiente vista

Y vamos a utilizar el siguiente código para representar nuestro Login, fíjate que hemos usado una imagen llamada swiftbeta, en este caso podéis usar cualquier otra, pero te la dejo aquí por si quieres usar la misma:

y aquí te dejo el código de la vista en SwiftUI:
import SwiftUI
struct ContentView: View {
@State var email: String = ""
@State var password: String = ""
var body: some View {
VStack {
Image("swiftbeta")
.resizable()
.frame(width: 200, height: 200)
Group {
HStack {
Image(systemName: "envelope.fill")
TextField("Email", text: $email)
.keyboardType(.emailAddress)
}
HStack {
Image(systemName: "key.fill")
TextField("Password", text: $password)
.keyboardType(.emailAddress)
}
}
.padding()
.overlay(
RoundedRectangle(cornerRadius: 12)
.stroke(.black, lineWidth: 1)
)
.padding(.horizontal, 80)
Button("Login") {
print("Login...")
}
.buttonStyle(.bordered)
.tint(.black)
Text("Suscríbete a SwiftBeta para apoyar el canal")
.font(.footnote)
.underline()
.foregroundStyle(.tertiary)
.padding(.top, 40)
Spacer()
}
}
}
Y a continuación vamos a crear el ViewModel con la lógica simulada de nuestro Login, ¿qué significa que simularemos el login? pues que para simplificar el código lo que haremos es que solo validaremos que el email introducido sea [email protected], el propósito del video no es crear un Login, es crear un módulo. Cuando el user se loguee mostraremos un alert para mostrar si ha sido exitoso o no.
En esta clase también añadiremos funciones que simularan el registro, recover password, etc.
import Foundation
enum LoginStatus: Error {
case success
case error
}
final class AuthenticationViewModel: ObservableObject {
@Published var loginStatus: LoginStatus = .success
@Published var didUpdateLoginStatus: Bool = false
func login(email: String, password: String) {
print("login...")
if email.lowercased() == "[email protected]" {
loginStatus = .success
} else {
loginStatus = .error
}
didUpdateLoginStatus = true
}
func signup(email: String, password: String) {
print("registrarse...")
}
func recoverPassword(email: String) {
print("Recuperar contraseña...")
}
func getAlertTitle() -> String {
loginStatus == .success ? "Login Success" : "Login Error"
}
}
Una vez tenemos nuestro AuthenticationViewModel vamos a usarlo en la vista:
import SwiftUI
struct ContentView: View {
@StateObject var authenticationViewModel = AuthenticationViewModel()
@State var email: String = ""
@State var password: String = ""
var body: some View {
VStack {
Image("swiftbeta")
.resizable()
.frame(width: 200, height: 200)
Group {
HStack {
Image(systemName: "envelope.fill")
TextField("Email", text: $email)
.keyboardType(.emailAddress)
}
HStack {
Image(systemName: "key.fill")
TextField("Password", text: $password)
.keyboardType(.emailAddress)
}
}
.padding()
.overlay(
RoundedRectangle(cornerRadius: 12)
.stroke(.black, lineWidth: 1)
)
.padding(.horizontal, 80)
Button("Login") {
print("Login...")
authenticationViewModel.login(email: email, password: password)
}
.buttonStyle(.bordered)
.tint(.black)
Text("Suscríbete a SwiftBeta para apoyar el canal")
.font(.footnote)
.underline()
.foregroundStyle(.tertiary)
.padding(.top, 40)
Spacer()
}
.alert(authenticationViewModel.getAlertTitle(),
isPresented: $authenticationViewModel.didUpdateLoginStatus) {
Button("Aceptar") {
print("Dismiss Alert")
}
}
}
}
Todo este código lo tenemos en la aplicación principal, pero lo que vamos hacer es crear un módulo, mover el código que acabamos de crear e importar el módulo en la app principal.
Creamos el módulo de Login con Swift Package Manager
Vamos a crear el módulo de Swift Package Manager, para hacerlo nos vamos a:
File -> New -> Package

Al hacerlo, aparecerá la siguiente pantalla:

Aquí, vamos a poner el nombre de nuestro nuevo módulo, vamos a llamarlo LoginModule y fíjate que en mi caso estoy en un raíz de proyectos diferentes. Aquí nos vamos a ir a los dos selectores de abajo, donde pone Add to y Group y justo en Add to vamos a seleccionar el proyecto en el que estábamos

Al hacerlo, selecciona solo el selector de Add to me rellena también el Group de la siguiente manera:

Antes de darle a Create, vamos a buscar nuestro proyecto y vamos abrir la carpeta que lo contiene, así al crear el módulo la carpeta que contiene todo su código también estará ahí. Es decir, en mi caso busco la carpeta con el nombre de mi proyecto llamado SwiftBeta-SPM-Modulos y cuando estoy dentro de la carpeta finalmente creo el módulo pulsando en el botón de Create.


A pulsar en Create vemos como la estructura de nuestros ficheros en Xcode ha cambiado, ahora podemos ver el módulo de LoginModule que acabamos de crear.

Tal y como explicamos en el primer video de Swift Package Manager, al usar código de terceros o al crear nuestro propio módulo, se genera un fichero package que contiene información relevante. Desde el nombre del módulo, targets, dependencias que puedas tener etc.
Al crear el módulo este está completamente vacío, hay que mover clases de la aplicación principal al módulo. La primera que vamos a mover va a ser AuthenticationViewModel
Pues vamos a arrastrar la clase al módulo y vamos a compilar a ver qué ocurre.

Al compilar obtenemos varios errores, el compilador se queja de que no encuentra la clase AuthenticationViewModel.

Para poder usar la clase AuthenticationViewModel hay que hacer varios cambios en nuestro código. Lo primero de todo es importar el nuevo módulo LoginModule en ContentView. Vamos a compilar otra vez para ver qué ocurre.

Al compilar seguimos obteniendo el mismo error. ¿Qué está pasando? Pues igual que podemos compilar el código de nuestra app, también podemos cambiar el target y probar el código que tenemos en nuestro módulo, esto nos ayudará a saber qué está pasando. Vamos a cambiar de target a LoginModule y vamos a compilar:

Al cambiar el target a LoginModule y compilar vemos el siguiente error:

Ahora ya sabemos qué estaba pasando. Al tener un error en el módulo, la app no compilaba. Vamos a arreglar el error del módulo, para ellos nos vamos al Package de nuestro LoginModule y añadimos cuál es el mínimo sistema operativo que puede usar nuestro módulo. En nuestro caso vamos a usar que sea iOS 15.
let package = Package(
name: "LoginModule",
platforms: [.iOS(.v15)],
...
Si ahora compilamos vemos, que en nuestro nuevo módulo han desaparecido los errores. Vamos a cambiar de target otra vez y vamos a seleccionar la app principal, a ver qué ocurre.

Al compilar seguimos teniendo un error, no encuentra la clase AuthenticationViewModel. Esto es debido a que cuando metemos código en un módulo debemos utilizar los Access Levels adecuados. Para no pasarnos con el video de hoy, vamos a decir que todo el código que queremos usar de nuestro módulo en la app principal debe ser público. Por eso debemos exponer qué clases y métodos queremos usar desde la app principal y para hacerlo debemos colocar la palabra public. Vamos a verlo mejor en código:
import Foundation
enum LoginStatus: Error {
case success
case error
}
final public class AuthenticationViewModel: ObservableObject {
@Published var loginStatus: LoginStatus = .success
@Published public var didUpdateLoginStatus: Bool = false
public init() { }
public func login(email: String, password: String) {
print("login...")
if email.uppercased() == "[email protected]" {
loginStatus = .success
} else {
loginStatus = .error
}
didUpdateLoginStatus = true
}
func signup(email: String, password: String) {
print("registrarse...")
}
func recoverPassword(email: String) {
print("Recuperar contraseña...")
}
public func getAlertTitle() -> String {
loginStatus == .success ? "Login Success" : "Login Error"
}
}
Fíjate que hay método en los que no hemos añadido public, ya que de momento no los queremos exponer fuera del módulo
Y el último paso que nos falta es importar el Framework a la app principal:

Una vez hemos hecho estos pasos:
- Añadir platforms: [.iOS(.v15)] en Package.swift
- Añadir public en AuthenticationViewModel para exponer el código que queremos usar desde la app principal.
- Hacer import de LoginModule
- Añadir nuestro módulo en la sección de Frameworks, Libraries and Embedded Content
Podemos compilar nuestro código y funciona perfectamente, al poner un email cualquiera y darle a login vemos como aparece el alert. Lo que hemos hecho ha sido mover una clase a un módulo pero podríamos mover la vista e incluso la imagen que usamos en la pantalla de Login. Todo esto lo vamos a ver en la siguiente sección.
Mover la Vista e Imagen al módulo LoginModule
La vista ContentView la vamos a arrastrar al módulo y vamos a crear un inicializador público para poderlo usar desde la app principal. También expondremos la struct y su propiedad body usando public.

El código quedaría de la siguiente manera:
public struct ContentView: View {
@StateObject var authenticationViewModel = AuthenticationViewModel()
@State var email: String = ""
@State var password: String = ""
public init() { }
public var body: some View {
...
Y lo único que tenemos que hacer en la app principal es importar LoginModule donde se esté utilizando ContentView(), en mi caso el código quedaría:
import SwiftUI
import LoginModule
@main
struct SwiftBeta_SPM_ModulosApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
Ahora lo único que nos falta por meter en el módulo es la imagen que hemos usado para mostrar el Login. Para hacerlo vamos a crear un nuevo fichero llamado Assets (como el que tenemos en la app principal). Pulsamos CMD+N y buscamos Asset Catalog.

Cuando lo creemos añadimos la imagen que estábamos usando en la app principal (y la borramos de allí, así nos aseguramos que la única imagen que tenemos está en el módulo LoginModule).
Si compilamos la app principal veremos que no nos aparece el icono, debemos hacer un pequeño cambio. Lo que vamos hacer es irnos a la vista ContentView que tenemos en el módulo LoginModule y vamos a añadir un nuevo parámetro cuando inicializamos la Image.
Image("swiftbeta", bundle: .module)
.resizable()
.frame(width: 200, height: 200)
Vamos a añadir un nuevo parámetro llamado bundle y vamos a darle el valor .module. De esta manera si compilamos la app vemos como la imagen de swiftbeta vuelve a aparecer, pero esta vez la imagen la tenemos dentro del módulo.
Lo que acabamos de hacer es muy potente, hemos creado un módulo que tiene una única finalidad y es la de tener código relacionado con el Login. Al tener este módulo totalmente aislado y funcional, podríamos crear un repo en Github y hacerlo open source para que otros developer lo utilicen (o incluso nosotros en otras app). Vamos a subir nuestro código a Github y vamos a crear un proyecto de cero para ver lo fácil que sería usarlo en un proyecto de cero.
Publicar nuestro módulo Swift Package Manager en Github
Para poder publicar nuestro código en Github primero debemos tener inicializado git en la carpeta del módulo.

Para ello nos vamos a la carpeta de nuestro módulo y pulsamos en la terminal
git init
Y comiteamos todos los cambios que tenemos. Te aconsejo que sigas este post que hice para crear un repo en Github y subir todo tu código local.


Una vez hemos comiteado los cambios y creado nuestro proyecto en Github (el cual hemos llamado LoginModule), vamos a pushear nuestro código a nuestro repo en Github. Y una vez subido quedaría como en la siguiente imagen:

Si tienes alguna duda por favor déjame un comentario en el video de Youtube.
Utilizar nuestro módulo Swift Package Manager en un nuevo Proyecto
Ahora, si quisieramos utilizar este código en un nuevo proyecto o lo quisiera utilizar otro iOS developer, lo único que tendríamos que hacer es crear el proyecto de cero e instalar el paquete usando la URL de Github

En lugar de usar Alamofire, usamos la URL del repositorio que acabamos de crear llamado LoginModule. Es decir, https://github.com/SwiftBeta/LoginModule
Después de crear un proyecto en Xcode de 0, y cambiar el deployment info a iOS 15. Si vamos a añadir una dependencia con Swift Package Manager, vemos lo siguiente:

Le damos a añadir paquete

Y al añadirlo, debemos ir a la struct que instancia ContentView e importar el módulo de LoginModule y borrar la ContentView de la app principal, ya que utilizaremos el ContentView del módulo LoginModule
Y el resultado es el siguiente:

Conclusión
Hoy hemos aprendido a crear un módulo con Swift Package Manager. Después de crearlo hemos aprendido a cómo subirlo a Github para que otros developers puedan usarlo y crear Pull Requests para añadir mejoras.