Search code examples
uiscrollviewuikitsnapkit

UIScrollView aligns its content's origin to its bound's center on first render, why is that?


I'm trying to learn UIKit and toying around with a UIScrollView. My goal is to create a UIScrollView that contains an image that I can pan and zoom.

Upon first render, as title says, the image's origin matches the UIScrollView's (bounds) center. As soon as I double tap to reset zoom, the issue fixes itself.

Issue visualization in simulator

This is my attempt:

import UIKit
import SnapKit
import SMScrollView

open class PinchableImageVC: UIViewController, UIScrollViewDelegate {
    
    private let dataSource: String
    
    private let minimumZoomScale: CGFloat = 1.0
    private let maximumZoomScale: CGFloat = 20.0
        
    lazy internal var scrollView: SMScrollView = { [unowned self] in
        let scrollView = SMScrollView(frame: .zero)
        scrollView.minimumZoomScale = self.minimumZoomScale
        scrollView.maximumZoomScale = self.maximumZoomScale
        
        scrollView.zoomScale = 1.0
        scrollView.bouncesZoom = true
        scrollView.isPagingEnabled = false
        scrollView.delegate = self
        scrollView.showsHorizontalScrollIndicator = false
        scrollView.decelerationRate = UIScrollView.DecelerationRate.fast
        scrollView.backgroundColor = .systemMint
        
        scrollView.stickToBounds = true
        scrollView.fitOnSizeChange = false

        return scrollView
    }()
    
    lazy internal var imageView: UIImageView = {
        guard let image = UIImage(named: dataSource) else {
            fatalError("assets.xcassets doesn't include any image named \'\(dataSource)\'.")
        }
        
        let imageView = UIImageView(image: image)
        return imageView
    }()
    
    internal init(dataSource: String) {
        self.dataSource = dataSource
        super.init(nibName: nil, bundle: nil)
    }
    
     public required init(coder: NSCoder) {
        fatalError("Uh uh you're not expected to invoke \(#function)")
    }


    open override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .systemRed
        view.addSubview(self.scrollView)
        
        scrollView.addSubview(self.imageView)
        
        scrollView.snp.makeConstraints { make in
            make.left.top.right.bottom.equalTo(self.view)
        }
         
    }
    
    open override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        
        imageView.frame = scrollView.bounds
    }
    
    public func viewForZooming(in scrollView: UIScrollView) -> UIView? {
        return self.imageView
    }
}

Solution

  • Assuming this is the code you are using: SMScrollView ...

    After taking a quick look, adding this to your controller should center the image on launch:

    open override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        scrollView.zoomScale = 1.0
    }
    

    You will probably quickly find out that SMScrollView isn't handling scroll view size changes (such as on device rotation)... and, if you want to keep the image aspect ratio and set imageView.contentMode = .scaleAspectFit, you can pan the image completely out of view.