1. NavigationLink

NavigationLink 是用于在具有导航能力的视图之间进行页面跳转的主要方式,包裹在 NavigationLink 中的视图都是目的地视图的预览。当用户点击这个预览时,应用会将用户导航到目的地视图。使用 NavigationLink 时,确保你在 NavigationView 中使用它,否则导航将不会发生。

基本使用

最基本的 NavigationLink 用法是在 NavigationView 中直接创建一个链接到目标视图的链接。

import SwiftUI

struct ContentView: View {
    var body: some View {
        NavigationView {
            List {
                NavigationLink("Go to Detail View", destination: DetailView())
            }
            .navigationTitle("Home")
        }
    }
}

struct DetailView: View {
    var body: some View {
        Text("Detail View")
    }
}

 

动态列表中使用

在动态列表中,为每个列表项创建一个 NavigationLink

import SwiftUI

struct ContentView: View {
    let items = ["Item 1", "Item 2", "Item 3"]

    var body: some View {
        NavigationView {
            List(items, id: \.self) { item in
                NavigationLink(item, destination: DetailView(item: item))
            }
            .navigationTitle("Items")
        }
    }
}

struct DetailView: View {
    let item: String

    var body: some View {
        Text("\(item) Detail View")
    }
}

 

使用 @State 触发导航

可以使用 @State 变量和 isActive 参数来控制 NavigationLink 的激活状态。

import SwiftUI

struct ContentView: View {
    @State private var isLinkActive = false

    var body: some View {
        NavigationView {
            VStack {
                NavigationLink(
                    destination: DetailView(),
                    isActive: $isLinkActive
                ) {
                    EmptyView()
                }
                Button("Tap to show details") {
                    isLinkActive = true
                }
            }
            .navigationTitle("Home")
        }
    }
}

struct DetailView: View {
    var body: some View {
        Text("Detail View")
    }
}

 

使用 NavigationLink 的 Label

NavigationLink 的构造器允许你定制触发链接的 UI 部分,这就是 Label。

import SwiftUI

struct ContentView: View {
    var body: some View {
        NavigationView {
            List {
                NavigationLink(destination: DetailView()) {
                    HStack {
                        Image(systemName: "star.fill")
                            .foregroundColor(.yellow)
                        Text("Go to Detail View")
                    }
                }
            }
            .navigationTitle("Home")
        }
    }
}

struct DetailView: View {
    var body: some View {
        Text("Detail View")
    }
}

 

使用 NavigationLink 在 ForEach 中

在 ForEach 结构中使用 NavigationLink 可以为集合中的每个元素创建导航链接。

import SwiftUI

struct ContentView: View {
    let data = ["First", "Second", "Third"]

    var body: some View {
        NavigationView {
            List {
                ForEach(data, id: \.self) { item in
                    NavigationLink(destination: Text("\(item) Detail View")) {
                        Text(item)
                    }
                }
            }
            .navigationTitle("List")
        }
    }
}

 

自定义 NavigationLink 的外观

你可以通过修改 NavigationLink 的 Label 部分来自定义其外观。

import SwiftUI

struct ContentView: View {
    var body: some View {
        NavigationView {
            NavigationLink(destination: DetailView()) {
                CustomCell()
            }
        }
    }
}

struct CustomCell: View {
    var body: some View {
        HStack {
            Image(systemName: "person.fill")
            VStack(alignment: .leading) {
                Text("Title")
                    .font(.headline)
                Text("Subtitle")
                    .font(.subheadline)
            }
        }
    }
}

struct DetailView: View {
    var body: some View {
        Text("Detail View")
    }
}

 

2. Sheet

Sheet 通常用于显示临时内容,如表单、详情或辅助选项。它不同于 NavigationLink,后者用于在导航层次结构中推送新的视图。 Sheet 更适用于不需要保持导航历史的情况。

基本的 Sheet 使用

使用 .sheet 修饰符来展示一个模态视图,当某个布尔值为 true 时显示。

import SwiftUI

struct ContentView: View {
    @State private var showingSheet = false

    var body: some View {
        Button("Show Sheet") {
            showingSheet.toggle()
        }
        .sheet(isPresented: $showingSheet) {
            SheetView()
        }
    }
}

struct SheetView: View {
    var body: some View {
        Text("Sheet View")
    }
}

使用 onDismiss 处理 Sheet 关闭

可以提供一个 onDismiss 的闭包来处理 Sheet 被关闭时的事件。

import SwiftUI

