View — sheet

SwiftUI 中,.sheet 是一个视图修饰符,用于展示一个模态弹窗(Modal Presentation)。它的特点是与某个视图绑定,用于呈现一个子视图。以下是关于 .sheet 的使用规范和它可以添加在哪些视图下面的详细解答。


.sheet 可以加在哪些视图下面? #

.sheet 可以添加到任意支持修饰符的视图下面。例如:

  • TextButtonImage 等基础视图。
  • 容器视图:如 VStackHStackZStack
  • 复杂视图:如 ListScrollView 等。

示例 1:添加到基础视图 #

struct SheetExample: View {
    @State private var showSheet = false

    var body: some View {
        Button("Show Sheet") {
            showSheet = true
        }
        .sheet(isPresented: $showSheet) { // Sheet 可以直接加在 Button 视图上
            Text("This is a sheet")
                .font(.largeTitle)
                .padding()
        }
    }
}

示例 2:添加到容器视图 #

struct SheetExample: View {
    @State private var showSheet = false

    var body: some View {
        VStack {
            Text("Hello, SwiftUI!")
                .padding()
            Button("Show Sheet") {
                showSheet = true
            }
        }
        .sheet(isPresented: $showSheet) { // 添加到 VStack 容器上
            Text("This is a sheet")
                .font(.largeTitle)
                .padding()
        }
    }
}

示例 3:添加到 List 视图 #

struct SheetExample: View {
    @State private var showSheet = false

    var body: some View {
        List {
            Button("Show Sheet") {
                showSheet = true
            }
        }
        .sheet(isPresented: $showSheet) { // 添加到 List 上
            Text("Inside the sheet!")
        }
    }
}

.sheet 的触发条件 #

.sheet 的触发条件必须满足以下两种之一:

  1. 基于布尔值的弹窗触发(常见模式)。
  2. 基于绑定的视图模型或可选值的传递

1. 基于布尔值的 .sheet #

这是最常见的场景,通过 @State@Binding 的布尔值来控制 sheet 是否显示。

示例 #
struct SheetWithBoolExample: View {
    @State private var isSheetPresented = false

    var body: some View {
        Button("Present Sheet") {
            isSheetPresented = true
        }
        .sheet(isPresented: $isSheetPresented) { // 绑定布尔值,控制视图呈现
            VStack {
                Text("Hello, this is a sheet!")
                Button("Dismiss") {
                    isSheetPresented = false // 手动关闭 sheet
                }
            }
            .padding()
        }
    }
}

2. 基于模型或可选值的 .sheet(推荐用于复杂场景) #

sheet 可以绑定到一个 对象模型(optional 类型),当模型不为空时自动展示弹窗。

示例 #
struct SheetWithOptionalExample: View {
    @State private var selectedItem: String? = nil

    var body: some View {
        VStack {
            Button("Select Item 1") {
                selectedItem = "Item 1"
            }

            Button("Select Item 2") {
                selectedItem = "Item 2"
            }
        }
        .sheet(item: $selectedItem) { item in // 绑定到 Optional 类型
            Text("Selected item: \(item)")
                .font(.largeTitle)
                .padding()
        }
    }
}

解释:

  • selectedItem 不为 nil 时,sheet 自动弹出;
  • 传递的 item(一个字符串)用于展示不同的内容。

注意:多个 .sheet 注意不要冲突 #

如果多个 .sheet 同时作用于同一个视图层级(比如都加在同一个容器或顶层视图上),它们可能会导致冲突 或者只有一个生效

解决方案#

  • 确保只对触发 .sheet 的视图绑定一个状态值。
  • 如果多个弹窗需要共存,可以将 .sheet 放置到子视图中。
示例:多个 .sheet #
struct MultipleSheetExample: View {
    @State private var showFirstSheet = false
    @State private var showSecondSheet = false

    var body: some View {
        VStack {
            Button("Show First Sheet") {
                showFirstSheet = true
            }
            .sheet(isPresented: $showFirstSheet) {
                Text("First Sheet")
            }

            Button("Show Second Sheet") {
                showSecondSheet = true
            }
            .sheet(isPresented: $showSecondSheet) {
                Text("Second Sheet")
            }
        }
    }
}

专注点:

  • 每个 .sheet 使用独立的状态值(showFirstSheetshowSecondSheet)。

