SwiftUI多栏导航界面NavigationSplitView
SwiftUI多栏导航界面NavigationSplitView

SwiftUI多栏导航界面NavigationSplitView

NavigationSplitView 是 SwiftUI 中的一种导航视图结构,适用于多栏导航界面,特别是在 iPad 或 macOS 等大屏设备上。它将界面分为主列表(主视图)和详细内容(详细视图)两个或多个部分。

基本语法

NavigationSplitView 使用一个三栏结构:

1、Sidebar(侧边栏) – 左侧栏,用于展示主导航列表。

2、Content(内容) – 中间栏,用于展示相关内容。

3、Detail(详细视图) – 右侧栏,用于显示选中项的详细信息。

NavigationSplitView {
    SidebarView()
} content: {
    ContentView()
} detail: {
    DetailView()
}

参数说明

columnVisibility

类型:Binding<NavigationSplitViewVisibility>

用于指定分栏视图中各栏的显示状态。NavigationSplitViewVisibility 是一个枚举类型,包含以下选项:

.all:显示所有分栏(默认行为)。

.detailOnly:仅显示详细视图(右侧的分栏)。

.automatic:由系统根据设备类型和屏幕大小自动选择显示哪些分栏。

示例

import SwiftUI

struct ContentView: View {
    var body: some View {
        NavigationSplitView(columnVisibility: .constant(.all)) {
            // Sidebar
            List(0..<10, id: \.self) { item in
                Text("Item \(item)")
            }
            .navigationTitle("Sidebar")
        } content: {
            // Main Content
            Text("Main Content View")
        } detail: {
            // Detail View
            Text("Detail View")
        }
    }
}

#Preview {
    ContentView()
}

效果

1、columnVisibility: .constant(.all) 保证

左侧的 Sidebar、中间的 Content View、右侧的 Detail View 同时显示。

适用于 iPad 或 Mac 等大屏设备。

2、如果修改为 .constant(.detailOnly),将只显示右侧的 Detail View,隐藏其余部分。

动态控制显示状态

如果需要根据用户操作或其他条件动态调整分栏显示状态,可以用绑定一个 State 或 Environment 变量来替代 .constant。

@State private var visibility: NavigationSplitViewVisibility = .all

NavigationSplitView(columnVisibility: $visibility) {
    // Sidebar
    List(0..<10, id: \.self) { item in
        Text("Item \(item)")
    }
    .navigationTitle("Sidebar")
} detail: {
    // Detail View
    Text("Detail View")
}

这种写法允许用户通过按钮或其他方式动态调整分栏的显示状态。

preferredCompactColumn

这是一个绑定(Binding),用于指定默认优先显示的列。

类型:Binding<NavigationSplitViewColumn?>

可取值

.sidebar: 优先显示侧边栏(Sidebar)。

.content: 优先显示中间内容(Content,如果有)。

.detail: 优先显示详细视图(Detail)。

nil: 不优先显示任何列。

NavigationSplitView(preferredCompactColumn:) 是用于配置当 NavigationSplitView 在紧凑模式(如 iPhone 的竖屏模式)下运行时,默认显示的列。这个参数的作用是指定在紧凑模式下哪个列会被优先展示。

1、优先显示 Detail 列

当应用在紧凑模式(如 iPhone 竖屏)下运行时,默认展示 Detail 列。

import SwiftUI

struct ContentView: View {
    @State private var compactColumn: NavigationSplitViewColumn = .detail
    var body: some View {
        NavigationSplitView(preferredCompactColumn: $compactColumn) {
            List(0..<10, id: \.self) { item in
                Text("Sidebar Item \(item)")
            }
            .navigationTitle("Sidebar")
        } detail: {
            Text("Detail View")
        }
    }
}
2、优先显示 Sidebar 列
@State private var compactColumn: NavigationSplitViewColumn? = .sidebar
适用场景

1、在 iPhone 上运行 NavigationSplitView

在紧凑模式下,无法同时显示多列,必须指定一个优先展示的列。

通过 preferredCompactColumn,可以选择默认显示的列(如 Sidebar 或 Detail)。

2、动态切换列

可以通过绑定动态切换当前紧凑模式下显示的列。例如,用户操作时切换显示内容。

注意事项

绑定动态更新:preferredCompactColumn 是一个 Binding,可以动态更新其值来切换显示的列。

无效时的行为:如果设置的列在当前布局下无效(例如,没有定义 Sidebar,但设置为 .sidebar),系统将自动选择一个有效的列。

完整示例

以下是一个 iPad 风格的导航分栏示例,显示水果列表,点击后显示详细信息:

import SwiftUI

struct ContentView: View {
    @State private var selectedFruit: String? // 用于记录当前选中的水果

    var body: some View {
        NavigationSplitView {
            // Sidebar
            List(["Apple", "Banana", "Cherry", "Date"], id: \.self, selection: $selectedFruit) { fruit in
                Text(fruit)
            }
            .navigationTitle("Fruits")
        } content: {
            // Content View
            if let selectedFruit = selectedFruit {
                Text("You selected: \(selectedFruit)")
                    .font(.title)
            } else {
                Text("Select a fruit")
                    .foregroundColor(.gray)
            }
        } detail: {
            // Detail View
            if let selectedFruit = selectedFruit {
                Text("Details for \(selectedFruit)")
                    .font(.largeTitle)
            } else {
                Text("No fruit selected")
                    .foregroundColor(.gray)
            }
        }
    }
}

