1
votes

I have searched many articles about UIScrollView with an UIImageView in, but all without AutoLayout info. What I have done is: place a UIScrollView in UIViewController with constraints top 10, leading 10, bottom 50, trailing 10; place a UIImageView inside of the UIScrollView with constraints top 0, leading 0, bottom 0, trailing 0. What I want is simple: let my UIImageView fit my UIScrollView when app first loaded, and let my UIImageView centered horizontally and vertically. Now I can see the image and just fit the screen nicely but the image is just at the top of UIScrollView.(notes: in this circumstance, the width of my image after being zoomed to minimum zoomScale equals to the width of UIScrollView, the height of my image after being zoom to minimum zoomScale less than the width of UIScrollView. ) I really can't move my UIImageView to center of UIScrollView vertically and can not find where I made an error. Thanks a lot.

import UIKit
class ViewController: UIViewController, UIScrollViewDelegate {
@IBOutlet weak var scrollView: UIScrollView!
@IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view.
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}
func centerScrollViewContents() {
    let boundsSize = scrollView.bounds.size
    var contentsFrame = imageView.frame
    if contentsFrame.size.width < boundsSize.width {
        contentsFrame.origin.x = (boundsSize.width - contentsFrame.size.width) / 2.0
    } else {
        contentsFrame.origin.x = 0.0
    }

    if contentsFrame.size.height < boundsSize.height {
        contentsFrame.origin.y = (boundsSize.height - contentsFrame.size.height) / 2.0
    } else {
        contentsFrame.origin.y = 0.0
    }
}

override func viewDidLayoutSubviews() {
    let myImage = UIImage(named: "7.jpg")
    imageView.image = myImage        
    let weightScale = scrollView.bounds.size.width / myImage!.size.width
    let heightScale = scrollView.bounds.size.height / myImage!.size.height
    let minScale = min(weightScale, heightScale)
    let imagew = myImage!.size.width * minScale
    let imageH = myImage!.size.height * minScale
    let imageRect = CGRectMake(0, 0, imagew, imageH)
    imageView.frame = imageRect
    scrollView.contentSize = myImage!.size
    scrollView.maximumZoomScale = 1.0
    scrollView.minimumZoomScale = minScale
    scrollView.zoomScale = minScale
    imageView.frame = imageRect
    centerScrollViewContents()
}
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
    return imageView
}

}
3
I think it is because auto layout problem. I need to do what?NorthBig

3 Answers

7
votes

Piggy backing on pic-o-matic's answer. I had a case where I needed to center the image only if it was smaller than the scrollview frame. Using insets made for a straightforward solution.

if imageView.frame.height <= scrollView.frame.height {
        let shiftHeight = scrollView.frame.height/2.0 - scrollView.contentSize.height/2.0
        scrollView.contentInset.top = shiftHeight
    }
    if imageView.frame.width <= scrollView.frame.width {
        let shiftWidth = scrollView.frame.width/2.0 - scrollView.contentSize.width/2.0
        scrollView.contentInset.left = shiftWidth
    }
3
votes

I experienced the same problem. I found a perfect solution for me, check out this github project: https://github.com/evgenyneu/ios-imagescroll-swift

How it essentially works is as follows: it adjusts your constraints as to center the image if it is zoomed out far enough and to enable it to zoom and scroll after the width or height exceeds the respective width or height of your container.

Found it thanks to this thread: Zooming UIImageView inside UIScrollView with autolayout (obj-c and swift solution available)!

//  ViewController.swift
//  image-scroll-swift
//
//  Created by Evgenii Neumerzhitckii on 4/10/2014.
//  Copyright (c) 2014 Evgenii Neumerzhitckii. All rights reserved.
//

import UIKit

let imageScrollLargeImageName = "wallabi.jpg"


class ViewController: UIViewController, UIScrollViewDelegate {

  @IBOutlet weak var scrollView: UIScrollView!
  @IBOutlet weak var imageView: UIImageView!

  @IBOutlet weak var imageConstraintTop: NSLayoutConstraint!
  @IBOutlet weak var imageConstraintRight: NSLayoutConstraint!
  @IBOutlet weak var imageConstraintLeft: NSLayoutConstraint!
  @IBOutlet weak var imageConstraintBottom: NSLayoutConstraint!

  var lastZoomScale: CGFloat = -1