实践中不要做的操作(错误用法) #

  1. 多个 .sheet 修饰同一个视图。
Button("Tap Me") {
    // 尝试加载两个 `.sheet`,可能会发生冲突
}
.sheet(isPresented: $someCondition) {
    Text("Sheet 1")
}
.sheet(isPresented: $anotherCondition) {
    Text("Sheet 2")
}

问题: 这种情况下,SwiftUI 的运行时将无法正确决定哪个 .sheet 生效。

解决方案 #

  • 将不同的 .sheet 修饰符分配到不同的视图上;
  • 如果需要动态判断,可以用 @ViewBuilder 来动态加载。

总结 #

  1. .sheet 可以加在哪些视图下?

    • 任意支持修饰符的视图,包括 TextButton、布局容器(如 VStack)和列表(如 List/ScrollView)。
  2. 通过两种主要触发方式实现弹窗:

    • 布尔值绑定(简单、常见)。
    • 对象或可选值绑定(灵活、推荐用于复杂场景)。
  3. 注意事项:

    • 避免在同一视图上绑定多个 .sheet
    • 对于复杂需求,建议采用 ViewBuilder 或条件逻辑。

PresentationDetent 的定义 #

在 SwiftUI 中,PresentationDetent 是 SwiftUI 5.0(iOS 15 和更高版本)引入的一个功能,用于控制 sheet 的弹出高度。通过 PresentationDetent,开发者可以设置 sheet 的不同高度,并允许用户在这些高度之间切换,如 .medium.large。这一功能使得 sheet 的使用更加灵活和直观。

标准高度 #

PresentationDetent 提供了几个预定义的标准高度:

  • .medium:高度覆盖屏幕的一半,大约 50%。
  • .large:高度覆盖整个屏幕(全屏)。
  • .fraction(CGFloat)(iOS 16 及更高版本):使用屏幕高度的一个特定百分比。

此外,在 UIKit 的 UISheetPresentationController 中,还可以自定义高度。

用法 #

要使用 PresentationDetent,你需要在 sheet 修饰符中配置 presentationDetents 参数。

示例代码 #

import SwiftUI

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

    var body: some View {
        VStack {
            Button("Present Sheet") {
                isSheetPresented.toggle()
            }
        }
        .sheet(isPresented: $isSheetPresented) {
            SheetView()
                .presentationDetents([.medium, .large]) // 定义预设的高度
        }
    }
}

struct SheetView: View {
    var body: some View {
        VStack {
            Text("This is a sheet")
                .font(.title)
                .padding()
            Spacer()
        }
        .background(Color.white)
    }
}

自定义高度 #

在 iOS 16 及更高版本中,PresentationDetent 提供了一种更加灵活的方式来设置 sheet 的高度,即通过 .fraction(CGFloat),允许你定义基于屏幕高度的百分比。

示例代码:自定义高度 #

import SwiftUI

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

    var body: some View {
        VStack {
            Button("Present Custom Height Sheet") {
                isSheetPresented.toggle()
            }
        }
        .sheet(isPresented: $isSheetPresented) {
            SheetView()
                .presentationDetents([.fraction(0.4)]) // 用屏幕高度的40%作为自定义高度
        }
    }
}

struct SheetView: View {
    var body: some View {
        VStack {
            Text("This is a custom height sheet")
                .font(.title)
                .padding()
            Spacer()
        }
        .background(Color.white)
    }
}

多种高度支持和动态调整 #

你可以将多种高度结合使用,并允许用户通过拖动在这些高度之间进行切换。

示例代码:多种高度 #

import SwiftUI

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

    var body: some View {
        VStack {
            Button("Present Resizable Sheet") {
                isSheetPresented.toggle()
            }
        }
        .sheet(isPresented: $isSheetPresented) {
            SheetView()
                .presentationDetents([.fraction(0.2), .medium, .large]) // 支持多种高度
                .presentationDragIndicator(.visible) // 显示可拖动指示器
        }
    }
}

struct SheetView: View {
    var body: some View {
        VStack {
            Text("This sheet supports multiple heights")
                .font(.title)
                .padding()
            Spacer()
        }
        .background(Color.white)
    }
}

解释: #

  • presentationDetents([.fraction(0.2), .medium, .large]):提供用户20%屏幕高度、50%屏幕高度和全屏高度的选择。
  • presentationDragIndicator(.visible):显示一个可拖动的指示器,让用户知道可以在不同高度之间拖动切换。