struct ContentView: View {
    @State private var showingSheet = false

    var body: some View {
        Button("Show Sheet") {
            showingSheet.toggle()
        }
        .sheet(isPresented: $showingSheet, onDismiss: {
            print("Sheet was dismissed.")
        }) {
            SheetView()
        }
    }
}

struct SheetView: View {
    var body: some View {
        Text("Sheet View")
    }
}

使用 item 展示 Sheet

当你需要根据某个可选的数据项展示 Sheet 时,可以使用 item 参数。

import SwiftUI

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

    var body: some View {
        Button("Show Sheet with Item") {
            selectedItem = "Detail"
        }
        .sheet(item: $selectedItem) { item in
            DetailView(item: item)
        }
    }
}

struct DetailView: View {
    let item: String

    var body: some View {
        Text("\(item) View")
    }
}

使用 sheet 结合列表

在列表中为每个条目展示不同的 Sheet。

import SwiftUI

struct ContentView: View {
    let items = ["Item 1", "Item 2", "Item 3"]
    @State private var selectedItem: String?

    var body: some View {
        NavigationView {
            List {
                ForEach(items, id: \.self) { item in
                    Button(action: {
                        selectedItem = item
                    }) {
                        Text(item)
                    }
                }
            }
            .sheet(item: $selectedItem) { item in
                DetailView(item: item)
            }
        }
    }
}

struct DetailView: View {
    let item: String

    var body: some View {
        Text("\(item) Detail View")
    }
}

使用 sheet 结合自定义触发器

你可以创建一个自定义的触发器,例如一个自定义的视图,当点击时,展示 Sheet。

import SwiftUI

struct ContentView: View {
    @State private var showingSheet = false

    var body: some View {
        CustomButton(showingSheet: $showingSheet)
            .sheet(isPresented: $showingSheet) {
                SheetView()
            }
    }
}

struct CustomButton: View {
    @Binding var showingSheet: Bool

    var body: some View {
        Button(action: {
            showingSheet = true
        }) {
            Text("Show Custom Sheet")
        }
    }
}

struct SheetView: View {
    var body: some View {
        Text("Custom Sheet View")
    }
}

3. FullScreenCover

fullScreenCover 是用来覆盖全屏显示内容的,类似于 sheet,但是它会占据整个屏幕。

基本的 fullScreenCover 使用

使用 .fullScreenCover 修饰符来展示一个全屏覆盖视图,它会在某个布尔值为 true 时显示。

import SwiftUI

struct ContentView: View {
    @State private var showingFullScreenCover = false

    var body: some View {
        Button("Show Full Screen Cover") {
            showingFullScreenCover.toggle()
        }
        .fullScreenCover(isPresented: $showingFullScreenCover) {
            FullScreenCoverView()
        }
    }
}

struct FullScreenCoverView: View {
    var body: some View {
        Text("Full Screen Cover View")
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(Color.blue)
    }
}

使用 onDismiss 处理全屏覆盖关闭

可以提供一个 onDismiss 的闭包来处理全屏覆盖被关闭时的事件。

import SwiftUI

struct ContentView: View {
    @State private var showingFullScreenCover = false

    var body: some View {
        Button("Show Full Screen Cover") {
            showingFullScreenCover.toggle()
        }
        .fullScreenCover(isPresented: $showingFullScreenCover, onDismiss: {
            print("Full Screen Cover was dismissed.")
        }) {
            FullScreenCoverView()
        }
    }
}

struct FullScreenCoverView: View {
    var body: some View {
        Text("Full Screen Cover View")
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(Color.green)
    }
}

使用 fullScreenCover 和 item

当你需要根据某个可选的数据项展示全屏覆盖时,可以使用 item 参数。

import SwiftUI

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

    var body: some View {
        Button("Show Full Screen Cover with Item") {
            selectedItem = "Detail"
        }
        .fullScreenCover(item: $selectedItem) { item in
            FullScreenDetailView(item: item)
        }
    }
}

struct FullScreenDetailView: View {
    let item: String

    var body: some View {
        VStack {
            Text("\(item) Full Screen Detail View")
            Button("Dismiss") {
                // Normally you would use an environment variable to dismiss
            }
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(Color.red)
    }
}

使用 fullScreenCover 结合环境变量

你可以使用环境变量 presentationMode 来关闭全屏覆盖。

import SwiftUI

struct ContentView: View {
    @State private var showingFullScreenCover = false

