Search code examples
iosswiftuiscrollviewuiimageviewautolayout

'CALayer position contains NaN: [nan nan]' on UIScrollView


I'm developing a screen which has just one UIImageView inside UIScrollView. UIScrollView enables users to pin and zoom image. I got help from below post. It uses storyboard and AutoLayout. Basically, It has 8 constraints which are top, bottom,leading and trailing (scrollview to superview and imageView to scrollView). Example post sets image inside storyboard so that It doesn't needed to give any specific height or width.

https://www.raywenderlich.com/560-uiscrollview-tutorial-getting-started

My problem is that when I use the same code but do everything programatically, I can see the image (which is extra zoomed) and I can scroll with one finger but when I want to pinch it (zoom out, zoom in) I got

'CALayer position contains NaN: [nan nan]'

error. I think It is because scrollview doesn't set contentSize or contentOffset but whether I set that attributes,I got the same error.

 override func viewDidLoad() {

    self.scrollView.delegate = self
    self.scrollView.translatesAutoresizingMaskIntoConstraints = false
    self.imageView.translatesAutoresizingMaskIntoConstraints = false
    imageView.image = photo
    imageView.contentMode = .scaleAspectFit
    scrollView.addSubview(imageView)
    self.view.addSubview(scrollView)
    self.imageView.setContentHuggingPriority(UILayoutPriority.init(251), for: NSLayoutConstraint.Axis.vertical)
    self.imageView.setContentHuggingPriority(UILayoutPriority.init(251), for: NSLayoutConstraint.Axis.horizontal)

    scrollView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
    scrollView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
    scrollView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
    scrollView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true

    imageViewTopConstraint = imageView.topAnchor.constraint(equalTo: self.scrollView.topAnchor, constant: 0)
    imageViewTopConstraint.isActive = true

    imageViewBottomConstraint = imageView.bottomAnchor.constraint(equalTo: self.scrollView.bottomAnchor, constant: 0)
    imageViewBottomConstraint.isActive = true

    imageViewLeadingConstraint = imageView.leadingAnchor.constraint(equalTo: self.scrollView.leadingAnchor, constant: 0)
    imageViewLeadingConstraint.isActive = true

    imageViewTrailingConstraint = imageView.trailingAnchor.constraint(equalTo: self.scrollView.trailingAnchor, constant: 0)
    imageViewTrailingConstraint.isActive = true

    scrollView.contentSize = imageView.bounds.size

}

fileprivate func updateMinZoomScaleForSize(_ size: CGSize) {
    let widthScale = size.width / imageView.bounds.width
    let heightScale = size.height / imageView.bounds.height
    let minScale = min(widthScale, heightScale)

    scrollView.minimumZoomScale = minScale

    scrollView.zoomScale = minScale
}

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    updateMinZoomScaleForSize(view.bounds.size)
}

fileprivate func updateConstraintsForSize(_ size: CGSize) {

    let yOffset = max(0, (size.height - imageView.frame.height) / 2)
    imageViewTopConstraint.constant = yOffset
    imageViewBottomConstraint.constant = yOffset

    let xOffset = max(0, (size.width - imageView.frame.width) / 2)
    imageViewLeadingConstraint.constant = xOffset
    imageViewTrailingConstraint.constant = xOffset

    view.layoutIfNeeded()
}

}

extension ZoomedPhotoViewController: UIScrollViewDelegate {
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
    return imageView
}

func scrollViewDidZoom(_ scrollView: UIScrollView) {
    updateConstraintsForSize(view.bounds.size)
}

}

Solution

  • I make it work by moving updateMinZoomScaleForSize(view.bounds.size) function from ViewDidLayoutSubViews() to ViewDidLoad() and called self.view.layoutIfNeeded() before calling ZoomScale function. Therefore my final and working ViewDidLoad()

        override func viewDidLoad() {
        super.viewDidLoad()
        self.scrollView.delegate = self
        self.scrollView.translatesAutoresizingMaskIntoConstraints = false
        self.imageView.translatesAutoresizingMaskIntoConstraints = false
        imageView.image = photo
        imageView.contentMode = .scaleAspectFit
        imageView.clipsToBounds = true
        scrollView.addSubview(imageView)
        //scrollView.contentSize = CGSize(width: imageView.image!.size.width, height: imageView.image!.size.height)
        self.view.addSubview(scrollView)
        self.imageView.setContentHuggingPriority(UILayoutPriority.init(251), for: NSLayoutConstraint.Axis.vertical)
       self.imageView.setContentHuggingPriority(UILayoutPriority.init(251), for: NSLayoutConstraint.Axis.horizontal)
    
        scrollView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
        scrollView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
        scrollView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
        scrollView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
    
        imageViewTopConstraint = imageView.topAnchor.constraint(equalTo: self.scrollView.topAnchor, constant: 0)
        imageViewTopConstraint.isActive = true
    
        imageViewBottomConstraint = imageView.bottomAnchor.constraint(equalTo: self.scrollView.bottomAnchor, constant: 0)
        imageViewBottomConstraint.isActive = true
    
        imageViewLeadingConstraint = imageView.leadingAnchor.constraint(equalTo: self.scrollView.leadingAnchor, constant: 0)
        imageViewLeadingConstraint.isActive = true
    
        imageViewTrailingConstraint = imageView.trailingAnchor.constraint(equalTo: self.scrollView.trailingAnchor, constant: 0)
        imageViewTrailingConstraint.isActive = true
    
    
        self.view.layoutIfNeeded()
        updateMinZoomScaleForSize(view.bounds.size)
    
    }