SwiftUI自定义ZoomableScrollView在iOS15.0.1下失效问题求助
问题根因
- iOS 15 对
UIScrollView缩放逻辑和UIHostingController的内存管理做了底层调整:UIScrollView资源回收阶段会对viewForZooming返回的视图对应的视图控制器尝试建立弱引用,你当前的实现中UIHostingController仅被 Coordinator 持有,没有加入到当前的视图控制器层级中。当 SwiftUIPager 滑动到新页面、旧页面销毁时,UIHostingController会先于 UIScrollView 进入释放流程,此时 UIScrollView 仍尝试访问就触发了报错中的弱引用创建失败崩溃。 - iOS 13、iOS 14 没有这套校验逻辑,所以原有代码可以正常运行。
修复方案
核心是将 UIHostingController 加入到当前视图控制器的层级中,让 UIKit 统一管理其生命周期,避免提前释放。修改后的完整代码如下:
import SwiftUI // 辅助扩展:获取UIView对应的当前父视图控制器 extension UIView { var parentViewController: UIViewController? { var nextResponder = next while let responder = nextResponder { if let vc = responder as? UIViewController { return vc } nextResponder = responder.next } return nil } } struct ZoomableScrollView<Content: View>: UIViewRepresentable { @Binding var didZoom: Bool private var content: Content init(didZoom: Binding<Bool>, @ViewBuilder content: () -> Content) { _didZoom = didZoom self.content = content() } func makeUIView(context: Context) -> UIScrollView { let scrollView = UIScrollView() scrollView.delegate = context.coordinator scrollView.maximumZoomScale = 20 scrollView.minimumZoomScale = 1 scrollView.bouncesZoom = true let hostedController = context.coordinator.hostingController let hostedView = hostedController.view! hostedView.translatesAutoresizingMaskIntoConstraints = true hostedView.autoresizingMask = [.flexibleWidth, .flexibleHeight] hostedView.frame = scrollView.bounds hostedView.backgroundColor = .black // 核心修复:将hostingController加入当前视图控制器层级 if let parentVC = scrollView.parentViewController { parentVC.addChild(hostedController) } scrollView.addSubview(hostedView) hostedController.didMove(toParent: scrollView.parentViewController) return scrollView } func makeCoordinator() -> Coordinator { return Coordinator(hostingController: UIHostingController(rootView: self.content), didZoom: $didZoom) } func updateUIView(_ uiView: UIScrollView, context: Context) { context.coordinator.hostingController.rootView = self.content assert(context.coordinator.hostingController.view.superview == uiView) } // 新增:页面销毁时清理hostingController关系,避免内存泄漏 static func dismantleUIView(_ uiView: UIScrollView, coordinator: Coordinator) { coordinator.hostingController.willMove(toParent: nil) coordinator.hostingController.view.removeFromSuperview() coordinator.hostingController.removeFromParent() } class Coordinator: NSObject, UIScrollViewDelegate { var hostingController: UIHostingController<Content> @Binding var didZoom: Bool init(hostingController: UIHostingController<Content>, didZoom: Binding<Bool>) { self.hostingController = hostingController _didZoom = didZoom } func viewForZooming(in scrollView: UIScrollView) -> UIView? { return hostingController.view } func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) { didZoom = !(scrollView.zoomScale == scrollView.minimumZoomScale) } } }
验证说明
修改后同时兼容iOS 13、iOS 14、iOS 15+系统,SwiftUIPager滑动切换页面时不会再触发释放阶段的弱引用访问崩溃,原有缩放功能完全不受影响。
内容的提问来源于stack exchange,提问作者iKK
相关产品推荐
相关产品推荐