动态调整高度 #

你可以动态改变 sheet 的高度以响应用户交互或状态变化。

示例代码:动态调整高度 #

import SwiftUI

struct ContentView: View {
    @State private var isSheetPresented = false
    @State private var currentDetent: PresentationDetent = .medium

    var body: some View {
        VStack {
            Button("Present Sheet") {
                isSheetPresented.toggle()
            }
            Button("Toggle Height") {
                currentDetent = (currentDetent == .medium) ? .large : .medium
            }
        }
        .sheet(isPresented: $isSheetPresented) {
            SheetView()
                .presentationDetents([currentDetent])
        }
    }
}

struct SheetView: View {
    var body: some View {
        VStack {
            Text("This sheet can change height dynamically")
                .font(.title)
                .padding()
            Spacer()
        }
        .background(Color.white)
    }
}

解释: #

  • 动态变化:通过状态变量 currentDetent 控制 sheet 的高度。
  • 交互控制:用户点击按钮可以切换 sheet 的高度。

自定义高度的 UISheetPresentationController #

如果需要更多自定义控制,可以通过 UIViewControllerRepresentable 结合 UISheetPresentationController 来实现。

示例代码:自定义 UISheetPresentationController #

import SwiftUI
import UIKit

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

    var body: some View {
        VStack {
            Button("Present Custom Sheet") {
                isSheetPresented.toggle()
            }
        }
        .sheet(isPresented: $isSheetPresented) {
            CustomSheetViewController()
        }
    }
}

struct CustomSheetViewController: UIViewControllerRepresentable {
    func makeUIViewController(context: Context) -> UIViewController {
        let viewController = UIViewController()
        viewController.view.backgroundColor = .white

        let sheetController = viewController.sheetPresentationController
        sheetController?.detents = [
            .medium(),
            .large(),
            .custom { context in
                return 150 // 自定义高度
            }
        ]
        sheetController?.prefersGrabberVisible = true // 显示抓手

        return viewController
    }

    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
}

struct SheetView: View {
    var body: some View {
        VStack {
            Text("This is a custom height sheet")
                .font(.title)
                .padding()
            Spacer()
        }
        .background(Color.white)
    }
}

解释: #

  • 集成 UIKit:通过 UIViewControllerRepresentable 在 SwiftUI 中嵌入 UIViewController
  • 细致控制:使用 UISheetPresentationController 提供更细化的高度控制。

总结 #

PresentationDetent 使 SwiftUI 中的 sheet 控制更加灵活和强大。它允许开发者轻松设置 sheet 的高度,并且可以利用:

  • 标准高度.medium.large 预定义高度。
  • 自定义高度:通过 .fraction(CGFloat) 指定自定义百分比高度。
  • 多高度支持:允许用户通过拖动在不同高度之间切换。
  • 动态调整:根据用户交互或状态变化动态调整高度。
  • 结合 UIKit:使用 UIViewControllerRepresentableUISheetPresentationController 实现更复杂的控制。

通过这些功能,你可以更加灵活地实现和控制 SwiftUI 中的 sheet,满足不同的应用需求和用户体验。

在 SwiftUI 中,除了 PresentationDetent,还有一些其他的 presentation 修饰符可以用来定制 sheet 的外观和行为。这些修饰符主要用于 sheet,但有些也可以在其他类型的视图展示中使用。

让我们详细介绍一下这些 presentation 修饰符以及它们的用途。


其他 #

1. PresentationDetent #

PresentationDetent 控制 sheet 的高度,之前已经讲解过。常用的高度包括:

  • .medium:覆盖一半屏幕。
  • .large:覆盖整个屏幕。
  • .fraction(CGFloat):自定义高度,以屏幕高度的百分比表示。

2. presentationCornerRadius #

presentationCornerRadius 用于设置 sheet 的圆角半径。你可以通过此修饰符设置 sheet 顶部圆角的弧度,使它在视觉上符合应用的设计需求。

示例代码:设置圆角半径 #

import SwiftUI

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

    var body: some View {
        VStack {
            Button("Present Sheet") {
                isSheetPresented.toggle()
            }
        }
        .sheet(isPresented: $isSheetPresented) {
            SheetView()
                .presentationDetents([.medium, .large])
                .presentationCornerRadius(20) // 设置圆角半径
        }
    }
}