  override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)

    imageView.image = UIImage(named: imageScrollLargeImageName)
    scrollView.delegate = self
    updateZoom()
  }

  // Update zoom scale and constraints
  // It will also animate because willAnimateRotationToInterfaceOrientation
  // is called from within an animation block
  //
  // DEPRECATION NOTICE: This method is said to be deprecated in iOS 8.0. But it still works.
  override func willAnimateRotationToInterfaceOrientation(
    toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval) {

    super.willAnimateRotationToInterfaceOrientation(toInterfaceOrientation, duration: duration)
    updateZoom()
  }

  func updateConstraints() {
    if let image = imageView.image {
      let imageWidth = image.size.width
      let imageHeight = image.size.height

      let viewWidth = view.bounds.size.width
      let viewHeight = view.bounds.size.height

      // center image if it is smaller than screen
      var hPadding = (viewWidth - scrollView.zoomScale * imageWidth) / 2
      if hPadding < 0 { hPadding = 0 }

      var vPadding = (viewHeight - scrollView.zoomScale * imageHeight) / 2
      if vPadding < 0 { vPadding = 0 }

      imageConstraintLeft.constant = hPadding
      imageConstraintRight.constant = hPadding

      imageConstraintTop.constant = vPadding
      imageConstraintBottom.constant = vPadding

      // Makes zoom out animation smooth and starting from the right point not from (0, 0)
      view.layoutIfNeeded()
    }
  }

  // Zoom to show as much image as possible unless image is smaller than screen
  private func updateZoom() {
    if let image = imageView.image {
      var minZoom = min(view.bounds.size.width / image.size.width,
        view.bounds.size.height / image.size.height)

      if minZoom > 1 { minZoom = 1 }

      scrollView.minimumZoomScale = minZoom

      // Force scrollViewDidZoom fire if zoom did not change
      if minZoom == lastZoomScale { minZoom += 0.000001 }

      scrollView.zoomScale = minZoom
      lastZoomScale = minZoom
    }
  }


  // UIScrollViewDelegate
  // -----------------------

  func scrollViewDidZoom(scrollView: UIScrollView) {
    updateConstraints()
  }

  func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
    return imageView
  }

}

Make sure your storyboard contains a viewcontroller. In the 'View' place a 'Scroll View' and in that 'Scroll View' place an 'Image View'.

Establish top/bottom/leading/trailing constraints of 0 (or whatever value you prefer) between the 'Scroll View' and the 'View'. Then establish the same constraints between the image and the scroll view and connect them to the @IBOutlets.

This should work fine as the code changes the constraints as you zoom.

1
votes

I had the same "issue".. without auto layout though. I came up with this solution(using insets to center the image). The automaticallyAdjustsScrollViewInsets property must be set to false.

override func viewDidLoad() {
    super.viewDidLoad()
    //create scrollview
    self.automaticallyAdjustsScrollViewInsets = false
    myScrollview = MyScrollview(frame: self.view.frame)
    myScrollview.alwaysBounceHorizontal = true
    myScrollview.alwaysBounceVertical = true
    myScrollview.delegate = self
    //....
}
override func viewWillLayoutSubviews() {
    //setup frame portrait and landscape
    myScrollview.frame = self.view.frame
    myScrollview.zoomScale = self.view.frame.width / photo.size.width
    myScrollview.minimumZoomScale = self.view.bounds.width / photo.size.width

    // calculate inset to center vertically
    let shift = myScrollview.frame.height/2.0 - myScrollview.contentSize.height/2.0
    myScrollview.contentInset.top = shift
}

override func viewDidLayoutSubviews() {

}

func scrollViewDidZoom(scrollView: UIScrollView) {
    let shift = myScrollview.frame.height/2.0 - myScrollview.contentSize.height/2.0
    myScrollview.contentInset.top = shift
}

I used a subclass of UIScrollView, "MyScrollview" to "see" whats going on, i think this is useful to learn about scrollview, bounds frames and content size:

//
//  MyScrollview.swift
//  CampingDeHooiberg  
//
//  Created by Andre Lam on 09-03-15.
//  Copyright (c) 2015 Andre Lam. All rights reserved.
//

import UIKit

class MyScrollview: UIScrollView {

    override var bounds: CGRect { willSet { println("bounds set: \(newValue) contentSize:\(self.contentSize) frame:\(self.frame)")}}
    override var contentSize: CGSize { willSet { println("bounds:\(self.bounds) set contentsize:\(newValue)  frame: \(self.frame)")}}
    override var frame: CGRect { willSet { println("bounds: \(self.bounds) contentSize:\(self.contentSize) frame set:\(newValue)")}}

override init(frame: CGRect) {
    super.init(frame: frame)
}

required init(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

}