SwiftUI - 可变高度的Sheet

3,420 阅读2分钟

公众号:RobotPBQ

在苹果升级到iOS16后,给sheet带来了非常好用的一个功能 presentationDetents高度可以自由控制,在iOS16之前高度是固定的,但是在iOS16以后你可以用sheet创造很多app中流行的效果。

presentationDetents 介绍

在 SwiftUI 中,sheet 修饰符的 presentationDetents 参数用于控制 sheet 被拖拽时的停靠位置。

presentationDetents 可以设置以下值:

  • .fraction(0.0) - 禁止停靠,可全范围拖拽
  • .fraction(0.5) - 中间停靠点
  • .fraction(1.0) - 最大停靠点
  • .medium - 预设中等停靠点
  • .large - 预设大停靠点

例子:

.sheet(isPresented: $showSheet) {
   // sheet内容 
}
.presentationDetents([.fraction(0.5)])

这将允许sheet被拖拽,并在屏幕垂直中心点停靠。

通过配置presentationDetents,可以控制sheet的拖拽和停靠交互,实现更丰富的用户体验。一般结合.interactive()使用。

他有两种初始化方法

方法一
public func presentationDetents(_ detents: Set<PresentationDetent>) -> some View

方法二
public func presentationDetents(_ detents: Set<PresentationDetent>, selection: Binding<PresentationDetent>) -> some View

第一个参数是一个Set集合, 内部类型是PresentationDetent. 内部提供了四种简便的方法来让你方便的控制高度

public struct PresentationDetent : Hashable {
    public static let medium: PresentationDetent // 高度大约是屏幕的一半
    public static let large: PresentationDetent // 和Sheet的高度相同
    public static func fraction(_ fraction: CGFloat) -> PresentationDetent // 屏幕的小数点比例,例如:0.5,就是屏幕的一半高度
    public static func height(_ height: CGFloat) -> PresentationDetent // 单位是PX,设置200,那么高度就是200

有了以上基本认识,我们来具体看看示例

先给出一个原来使用 sheet 的方法

struct ResizableSheetSample: View {
    @State var showSheet: Bool = false
    var body: some View {
        Button {
            showSheet.toggle()
        } label: {
            Text("Click me")
        }
        .sheet(isPresented: $showSheet) {
            MyCustomeView()
        }
    }
}

struct MyCustomeView: View {
    var body: some View {
        ZStack {
            Color.mint.ignoresSafeArea()
        }
    }
}

效果就是点击按钮,会弹出一个青色背景色的页面

ezgif.com-video-to-gif.gif

presentationDetents 使用

大视图

这个效果和之前默认的效果是一样的

MyCustomeView()
.presentationDetents([.large])
ezgif.com-video-to-gif (1).gif

中等试图

弹出的页面只有页面的一半高度

MyCustomeView()
.presentationDetents([.medium])
ezgif.com-video-to-gif (2).gif

指定高度

弹出页面的高度为指定高度,单位为:PX

MyCustomeView()
.presentationDetents([.height(100)])
ezgif.com-video-to-gif (3).gif

指定比例

弹出页面占整个页面的比例,范围为:0 ~ 1

MyCustomeView()
.presentationDetents([.fraction(0.6)])
ezgif.com-video-to-gif (4).gif

是否隐藏指示器

可以隐藏或显示指示器,就是图片中绿色背景顶部的小黑杠

.presentationDragIndicator(.visible)
Screenshot 2023-08-03 at 20.56.39.png

interactiveDismissDisabled 参数使用

使用此参数,弹出的sheet页面将无法收回。

.interactiveDismissDisabled()
ezgif.com-video-to-gif (5).gif

实现不同位置的悬停

presentationDetents 可以设置多个值,实现不同位置的悬停效果

.presentationDetents([.fraction(0.3), .large, .medium, .height(130)])
ezgif.com-video-to-gif (6).gif

在子页面改变页面的高度

要实现在子页面来改变页面的高度,我们就需要使用另一个方法来实现,它多了一个参数

public func presentationDetents(_ detents: Set<PresentationDetent>, selection: Binding<PresentationDetent>) -> some View
如何使用

我们需要在父视图传递一个参数到子视图

// 父视图
MyCustomeView(detentState: $detentState)
    .presentationDetents(
    [.fraction(0.3), .large, .medium, .height(600)],
    selection: $detentState
    )
    
// 子视图
struct MyCustomeView: View {
    @Binding var detentState: PresentationDetent
    var body: some View {
        ZStack {
            Color.mint.ignoresSafeArea()
            VStack(spacing: 30) {
                Button("large") {
                    detentState = .large
                }

                Button("medium") {
                    detentState = .medium
                }

                Button("height") {
                    detentState = .height(600)
                }

                Button("fraction") {
                    detentState = .fraction(0.3)
                }
            }
            .foregroundColor(.black)
        }
    }
}

我们在子视图中去改变这个值,就可以达到改变页面高度的效果

ezgif.com-video-to-gif (7).gif

大家有什么看法呢?欢迎留言讨论。