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()
}
}