Para presentar una vista de forma modal:
-
sheet(isPresented:onDismiss:content:):isPresentedes unBinding<Bool>.onDismisses un closure que se ejecuta cuando se cierra el modal.contentes el contenido del modal. -
sheet(item:onDismiss:content:):contentpinta el contenido del modal, recibiendo un argumento de tipoItem(requerido).itemes unBinding<Item?>opcional que sirve como argumento decontent: si esnilse cierra el modal, si es distinto de nil se pinta el modal, y si se cambia el valor deitementonces se cierra y vuelve a abrir el modal.onDismisses una acción a ejecutar cuando se cierra el modal.
Para presentar una vista de pantalla completa se usa:
-
fullScreenCover(isPresented:onDismiss:content:):isPresentedes unBinding<Bool>. -
fullScreenCover(item:onDismiss:content:):itemes unBinding<Item?>.
Tener en cuenta que las vistas presentadas con sheet o fullScreenCover no hacen parte del stack del NavigationStack.
struct DetailView: View {
let person: Person
var body: some View {
VStack {
Text("Esta es la vista detalle para el siguiente personaje:")
Text("\(person.name) \(person.lastname)")
}
.navigationTitle("Detalle")
.navigationBarTitleDisplayMode(.inline)
}
}
struct PersonForm: View {
// ⚠️ Se recibe la "base de datos" por Binding
@Binding var people: [Person]
@State private var name: String = ""
@State private var lastname: String = ""
@Environment(\.dismiss) private var dismissAction
var body: some View {
Form {
TextField("Nombre", text: $name)
TextField("Nombre", text: $lastname)
Button("Guardar") {
// ⚠️ Se crea el nuevo Person
let newPerson = Person(name: name, lastname: lastname)
// ⚠️ Se agrega en la "base de datos"
people.append(newPerson)
// ⚠️ Se cierra el formulario con dismissAction()
dismissAction()
}
}
}
}
struct ContentView: View {
@State private var searchText: String = ""
@State private var navigationPath = NavigationPath()
@State private var _people = people
@State private var formVisible = false
private var filteredPeople: [Person] {
if searchText.isEmpty {
_people
} else {
_people.filter {
$0.name.localizedStandardContains(searchText) }
}
}
var body: some View {
NavigationStack(path: $navigationPath) {
List(filteredPeople) { person in
NavigationLink(value: person) {
Label("\(person.name) \(person.lastname)", systemImage: "person")
}
}
.navigationDestination(for: Person.self, destination: { person in
DetailView(person: person)
})
.navigationTitle("Personas")
.searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .automatic), prompt: "¿A quién busca?")
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button("Nuevo", systemImage: "plus") {
// ⚠️ Se pone formVisible=true para mostrar el sheet
formVisible = true
}
}
}
// ⚠️ el sheet (PersonForm) es visible si formVisible=true
.sheet(isPresented: $formVisible) {
// Se crea la instancia de PersonForm y se pasa la
// "base de datos"
PersonForm(people: $_people)
}
}
}
}
Arrastrar para cerrar el Sheet
Por defecto, se puede arrastrar la vista hacia abajo para cerrar el sheet. En caso de que este comportamiento no sea deseado, se puede desactivar aplicando el modificador interactiveDismissDisabled(_:) sobre la vista presentada de forma modal.
En caso de desactivar la función de arrastre por defecto, conviene agregar un botón adicional que le permita al usuario cerrar el sheet.
struct PersonForm: View {
@Binding var people: [Person]
@State private var name: String = ""
@State private var lastname: String = ""
@Environment(\.dismiss) private var dismissAction
var body: some View {
NavigationStack {
Form {
TextField("Nombre", text: $name)
TextField("Nombre", text: $lastname)
Button("Guardar") {
let newPerson = Person(name: name, lastname: lastname)
people.append(newPerson)
dismissAction()
}
}
.toolbar {
// ⚠️ Con este botón el usuario puede cerrar el sheet
Button("Cerrar", systemImage: "x.circle") {
dismissAction()
}
}
}
}
}
struct ContentView: View {
@State private var searchText: String = ""
@State private var navigationPath = NavigationPath()
@Namespace var personNamespace
@State private var _people = people
@State private var formVisible = false
private var filteredPeople: [Person] {
if searchText.isEmpty {
_people
} else {
_people.filter {
$0.name.localizedStandardContains(searchText) }
}
}
var body: some View {
NavigationStack(path: $navigationPath) {
List(filteredPeople) { person in
NavigationLink(value: person) {
Label("\(person.name) \(person.lastname)", systemImage: "person")
.matchedTransitionSource(id: person.id, in: personNamespace)
}
}
.navigationDestination(for: Person.self, destination: { person in
DetailView(person: person)
.navigationTransition(.zoom(sourceID: person.id, in: personNamespace))
})
.navigationTitle("Personas")
.searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .automatic), prompt: "¿A quién busca?")
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button("Nuevo", systemImage: "plus") {
formVisible = true
}
}
}
.sheet(isPresented: $formVisible) {
// ⚠️ Se aplica interactiveDismissDisabled para quitar
// la función de arrastre por defecto
PersonForm(people: $_people)
.interactiveDismissDisabled()
}
}
}
}
Configurar el tope ("detent") del sheet
presentationDetents(_:) define los topes (PresentationDetent) de un sheet con los siguientes valores:
largemedium-
custom(_:): Recibe un argumento que conformaCustomPresentationDetent. fraction(_:)height(_:)
Cuando hay varios topes disponibles, el sistema muestra un indicador que se puede ocultar con presentationDragIndicator(_:).
Notar en el siguiente ejemplo que se aplican los modificadores a la vista a presentar como sheet.
struct ContentView: View {
@State private var searchText: String = ""
@State private var navigationPath = NavigationPath()
@Namespace var personNamespace
@State private var _people = people
@State private var formVisible = false
private var filteredPeople: [Person] {
if searchText.isEmpty {
_people
} else {
_people.filter {
$0.name.localizedStandardContains(searchText) }
}
}
var body: some View {
NavigationStack(path: $navigationPath) {
List(filteredPeople) { person in
NavigationLink(value: person) {
Label("\(person.name) \(person.lastname)", systemImage: "person")
.matchedTransitionSource(id: person.id, in: personNamespace)
}
}
.navigationDestination(for: Person.self, destination: { person in
DetailView(person: person)
.navigationTransition(.zoom(sourceID: person.id, in: personNamespace))
})
.navigationTitle("Personas")
.searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .automatic), prompt: "¿A quién busca?")
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button("Nuevo", systemImage: "plus") {
formVisible = true
}
}
}
.sheet(isPresented: $formVisible) {
PersonForm(people: $_people)
// ⚠️ El sheet tendrá topes .medium y .large
.presentationDetents([.medium, .large])
// ⚠️ Quitar el indicador de arrastre con .hidden
.presentationDragIndicator(.automatic)
.interactiveDismissDisabled()
}
}
}
}
Cambiar la forma del sheet
-
presentationBackground(_:)define el fondo de un sheet de tipoShapeStylecomo unColoroMaterial. Antes de iOS 26 tenía sentido aplicar unMaterialpara hacer translúcido el sheet, sin embargo, a partir de iOS 26 cualquier material lo hace ver opaco, mientras que el comportamiento por defecto tiene el efecto vidrio.
.sheet(isPresented: $formVisible) {
PersonForm(people: $_people)
.presentationBackground(.ultraThinMaterial)
.presentationDetents([.medium])
.interactiveDismissDisabled()
}
Fondo por defecto:
Con Material.ultraThinMaterial:
Con Color.red:
Interacción con el fondo con el sheet visible
-
presentationBackgroundInteraction(_:): Indica si el usuario puede interactuar con la vista detrás del sheet. Recibe unPresentationBackgroundInteractionque puede tener valoresautomatic,disabled,enabledyenabled(upThrough:).
.sheet(isPresented: $formVisible) {
PersonForm(people: $_people)
.presentationBackground(.ultraThickMaterial)
.presentationDetents([.height(120), .medium, .large])
.presentationBackgroundInteraction(.enabled(upThrough: .height(120)))
.interactiveDismissDisabled()
}
En modo .medium no se puede interactuar con el fondo:
En .height(120), sí se puede interactuar con el fondo:
Fijar el radio de las esquinas de un sheet
-
presentationCornerRadius(_:)asigna un radio específico a las esquinas del sheet.
.sheet(isPresented: $formVisible) {
PersonForm(people: $_people)
.presentationBackground(.ultraThickMaterial)
.presentationDetents([.height(120), .medium, .large])
.presentationCornerRadius(10)
.interactiveDismissDisabled()
}
Parece casi recto (y antinatural):











Top comments (0)