    var body: some View {
        Button("Show Full Screen Cover") {
            showingFullScreenCover.toggle()
        }
        .fullScreenCover(isPresented: $showingFullScreenCover) {
            FullScreenCoverViewWithDismiss()
        }
    }
}

struct FullScreenCoverViewWithDismiss: View {
    @Environment(\.presentationMode) var presentationMode

    var body: some View {
        Button("Tap to dismiss") {
            presentationMode.wrappedValue.dismiss()
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(Color.purple)
    }
}

自定义触发 fullScreenCover 的按钮

可以创建一个自定义的按钮视图,当点击时,展示全屏覆盖。

import SwiftUI

struct ContentView: View {
    @State private var showingFullScreenCover = false

    var body: some View {
        CustomFullScreenCoverButton(showingFullScreenCover: $showingFullScreenCover)
            .fullScreenCover(isPresented: $showingFullScreenCover) {
                FullScreenCoverViewWithDismiss()
            }
    }
}

struct CustomFullScreenCoverButton: View {
    @Binding var showingFullScreenCover: Bool

    var body: some View {
        Button(action: {
            showingFullScreenCover = true
        }) {
            Text("Show Custom Full Screen Cover")
        }
    }
}

struct FullScreenCoverViewWithDismiss: View {
    @Environment(\.presentationMode) var presentationMode

    var body: some View {
        Button("Tap to dismiss") {
            presentationMode.wrappedValue.dismiss()
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(Color.orange)
    }
}

4. Programmatic Navigation

程序化导航(Programmatic Navigation)指的是通过代码控制页面跳转,而不是使用用户界面元素(如按钮)的默认行为。这种方式在某些情况下非常有用,比如在后台任务完成后自动跳转页面,或者基于复杂的逻辑条件来导航。

要在 SwiftUI 中实现程序化导航,通常会用到 NavigationLink 结合一个绑定的布尔值,或者使用 NavigationLink 的 isActive 参数。

使用 NavigationLink 和布尔值

下面这个例子中,NavigationLink 被隐藏了,因为我们不需要显示它的视图。当 navigate 变为 true 时,会触发导航到 DetailView

import SwiftUI

struct ContentView: View {
    @State private var navigate = false

    var body: some View {
        NavigationView {
            VStack {
                Button("Navigate") {
                    navigate = true
                }
                // 隐藏的 NavigationLink
                NavigationLink(destination: DetailView(), isActive: $navigate) {
                    EmptyView()
                }
            }
        }
    }
}

struct DetailView: View {
    var body: some View {
        Text("Detail View")
    }
}

使用 NavigationLink 的 tag 和 selection 参数

如果有多个导航目标,你可以使用 tag 和 selection 来区分不同的导航路径。

import SwiftUI

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

    var body: some View {
        NavigationView {
            VStack {
                Button("Navigate to Detail 1") {
                    selection = "Detail1"
                }
                Button("Navigate to Detail 2") {
                    selection = "Detail2"
                }
                NavigationLink(destination: DetailView1(), tag: "Detail1", selection: $selection) { EmptyView() }
                NavigationLink(destination: DetailView2(), tag: "Detail2", selection: $selection) { EmptyView() }
            }
        }
    }
}

struct DetailView1: View {
    var body: some View {
        Text("Detail View 1")
    }
}

struct DetailView2: View {
    var body: some View {
        Text("Detail View 2")
    }
}

使用 .onAppear 触发程序化导航

有时候,你可能希望在视图出现时自动触发导航,比如根据某个状态或者数据加载完成后。

import SwiftUI

struct ContentView: View {
    @State private var autoNavigate = false

    var body: some View {
        NavigationView {
            VStack {
                NavigationLink(destination: DetailView(), isActive: $autoNavigate) {
                    EmptyView()
                }
                .isDetailLink(false)
                .onAppear {
                    // 假设这里有一些逻辑判断或者异步操作完成后
                    DispatchQueue.main.asyncAfter(deadline: .now() + 2) { // 延迟2秒
                        autoNavigate = true
                    }
                }
            }
        }
    }
}

struct DetailView: View {
    var body: some View {
        Text("Detail View")
    }
}

 

使用 NavigationLink 无界面跳转

有时候,你可能希望隐藏 NavigationLink,让导航完全由代码控制。你可以将 NavigationLink 放在 EmptyView 中,然后使用状态变量控制导航。

import SwiftUI

struct ContentView: View {
    @State private var isLinkActive = false

