You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

SwiftUI实现不同自定义高度堆叠Sheet的问题:避免下层Sheet被上层拉伸

SwiftUI实现不同自定义高度堆叠Sheet的问题:避免下层Sheet被上层拉伸

我完全理解你的需求——想要堆叠两个不同高度的Sheet,下层展示功能预览(半高),上层作为付费墙(1/3高度),同时保留Sheet自带的滑动关闭特性,而不是用Overlay替代。你遇到的问题确实是SwiftUI默认Sheet交互的一个小坑:当你在子Sheet设置自定义Detent时,父Sheet会自动被拉伸到和子Sheet一样的高度,这显然破坏了你想要的分层展示效果。

下面给你两种可行的解决方案,分别对应SwiftUI原生实现和兼容旧版本的UIKit桥接方案:


方案一:SwiftUI原生实现(iOS 16+)

从iOS 16开始,SwiftUI提供了presentationBackgroundInteraction修饰符,我们可以用它来禁用上层Sheet对下层Sheet的尺寸影响,让下层Sheet保持自己的自定义高度。

首先先定义我们需要的自定义Detent:

extension PresentationDetent {
    // 屏幕半高的自定义Detent
    static let half = Self.height(UIScreen.main.bounds.height / 2)
    // 屏幕1/3高度的自定义Detent
    static let oneThird = Self.height(UIScreen.main.bounds.height / 3)
}

然后修改你的ContentView代码,关键是给上层Sheet添加.presentationBackgroundInteraction(.disabled)

struct ContentView: View {
    @State private var showingFirst = false
    @State private var showingSecond = false

    var body: some View {
        VStack {
            Button("Show First Sheet") {
                showingFirst = true
            }
        }
        .sheet(isPresented: $showingFirst) {
            VStack {
                Button("Show Second Sheet") {
                    showingSecond = true
                }
                .sheet(isPresented: $showingSecond) {
                    Text("Second Sheet (One-Third Height)")
                        .presentationDetents([.oneThird])
                        // 核心:禁用背景交互,阻止上层Sheet影响下层Sheet的尺寸
                        .presentationBackgroundInteraction(.disabled)
                        // 显示滑动指示器,保留Sheet的原生交互提示
                        .presentationDragIndicator(.visible)
                }
            }
            .presentationDetents([.half])
            .presentationDragIndicator(.visible)
        }
    }
}

这个方法的原理是:.presentationBackgroundInteraction(.disabled)会告诉系统,上层Sheet的背景区域(也就是下层Sheet的部分)不响应交互,这样系统就不会自动调整下层Sheet的高度来适配上层,完美保留了两个Sheet的独立自定义高度,同时滑动关闭的特性也完全保留。


方案二:UIKit桥接实现(兼容iOS 15及以下)

如果需要支持iOS 15或更早版本,我们可以通过包装UISheetPresentationController来实现自定义Sheet行为,绕过SwiftUI的默认限制:

import SwiftUI
import UIKit

// 自定义Sheet的UIKit包装器
struct CustomSheet<Content: View>: UIViewControllerRepresentable {
    let content: Content
    let detents: [UISheetPresentationController.Detent]
    let keepsParentSheetSize: Bool

    init(detents: [UISheetPresentationController.Detent], keepsParentSheetSize: Bool, @ViewBuilder content: () -> Content) {
        self.detents = detents
        self.keepsParentSheetSize = keepsParentSheetSize
        self.content = content()
    }

    func makeUIViewController(context: Context) -> UIHostingController<Content> {
        UIHostingController(rootView: content)
    }

    func updateUIViewController(_ uiViewController: UIHostingController<Content>, context: Context) {
        guard let sheet = uiViewController.sheetPresentationController else { return }
        sheet.detents = detents
        sheet.prefersGrabberVisible = true // 显示滑动指示器
        
        if keepsParentSheetSize {
            // 禁用滚动扩展,避免父Sheet被拉伸
            sheet.prefersScrollingExpandsWhenScrolledToEdge = false
            // 不设置最大未遮罩Detent,让父Sheet保持原高度
            sheet.largestUndimmedDetentIdentifier = nil
            // 确保父Sheet区域仍然可交互(可选,根据需求调整)
            sheet.presentationController?.containerView?.superview?.isUserInteractionEnabled = true
        }
    }
}

// 使用示例
struct ContentView: View {
    @State private var showingFirst = false
    @State private var showingSecond = false

    var body: some View {
        VStack {
            Button("Show First Sheet") {
                showingFirst = true
            }
        }
        .sheet(isPresented: $showingFirst) {
            VStack {
                Button("Show Second Sheet") {
                    showingSecond = true
                }
                .sheet(isPresented: $showingSecond) {
                    CustomSheet(
                        detents: [.height(UIScreen.main.bounds.height/3)], 
                        keepsParentSheetSize: true
                    ) {
                        Text("Second Sheet (One-Third Height)")
                    }
                }
            }
            .presentationDetents([.height(UIScreen.main.bounds.height/2)])
            .presentationDragIndicator(.visible)
        }
    }
}

这个方案通过直接操作UIKit的UISheetPresentationController,手动配置Sheet的行为,确保上层Sheet弹出时不会影响下层Sheet的尺寸,同时也保留了滑动关闭的原生特性。


备注:内容来源于stack exchange,提问作者Jim Margolis

火山引擎 最新活动