DEV Community

GoyesDev
GoyesDev

Posted on

[SUI] Toolbar en iOS 26 (LiquidGlass)

Se puede agregar elementos a y configurar la barra de navegación con los siguientes modificadores:

En el siguiente ejemplo se agrega un botón en la barra de navegación:

struct ContentView: View {
  let scrums: [DailyScrum]

  var body: some View {
    NavigationStack {
      List(scrums) { scrum in
        NavigationLink {
          Text(scrum.title)
        } label: {
          CardView(scrum: scrum)
        }
        .listRowBackground(scrum.theme)
      }
      // ⚠️ Se cambió el navigationBar para que sea .inline
      // y así se pueda ver mejor el toolbar
      .navigationBarTitleDisplayMode(.inline)
      .navigationTitle("Daily Scrums")
      .toolbar {
        // ⚠️ Se definen los elementos dentro de la barra
        Button(action: {}) {
          Image(systemName: "plus")
        }
        .accessibilityLabel("New Scrum")
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode
struct DailyScrum: Identifiable {
  let id: UUID = UUID()
  let theme: Color
  let title: String
  let participants: Int
  let length: Int
}

struct CardView: View {
  let scrum: DailyScrum
  var body: some View {
    VStack(alignment: .leading) {
      Text(scrum.title)
        .fontWeight(.bold)

      HStack {
        HStack {
          Image(systemName: "person.3")
          Text(scrum.participants.description)
        }
        .accessibilityLabel("Number of participants")

        Spacer()

        HStack {
          Text(scrum.length.description)
          Image(systemName: "clock")
        }
        .accessibilityLabel("Meeting length")
      }
      .padding(.top)
      .font(.caption)
      .foregroundStyle(.primary)
    }
    .padding(.horizontal)
  }
}
Enter fullscreen mode Exit fullscreen mode

Puedo agregar tantos botones como sean necesarios:

.toolbar {
  Button(action: {}) {
    Image(systemName: "plus")
  }
  .accessibilityLabel("New Scrum")

  Button(action: {}) {
    Image(systemName: "trash")
  }
  .accessibilityLabel("Remove Scrum")
}
Enter fullscreen mode Exit fullscreen mode

Envolviendo en ToolbarItem

Como se mencionó arriba, los elementos del toolbar pueden envolverse con ToolbarItem. No es obligatorio, aunque la estrategia a usar debe ser uniforme en toda la colección: es decir, o todos los elementos están envueltos con ToolbarItem o ninguno lo está. Se usa el siguiente constructor:

Se usa ToolbarItem para configurar con más precisión la forma como se pintan en pantalla. Por ejemplo:

.toolbar {
  ToolbarItem(placement: .topBarLeading) {
    Button(action: {}) {
      Image(systemName: "plus")
    }
    .accessibilityLabel("New Scrum")
  }

  ToolbarItem {
    Button(action: {}) {
      Image(systemName: "trash")
    }
    .accessibilityLabel("Remove Scrum")
  }
}
Enter fullscreen mode Exit fullscreen mode

Hay que tener en cuenta que el uso de ToolbarItem debe ser uniforme: o todos los elementos de la colección lo tienen, o ninguno. De lo contrario, aparece el siguiente error de compilación:

Failed to produce diagnostic for expression; please submit a bug report (https://swift.org/contributing/#reporting-bugs)

ToolbarItemGroup

Se pueden definir varios botones a la vez con ToolbarItemGroup.

Al crear ToolbarItem para configurar los botones de una barra inferior (bottomBar), estos estarán alineados al centro por defecto.

.toolbar {
  ToolbarItem(placement: .bottomBar) {
    Button(action: {}) {
      Image(systemName: "plus")
    }
    .accessibilityLabel("New Scrum")
  }

  ToolbarItem(placement: .bottomBar) {
    Button(action: {}) {
      Image(systemName: "trash")
    }
    .accessibilityLabel("Remove Scrum")
  }
}
Enter fullscreen mode Exit fullscreen mode

Sin embargo, al usar un ToolbarItemGroup, se pueden alinear los elementos a un extremo usando Spacer.

.toolbar {
  ToolbarItemGroup(placement: .bottomBar) {
    Spacer()

    Button(action: {}) {
      Image(systemName: "plus")
    }
    .accessibilityLabel("New Scrum")

    Button(action: {}) {
      Image(systemName: "trash")
    }
    .accessibilityLabel("Remove Scrum")
  }
}
Enter fullscreen mode Exit fullscreen mode

Alineando ToolbarItems según su función

Aunque se puede establecer de forma explícita la ubicación de un ToolbarItem (e.g. topBarLeading y topBarTrailing) , se puede definir la función del botón y dejar que el sistema se encargue de ubicarlo.

Por ejemplo, primaryAction es una acción usada con mucha frecuencia en el contexto actual de la pantalla que está viendo el usuario. Esta acción aparece en el extremo final ("trailing") de la barra de navegación en iOS y en el extremo inicial ("leading") del toolbar en MacOS.

Por otro lado, secondaryAction es otro tipo de acción que también se usa con frecuencia en el contexto actual de la pantalla, pero que no es requerido para funcionar.

NOTA DE VITAL IMPORTANCIA: Los botones con ubicación secondaryAction NO VAN A FUNCIONAR si se crean con el inicializador init(action:label:). Debe usarse el inicializador: init(_:systemImage:role:action:)

.toolbar {
  ToolbarItem(placement: .primaryAction) {
    Button(action: {
      print("Adding")
    }) {
      Image(systemName: "plus")
    }
  }

  ToolbarItem(placement: .secondaryAction) {
    Button("Removing", systemImage: "trash") {
      print("Removing")
    }
    .accessibilityLabel("Remove Scrum")
  }
}
Enter fullscreen mode Exit fullscreen mode

Rol del toolbar

Los botones del toolbar pueden tener funciones diferentes según el contexto de la pantalla. A esto se le conoce como ToolbarRole y puede ser configurado con toolbarRole(_:). Por defecto está en automatic que, al ser usado dentro de un NavigationStack querría decir que adquiere un rol de navigationStack. Sin embargo, puede ser que las funciones del toolbar estén relacionadas con la edición del contenido de la pantalla actual. En ese caso, se puede usar el rol editor.

Si se usa el rol por defecto o navigationStack:

Si se usa el rol de editor:

Distanciando botones

Dos ToolbarItem con la misma ubicación estarán agrupados por defecto.

.toolbar {
  ToolbarItem(placement: .topBarTrailing) {
    Button(action: {
      print("Adding")
    }) {
      Image(systemName: "plus")
    }
  }
  ToolbarItem(placement: .topBarTrailing) {
    Button("Removing", systemImage: "trash") {
      print("Removing")
    }
    .accessibilityLabel("Remove Scrum")
  }
}
Enter fullscreen mode Exit fullscreen mode

Para separarlos se usa ToolbarSpacer que puede tener tamaño fijo (fixed) o rellenar tanto espacio como pueda (flexible).

.toolbar {
  ToolbarItem(placement: .topBarTrailing) {
    Button(action: {
      print("Adding")
    }) {
      Image(systemName: "plus")
    }
  }
  ToolbarSpacer(.fixed, placement: .topBarTrailing)
  ToolbarItem(placement: .topBarTrailing) {
    Button("Removing", systemImage: "trash") {
      print("Removing")
    }
    .accessibilityLabel("Remove Scrum")
  }
}
Enter fullscreen mode Exit fullscreen mode

Se puede usar ToolbarItemGroup en conjunto con ToolbarSpacer para crear grupos aislados dentro del toolbar:

.toolbar {
  ToolbarItemGroup(placement: .topBarTrailing) {
    Button(action: {
      print("Adding")
    }) {
      Image(systemName: "plus")
    }

    Button("Drawing", systemImage: "pencil") {
      print("Dibujando")
    }
  }
  ToolbarSpacer(.flexible, placement: .topBarTrailing)
  ToolbarItem(placement: .topBarTrailing) {
    Button("Removing", systemImage: "trash") {
      print("Removing")
    }
    .accessibilityLabel("Remove Scrum")
  }
}
Enter fullscreen mode Exit fullscreen mode

ToolbarSpacer con tamaño flexible sirve para separar ToolbarItems hacia los extremos del bottomBar:

.toolbar {
  ToolbarItem(placement: .bottomBar) {
    Button(action: {
      print("Adding")
    }) {
      Image(systemName: "plus")
    }
  }
  ToolbarSpacer(.flexible, placement: .bottomBar)
  ToolbarItem(placement: .bottomBar) {
    Button("Removing", systemImage: "trash") {
      print("Removing")
    }
    .accessibilityLabel("Remove Scrum")
  }
}
Enter fullscreen mode Exit fullscreen mode


.toolbar {
  ToolbarSpacer(.flexible, placement: .bottomBar)
  ToolbarItem(placement: .bottomBar) {
    Button("Removing", systemImage: "trash") {
      print("Removing")
    }
    .accessibilityLabel("Remove Scrum")
  }
}
Enter fullscreen mode Exit fullscreen mode

Alineando Toolbar con Navigationbar

Usando toolbarTitleDisplayMode(_:) se puede alinear la barra de navegación con el toolbar con los modos inline e inlineLarge:

.navigationTitle("Daily Scrums")
.navigationSubtitle("Testing toolbar")
.toolbarTitleDisplayMode(.inlineLarge)
.toolbar {
  ToolbarItemGroup(placement: .topBarTrailing) {
    Button(action: {
      print("Adding")
    }) {
      Image(systemName: "plus")
    }

    Button("Drawing", systemImage: "pencil") {
      print("Dibujando")
    }
  }
  ToolbarSpacer(.flexible, placement: .topBarTrailing)
  ToolbarItem(placement: .topBarTrailing) {
    Button("Removing", systemImage: "trash") {
      print("Removing")
    }
    .accessibilityLabel("Remove Scrum")
  }
}
Enter fullscreen mode Exit fullscreen mode

Título y subtítulo del toolbar

Solo cuando el ToolbarTitleDisplayMode esté configurado en large (o automatic), se puede poner un título y un subtítulo al toolbar al usar las ubicaciones principal, title y largeTitle; y subtitle y largeSubtitle, respectivamente.

.navigationTitle("Daily Scrums")
.navigationSubtitle("Testing toolbar")
.toolbar {
  ToolbarItem(placement: .title) {
    Text("Título")
  }
  ToolbarItem(placement: .subtitle) {
    Text("Subtítulo")
  }

  ToolbarItemGroup(placement: .topBarTrailing) {
    Button(action: {
      print("Adding")
    }) {
      Image(systemName: "plus")
    }

    Button("Drawing", systemImage: "pencil") {
      print("Dibujando")
    }
  }
  ToolbarSpacer(.flexible, placement: .topBarTrailing)
  ToolbarItem(placement: .topBarTrailing) {
    Button("Removing", systemImage: "trash") {
      print("Removing")
    }
    .accessibilityLabel("Remove Scrum")
  }
}
Enter fullscreen mode Exit fullscreen mode

Menú

Para agregar manualmente un menú desplegable en el toolbar, en lugar de un Button, se debe agregar un Menu envuelto en un ToolbarItem.

.toolbar() {  
  ToolbarItem(placement: .topBarTrailing) {
    Menu {
      Button("Draw", systemImage: "pencil") {
        print("Drawing")
      }
      Button("Call", systemImage: "phone") {
        print("Calling")
      }
    } label: {
      Image(systemName: "ellipsis.circle")
    }
  }

  ToolbarSpacer(placement: .topBarTrailing)

  ToolbarItem(id: "removing", placement: .destructiveAction) {
    Button("Removing", systemImage: "trash") {
      print("Removing")
    }
    .accessibilityLabel("Remove Scrum")
  }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)