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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 18:18:01