Joseph
Joseph

Reputation: 9361

UIImageView in UIScrollView does not zoom correctly

I want to allow the user to side pan an image. The image should be scaled to the height of the device and the user is supposed to only be able to scroll left and right. The users is not supposed to be able to zoom. I have a UIViewController, to which I add a custom subclass ImageScrollView. This is supposed to display an image in full height, but instead the image is basically displayed un-zoomed. Even though the zoomScale gets calculated correctly, but does not have an effect.

What am I doing wrong?

Thanks

override func viewDidLoad() {
        super.viewDidLoad()

        let i = UIImage(named: "test.jpg")

        let iSV = ImageScrollView(image: i)
        self.view.addSubview(iSV)
        iSV.fillSuperview()
}

class ImageScrollView: UIScrollView {

    let image: UIImage
    let imageView = UIImageView()

    init(image img: UIImage) {    
        image = img
        imageView.image = image

        super.init(frame: .zero)

        self.addSubview(imageView)
        imageView.fillSuperview()

        self.contentSize = image.size

        self.showsHorizontalScrollIndicator = false
        self.showsVerticalScrollIndicator = false
    }

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

    override func layoutSubviews() {
        super.layoutSubviews()
        self.zoomScale = getZoomScale()    
    }

    func getZoomScale() -> CGFloat{
        let boundSize = self.frame.size
        let yScale = boundSize.height / image.size.height
        return yScale   
    }    
}

And just for the case it has to do with auto-layout I included the fillSuperview extension.

extension UIView {    
    public func fillSuperview() {
        translatesAutoresizingMaskIntoConstraints = false
        if let superview = superview {
            topAnchor.constraint(equalTo: superview.topAnchor, constant: 0).isActive = true
            leftAnchor.constraint(equalTo: superview.leftAnchor, constant: 0).isActive = true
            bottomAnchor.constraint(equalTo: superview.bottomAnchor, constant: 0).isActive = true
            rightAnchor.constraint(equalTo: superview.rightAnchor, constant: 0).isActive = true
        }
    }
}

Upvotes: 0

Views: 743

Answers (2)

vacawama
vacawama

Reputation: 154711

Since you don't want the image to zoom, I recommend you don't even bother with the zoom controls. A UIImageView knows how to scale its content.

I recommend you do it like this:

  1. Add a constraint that sets the imageView height equal to the scrollView height. This will prevent vertical scrolling.
  2. Add a constraint that sets the imageView width equal to the imageView height with multiplier image.size.width / image.size.height.
  3. Set imageView content mode to .scaleToFill.

To allow you to change the image, keep an aspectRatio property that retains the aspect ratio constraint set in step 2. Set aspectRatio.isActive = false, and then create and activate a new constraint for the new image.

Also, if you might ever have images that aren't wide enough to fill the scrollView horizontally when scaled to fit vertically, consider these changes:

  1. Replace the constraint that sets the imageView width with one that sets the width equal to the imageView height with multiplier max(image.size.width / image.size.height, scrollView.bounds.width / scrollView.bounds.height).
  2. Set imageView content mode to .scaleAspectFit.

Then, when you have a narrow image, the imageView will fill the scrollView, but the .scaleAspectFit will show the entire image centered in the scrollView. This will still work correctly for wide images because the multiplier will match the image aspect ratio and .scaleAspectFit will fill the entire imageView.

Upvotes: 1

Lal Krishna
Lal Krishna

Reputation: 16190

You forgot to implement scrollview delegate. And set min & max zoom level for scrollview.

var iSV: ImageScrollView?

let i = UIImage(named: "noWiFi")!
iSV = ImageScrollView(image: i)
if let iSV = iSV {
    self.view.addSubview(iSV)
    iSV.fillSuperview()
    iSV.delegate = self
}

override func viewDidLayoutSubviews() {
        if let iSV = iSV {
            let scale = iSV.getZoomScale()
            iSV.minimumZoomScale = scale
            iSV.maximumZoomScale = scale
        }
}

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

Note: I just did rough. It may not fulfil your complete requirement

Upvotes: 1

Related Questions