    var body: some View {
        NavigationView {
            VStack {
                Button("Navigate Programmatically") {
                    isLinkActive = true
                }
                
                // 隐藏的 NavigationLink
                NavigationLink("", destination: DetailView(), isActive: $isLinkActive)
            }
        }
    }
}

struct DetailView: View {
    var body: some View {
        Text("Detail View")
    }
}

使用 NavigationPath 和 NavigationStack

在最新版本的 SwiftUI 中,NavigationPath 和 NavigationStack 提供了更灵活的导航方式。你可以通过直接操作路径来进行导航。

下面这个例子中,点击按钮将 DetailView 添加到 navigationPath 中,这将触发导航到 DetailView

import SwiftUI

struct ContentView: View {
    @State private var navigationPath = NavigationPath()

    var body: some View {
        NavigationStack(path: $navigationPath) {
            Button("Navigate Programmatically") {
                navigationPath.append(DetailView())
            }
            .navigationDestination(for: DetailView.self) { detailView in
                detailView
            }
        }
    }
}

struct DetailView: View {
    var body: some View {
        Text("Detail View")
    }
}

5. TabView

TabView 可以用来创建一个包含多个子视图的标签页界面,每个子视图都与一个标签关联。用户可以通过点击不同的标签来切换不同的视图。

静态标签页

在最基本的情况下,TabView 中的每个标签和视图是静态定义的,通过直接嵌套视图来创建。

import SwiftUI

struct ContentView: View {
    var body: some View {
        TabView {
            Text("首页")
                .tabItem {
                    Image(systemName: "house.fill")
                    Text("首页")
                }
            
            Text("设置")
                .tabItem {
                    Image(systemName: "gear")
                    Text("设置")
                }
        }
    }
}

动态标签页

TabView 也可以根据数据动态生成标签页。使用 ForEach 结构来循环创建多个标签。

import SwiftUI

struct ContentView: View {
    private var tabs = ["首页", "设置", "个人"]

    var body: some View {
        TabView {
            ForEach(tabs, id: \.self) { tab in
                Text(tab)
                    .tabItem {
                        Image(systemName: "\(tab).fill")
                        Text(tab)
                    }
            }
        }
    }
}

使用 @State 控制当前选中的标签

你可以通过绑定一个 @State 变量到 TabView 的 selection 参数来控制当前选中的标签。

下面例子中,使用 .tag 标记每个视图,selectedTab 变量的值与标签的标记相对应,通过改变 selectedTab 的值来改变当前选中的标签。

import SwiftUI

struct ContentView: View {
    @State private var selectedTab = "首页"

    var body: some View {
        TabView(selection: $selectedTab) {
            Text("首页")
                .tabItem {
                    Image(systemName: "house.fill")
                    Text("首页")
                }
                .tag("首页") // 每个页面需要一个唯一的 tag
                
            Text("设置")
                .tabItem {
                    Image(systemName: "gear")
                    Text("设置")
                }
                .tag("设置") // 每个页面需要一个唯一的 tag
        }
    }
}

程序化地切换标签

在某些情况下,你可能希望能够编程方式切换当前的标签页,比如响应某个事件。你可以通过修改绑定到 TabView 的 selection 属性的变量来实现。

import SwiftUI

struct ContentView: View {
    @State private var selectedTab = "首页"

    var body: some View {
        VStack {
            Button("切换到设置") {
                selectedTab = "设置"
            }
            
            TabView(selection: $selectedTab) {
                Text("首页")
                    .tabItem {
                        Image(systemName: "house.fill")
                        Text("首页")
                    }
                    .tag("首页")
                
                Text("设置")
                    .tabItem {
                        Image(systemName: "gear")
                        Text("设置")
                    }
                    .tag("设置")
            }
        }
    }
}

自定义 TabView 样式

自定义 TabView 的样式,比如修改标签栏的背景色、字体颜色等。

通过直接访问 UITabBar 的 appearance 方法来改变标签栏的背景色。这种方法适用于更改全局的样式,但需要注意这种更改影响到整个 app 的 TabView 样式。

import SwiftUI

struct ContentView: View {
    init() {
        // 自定义 TabBar 的样式
        UITabBar.appearance().backgroundColor = UIColor.systemGray6
    }

