UIScrollView Layout issue

I run into a layout problem where I cannot center an image inside ScrollView which is also inside Navigation Controller. The problem is surely the fact that there is a navigation bar because using this view without NavigationContoller works fine and the image is centered but I don’t know how to account for the space that navigation bar takes up.

Here is the code:

import UIKit

class PhotoViewController: UIViewController {
    var photoName: String
    
    private lazy var photoView  = {
        let image = UIImageView()
        image.translatesAutoresizingMaskIntoConstraints = false
        image.contentMode = .scaleAspectFit
        image.clipsToBounds = true
        
        return image
    }()
    
    var photoViewBottomConstraint: NSLayoutConstraint?
    var photoViewLeadingConstraint: NSLayoutConstraint?
    var photoViewTopConstraint: NSLayoutConstraint?
    var photoViewTrailingConstraint: NSLayoutConstraint?
    
    private lazy var scrollView  = {
        let sv = UIScrollView()
        sv.translatesAutoresizingMaskIntoConstraints = false
        
        return sv
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
    }
    
    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
        updateMinZoomScaleForSize(view.bounds.size)
    }
    
    func updateMinZoomScaleForSize(_ size: CGSize) {
        let widthScale = size.width / photoView.bounds.width
        let heightScale = size.height / photoView.bounds.height
        let minScale = min(widthScale, heightScale)
        
        scrollView.minimumZoomScale = minScale
        scrollView.zoomScale = minScale
    }
    
    func setupUI() {        
        photoView.image = UIImage(named: photoName)
        scrollView.delegate = self
        view.addSubview(scrollView)
        scrollView.addSubview(photoView)
        setupConstraints()
    }
    
    func setupConstraints() {
        NSLayoutConstraint.activate([
            scrollView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
            scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            scrollView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
            scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
        ])
        
        photoViewLeadingConstraint = NSLayoutConstraint(
            item: photoView,
            attribute: .leading,
            relatedBy: .equal,
            toItem: scrollView,
            attribute: .leading,
            multiplier: 1,
            constant: 0
        )
        
        photoViewTopConstraint = NSLayoutConstraint(
            item: photoView,
            attribute: .top,
            relatedBy: .equal,
            toItem: scrollView,
            attribute: .top,
            multiplier: 1,
            constant: 0
        )
        
        photoViewTrailingConstraint = NSLayoutConstraint(
            item: photoView,
            attribute: .trailing,
            relatedBy: .equal,
            toItem: scrollView,
            attribute: .trailing,
            multiplier: 1,
            constant: 0
        )
        
        photoViewBottomConstraint = NSLayoutConstraint(
            item: photoView,
            attribute: .bottom,
            relatedBy: .equal,
            toItem: scrollView,
            attribute: .bottom,
            multiplier: 1,
            constant: 0
        )
        
        photoViewLeadingConstraint?.isActive = true
        photoViewTopConstraint?.isActive = true
        photoViewTrailingConstraint?.isActive = true
        photoViewBottomConstraint?.isActive = true
    }
    
    init(photoName: String) {
        self.photoName = photoName
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

extension PhotoViewController: UIScrollViewDelegate {
    func viewForZooming(in scrollView: UIScrollView) -> UIView? {
        photoView
    }
    
    func scrollViewDidZoom(_ scrollView: UIScrollView) {
        updateConstraintsForSize(view.bounds.size)
    }
    
    func updateConstraintsForSize(_ size: CGSize) {
        let yOffset = max(0, (size.height - photoView.frame.height) / 2)
        photoViewTopConstraint?.constant = yOffset
        photoViewBottomConstraint?.constant = yOffset
        
        let xOffset = max(0, (size.width - photoView.frame.width) / 2)
        photoViewLeadingConstraint?.constant = xOffset
        photoViewTrailingConstraint?.constant = xOffset
        
        view.layoutIfNeeded()
    }
}

Hello ArsD,

Thank you for providing sample code. For completeness, can you please place it into a test project that also has the navigation controller which contains the PhotoViewController and the sample image you are using? If so, please share a link to it. That'll help us better understand what's going on. If you're not familiar with preparing a test project, take a look at Creating a test project.

Thanks for your question,

Richard Yeh  Developer Technical Support

UIScrollView Layout issue
 
 
Q