ZoomableScrollView no longer works in SwiftUI
Asked Answered
P

1

0

Using Swift 5.5, Xcode 13.0, iOS 15.0.1,

Since Apple does not a good job on ScrollViews for SwiftUI (yet?), I had to implement my own zoomable ScrollView. See code below.

I had it successfully running for iOS13 and iOS14 - but now updating to iOS 15.0.1 I had to realise that it no longer works !

The error message is as follows:

objc[10882]: Cannot form weak reference to instance (0x1078d5540) of class _TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_5ImageVS_18_AspectRatioLayout__. It is possible that this object was over-released, or is in the process of deallocation.
dyld4 config: DYLD_LIBRARY_PATH=/usr/lib/system/introspection DYLD_INSERT_LIBRARIES=/Developer/usr/lib/libBacktraceRecording.dylib:/Developer/usr/lib/libMainThreadChecker.dylib:/Developer/Library/PrivateFrameworks/DTDDISupport.framework/libViewDebuggerSupport.dylib
(lldb) 

I have everything inside a SwiftUIPager, and the problem occurs on the very last page when swiping. However, I don't think it is the Pagers fault since I can replace the ZoomableScrolView by a normal ImageView and everything works. Therefore I think Apple messed something up in their UIViewRepresentable. But maybe you can tell me more ??

Here is the entire code of my ZoomableScrolView:

import SwiftUI

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 {
        
        // set up the UIScrollView
        let scrollView = UIScrollView()
        scrollView.delegate = context.coordinator  // for viewForZooming(in:)
        scrollView.maximumZoomScale = 20
        scrollView.minimumZoomScale = 1
        scrollView.bouncesZoom = true
        
        // create a UIHostingController to hold our SwiftUI content
        let hostedView = context.coordinator.hostingController.view!
        hostedView.translatesAutoresizingMaskIntoConstraints = true
        hostedView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        hostedView.frame = scrollView.bounds
        hostedView.backgroundColor = .black
        scrollView.addSubview(hostedView)
        
        return scrollView
    }
    
    func makeCoordinator() -> Coordinator {
        return Coordinator(hostingController: UIHostingController(rootView: self.content), didZoom: $didZoom)
    }
    
    func updateUIView(_ uiView: UIScrollView, context: Context) {
        // update the hosting controller's SwiftUI content
        context.coordinator.hostingController.rootView = self.content
        assert(context.coordinator.hostingController.view.superview == uiView)
    }
    
    // MARK: - Coordinator
    
    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)
        }
    }
}
Prawn answered 10/10, 2021 at 16:46 Comment(0)
P
6

I finally found a workaround.

Add the following code inside the ZoomableScrollView: UIViewRepresentable struct:

    static func dismantleUIView(_ uiView: UIScrollView, coordinator: Coordinator) {
        uiView.delegate = nil
        coordinator.hostingController.view = nil
    }
Prawn answered 1/12, 2021 at 13:47 Comment(3)
Fixed my problem, but that static func must be inside UIViewRepresentable struct, not in Coordinator class. I think that is a misprint, but thank you.Harhay
Yes, you are completely right - the dismantleUIView func must be inside struct ZoomableScrollView<Content: View>: UIViewRepresentable { ... }. Thank you. I corrected the text above.Prawn
@Prawn still text needs to be corrected from "class:" to "struct:".Youngman

© 2022 - 2025 — McMap. All rights reserved.