Splash screen animado en SwiftUI (patrón de código)
Por el equipo de Charmotion · Actualizado el

No puedes animar la pantalla de inicio de iOS en sí, pero sí puedes hacer que tu primera vista de SwiftUI empiece con el aspecto exacto de la pantalla de inicio y reproduzca ahí una intro breve. Superpón una vista de intro sobre ContentView en un ZStack, quítala con un fundido cuando termine la animación y omítela cuando Reducir movimiento esté activado.
Puntos clave
- La pantalla de inicio es una configuración estática que renderiza el sistema; no ejecuta código.
- Haz que el primer fotograma de la intro coincida con la pantalla de inicio (mismo color de fondo, misma posición de la imagen) para que no haya un salto visible.
- Carga
ContentViewdebajo de la intro para que la app esté lista en el momento en que termine la intro. - Termina la intro con una señal de la animación (un stream de triggers) y un timeout de respaldo, no solo con un temporizador estimado.
- Omite la intro con Reducir movimiento activado y en los inicios en caliente (warm launches).
¿Se puede animar la propia pantalla de inicio de iOS?
No. El sistema dibuja la pantalla de inicio antes de que se ejecute tu código, a partir de un diccionario UILaunchScreen en Info.plist o de un storyboard de inicio. Ninguno de los dos puede ejecutar clases ni animaciones personalizadas.
Las Human Interface Guidelines de Apple también limitan lo que debe mostrar: "Diseña una pantalla de inicio casi idéntica a la primera pantalla de tu app" y "No hagas publicidad. La pantalla de inicio no es una oportunidad de branding". Las HIG añaden que, si necesitas una splash screen, "considera mostrarla al comienzo de tu flujo de onboarding". Las apps de macOS, visionOS y watchOS no requieren pantalla de inicio en absoluto.
Por eso, una "splash screen animada" en iOS son en realidad dos cosas en secuencia:
| Pantalla de inicio | Intro (tu splash) | |
|---|---|---|
| Renderizado por | El sistema | Tu primera vista de SwiftUI |
| Contenido | Color e imagen estáticos | Cualquier cosa, incluidos Rive o Lottie |
| Se configura en | UILaunchScreen en Info.plist o un storyboard | Código |
| Duración | Hasta que tu primer fotograma esté listo | Tú decides |

