How to track the scaling of a UIScrollView?

I have a scaling UIScrollView and a non-scalable overlay view on which I animate the markers. These markers should track the location of some UIScrollView content (much like a dropped pin should track a spot on a map when panning and zooming).

I do this by calling the update of the overlay view in response to the UIScrollView layoutSubviews

. This works and the overlays are great when zooming and panning.

But when the pinch gesture ends, the UIScrollView automatically performs the final animation, and the overlay view is out of sync throughout this animation.

I made a simplified project to isolate this issue. The UIScrollView contains an orange square, and the overlay shows a 2-pixel red outline around the border of that orange square. As you can see below, the red outline always moves to where it should be, except for a short period of time after being touched, when it clearly jumps to the final position of the orange square.

red line around a zooming rectangle

The complete Xcode project for this test is available here: https://github.com/Clafou/ScrollviewZoomTrackTest , but all the code is in the two files shown below:

TrackedScrollView.swift

class TrackedScrollView: UIScrollView {

    @IBOutlet var overlaysView: UIView?

    let square: UIView

    required init(coder aDecoder: NSCoder) {
        square = UIView(frame: CGRect(x: 50, y: 300, width: 300, height: 300))
        square.backgroundColor = UIColor.orangeColor()

        super.init(coder: aDecoder)

        self.addSubview(square)
        self.maximumZoomScale = 1
        self.minimumZoomScale = 0.5
        self.contentSize = CGSize(width: 500, height: 900)
        self.delegate = self
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        overlaysView?.setNeedsLayout()
    }
}

extension TrackedScrollView: UIScrollViewDelegate {
    func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
        return square
    }
}

      

OverlaysView.swift

class OverlaysView: UIView {

    @IBOutlet var trackedScrollView: TrackedScrollView?

    let outline: CALayer

    required init(coder aDecoder: NSCoder) {
        outline = CALayer()
        outline.borderColor = UIColor.redColor().CGColor
        outline.borderWidth = 2
        super.init(coder: aDecoder)
        self.layer.addSublayer(outline)
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        if let trackedScrollView = self.trackedScrollView {
            CATransaction.begin()
            CATransaction.setDisableActions(true)
            let frame = trackedScrollView.convertRect(trackedScrollView.square.frame, toView: self)
            outline.frame = CGRectIntegral(CGRectInset(frame, -3, -3))
            CATransaction.commit()
        }
    }
}

      

Among the things I tried was using CADisplayLink

and presentationLayer

, and it allowed me to animate the overlay, but the coordinates I got from presentationLayer

were slightly behind the actual UIScrollView, so it still doesn't look straight. I think the correct approach would be to tie my overlay update to the system generated UIScrollView animation, but I haven't had time to hack that yet.

How can I update this code to always track the scaling of the UIScrollView content?

+3


source to share


1 answer


UIScrollView

sends scrollViewDidZoom:

to its delegate in its animation block if it "bounces" to the minimum or maximum magnification when the pinch ends. Update the frames of your overlays to scrollViewDidZoom:

if zoomBouncing

true. If you are using automatic call layout layoutIfNeeded

.

scrollViewDidZoom:

is called only once during a zoom animation, but tweaking your frames or calling layoutIfNeeded

will ensure that those changes animate along with the zoom bounce, thus keeping them perfect.

Demo:



demo of correct animation

Fixed example project: https://github.com/mayoff/ScrollviewZoomTrackTest

+4


source







All Articles