iOS 16.4中SwiftUI拖拽Sheet时能否实时获取其高度?
iOS 16.4中获取Sheet拖拽实时高度及实现动画的方法
在iOS 16.4中,官方并没有直接提供API来获取Sheet拖拽过程中的实时高度,但可以通过结合UIKit的底层交互能力,间接实现这一需求,并且能基于实时高度值完成相关动画效果。
获取实时拖拽高度
核心思路是借助UISheetPresentationController(SwiftUI Sheet的底层实现),通过监听其容器视图的状态变化来获取实时高度:
- 在Sheet的内容视图中,通过
UIViewControllerRepresentable桥接到UIKit,获取对应的UISheetPresentationController实例。 - 监听Sheet容器视图的frame变化,计算出当前Sheet的实时高度。
- 将计算结果传递回SwiftUI的状态变量,供视图逻辑使用。
示例代码如下:
import SwiftUI // 用于监听Sheet实时高度的UIKit桥接视图 struct SheetHeightMonitor: UIViewControllerRepresentable { @Binding var currentHeight: CGFloat func makeUIViewController(context: Context) -> UIViewController { let vc = UIViewController() vc.view.backgroundColor = .clear return vc } func updateUIViewController(_ uiViewController: UIViewController, context: Context) { guard let sheetController = uiViewController.parent?.sheetPresentationController else { return } // 监听容器视图frame变化 context.coordinator.observer = NSKeyValueObservation.observe( sheetController.containerView!, keyPath: \.frame, options: [.new] ) { [weak self] _, change in guard let newFrame = change.newValue else { return } // 计算实时高度:屏幕总高度减去容器视图的顶部偏移 self?.currentHeight = UIScreen.main.bounds.height - newFrame.minY } } func makeCoordinator() -> Coordinator { Coordinator() } class Coordinator { var observer: NSKeyValueObservation? } } // 使用示例 struct ContentView: View { @State var isSheetPresented = false @State var detent: PresentationDetent = .height(200) @State var sheetRealTimeHeight: CGFloat = 200 // 存储实时高度 var body: some View { Button("打开Sheet") { isSheetPresented = true } .sheet(isPresented: $isSheetPresented) { VStack { Text("当前Sheet高度:\(sheetRealTimeHeight, specifier: "%.1f")") Spacer() } .padding() .background(Color.white) // 添加高度监听视图 .background(SheetHeightMonitor(currentHeight: $sheetRealTimeHeight)) } .presentationDetents([.height(200), .height(500)], selection: $detent) } }
基于实时高度实现动画
拿到实时高度值后,可直接在SwiftUI视图中基于该值驱动动画效果,比如根据高度变化调整视图的缩放比例、透明度或位置:
// 在Sheet内容视图中添加动画效果 VStack { Text("拖拽我试试") .font(.title) // 高度从200到500时,缩放比例从0.8过渡到1.0 .scaleEffect(sheetRealTimeHeight / 500 * 0.2 + 0.8) .animation(.easeInOut(duration: 0.1), value: sheetRealTimeHeight) Circle() .fill(Color.blue) .frame(width: 80, height: 80) // 高度越高,透明度越高 .opacity(sheetRealTimeHeight / 500) .animation(.easeInOut(duration: 0.1), value: sheetRealTimeHeight) Spacer() }
需要注意的是,frame变化的回调会比较频繁,若有性能顾虑可添加节流处理,避免不必要的视图刷新。
内容的提问来源于stack exchange,提问作者Nicolas Gimelli
相关产品推荐
相关产品推荐

