TIL by Ed

Writing up things I’ve learnt.

Zooming With UIScrollView

I had the need to debug some code recently that delt with zooming on a UIScrollView. Never implemented something like this before, but turns out the basics to get started are very simple.

Some basic setup

Lets setup a simple view called ZoomView with a scroll view with a single subview that we will style as a red rectangle.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class ZoomView: UIView {

    let scrollView = UIScrollView()
    let rectangle = UIView()

    override func awakeFromNib() {
        super.awakeFromNib()

        backgroundColor = UIColor(white: 0.99, alpha: 1.0)

        rectangle.backgroundColor = UIColor.redColor()

        scrollView.setTranslatesAutoresizingMaskIntoConstraints(false)
        rectangle.setTranslatesAutoresizingMaskIntoConstraints(false)

        addSubview(scrollView)
        scrollView.addSubview(rectangle)

        let views = ["scrollView": scrollView, "rectangle": rectangle]
        var constraints = [NSLayoutConstraint]()
        constraints += NSLayoutConstraint.constraintsWithVisualFormat("V:|[scrollView]|", options: NSLayoutFormatOptions(0), metrics: nil, views: views) as! [NSLayoutConstraint]
        constraints += NSLayoutConstraint.constraintsWithVisualFormat("H:|[scrollView]|", options: NSLayoutFormatOptions(0), metrics: nil, views: views) as! [NSLayoutConstraint]
        constraints += NSLayoutConstraint.constraintsWithVisualFormat("V:|[rectangle]|", options: NSLayoutFormatOptions(0), metrics: nil, views: views) as! [NSLayoutConstraint]
        constraints += NSLayoutConstraint.constraintsWithVisualFormat("H:|[rectangle]|", options: NSLayoutFormatOptions(0), metrics: nil, views: views) as! [NSLayoutConstraint]
        constraints.append(NSLayoutConstraint(item: rectangle, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 0, constant: 40))
        constraints.append(NSLayoutConstraint(item: rectangle, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 0, constant: 60))
        NSLayoutConstraint.activateConstraints(constraints)
    }
}

I’ve sized the rectangle view as (60,40) so it will look something like this.

Making the red rectangle zoomable

To do this, we let the scroll view know which view is zoomable. We do this through implementing viewForZoomingInScrollView(_:) in the UIScrollViewDelegate protocol.

1
2
3
4
5
// MARK: UIScrollViewDelegate

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

Doing this alone won’t make the view zoomable. We also have to tell the scroll view how much zooming can be done. This is done via a couple properties on UIScrollView

  • minimumZoomScale: the minimum scale factor that can be applied to the zommable view
  • maximumZoomScale: the maximum scale factor that can be applied to the zommable view
  • zoomScale: the current scale factor applied to the zommable view

The zoom behaviour is achieved by the scroll view by applying a transform to the zoomable view, where the magnitude of the transform is zoomScale.

Let set these properties as

1
2
3
scrollView.minimumZoomScale = 1.0
scrollView.maximumZoomScale = 10.0
scrollView.zoomScale = scrollView.minimumZoomScale

This says that currently the scroll view is not zoomed as the zoomScale is set as the minimumZoomScale. And that we can zoom this view by 10x as the maximumZoomScale is 10 times that of the minimumZoomScale.

All up

So you should end up with something like this for ZoomView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class ZoomView: UIView, UIScrollViewDelegate {

    let scrollView = UIScrollView()
    let rectangle = UIView()

    override func awakeFromNib() {
        super.awakeFromNib()

        backgroundColor = UIColor(white: 0.99, alpha: 1.0)

        scrollView.minimumZoomScale = 1.0
        scrollView.maximumZoomScale = 10.0
        scrollView.zoomScale = scrollView.minimumZoomScale
        scrollView.delegate = self

        rectangle.backgroundColor = UIColor.redColor()

        scrollView.setTranslatesAutoresizingMaskIntoConstraints(false)
        rectangle.setTranslatesAutoresizingMaskIntoConstraints(false)

        addSubview(scrollView)
        scrollView.addSubview(rectangle)

        let views = ["scrollView": scrollView, "rectangle": rectangle]
        var constraints = [NSLayoutConstraint]()
        constraints += NSLayoutConstraint.constraintsWithVisualFormat("V:|[scrollView]|", options: NSLayoutFormatOptions(0), metrics: nil, views: views) as! [NSLayoutConstraint]
        constraints += NSLayoutConstraint.constraintsWithVisualFormat("H:|[scrollView]|", options: NSLayoutFormatOptions(0), metrics: nil, views: views) as! [NSLayoutConstraint]
        constraints += NSLayoutConstraint.constraintsWithVisualFormat("V:|[rectangle]|", options: NSLayoutFormatOptions(0), metrics: nil, views: views) as! [NSLayoutConstraint]
        constraints += NSLayoutConstraint.constraintsWithVisualFormat("H:|[rectangle]|", options: NSLayoutFormatOptions(0), metrics: nil, views: views) as! [NSLayoutConstraint]
        constraints.append(NSLayoutConstraint(item: rectangle, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 0, constant: 40))
        constraints.append(NSLayoutConstraint(item: rectangle, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 0, constant: 60))
        NSLayoutConstraint.activateConstraints(constraints)
    }

    // MARK: UIScrollViewDelegate

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

When we run this we can see we can scale the red rectangle by zooming anywhere in the scroll view. We also notice a couple more things

  • the red rectangle does not zoom beyond a certain size, this is because we set the maximumZoomScale. Since the maximumZoomScale is 10, the size of the red rectangle is (600, 400) (i.e. which is 10x (60, 40)) when we have achieved maximum zoom.
  • the red rectangle cannot be zoomed out smaller than the starting size, this is because we set the zommScale to match the minimumZoomScale

Another interesting thing to note is as the scroll view scales the zoomable view, it also scales the contentSize of the scroll view by the same factor. We can see this below courtesy of Reveal

That’s the basics. Enjoy and play around.

Comments