La barra del partido en juego, en iOS
Al terminar: Mostrar en todas las pestañas una barra con el marcador en vivo que lleva al partido.
Al terminar, Marcador tendrá una barra abajo, encima de la barra de pestañas y en todas las pestañas, con el partido que se está jugando: «Rayo FC 2–1 Toros · 34'». En el propio partido la barra no sale, porque ya lo estás mirando. Al terminar el partido, la barra dice «Final · Rayo FC 3–2 Toros» y gana una X que la cierra; mientras se juega, no se puede cerrar. Una cosa todavía no funciona, y es a propósito: tocar la barra no hace nada. Que te lleve al partido es la lección siguiente, La barra abre el partido.
Importa porque es la primera pieza de Marcador que no es de ninguna pantalla: pinta lo que dice un objeto de toda la app, esté donde esté el usuario, y se pega a cualquier pantalla como si fuera un modificador más. Para eso necesitas dos piezas que no has escrito nunca: un valor del entorno que es tuyo y un modificador propio. Temas: @Entry, ViewModifier, .safeAreaInset, etiquetas de accesibilidad y las pruebas del texto de la barra. Nivel 2: usar.
@Entry: tu propio valor del entorno
Conoces dos formas de leer del entorno. Una, para valores del sistema: @Environment(\.dismiss) o @Environment(\.scenePhase). Otra, para un objeto tuyo: @Environment(PartidoEnJuego.self). Falta la tercera: un valor tuyo, con la misma forma que los del sistema.
Los valores del sistema viven todos en una estructura llamada EnvironmentValues, «los valores del entorno»: cada propiedad suya (dismiss, scenePhase…) se lee con una ruta como \.dismiss. Para añadir una propiedad tuya se usa una extension, que añade cosas a un tipo que ya existe (Equatable y Hashable), y se marca la propiedad con @Entry. @Entry es una macro: una instrucción que, al compilar, Xcode sustituye por código que tú no tienes que escribir (aquí, todo el trámite de dar de alta la clave en el entorno), igual que las anotaciones de Hilt generaban código en Android.
En Marcador el valor es una función: abrirPartido, de tipo (Int) -> Void, que recibe el número de un partido y no devuelve nada. Más adelante la raíz de la app pondrá la función de verdad, y cualquier vista de debajo la llamará sin saber cómo se abre un partido: es la misma idea de Closures, pasada por el entorno en vez de por un parámetro.
Un valor del entorno siempre tiene uno por defecto. Aquí es { _ in }: una función que recibe un número y no hace nada con él. El guion bajo _ significa «un parámetro que me dan y no uso»: la función se llama con el número del partido, pero lo ignora.
Esa es la diferencia con @Environment(Tipo.self): con @Entry nunca se detiene la app por falta de valor. Y esta lección usa justo eso: la barra llama a abrirPartido, nadie lo ha cableado todavía y por eso, al tocarla, no pasa nada.
Un modificador propio: ViewModifier
Un modificador es lo que escribes después de un punto: .padding(16), .background(…). Cada uno recibe una vista y devuelve otra con algo más. También puedes escribir los tuyos: un tipo que cumple ViewModifier y tiene una función body(content:). El parámetro content es la vista a la que se aplica, y su tipo se llama Content (un nombre que pone SwiftUI: no tienes que escribir nada más sobre él). Para poder usarlo con un punto, se añade una función a View con una extension.
.safeAreaInset: una barra que no tapa nada
La zona segura es la parte de la pantalla donde cabe el contenido sin quedar debajo de la barra de inicio ni de la de pestañas. .safeAreaInset(edge: .bottom) pone una vista abajo y encoge la zona segura de lo que modifica: la lista llega hasta encima de la barra y la última fila no queda tapada.
iOS 26 trae una pieza hecha justo para poner algo encima de la barra de pestañas, tabViewBottomAccessory. Marcador es para iOS 18, donde no existe, así que la barra se hace a mano con .safeAreaInset.
Lo que dice la barra, para quien no la ve
VoiceOver es el lector de pantalla de iOS: lee en voz alta lo que tocas. Una persona ciega que lo use no ve la barra, así que cada botón necesita palabras. Dos modificadores se las dan:
.accessibilityLabel("…")pone el nombre del botón. Hace falta cuando el botón es solo un dibujo, porque un dibujo no tiene texto que leer..accessibilityHint("…")añade qué ocurre al tocarlo.
// El botón es solo un corazón: sin etiqueta, VoiceOver no tendría nada que decir.
// `toggle()` cambia un valor verdadero/falso por el contrario: si era `false`, queda `true`.
Button { favorito.toggle() } label: {
Image(systemName: "heart")
}
.accessibilityLabel("Marcar como favorito") // el nombre del botón
.accessibilityHint("Lo añade a tu lista") // qué pasa al tocarloEn la barra del partido, la X lleva .accessibilityLabel("Cerrar") y la barra entera lleva .accessibilityHint("Abre el partido"). Las pruebas de interfaz usan esas mismas palabras para encontrar el botón: app.buttons["Cerrar"].
Ejemplo: una barra de pedido
Todo junto, con otros datos: una tienda con una barra «Pedido n.º 12 · llega en 8 min» pegada abajo. Es un ejemplo para leer, sin ningún PartidoEnJuego. Su último trozo (el punto 5, que da al valor del entorno una función de verdad con .environment) lo haces tú en la lección siguiente: aquí solo míralo para ver la diferencia entre tener el valor por defecto y tenerlo cableado.
import SwiftUI
// 1) UN VALOR PROPIO DEL ENTORNO. `@Entry` declara la clave y su valor por defecto en una sola línea.
// Este valor es una función: «abre el pedido con este número». Por defecto no hace nada.
extension EnvironmentValues {
@Entry var abrirPedido: (Int) -> Void = { _ in }
}
// 2) UN MODIFICADOR PROPIO. Recibe una vista (`content`) y devuelve esa vista con algo más.
struct BarraDelPedido: ViewModifier {
// Lee del entorno la función que haya puesto alguien (o la de por defecto).
@Environment(\.abrirPedido) private var abrirPedido
let numero: Int
let minutos: Int
func body(content: Content) -> some View {
// `safeAreaInset(edge: .bottom)`: pega la barra abajo y encoge la zona segura:
// lo de dentro (la lista) acaba encima de la barra, no debajo.
content.safeAreaInset(edge: .bottom, spacing: 0) {
Button {
abrirPedido(numero)
} label: {
Text("Pedido n.º \(numero) · llega en \(minutos) min")
.frame(maxWidth: .infinity, minHeight: 44, alignment: .leading)
.contentShape(.rect) // se puede tocar todo el rectángulo, no solo las letras
}
.padding(.horizontal)
.background(.bar)
}
}
}
// 3) UNA FORMA CÓMODA DE USARLO: `.barraDelPedido(...)` en vez de `.modifier(BarraDelPedido(...))`.
extension View {
func barraDelPedido(numero: Int, minutos: Int) -> some View {
modifier(BarraDelPedido(numero: numero, minutos: minutos))
}
}
// 4) UNA PANTALLA CON LA BARRA: treinta platos y, debajo, el pedido.
struct MenuView: View {
var body: some View {
List(1...30, id: \.self) { Text("Plato \($0)") }
.barraDelPedido(numero: 12, minutos: 8)
}
}
// 5) LA MISMA PANTALLA, PERO CON EL VALOR CABLEADO: al tocar la barra, arriba sale el número.
struct MenuCableadoView: View {
@State private var abierto: Int?
var body: some View {
VStack(spacing: 0) {
if let abierto {
Text("Abriste el pedido n.º \(abierto)").padding()
} else {
Text("Toca la barra").padding()
}
MenuView()
}
// La función va como segundo argumento, escrita con llaves al final.
.environment(\.abrirPedido) { numero in abierto = numero }
}
}
#Preview("Sin cablear") { MenuView() }
#Preview("Cableada") { MenuCableadoView() }Lo que verás en las vistas previas:
- «Sin cablear»: treinta platos y, abajo, justo encima del borde, la barra «Pedido n.º 12 · llega en 8 min». La lista llega hasta encima de ella. Al tocar la barra no pasa nada: es el valor por defecto.
- «Cableada»: igual, pero arriba pone «Toca la barra» y, al tocarla, cambia a «Abriste el pedido n.º 12».
Reto · Ejercicio: la barra de la música
Es el gemelo de la barra del partido, con otros datos. En la carpeta Ejercicios de tu proyecto, crea la subcarpeta BarraDeMusica (clic derecho sobre MarcadorApp/Ejercicios → New Folder) y dentro un fichero BarraDeMusicaView.swift (clic derecho sobre BarraDeMusica → New File from Template… → Swift File, solo con el target MarcadorApp).
Escribe una vista raíz RaizDeMusicaView, con su vista previa, con dos pestañas: «Inicio», con una lista de treinta canciones («Canción 1» a «Canción 30»), y «Buscar», con el texto «Buscar» en el centro. En las dos pestañas, abajo y encima de la barra de pestañas, hay una barra con el texto «Imagine · 1:15». La barra no sabe nada de las pestañas: al tocarla llama a un valor del entorno que es tuyo, abrirReproductor, que por defecto no hace nada. Todavía nadie lo cablea.
Lo que debe verse en la vista previa:
| Haces | Debe verse |
|---|---|
| Abrir la vista previa | Pestaña «Inicio» con la lista, y abajo la barra «Imagine · 1:15»; la última canción se puede desplazar hasta quedar encima de la barra |
| Pasar a «Buscar» | «Buscar» en el centro y la barra abajo, la misma |
| Tocar la barra | No pasa nada |
Ver solución
import SwiftUI
extension EnvironmentValues {
// Tu propio valor del entorno; por defecto, no hace nada.
@Entry var abrirReproductor: () -> Void = {}
}
struct BarraDeMusica: ViewModifier {
@Environment(\.abrirReproductor) private var abrirReproductor
let titulo: String
func body(content: Content) -> some View {
content.safeAreaInset(edge: .bottom, spacing: 0) {
Button {
abrirReproductor()
} label: {
Text(titulo)
.frame(maxWidth: .infinity, minHeight: 44, alignment: .leading)
.contentShape(.rect) // se puede tocar todo el rectángulo, no solo las letras
}
.padding(.horizontal)
.background(.bar)
}
}
}
struct RaizDeMusicaView: View {
var body: some View {
TabView {
Tab("Inicio", systemImage: "house") {
List(1...30, id: \.self) { Text("Canción \($0)") }
.modifier(BarraDeMusica(titulo: "Imagine · 1:15"))
}
Tab("Buscar", systemImage: "magnifyingglass") {
Text("Buscar")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.modifier(BarraDeMusica(titulo: "Imagine · 1:15"))
}
}
}
}
#Preview { RaizDeMusicaView() }La barra es un ViewModifier que lee del entorno una función, abrirReproductor, y la llama al tocar. Como nadie le da valor, vale el de por defecto ({}, una función vacía) y tocar no hace nada: la barra no se rompe por estar sin cablear. Cada pestaña lleva su propia barra porque cada pestaña es una vista distinta. El proyecto sigue compilando con este ejercicio dentro: no lo borres, porque en la lección siguiente lo cableas.
Paso · La barra, en todas las pestañas
Objetivo: que la barra salga en todas las pestañas con el partido en juego, que no salga en el propio partido, que al terminar enseñe «Final · …» con una X, y que sus textos tengan pruebas. Tocarla no hace nada todavía.
1 · La barra: BarraDelPartidoEnJuego.swift
Crea el fichero: clic derecho sobre la carpeta MarcadorApp con el código (la raíz, sin entrar en ninguna otra) → New File from Template… → Swift File, solo con el target MarcadorApp. La barra no es de una funcionalidad: se pone encima de todas las pantallas, como ContentView.swift, y por eso se queda en la raíz.
Piezas nuevas de este paso: las de arriba (@Entry, ViewModifier con extension View, .safeAreaInset, .accessibilityLabel y .accessibilityHint) y estas:
.background(.bar): el fondo translúcido de las barras de iOS, el mismo que el de la barra de pestañas..overlay(alignment: .top) { Divider() }: una línea fina sobre el borde de arriba.overlaypone una vista encima..contentShape(.rect): hace que se pueda tocar todo el rectángulo de la vista, no solo lo que se dibuja dentro. Lo llevan el texto de la barra y la X (más abajo se explica por qué la X lo necesita).terminado ? "Final · …" : "…": el operador? :que ya usaste endirecto(id:)(Estado de toda la app en SwiftUI): unifde una sola línea que da un valor: «siterminadoes verdadero, el texto de antes de los dos puntos; si no, el de después»..buttonStyle(.plain): botones sin el aspecto azul de siempre, para que parezca una barra..monospacedDigit(): que todos los dígitos midan lo mismo, para que el texto no baile al cambiar el minuto.Image(systemName: "xmark"): el símbolo del sistema de la X, como elsystemImagede las pestañas.pantallaActual != .partido(directo.partidoId): una comparación entre un opcional (Destino?, que puede sernil) y un valor, para esconder la barra en el propio partido.
Intenta escribirlo tú. Un fichero con cuatro cosas:
- El valor del entorno
abrirPartido, de tipo(Int) -> Void, que por defecto no hace nada. - Una función
textoDeLaBarra(local:visitante:marcador:minuto:terminado:)que devuelve «Rayo FC 2–1 Toros · 34'» mientras se juega (con la raya larga–entre los goles) y «Final · Rayo FC 3–2 Toros» al terminar. - Un
ViewModifier,BarraDelPartidoEnJuego, que lee del entorno elPartidoEnJuegoyabrirPartido, y recibe la pantalla que se ve (pantallaActual, unDestino?). Pone abajo, con.safeAreaInset, una fila con un botón que enseña el texto y llama aabrirPartidocon el número del partido; y, solo si el partido terminó, un botón con una X (un cuadro de 44 × 44 que se pueda tocar entero) que llama acerrar(). La barra solo se pinta si hay un partido en juego y la pantalla actual no es la de ese partido. - Una función
barraDelPartidoEnJuego(pantallaActual:)enView, y una vista previa con una lista y un partido ya empezado.
Intenta primero. Cuando termines, compara con la solución.Mostrar el código
import SwiftUI
import Torneo
// TU PROPIO VALOR DEL ENTORNO: `abrirPartido` (como `\.dismiss`, pero tuyo).
//
// `@Entry` declara una clave del entorno con su valor por defecto en una línea. La raíz pone el valor
// de verdad (`.environment(\.abrirPartido) { … }`) y cualquier vista de debajo lo lee con
// `@Environment(\.abrirPartido)`. Si nadie lo pone (una vista previa), vale el de por defecto: no
// hace nada. Compáralo con `@Environment(Tipo.self)`: sin el objeto en el entorno, la app se detiene.
extension EnvironmentValues {
@Entry var abrirPartido: (Int) -> Void = { _ in }
}
// El texto de la barra (gemelo de `textoDeLaBarra` en Android):
// en juego -> «Rayo FC 2–1 Toros · 34'»
// al final -> «Final · Rayo FC 3–2 Toros»
func textoDeLaBarra(local: String, visitante: String, marcador: Marcador, minuto: Int, terminado: Bool) -> String {
let resultado = "\(local) \(marcador.local)–\(marcador.visitante) \(visitante)"
return terminado ? "Final · \(resultado)" : "\(resultado) · \(minuto)'"
}
// LA BARRA DEL PARTIDO EN JUEGO: abajo, encima de las pestañas, en todas las pantallas menos la del
// propio partido. Al tocarla se abre el partido; al terminar, una X la cierra.
//
// `.safeAreaInset(edge: .bottom)` coloca la barra abajo y ENCOGE la zona segura del contenido: una
// lista se desplaza por encima de la barra, sin quedar tapada. (En iOS 26 existe
// `tabViewBottomAccessory`, hecho para esto; la app es para iOS 18.)
struct BarraDelPartidoEnJuego: ViewModifier {
@Environment(PartidoEnJuego.self) private var enJuego
@Environment(\.abrirPartido) private var abrirPartido
// La pantalla que se ve en esta pestaña: si es el propio partido, la barra no sale.
let pantallaActual: Destino?
func body(content: Content) -> some View {
content.safeAreaInset(edge: .bottom, spacing: 0) {
if let directo = enJuego.directo, pantallaActual != .partido(directo.partidoId) {
HStack(spacing: 4) {
Button {
abrirPartido(directo.partidoId)
} label: {
Text(textoDeLaBarra(
local: directo.partido.local.nombre, visitante: directo.partido.visitante.nombre,
marcador: directo.marcador, minuto: directo.minuto, terminado: directo.terminado
))
.font(.subheadline.weight(.semibold))
.monospacedDigit()
.frame(maxWidth: .infinity, minHeight: 44, alignment: .leading)
.contentShape(.rect)
}
.accessibilityHint("Abre el partido")
.accessibilityIdentifier("barraDelPartido")
// Mientras se juega no se puede cerrar: la X solo sale al final.
if directo.terminado {
Button {
enJuego.cerrar()
} label: {
Image(systemName: "xmark")
.frame(width: 44, height: 44)
.contentShape(.rect)
}
.accessibilityLabel("Cerrar")
}
}
.buttonStyle(.plain)
.padding(.horizontal, 16)
.background(.bar)
.overlay(alignment: .top) { Divider() }
}
}
}
}
extension View {
func barraDelPartidoEnJuego(pantallaActual: Destino? = nil) -> some View {
modifier(BarraDelPartidoEnJuego(pantallaActual: pantallaActual))
}
}
#Preview("En juego") {
let enJuego = PartidoEnJuego()
_ = enJuego.empezar(Directo(partidoId: 1, partido: Ejemplo.rayoContraToros, duracion: 90, msPorMinuto: 250))
return List(1...20, id: \.self) { Text("Fila \($0)") }
.barraDelPartidoEnJuego()
.environment(enJuego)
}Cuatro detalles del fichero:
_ = enJuego.empezar(…)descarta el resultado deempezar(verdadero o falso, según se haya podido empezar): en la vista previa no hace falta. Yreturnmarca cuál es la vista que se dibuja, porque antes hay otras dos líneas.- La barra lee el
PartidoEnJuegodirectamente del entorno, sinViewModel. Es solo mostrar un dato y llamar a dos funciones: unViewModelaquí no añadiría nada. - El texto vive en una función normal,
textoDeLaBarra, y no dentro de la vista. Una función se prueba sin pintar nada (apartado 3). Y la X es un símbolo del sistema (xmark): Marcador no necesita una librería de iconos para eso. - La X lleva
.contentShape(.rect)justo después del.frame. Con.buttonStyle(.plain), el.frame(width: 44, height: 44)agranda el hueco de la X, pero solo respondería al toque el dibujo del símbolo, de unos 13 puntos..contentShape(.rect)hace tocable todo el cuadro de 44 × 44. Importa porque Xcode puede hacer una auditoría de accesibilidad: revisa la pantalla en busca de cosas que dificultan usar la app a quien no ve bien o toca con poca precisión. Sin esa línea, la auditoría marcaba la X con «Hit area is too small» («zona táctil demasiado pequeña»); Apple recomienda como mínimo 44 × 44 puntos.
2 · Ponerla en cada pestaña: ContentView.swift y ListaDePartidosView.swift
La barra se pega con un punto, como cualquier modificador. En «Marcador» y «Laboratorio» no hay un partido que esconder, así que la barra sale siempre. En «Partidos» sí: dentro está la pila de pantallas, y la barra tiene que saber cuál se ve (ruta.last, la última de la pila) para esconderse en la del propio partido. Por eso en esa pestaña la barra se pone dentro de ListaDePartidosView, justo después de la NavigationStack: así queda por fuera de la pila y se queda quieta cuando se apilan pantallas.
Intenta escribirlo tú.
ContentView: añade.barraDelPartidoEnJuego()a las pestañas «Marcador» y «Laboratorio», y.environment(contenedor.partidoEnJuego)a la vista previa (la barra lee el partido en juego del entorno).ListaDePartidosView: después de laNavigationStack,.barraDelPartidoEnJuego(pantallaActual: ruta.last); y.environment(contenedor.partidoEnJuego)en su vista previa.
Intenta primero. Cuando termines, compara con la solución.Mostrar el código
Sustituye todo el contenido de ContentView.swift:
import SwiftUI
// La raíz de la app. `TabView` es la barra de pestañas de iOS.
struct ContentView: View {
var body: some View {
TabView {
Tab("Partidos", systemImage: "list.bullet") {
ListaDePartidosView()
}
Tab("Marcador", systemImage: "sportscourt") {
PantallaMarcador()
.barraDelPartidoEnJuego()
}
Tab("Laboratorio", systemImage: "flask") {
LaboratorioView()
.barraDelPartidoEnJuego()
}
}
}
}
#Preview {
// La lista y el partido leen el contenedor, el repositorio, el notificador y el partido en juego del
// entorno: aquí se los da la vista previa, con un contenedor sin almacenes (no toca el disco).
let contenedor = ContenedorDeLaApp(servicio: ServicioJSONPlaceholder(), almacenDelTorneo: nil, almacenDeArbitros: nil)
ContentView()
.environment(contenedor)
.environment(contenedor.partidos)
.environment(contenedor.notificador)
.environment(contenedor.partidoEnJuego)
}En ListaDePartidosView.swift, el primer bloque va al final del body, tras la llave que cierra la NavigationStack, y el segundo es la última línea de la vista previa:
}
// La barra del partido en juego, debajo de toda la pila. Con la pantalla de arriba (`ruta.last`)
// sabe si se está viendo el propio partido, y entonces no sale.
.barraDelPartidoEnJuego(pantallaActual: ruta.last)
}
} .environment(contenedor.partidoEnJuego) // lo lee la barra del partido en juego
}El resto de ListaDePartidosView.swift no cambia: el primer bloque añade las tres líneas de la barra tras la NavigationStack y el segundo, una línea al final de la vista previa.
Ejecuta con Cmd+R en un iPhone del simulador.
- Entra en el partido 1 y pulsa «Empezar partido». En el propio partido no hay barra.
- Vuelve atrás: en la lista, abajo, encima de las pestañas, sale «Rayo FC … · N'» con el minuto en marcha. Pasa a «Laboratorio» y a «Marcador»: la barra sigue ahí.
- Toca la barra: no pasa nada. Es el valor por defecto de
abrirPartido, y es lo que arregla la lección siguiente. - Espera a que el partido termine (con la duración de 90 minutos, unos veinte segundos): la barra dice «Final · …» y aparece una X. Pulsa la X y la barra se quita.
- Gira el simulador (Cmd + flecha izquierda): la barra sigue en su sitio.
3 · Las pruebas
Las del texto (BarraDelPartidoEnJuegoTests.swift). Crea el fichero en la raíz de MarcadorAppTests (como la barra, que está en la raíz de la app): clic derecho sobre la carpeta MarcadorAppTests → New File from Template… → Swift File, solo con el target MarcadorAppTests. Dos pruebas: con 34 minutos y 2-1, «Rayo FC 2–1 Toros · 34'»; terminado y 3-2, «Final · Rayo FC 3–2 Toros». No llevan vistas, y tampoco @MainActor, a diferencia de las pruebas del ViewModel: el ViewModel está marcado a mano como del hilo principal y por eso quien lo llama desde una prueba tiene que estarlo también, mientras que textoDeLaBarra es una función suelta que solo trabaja con textos y números, y aquí se llama tal cual. Si Xcode te enseñara un aviso amarillo sobre «main actor-isolated» en estas pruebas, escribe @MainActor encima de la línea struct BarraDelPartidoEnJuegoTests y desaparece.
Intenta primero. Cuando termines, compara con la solución.Mostrar el código
import Testing
import Torneo
@testable import MarcadorApp
// El texto de la barra es una función normal: se prueba sin pintar nada.
struct BarraDelPartidoEnJuegoTests {
@Test func enJuegoMuestraElMinuto() {
let texto = textoDeLaBarra(local: "Rayo FC", visitante: "Toros", marcador: Marcador(local: 2, visitante: 1), minuto: 34, terminado: false)
#expect(texto == "Rayo FC 2–1 Toros · 34'")
}
@Test func alTerminarMuestraFinal() {
let texto = textoDeLaBarra(local: "Rayo FC", visitante: "Toros", marcador: Marcador(local: 3, visitante: 2), minuto: 90, terminado: true)
#expect(texto == "Final · Rayo FC 3–2 Toros")
}
}Lanza Cmd+U.
Punto de control
- Cmd+B termina en
Build Succeededy Cmd+U da 72 pruebas en verde: 56 unitarias y 16 de interfaz (con tus ejercicios de pruebas, 75). Las nuevas son las dos deBarraDelPartidoEnJuegoTests. - En el simulador, la barra no sale en el propio partido y sí en la lista, en «Marcador» y en «Laboratorio». Al terminar dice «Final · …» con una X, y la X la quita. Al tocarla, no pasa nada: todavía no la has cableado.
- La carpeta
EjerciciostieneBarraDeMusica, y su vista previa deja la barra «Imagine · 1:15» en las dos pestañas. - Sabes decir en qué se diferencia
@Entryde@Environment(Tipo.self)cuando nadie pone el valor, qué hace unViewModifiery por qué se le añade una función aView, y qué hace.safeAreaInsetcon la lista que tiene detrás.
Comprueba lo que entendiste
5 preguntas · necesitas 4 para completar la lección
1.Una vista lee
@Environment(\.abrirPartido)y nadie ha puesto ese valor (por ejemplo, en una vista previa). ¿Qué pasa al llamarlo?2.¿Qué consigue
.safeAreaInset(edge: .bottom)con la barra y la lista que tiene detrás?3.¿Para qué lleva la X de la barra
.accessibilityLabel("Cerrar")?4.Después de escribir
struct BarraDelPartidoEnJuego: ViewModifier, se añadefunc barraDelPartidoEnJuego(...)aViewcon unaextension. ¿Para qué?5.¿Por qué
textoDeLaBarraes una función normal y no código escrito dentro de la vista de la barra?