解释

1、NavigationSplitView

定义了一个分栏导航界面,包含三个部分:

Sidebar: 用于主导航(如水果列表)。

Content: 显示选中项的相关信息。

Detail: 显示更详细的信息。

2、状态绑定:$selectedFruit

使用 @State 管理选中项。

List 中的 selection 属性会自动绑定到状态,当用户选择列表项时,状态会更新。

3、分栏适配

在 iPad 和 macOS 上,会显示三栏布局。

在 iPhone 上,分栏会以堆栈式呈现,每次只显示一栏。

动态生成数据

也可以通过动态数据源生成列表:

struct ContentView: View {
    @State private var selectedItem: Item? // 选中的项
    let items = [
        Item(name: "Swift", description: "A powerful programming language."),
        Item(name: "SwiftUI", description: "Declarative UI framework."),
        Item(name: "Combine", description: "Reactive programming framework.")
    ]

    var body: some View {
        NavigationSplitView {
            // Sidebar
            List(items, id: \.self, selection: $selectedItem) { item in
                Text(item.name)
            }
            .navigationTitle("Topics")
        } content: {
            // Content View
            if let selectedItem = selectedItem {
                Text(selectedItem.name)
                    .font(.title)
            } else {
                Text("Select a topic")
                    .foregroundColor(.gray)
            }
        } detail: {
            // Detail View
            if let selectedItem = selectedItem {
                Text(selectedItem.description)
                    .font(.body)
            } else {
                Text("No topic selected")
                    .foregroundColor(.gray)
            }
        }
    }
}

struct Item: Identifiable,Hashable {
    let id = UUID()
    let name: String
    let description: String
}

适配性与优势

1、自动适配

在 iPhone:界面会以单栏堆栈导航形式展现。

在 iPad 和 macOS:界面会以多栏布局展现。

2、易用性

使用状态绑定管理选中项。

分栏逻辑明确,开发复杂界面时更直观。

3、灵活性

可以根据屏幕尺寸和使用场景自定义每栏的内容布局。

总结

NavigationSplitView 是构建多栏导航界面的核心组件,非常适合 iPad 和 macOS 应用程序。通过简单的绑定和分栏设置,可以快速实现复杂的多栏布局,同时提供良好的用户体验。

扩展知识

navigationSplitViewStyle修饰符

.navigationSplitViewStyle(.balanced)

示例代码

NavigationSplitView(columnVisibility: $visibility) {
    // Sidebar
    List(0..<10, id: \.self) { item in
        Text("Item \(item)")
    }
    .navigationTitle("Sidebar")
} detail: {
    // Detail View
    Text("Detail View")
}
.navigationSplitViewStyle(.balanced) // 设置为平衡样式

支持的样式

.automatic: 默认行为,根据设备类型自动调整样式。例如:

在 iPad 上会显示多列。

在 iPhone 上通常会以单列显示。。

.balanced: 平衡布局,各列(Sidebar 和 Content 或 Detail)的宽度尽可能平衡分配。

.prominentDetail: 优化 Detail 列的显示,确保其在多列布局中占据更多空间。

NavigationSplitView和NavigationStack

可以在同一个应用中同时使用 NavigationSplitView 和 NavigationStack,从而实现跨设备的最佳用户体验。例如:

1、iPad 上使用 NavigationSplitView

显示多栏布局,提升大屏幕设备的生产力。

2、iPhone 上使用 NavigationStack

提供堆栈式的线性导航体验,更适合小屏幕用户。

代码示例

struct ContentView: View {
    @State private var selectedItem: String?

    var body: some View {
        if UIDevice.current.userInterfaceIdiom == .pad {
            // 使用 NavigationSplitView 在 iPad 上显示多栏布局
            NavigationSplitView {
                List(["Item 1", "Item 2", "Item 3"], id: \.self, selection: $selectedItem) { item in
                    Text(item)
                }
                .navigationTitle("Sidebar")
            } content: {
                if let selectedItem = selectedItem {
                    Text("Content for \(selectedItem)")
                } else {
                    Text("Select an item")
                }
            } detail: {
                if let selectedItem = selectedItem {
                    Text("Details for \(selectedItem)")
                } else {
                    Text("No item selected")
                }
            }
        } else {
            // 使用 NavigationStack 在 iPhone 上显示堆栈式导航
            NavigationStack {
                List(["Item 1", "Item 2", "Item 3"], id: \.self) { item in
                    NavigationLink(item, value: item)
                }
                .navigationDestination(for: String.self) { item in
                    Text("Details for \(item)")
                }
                .navigationTitle("Items")
            }
        }
    }
}

NavigationSplitView 和 NavigationStack 各有优势,适用的设备和导航需求不同。

在 iPad 或 macOS 应用中,NavigationSplitView 是主流选择,提升多任务效率。

在 iPhone 上,NavigationStack 更适合单层级的线性导航。

通过适配判断(如设备类型或屏幕尺寸),两者可以结合使用以实现最佳用户体验。

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开放者。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注