You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 03:10:17