¿Cómo paso de la pantalla de inicio estática a una primera vista animada?
Haz que la vista raíz sea un ZStack: tu ContentView real debajo y la intro encima mientras haya un flag activo. ContentView empieza a cargar de inmediato, así que la intro oculta el tiempo de carga en lugar de sumarlo.
import SwiftUI
@main
struct PipApp: App {
var body: some Scene {
WindowGroup {
RootView()
}
}
}
struct RootView: View {
@Environment(\.accessibilityReduceMotion) private var reduceMotion
@State private var showIntro = true
var body: some View {
ZStack {
ContentView()
if showIntro && !reduceMotion {
IntroView {
withAnimation(.easeOut(duration: 0.25)) { showIntro = false }
}
.transition(.opacity)
.zIndex(1)
}
}
}
}
.zIndex(1) mantiene la intro que se desvanece por encima de ContentView durante la transición de salida; sin esto, la vista saliente puede dibujarse debajo y el fundido desaparece.
¿Cómo reproduzco una intro puntual de Rive y luego muestro ContentView?
La intro carga un archivo de inicio con el nuevo runtime de Rive para Apple, lo reproduce y llama a onFinish cuando la animación indica que terminó. Como el nuevo runtime no tiene callback de cambio de estado, la señal más limpia es una propiedad trigger que el archivo dispara al final de la intro; obsérvala con stream(of:). Un timeout garantiza que la app nunca se quede atascada.
import SwiftUI
import RiveRuntime
struct IntroView: View {
let onFinish: () -> Void
@State private var rive: Rive?
@State private var instance: ViewModelInstance?
var body: some View {
ZStack {
Color("LaunchBackground").ignoresSafeArea()
RiveUIViewRepresentable(rive: rive)
.frame(width: 200, height: 200)
.accessibilityHidden(true)
}
.task { await play() }
}
private func play() async {
let timeout = Task { @MainActor in
try? await Task.sleep(for: .seconds(3))
onFinish()
}
do {
let worker = try await Worker()
let file = try await File(source: .local("launch", Bundle.main), worker: worker)
let artboard = try await file.createArtboard()
let instance = try await file.createViewModelInstance(
.viewModelDefault(from: .artboardDefault(artboard))
)
let machine = try await artboard.createStateMachine("LaunchStateMachine", binding: instance)
self.instance = instance
rive = try await Rive(file: file, artboard: artboard, stateMachine: machine)
// A Charmotion LaunchStateMachine plays its 2.6 s intro once and settles on idle;
// it fires no "done" trigger, so wait for the intro's length. If your own file
// fires an end trigger, await it instead: instance.stream(of: TriggerProperty(path: …)).
try await Task.sleep(for: .seconds(2.6))
} catch {
// Load failure or cancellation: fall through and show the app.
}
timeout.cancel()
onFinish()
}
}
Llamar a onFinish dos veces no causa problemas aquí, porque solo pone showIntro en false. Si tu archivo de inicio no tiene view model, createViewModelInstance lanza un error; en ese caso usa try await Rive(file: file) y confía en el timeout, configurado con la duración de la animación.
Los sets de Charmotion incluyen una LaunchStateMachine aparte para este tipo de intro puntual: reproduce la animación de inicio de 2.6 segundos una sola vez y termina en la pose de reposo (idle), por eso el ejemplo simplemente espera ese tiempo.
¿Cómo evito un parpadeo entre la pantalla de inicio y la primera vista?
Se ve un "parpadeo" cuando la pantalla de inicio y el primer fotograma de la intro no coinciden. Corrígelo desde el origen:
- Usa un color con nombre para ambos. En Info.plist, configura
UILaunchScreen→UIColorNamecon un color del asset catalog (aquíLaunchBackground) y usa el mismoColor("LaunchBackground")detrás de la intro. Así ambos siguen el modo claro y el oscuro de forma idéntica. - Haz coincidir la posición de la imagen. Si la pantalla de inicio muestra una imagen (
UIImageName), dibuja el primer fotograma de la intro con el personaje al mismo tamaño y centrado igual. Lo más fácil: deja la pantalla de inicio solo con color y haz que el personaje aparezca en la intro. - No bloquees el primer fotograma. El trabajo síncrono pesado en
App.inito en elinitde una vista retrasa el primer fotograma y alarga la pantalla de inicio estática. Muévelo a.task. - Mantén la vista de Rive transparente hasta que cargue.
RiveUIViewRepresentable(rive: nil)no dibuja nada, así que solo se ve el color de fondo hasta que el archivo esté listo.
La guía de Apple sobre cómo especificar la pantalla de inicio explica en detalle las claves de Info.plist y la opción de storyboard.
¿Cuánto debe durar una animación de splash?
Apple no publica una duración para las intros dentro de la app, pero sus recomendaciones apuntan en una dirección: el inicio debe sentirse instantáneo, y la pantalla de inicio existe para que la app parezca lista, no para mostrar la marca. Trata la intro de la misma manera.
- Superponla con trabajo real. Si la intro cubre la autenticación o una primera petición de red que tardaría lo mismo de todos modos, no le cuesta nada al usuario.
- Reprodúcela una vez por arranque en frío. Al volver desde segundo plano, la app debe mostrarse directamente.
- Permite saltarla con un toque. Agrega
.onTapGesture { onFinish() }a la intro. - Considera mostrarla solo en el primer inicio. Guarda un flag con
@AppStoragey muestra la intro solo la primera vez o después de una actualización.
Para recomendaciones de tiempos en animaciones de carga y de feedback en general, consulta duración de animaciones y UX de carga, y para la visión multiplataforma, animación de inicio y splash screen de apps.
¿Cómo salto la intro con Reducir movimiento activado?
La RootView de arriba ya lo hace: if showIntro && !reduceMotion nunca inserta la intro cuando accessibilityReduceMotion es true. El usuario pasa directamente de la pantalla de inicio estática a ContentView, que ya coincide con ella.
Si la intro también oculta una carga, reemplázala por una versión estática en lugar de nada: el mismo fondo y una pose fija del personaje, que se retira con un fundido corto cuando termina la carga. El objetivo de Reducir movimiento es eliminar el movimiento, no mostrar una pantalla vacía.
if showIntro {
if reduceMotion {
StaticIntro() // background + still pose
.transition(.opacity)
.zIndex(1)
} else {
IntroView { withAnimation { showIntro = false } }
.transition(.opacity)
.zIndex(1)
}
}
Oculta el personaje de la intro a VoiceOver con .accessibilityHidden(true); no aporta nada a la experiencia de quienes usan lector de pantalla.
Agrega una intro animada después de la pantalla de inicio de iOS
- Configura la pantalla de inicio. En Info.plist, configura
UILaunchScreencon unUIColorNameque apunte a un color del asset catalog. - Crea un ZStack raíz. Coloca
ContentView()debajo y unaIntroViewencima mientrasshowIntrosea true. - Haz coincidir el primer fotograma. Usa el mismo color con nombre detrás de la intro y mantén vacía la vista de Rive hasta que cargue.
- Reproduce la intro. Carga el .riv de inicio con el nuevo runtime. Si tu archivo dispara un trigger de fin, espéralo con
stream(of:); unaLaunchStateMachinede Charmotion no dispara ninguno, así que espera sus 2.6 s de duración. En ambos casos, mantén un timeout de respaldo. - Fundido de salida. Establece
showIntro = falsedentro dewithAnimation, con.transition(.opacity)y.zIndex(1)en la intro. - Respeta Reducir movimiento. Salta la intro o muestra una versión estática cuando
accessibilityReduceMotionsea true.
Preguntas frecuentes
¿Puedo usar un GIF o un video como pantalla de inicio de iOS?
No. La pantalla de inicio admite un color y una imagen estáticos mediante Info.plist, o un storyboard sin código personalizado, y el sistema la muestra antes de que tu app se ejecute. En su lugar, reproduce un GIF, un video o una animación de Lottie o Rive en tu primera vista, partiendo del aspecto de la pantalla de inicio.
¿Por qué mi animación de splash parpadea en blanco al principio?
El fondo de la pantalla de inicio y el primer fotograma de la intro no coinciden, a menudo porque uno usa un color fijo y el otro sigue el modo oscuro. Usa el mismo color del asset catalog para el UIColorName de UILaunchScreen y para el fondo de la intro, y saca el trabajo pesado de App.init.
¿La intro de splash debe reproducirse cada vez que se abre la app?
Normalmente solo en un arranque en frío, y muchas apps la limitan al primer inicio o a después de una actualización. Al volver desde segundo plano, la app debe mostrarse de inmediato. Permite que los usuarios la salten con un toque y sáltala automáticamente cuando Reducir movimiento esté activado.