DEV Community

GoyesDev
GoyesDev

Posted on

[SUI] Master/content/detail con NavigationSplitView

NavigationSplitView también permite construir el patrón de navegación "master"/"content"/"detail", que básicamente son tres columnas, donde "master" suele ocultarse mientras se presenta "content" y "detail".

Estructura de datos

Vamos a usar la misma estructura de datos del ejemplo de "master"/"detail".

struct Item: Identifiable, Hashable {
  let id: UUID = UUID()
  let title: String
  let children: [Item]?
}
Enter fullscreen mode Exit fullscreen mode

Y el mismo dataset:

let items: [Item] = [
  .init(title: "Súpergrupo 1", children: [
    .init(title: "Grupo 1", children: [
      .init(title: "Hoja 1", children: nil),
      .init(title: "Hoja 2", children: nil),
      .init(title: "Hoja 3", children: nil),
    ])
  ]),
  .init(title: "Súpergrupo 2", children: [
    .init(title: "Grupo 2", children: [
      .init(title: "Hoja 4", children: nil),
      .init(title: "Hoja 5", children: nil),
      .init(title: "Hoja 6", children: nil),
    ]),
    .init(title: "Grupo 3", children: [
      .init(title: "Hoja 6", children: nil),
    ]),
  ]),
  .init(title: "Súpergrupo 3", children: [
    .init(title: "Grupo 4", children: [
      .init(title: "Hoja 7", children: nil),
    ]),
    .init(title: "Grupo 5", children: [
      .init(title: "Hoja 7", children: nil),
      .init(title: "Hoja 8", children: nil),
    ])
  ]),
]
Enter fullscreen mode Exit fullscreen mode

Sidebar

En el bloque sidebar del NavigationSplitView sigue siendo un List que guarda el id del Item seleccionado.

@State private var selectedSupergroupId: Item.ID?
// ...
List(items, selection: $selectedSupergroupId) { supergroup  in
  Text(supergroup.title)
}
.listStyle(.sidebar)
.navigationTitle("Supergroups")
Enter fullscreen mode Exit fullscreen mode

Content

El bloque content del NavigationSplitView se calcula de forma condicionada: Si el selectedSupergroupId es nil se pinta el Placeholder, sino se pinta un List que pinta a los hijos (children) del Item seleccionado.

Cuando se seleccione un Item en esta lista, se guardará su id en selectedGroupId.

@State private var selectedSupergroupId: Item.ID?
@State private var selectedGroupId: Item.ID?

if let selectedSupergroupId, let selectedSupergroup = items.first(where: { $0.id == selectedSupergroupId }), let groups = selectedSupergroup.children {
  List(groups, selection: $selectedGroupId) { group  in
    Text(group.title)
  }
  .listStyle(.sidebar)
  .navigationTitle("Groups")
} else {
  Placeholder()
}
Enter fullscreen mode Exit fullscreen mode

Detail

El bloque detail del NavigationSplitView también se calcula de forma condicionada: Se necesita que tanto selectedSupergroupId como selectedGroupId sean distintos de nil. En caso contrario, se pinta un Placeholder.

if let selectedSupergroupId, let selectedGroupId, let selectedSupergroup = items.first(where: { $0.id == selectedSupergroupId }), let groups = selectedSupergroup.children, let selectedGroup = groups.first(where: { $0.id == selectedGroupId }), let firstItem = selectedGroup.children?.first {
  DetailView(item: firstItem)
} else {
  Placeholder()
}
Enter fullscreen mode Exit fullscreen mode

Código final

A continuación se presenta el código completo y algunas capturas de pantalla:

struct ContentView: View {
  @State private var selectedSupergroupId: Item.ID?
  @State private var selectedGroupId: Item.ID?
  @State private var visibility: NavigationSplitViewVisibility = .automatic

  var body: some View {
    NavigationSplitView(columnVisibility: $visibility) {
      List(items, selection: $selectedSupergroupId) { supergroup  in
        Text(supergroup.title)
      }
      .listStyle(.sidebar)
      .navigationTitle("Supergroups")
    } content: {
      if let selectedSupergroupId, let selectedSupergroup = items.first(where: { $0.id == selectedSupergroupId }), let groups = selectedSupergroup.children {
        List(groups, selection: $selectedGroupId) { group  in
          Text(group.title)
        }
        .listStyle(.sidebar)
        .navigationTitle("Groups")
      } else {
        Placeholder()
      }
    } detail: {
      if let selectedSupergroupId, let selectedGroupId, let selectedSupergroup = items.first(where: { $0.id == selectedSupergroupId }), let groups = selectedSupergroup.children, let selectedGroup = groups.first(where: { $0.id == selectedGroupId }), let firstItem = selectedGroup.children?.first {
        DetailView(item: firstItem)
      } else {
        Placeholder()
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode


Bibliografía

Top comments (0)