struct SheetView: View {
    var body: some View {
        VStack {
            Text("This sheet has rounded corners")
                .font(.title)
                .padding()
            Spacer()
        }
        .background(Color.white)
    }
}

解释: #

  • presentationCornerRadius(20):将 sheet 的顶部圆角半径设置为 20。

3. presentationDragIndicator #

presentationDragIndicator 用于设置 sheet 是否显示拖动指示器。拖动指示器是一个视觉提示,告诉用户可以对 sheet 进行拖动操作。

示例代码:显示拖动指示器 #

import SwiftUI

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

    var body: some View {
        VStack {
            Button("Present Sheet") {
                isSheetPresented.toggle()
            }
        }
        .sheet(isPresented: $isSheetPresented) {
            SheetView()
                .presentationDetents([.medium, .large])
                .presentationDragIndicator(.visible) // 显示拖动指示器
        }
    }
}

struct SheetView: View {
    var body: some View {
        VStack {
            Text("This sheet has a drag indicator")
                .font(.title)
                .padding()
            Spacer()
        }
        .background(Color.white)
    }
}

解释: #

  • presentationDragIndicator(.visible):显示 sheet 的拖动指示器。
  • presentationDragIndicator(.hidden):隐藏 sheet 的拖动指示器。

4. presentationBackgroundInteraction #

在 iOS 17 中,SwiftUI 引入了 presentationBackgroundInteraction 修饰符。该修饰符允许你控制 sheet 背后的交互行为。

示例代码:背景交互 #

import SwiftUI

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

    var body: some View {
        VStack {
            Button("Present Sheet") {
                isSheetPresented.toggle()
            }
        }
        .sheet(isPresented: $isSheetPresented) {
            SheetView()
                .presentationDetents([.medium, .large])
                .presentationBackgroundInteraction(.enabled) // 启用背景交互
        }
    }
}

struct SheetView: View {
    var body: some View {
        VStack {
            Text("This sheet allows background interaction")
                .font(.title)
                .padding()
            Spacer()
        }
        .background(Color.white)
    }
}

解释: #

  • presentationBackgroundInteraction(.enabled):允许用户与 sheet 背后的视图进行交互。
  • presentationBackgroundInteraction(.disabled):禁用与 sheet 背后的视图进行交互。

5. 结合使用多个修饰符 #

这些 presentation 修饰符通常可以组合使用,以实现更复杂和精细的控制。

示例代码:结合使用多个修饰符 #

import SwiftUI

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

    var body: some View {
        VStack {
            Button("Present Sheet") {
                isSheetPresented.toggle()
            }
        }
        .sheet(isPresented: $isSheetPresented) {
            SheetView()
                .presentationDetents([.medium, .large])
                .presentationCornerRadius(20)
                .presentationDragIndicator(.visible)
                .presentationBackgroundInteraction(.enabled) // 组合多个修饰符
        }
    }
}

struct SheetView: View {
    var body: some View {
        VStack {
            Text("This sheet is fully customized")
                .font(.title)
                .padding()
            Spacer()
        }
        .background(Color.white)
    }
}

在非 sheet 中使用 presentation 修饰符 #

虽然这些 presentation 修饰符主要是为 sheet 设计的,但某些修饰符可能也可以用在 popover 等其他弹出式视图中,不过详细的支持情况需要查看具体的 SwiftUI 更新文档。当前这些修饰符在 sheet 中最为常用,并且提供了丰富的自定义选项。


总结 #

SwiftUI 提供了一组丰富的 presentation 修饰符,用于定制 sheet 的外观和行为。通过这些修饰符,你可以创建更符合设计需求和用户期望的 sheet 体验:

  1. PresentationDetent:控制 sheet 的高度。
  2. presentationCornerRadius:设置 sheet 顶部的圆角半径。
  3. presentationDragIndicator:显示或隐藏 sheet 的拖动指示器。
  4. presentationBackgroundInteraction:控制 sheet 背后的交互行为(iOS 17)。

这些修饰符提供了高度灵活性和可定制性,允许你精细控制 sheet 的呈现方式,以提升应用的用户体验。不妨在你的项目中尝试使用这些修饰符,看看它们能为你的应用带来怎样的提升。

本文共 3966 字,上次修改于 Jan 20, 2025