Domina Core Animation: Creando Animaciones Impresionantes en iOS con Swift
Este tutorial te guiará a través del poderoso framework Core Animation de Apple para iOS. Descubrirás cómo transformar tus interfaces estáticas en experiencias dinámicas e interactivas utilizando animaciones de capa, transiciones y efectos visuales avanzados, todo ello programado en Swift.

Core Animation es la piedra angular de toda la magia visual que vemos en iOS. Es el motor que impulsa las animaciones fluidas, las transiciones suaves y los efectos visuales que hacen que las aplicaciones de Apple sean tan atractivas. Aunque SwiftUI y UIKit ofrecen sus propias formas de animar vistas, entender Core Animation te da un control mucho más granular y te permite crear efectos que van más allá de las capacidades de alto nivel.
En este tutorial, profundizaremos en Core Animation, explorando sus conceptos clave, cómo interactúa con CALayer y UIView, y cómo puedes aprovecharlo para crear animaciones personalizadas y de alto rendimiento en tus propias aplicaciones iOS utilizando Swift.
🚀 ¿Qué es Core Animation?
Core Animation es un framework de composición y animación que te permite animar las propiedades de los objetos de capa (CALayer). No es una API de dibujo en sí misma, sino más bien un sistema de renderizado que organiza y compone el contenido de tus capas, permitiendo que la GPU realice gran parte del trabajo pesado para lograr animaciones fluidas a 60 FPS (o 120 FPS en dispositivos ProMotion).
Cada UIView en iOS está respaldada por una instancia de CALayer. Cuando cambias propiedades como la posición, el tamaño o el color de fondo de una vista, en realidad estás cambiando las propiedades de su CALayer subyacente. Core Animation intercepta estos cambios y puede animarlos por ti.
Propiedades animables comunes de CALayer:
positionboundstransform(escalado, rotación, traslación 3D)opacitybackgroundColorcornerRadiusborderWidth,borderColorshadowOpacity,shadowOffset,shadowRadius,shadowColor
🛠️ Configurando tu Proyecto
Para empezar, abriremos Xcode y crearemos un nuevo proyecto de tipo App para iOS. Puedes elegir la interfaz UIKit App Delegate o SwiftUI App, aunque la mayoría de los ejemplos se centrarán en UIKit para demostrar la interacción directa con CALayer.
- Abre Xcode.
- Selecciona
Create a new Xcode project. - Elige la plantilla
iOS->App. - Nombra tu proyecto (ej.
CoreAnimationDemo). - Asegúrate de que la interfaz sea
StoryboardoProgrammaticsi quieres seguir los ejemplos de UIKit, oSwiftUIsi prefieres integrar con SwiftUI (algunos ejemplos se adaptarán). El idioma debe serSwift. - Guarda el proyecto.
Para este tutorial, utilizaremos un UIViewController simple donde añadiremos y manipularemos CALayers programáticamente.
🎨 Animaciones Implícitas vs. Explícitas
Core Animation ofrece dos tipos principales de animaciones:
🎭 Animaciones Implícitas
Las animaciones implícitas son las más sencillas. Ocurren automáticamente cuando cambias una propiedad animable de CALayer fuera de una CATransaction explícita, y Core Animation decide cómo animar ese cambio. Esto es lo que sucede cuando animas UIView utilizando UIView.animate.
Por defecto, CALayer tiene acciones implícitas asociadas a sus propiedades animables. Cuando se modifica una de estas propiedades, Core Animation busca una CAAction asociada y la ejecuta. En el contexto de un UIView y su capa subyacente, UIView deshabilita muchas de estas acciones implícitas por defecto para tener un control más preciso.
Ejemplo Básico de Animación Implícita (sin UIView.animate):
Crearemos una CALayer y la animaremos cambiando su color de fondo.
import UIKit
class ImplicitAnimationViewController: UIViewController {
let animatedLayer = CALayer()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
animatedLayer.frame = CGRect(x: 50, y: 100, width: 100, height: 100)
animatedLayer.backgroundColor = UIColor.blue.cgColor
view.layer.addSublayer(animatedLayer)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap))
view.addGestureRecognizer(tapGesture)
}
@objc func handleTap() {
// Animación implícita por defecto para CALayer
animatedLayer.backgroundColor = (animatedLayer.backgroundColor == UIColor.blue.cgColor) ? UIColor.red.cgColor : UIColor.blue.cgColor
animatedLayer.cornerRadius = (animatedLayer.cornerRadius == 0) ? 50 : 0
}
}
Si ejecutas este código, verás que los cambios en backgroundColor y cornerRadius se animan suavemente. Esto se debe a que CALayer tiene acciones implícitas configuradas para estas propiedades.
🎬 Animaciones Explícitas (CAAnimation)
Las animaciones explícitas te dan control total. Tú defines exactamente cómo y cuándo ocurre la animación. Esto se logra creando instancias de CAAnimation y sus subclases, configurándolas y luego añadiéndolas a un CALayer.
Las subclases principales de CAAnimation son:
CABasicAnimation: Anima una única propiedad de la capa de un valor a otro.CAKeyframeAnimation: Anima una propiedad a través de una serie de valores clave en momentos específicos.CAAnimationGroup: Combina múltiples animaciones y las ejecuta simultáneamente.CATransition: Realiza transiciones de contenido o efectos visuales, comofade,pushomove.
CABasicAnimation: Animando una Propiedad Única
CABasicAnimation es la forma más común de animar una propiedad. Especificas una keyPath (la propiedad a animar), un fromValue y un toValue.
Ejemplo: Animando la posición de una capa
import UIKit
class BasicAnimationViewController: UIViewController {
let animatedLayer = CALayer()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
animatedLayer.frame = CGRect(x: 50, y: 100, width: 100, height: 100)
animatedLayer.backgroundColor = UIColor.systemTeal.cgColor
view.layer.addSublayer(animatedLayer)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap))
view.addGestureRecognizer(tapGesture)
}
@objc func handleTap() {
let basicAnimation = CABasicAnimation(keyPath: "position.x") // Animamos la posición X
basicAnimation.fromValue = animatedLayer.position.x
basicAnimation.toValue = animatedLayer.position.x + 200
basicAnimation.duration = 1.0 // Duración de 1 segundo
basicAnimation.repeatCount = 1 // Ejecutar una vez
basicAnimation.autoreverses = true // Volver al estado inicial
basicAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) // Curva de aceleración
// Es crucial que el modelo (la propiedad de la capa) se actualice al final de la animación
// para que la capa permanezca en su estado final.
// Si no lo haces, la capa volverá a su estado original una vez finalizada la animación.
// animatedLayer.position.x = animatedLayer.position.x + 200 // Esto solo para el estado final si no autoreverses
animatedLayer.add(basicAnimation, forKey: "moveLayer")
}
}
Para el ejemplo anterior, si autoreverses es false, deberías hacer animatedLayer.position.x = animatedLayer.position.x + 200 al final de la animación o antes de añadirla para que la capa no "salte" de vuelta.
CAKeyframeAnimation: Animación por Fotogramas Clave
CAKeyframeAnimation te permite especificar una serie de valores (fotogramas clave) a lo largo del tiempo. Esto es útil para crear animaciones más complejas o trayectorias personalizadas.
Ejemplo: Animando una capa a lo largo de una trayectoria
import UIKit
class KeyframeAnimationViewController: UIViewController {
let animatedLayer = CALayer()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
animatedLayer.frame = CGRect(x: 50, y: 100, width: 50, height: 50)
animatedLayer.backgroundColor = UIColor.systemOrange.cgColor
animatedLayer.cornerRadius = 25
view.layer.addSublayer(animatedLayer)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap))
view.addGestureRecognizer(tapGesture)
}
@objc func handleTap() {
let keyframeAnimation = CAKeyframeAnimation(keyPath: "position")
keyframeAnimation.duration = 2.0
keyframeAnimation.repeatCount = .infinity // Animación infinita
let path = UIBezierPath()
path.move(to: animatedLayer.position)
path.addCurve(to: CGPoint(x: view.bounds.width - 50, y: 200), controlPoint1: CGPoint(x: 150, y: 50), controlPoint2: CGPoint(x: view.bounds.width - 150, y: 300))
path.addLine(to: CGPoint(x: 50, y: 400))
path.addLine(to: animatedLayer.position)
keyframeAnimation.path = path.cgPath
keyframeAnimation.calculationMode = .paced // Distribuye el tiempo de manera uniforme
animatedLayer.add(keyframeAnimation, forKey: "pathAnimation")
}
}
Este ejemplo animará la capa siguiendo una trayectoria definida por UIBezierPath. calculationMode = .paced intenta mantener una velocidad constante a lo largo de la trayectoria.
CAAnimationGroup: Combinando Múltiples Animaciones
Para animar múltiples propiedades simultáneamente, puedes usar CAAnimationGroup. Esto es muy útil para coordinar efectos complejos.
Ejemplo: Rotación, escalado y cambio de color al mismo tiempo
import UIKit
class AnimationGroupViewController: UIViewController {
let animatedLayer = CALayer()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
animatedLayer.frame = CGRect(x: 50, y: 100, width: 100, height: 100)
animatedLayer.backgroundColor = UIColor.systemGreen.cgColor
animatedLayer.cornerRadius = 10
view.layer.addSublayer(animatedLayer)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap))
view.addGestureRecognizer(tapGesture)
}
@objc func handleTap() {
// Animación de rotación
let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotationAnimation.toValue = CGFloat.pi * 2.0 // 360 grados
rotationAnimation.duration = 2.0
// Animación de escalado
let scaleAnimation = CABasicAnimation(keyPath: "transform.scale")
scaleAnimation.toValue = 1.5
scaleAnimation.duration = 2.0
scaleAnimation.autoreverses = true
// Animación de color de fondo
let colorAnimation = CABasicAnimation(keyPath: "backgroundColor")
colorAnimation.toValue = UIColor.systemPurple.cgColor
colorAnimation.duration = 2.0
colorAnimation.autoreverses = true
// Grupo de animaciones
let groupAnimation = CAAnimationGroup()
groupAnimation.animations = [rotationAnimation, scaleAnimation, colorAnimation]
groupAnimation.duration = 4.0 // La duración total del grupo, ajusta según autoreverses
groupAnimation.repeatCount = .infinity
animatedLayer.add(groupAnimation, forKey: "combinedAnimations")
}
}
CATransition: Efectos de Transición
CATransition se utiliza para crear transiciones visuales entre diferentes estados de una capa o para cambiar el contenido de una capa con un efecto animado.
Ejemplo: Transición de imagen en una capa
import UIKit
class TransitionAnimationViewController: UIViewController {
let imageLayer = CALayer()
var isFirstImage = true
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
imageLayer.frame = CGRect(x: 50, y: 100, width: 200, height: 200)
imageLayer.contentsGravity = .resizeAspectFill
imageLayer.masksToBounds = true
view.layer.addSublayer(imageLayer)
updateImage(image: UIImage(systemName: "photo.fill")!)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap))
view.addGestureRecognizer(tapGesture)
}
func updateImage(image: UIImage) {
imageLayer.contents = image.cgImage
}
@objc func handleTap() {
let transition = CATransition()
transition.type = .fade // Otros tipos: .push, .moveIn, .reveal
transition.subtype = .fromRight // Subtipo para .push, .moveIn, .reveal
transition.duration = 1.0
transition.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
imageLayer.add(transition, forKey: "imageTransition")
if isFirstImage {
updateImage(image: UIImage(systemName: "star.fill")!)
} else {
updateImage(image: UIImage(systemName: "photo.fill")!)
}
isFirstImage.toggle()
}
}
Tabla de tipos de CATransition y subtype comunes:
transition.type | Descripción | transition.subtype (ejemplos) |
|---|---|---|
.fade | Fundido de entrada/salida | nil |
.push | Empuja el nuevo contenido desde un borde | .fromRight, .fromLeft |
.moveIn | Mueve el nuevo contenido sobre el existente | .fromTop, .fromBottom |
.reveal | Revela el nuevo contenido detrás del existente | .fromRight, .fromLeft |
kCATransitionCube | Efecto de cubo (privado, usar con precaución) | .fromTop, .fromBottom |
🔄 CAMediaTimingFunction: Controlando la Velocidad de la Animación
La timingFunction de una animación define cómo cambia la velocidad de la animación a lo largo del tiempo. Esto le da a tus animaciones una sensación más natural y pulida.
.linear: Velocidad constante..easeIn: Empieza lento y acelera..easeOut: Empieza rápido y desacelera..easeInEaseOut: Empieza lento, acelera en el medio y desacelera al final.default: Lo mismo que.easeInEaseOut.
También puedes crear tus propias funciones de temporización con puntos de control Bézier cúbicos:
// Una función de temporización personalizada (curva de Bézier cúbica)
let customTiming = CAMediaTimingFunction(controlPoints: 0.1, 0.7, 1.0, 0.1)
animation.timingFunction = customTiming
🎯 CAAnimationDelegate: Respondiendo a Eventos de Animación
Para ser notificado cuando una animación comienza o termina, puedes asignar un delegado a tu objeto CAAnimation.
import UIKit
class AnimationDelegateViewController: UIViewController, CAAnimationDelegate {
let animatedLayer = CALayer()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
animatedLayer.frame = CGRect(x: 50, y: 100, width: 100, height: 100)
animatedLayer.backgroundColor = UIColor.systemBlue.cgColor
view.layer.addSublayer(animatedLayer)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap))
view.addGestureRecognizer(tapGesture)
}
@objc func handleTap() {
let basicAnimation = CABasicAnimation(keyPath: "position.x")
basicAnimation.toValue = animatedLayer.position.x + 200
basicAnimation.duration = 1.5
basicAnimation.delegate = self // Asignamos el delegado
animatedLayer.add(basicAnimation, forKey: "delegateAnimation")
}
// MARK: - CAAnimationDelegate Methods
func animationDidStart(_ anim: CAAnimation) {
print("Animación iniciada: \(anim.debugDescription)")
// Puedes deshabilitar interacciones o mostrar un indicador de actividad aquí
}
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
print("Animación detenida: \(anim.debugDescription), finalizada: \(flag)")
if flag {
// La animación terminó completamente
// Actualizar el estado final de la capa si es necesario
if let basicAnim = anim as? CABasicAnimation, basicAnim.keyPath == "position.x",
let toValue = basicAnim.toValue as? CGFloat {
animatedLayer.position.x = toValue
}
} else {
// La animación fue cancelada (ej. otra animación la reemplazó)
}
}
}
✨ Transformaciones 3D con CATransform3D
Core Animation te permite aplicar transformaciones 3D a tus capas utilizando la propiedad transform de CALayer, que es de tipo CATransform3D.
CATransform3D es una matriz 4x4 que se utiliza para realizar escalado, rotación, traslación y otras transformaciones 3D.
Ejemplo: Rotación 3D y Perspectiva
import UIKit
import QuartzCore // Para CATransform3D
class Transform3DViewController: UIViewController {
let cubeLayer = CALayer()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
cubeLayer.frame = CGRect(x: (view.bounds.width - 100) / 2, y: 200, width: 100, height: 100)
cubeLayer.backgroundColor = UIColor.systemBlue.cgColor
cubeLayer.borderWidth = 2
cubeLayer.borderColor = UIColor.white.cgColor
view.layer.addSublayer(cubeLayer)
// Añadir gestos para interactuar con la rotación
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
view.addGestureRecognizer(panGesture)
}
@objc func handlePan(_ gesture: UIPanGestureRecognizer) {
let translation = gesture.translation(in: view)
let rotationAngleX = translation.y / view.bounds.height * CGFloat.pi * 2 // Proporción de altura para rotación X
let rotationAngleY = translation.x / view.bounds.width * CGFloat.pi * 2 // Proporción de ancho para rotación Y
// Configurar la perspectiva
var transform = CATransform3DIdentity
transform.m34 = -1.0 / 500.0 // Valor crucial para la perspectiva (distancia al "observador")
// Aplicar rotaciones en X e Y
transform = CATransform3DRotate(transform, rotationAngleX, 1, 0, 0) // Rotar alrededor del eje X
transform = CATransform3DRotate(transform, rotationAngleY, 0, 1, 0) // Rotar alrededor del eje Y
cubeLayer.transform = transform
if gesture.state == .ended {
// Resetear la traslación para el próximo arrastre
gesture.setTranslation(.zero, in: view)
}
}
}
¿Qué es `m34` en `CATransform3D`?
La propiedad `m34` de la matriz `CATransform3D` es fundamental para crear un efecto de perspectiva. Actúa como el inverso de la distancia al observador (o cámara). Un valor de `-1.0 / 500.0` significa que el observador está a 500 puntos de distancia. Sin un valor no nulo para `m34`, las transformaciones 3D solo parecerán 2D, ya que no habrá profundidad aparente.🛑 Pausar y Reanudar Animaciones
Puedes pausar y reanudar animaciones en una capa manipulando su speed y timeOffset.
layer.speed = 0.0: Pausa la animación.layer.timeOffset = <current_time_of_animation>: Guarda el punto donde se pausó.layer.speed = 1.0: Reanuda la animación.layer.beginTime = CACurrentMediaTime() - layer.timeOffset: Ajusta elbeginTimepara que la animación continúe desde donde se pausó.
Ejemplo: Botón de Pausar/Reanudar
import UIKit
class PauseResumeViewController: UIViewController {
let animatedLayer = CALayer()
var isPaused = false
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
animatedLayer.frame = CGRect(x: 50, y: 100, width: 100, height: 100)
animatedLayer.backgroundColor = UIColor.systemIndigo.cgColor
view.layer.addSublayer(animatedLayer)
// Añadir una animación de rotación infinita
let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotationAnimation.toValue = CGFloat.pi * 2.0
rotationAnimation.duration = 3.0
rotationAnimation.repeatCount = .infinity
animatedLayer.add(rotationAnimation, forKey: "rotatingAnimation")
// Botón para pausar/reanudar
let toggleButton = UIButton(type: .system)
toggleButton.setTitle("Pausar", for: .normal)
toggleButton.frame = CGRect(x: (view.bounds.width - 100) / 2, y: 400, width: 100, height: 50)
toggleButton.addTarget(self, action: #selector(toggleAnimation), for: .touchUpInside)
view.addSubview(toggleButton)
}
@objc func toggleAnimation(sender: UIButton) {
if isPaused {
// Reanudar
let pausedTime = animatedLayer.timeOffset
animatedLayer.speed = 1.0
animatedLayer.timeOffset = 0.0
animatedLayer.beginTime = 0.0
let timeSincePause = animatedLayer.convertTime(CACurrentMediaTime(), from: nil) - pausedTime
animatedLayer.beginTime = timeSincePause
sender.setTitle("Pausar", for: .normal)
} else {
// Pausar
let pausedTime = animatedLayer.convertTime(CACurrentMediaTime(), from: nil)
animatedLayer.speed = 0.0
animatedLayer.timeOffset = pausedTime
sender.setTitle("Reanudar", for: .normal)
}
isPaused.toggle()
}
}
📈 Rendimiento y Buenas Prácticas
Crear animaciones fluidas es crucial para una buena experiencia de usuario. Aquí tienes algunas consideraciones para el rendimiento:
- Propiedades animables que afectan al rendimiento: Animar propiedades que requieren que la CPU vuelva a calcular el layout (
frame,bounds,positionsi afecta al layout de otras vistas) puede ser costoso. Propiedades comotransform(rotación, escala, traslación) yopacityson mucho más eficientes porque se gestionan directamente por la GPU. - Offscreen Rendering: Algunas propiedades como
shadowsycornerRadiuscombinadas conmasksToBounds = truepueden activar el offscreen rendering, lo que puede ralentizar la animación. Considera optimizaciones o evita estas combinaciones si el rendimiento es crítico. - Caché de Capas: Para capas complejas que no cambian a menudo, puedes rasterizarlas usando
layer.shouldRasterize = true. Core Animation creará una imagen de mapa de bits de la capa y la animará, lo que puede mejorar el rendimiento. Asegúrate de establecerlayer.rasterizationScale = UIScreen.main.scalepara evitar que se vea pixelado.
// Ejemplo de rasterización
animatedLayer.shouldRasterize = true
animatedLayer.rasterizationScale = UIScreen.main.scale
🔗 Integración con SwiftUI
Aunque Core Animation es más directamente accesible en UIKit, puedes usar capas Core Animation en SwiftUI a través de UIViewRepresentable o CALayer. Esto te permite traer animaciones personalizadas y complejas que SwiftUI por sí mismo podría no ofrecer de forma directa.
Ejemplo: CALayer en SwiftUI
Podrías crear un UIViewRepresentable que contenga tu CALayer y luego animar sus propiedades.
import SwiftUI
struct CoreAnimationView: UIViewRepresentable {
@Binding var animate: Bool
func makeUIView(context: Context) -> UIView {
let view = UIView()
view.backgroundColor = .clear
let layer = CALayer()
layer.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
layer.backgroundColor = UIColor.blue.cgColor
layer.cornerRadius = 10
view.layer.addSublayer(layer)
context.coordinator.animatedLayer = layer
return view
}
func updateUIView(_ uiView: UIView, context: Context) {
if animate {
context.coordinator.startAnimation()
} else {
context.coordinator.stopAnimation()
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject {
var parent: CoreAnimationView
var animatedLayer: CALayer?
init(_ parent: CoreAnimationView) {
self.parent = parent
}
func startAnimation() {
guard let layer = animatedLayer else { return }
let basicAnimation = CABasicAnimation(keyPath: "position.y")
basicAnimation.toValue = layer.position.y + 100
basicAnimation.duration = 1.0
basicAnimation.autoreverses = true
basicAnimation.repeatCount = .infinity
layer.add(basicAnimation, forKey: "bounceAnimation")
}
func stopAnimation() {
animatedLayer?.removeAnimation(forKey: "bounceAnimation")
}
}
}
struct ContentView: View {
@State private var shouldAnimate = false
var body: some View {
VStack {
CoreAnimationView(animate: $shouldAnimate)
.frame(width: 200, height: 200)
.border(Color.gray)
Toggle(isOn: $shouldAnimate) {
Text("Animar")
}
.padding()
}
}
}
Este ejemplo básico muestra cómo incrustar un CALayer dentro de una vista SwiftUI y controlarlo desde el estado de SwiftUI. Para animaciones más avanzadas o complejas, la integración podría requerir más lógica dentro del coordinador o del UIViewRepresentable.
🔚 Conclusión
Core Animation es una herramienta increíblemente potente y flexible para crear animaciones de alta calidad en iOS. Aunque puede parecer complejo al principio, dominar sus conceptos te abrirá un mundo de posibilidades para enriquecer la experiencia de usuario de tus aplicaciones.
Desde las animaciones implícitas más sencillas hasta las complejas trayectorias de CAKeyframeAnimation y las transformaciones 3D, Core Animation te proporciona el control y el rendimiento necesarios para hacer que tus apps cobren vida. Practica con los ejemplos, experimenta con diferentes propiedades y funciones de temporización, y pronto estarás creando interfaces dinámicas e impresionantes.
¡Anímate a explorar todas las posibilidades que Core Animation tiene para ofrecer! 🚀
Tutoriales relacionados
- ¡Maestría en Core Data! Persistencia de Datos en iOS con SwiftUI y MVVMintermediate25 min
- Gestión Eficaz de Dependencias en iOS: Integrando Swift Package Manager como Profesionalintermediate25 min
- Navegación Avanzada en iOS: Coordinadores y Flow Controllers con SwiftUIintermediate18 min
- Arquitecturas Modulares en iOS: Construyendo Apps Escalables con Enfoque de Módulos y SwiftPMintermediate20 min
Comentarios (0)
Aún no hay comentarios. ¡Sé el primero!