SwiftUI中如何通过UIViewControllerRepresentable Coordinator使用Pulley代理
问题描述
在SwiftUI项目中使用基于UIKit的地图抽屉库Pulley,通过UIHostingController引入了SwiftUI的ListView,需要在抽屉未展开时禁用列表的滚动功能。明确要借助Pulley的drawerPositionDidChange代理方法实现,但不清楚如何在UIViewControllerRepresentable的Coordinator中使用该代理,或者是否应该通过状态变量来达成需求。
Pulley代理定义
@objc public protocol PulleyDelegate: AnyObject { /** This is called after size changes, so if you care about the bottomSafeArea property for custom UI layout, you can use this value. * NOTE: It's not called *during* the transition between sizes (such as in an animation coordinator), but rather after the resize is complete. */ @objc optional func drawerPositionDidChange(drawer: PulleyViewController, bottomSafeArea: CGFloat) }
尝试的UIViewControllerRepresentable代码
import SwiftUI struct DrawerPosition: UIViewControllerRepresentable { @Binding var bottomSafeArea: CGFloat? func makeCoordinator() -> Coordinator { Coordinator(self) } func makeUIViewController(context: Context) -> some UIViewController { let vc = PulleyViewController() vc.delegate = context.coordinator return vc } func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { // Updates the state of the specified view controller with new information from SwiftUI. } class Coordinator: NSObject, PulleyDrawerViewControllerDelegate { var parent: DrawerPosition init (_ parent: DrawerPosition) { self.parent = parent } func drawerPositionDidChange(drawer: PulleyViewController, bottomSafeArea: CGFloat){ self.parent.bottomSafeArea = bottomSafeArea } } }
ListView代码
import SwiftUI struct ListView: View { @State private var bottomSafeArea: CGFloat? var body: some View { ScrollViewReader { proxy in VStack { Button("Jump to #50") { proxy.scrollTo(50) } List(0..<100, id: \.self) { i in Text("Example") .id(i) }.scrollDisabled(bottomSafeArea == 0 ? true : false) } } } } class ListViewVHC: UIHostingController<ListView> { required init?(coder: NSCoder) { super.init (coder: coder, rootView: ListView()) } } struct ListView_Previews: PreviewProvider { static var previews: some View { ListView() } }
解决方案
1. 修复代理协议一致性问题
你的Coordinator类错误实现了PulleyDrawerViewControllerDelegate,但Pulley的代理实际是PulleyDelegate,这会导致代理方法无法被触发。修改Coordinator的协议实现:
class Coordinator: NSObject, PulleyDelegate { // 原有代码不变 }
2. 绑定状态并传递给ListView
当前ListView的bottomSafeArea是本地@State,无法接收Pulley的状态更新。需要改为@Binding,并在父视图中共享状态:
修改ListView:
struct ListView: View { @Binding var bottomSafeArea: CGFloat? // 改为@Binding var body: some View { ScrollViewReader { proxy in VStack { Button("Jump to #50") { proxy.scrollTo(50) } List(0..<100, id: \.self) { i in Text("Example") .id(i) } // 优化判断逻辑,避免nil导致的异常 .scrollDisabled(bottomSafeArea != nil ? bottomSafeArea! > 0 : false) } } } } // 更新UIHostingController,支持传递绑定 class ListViewVHC: UIHostingController<ListView> { init(rootView: ListView) { super.init(rootView: rootView) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } }
父视图整合Pulley与ListView:
在包含Pulley的父视图中创建共享状态,绑定到DrawerPosition和ListView:
struct MainView: View { @State private var drawerBottomSafeArea: CGFloat? var body: some View { ZStack { // 你的地图视图等底层内容 DrawerPosition(bottomSafeArea: $drawerBottomSafeArea) // 将ListView绑定到共享状态,根据实际布局调整嵌入方式 ListView(bottomSafeArea: $drawerBottomSafeArea) } } }
3. 优化滚动禁用逻辑
仅通过bottomSafeArea == 0判断抽屉状态可能不准确,建议结合Pulley的DrawerPosition枚举(如.open)更精准控制:
func drawerPositionDidChange(drawer: PulleyViewController, bottomSafeArea: CGFloat) { self.parent.bottomSafeArea = bottomSafeArea // 直接获取抽屉展开状态,可新增绑定变量传递给ListView let isDrawerOpen = drawer.drawerPosition == .open }
修改完成后,抽屉位置变化时代理方法会触发更新绑定状态,ListView会自动调整滚动禁用状态。
内容的提问来源于stack exchange,提问作者zer0day
相关产品推荐
相关产品推荐