    var body: some View {
        TabView {
            Text("首页")
                .tabItem {
                    Image(systemName: "house.fill")
                    Text("首页")
                }
            
            Text("设置")
                .tabItem {
                    Image(systemName: "gear")
                    Text("设置")
                }
        }
        // 更多自定义样式可以在这里设置
    }
}

6. Pop-ups with Popover

Popover 是一种弹出视图,当用户与某个视图交互时,它可以从该视图或按钮弹出提供额外信息或选项。Popover 在 iPad 上以弹出框的形式显示,在 iPhone 上则通常全屏显示。

要在 SwiftUI 中使用 Popover,你需要绑定一个布尔值来控制 Popover 的显示和隐藏。

基础 Popover

这里展示一个简单的 Popover 示例,当点击一个按钮时,它会显示一个弹出视图。

import SwiftUI

struct ContentView: View {
    // 用于控制 Popover 是否显示
    @State private var showingPopover = false

    var body: some View {
        Button("显示 Popover") {
            self.showingPopover = true
        }
        .popover(isPresented: $showingPopover) {
            // 这里是 Popover 的内容
            Text("这是一个 Popover")
                .font(.headline)
                .padding()
        }
    }
}

带有自定义视图的 Popover

你可以定义一个自定义视图,并在 Popover 中显示它。

import SwiftUI

struct PopoverContentView: View {
    var body: some View {
        VStack {
            Text("这是自定义内容")
            Button("关闭") {
                // 这里需要一个方法来关闭 Popover
            }
        }
        .padding()
    }
}

struct ContentView: View {
    @State private var showingPopover = false

    var body: some View {
        Button("显示 Popover") {
            self.showingPopover = true
        }
        .popover(isPresented: $showingPopover) {
            PopoverContentView()
        }
    }
}

使用 Popover 完成页面跳转

在某些情况下,你可能想在 Popover 中放置一个导航链接,允许用户导航到另一个视图。

import SwiftUI

struct DetailView: View {
    var body: some View {
        Text("详细视图内容")
    }
}

struct ContentView: View {
    @State private var showingPopover = false

    var body: some View {
        NavigationView {
            Button("点击显示 Popover") {
                self.showingPopover.toggle()
            }
            .popover(isPresented: $showingPopover) {
                NavigationLink(destination: DetailView()) {
                    Text("前往详细视图")
                }
                .padding()
            }
        }
    }
}

带有关闭按钮的 Popover

通常,你可能希望在 Popover 内部提供一个关闭按钮,下面的示例展示了如何实现:

import SwiftUI

struct ContentView: View {
    @State private var showingPopover = false

    var body: some View {
        Button("显示 Popover") {
            self.showingPopover = true
        }
        .popover(isPresented: $showingPopover) {
            VStack {
                Text("带关闭按钮的 Popover")
                Button("关闭") {
                    self.showingPopover = false
                }
                .padding()
            }
        }
    }
}

使用 sheet 和 popover 进行适配

在 iPad 和 iPhone 设备上,你可能想要根据不同的屏幕大小来适配 Popover 或 Sheet 的显示。可以使用 .popover 和 .sheet 的组合来实现。

针对不同的设备和屏幕尺寸适配 Popover 和 Sheet 的展示方式,我们通常需要依据环境变量 horizontalSizeClass 来决定使用哪一种。为了简化示例,我将提供一个针对 horizontalSizeClass 进行条件判断的代码示例,在 .regular 大小类时使用 Popover,而在 .compact 大小类时使用 Sheet

import SwiftUI

struct DetailView: View {
    var body: some View {
        Text("详细视图内容")
            .font(.title)
    }
}

struct ContentView: View {
    @State private var showDetails = false
    @Environment(\.horizontalSizeClass) var sizeClass

    var body: some View {
        Button("显示详情") {
            self.showDetails = true
        }
        .sheet(isPresented: $showDetails, onDismiss: {
            self.showDetails = false
        }) {
            // 在 compact size class 下显示 Sheet
            if sizeClass == .compact {
                DetailView()
            }
        }
        .popover(isPresented: $showDetails) {
            // 在 regular size class 下显示 Popover
            if sizeClass == .regular {
                DetailView()
            }
        }
    }
}

条件 Popover

有时你可能需要根据特定条件来显示 Popover。你可以在 .popover 修饰符中使用条件语句来实现这一点。

import SwiftUI

struct ContentView: View {
    @State private var showingPopover = false
    @State private var condition = false

    var body: some View {
        Button("点击显示 Popover") {
            condition = true
            self.showingPopover.toggle()
        }
        .popover(isPresented: $showingPopover) {
            if condition {
                Text("条件为真时显示的视图")
            } else {
                Text("条件为假时显示的视图")
            }
        }
    }
}

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