Search code examples
iosswiftuiviewuiscrollviewrendering

Snapshot scaled UIView


Snapshotting a UIView using UIGraphicsGetImageFromCurrentImageContext() and a variety of other approaches including the render(in:) and drawHierarchy(in: afterScreenUpdates:) functions works great when you need to render a regular ol' view.

I have been unable to get any of this working properly when trying to draw a scaled UIView hierarchy. The view in question is a UIScrollView's content & zoom view.

The view and its superviews refuse to do any transforms just before the snapshot that would fix the scaling problem. The result is that the snapshot either looks like:

  • A) an unfortunately cropped view, with its origin in the upper left (as expected on iOS), but then only expanding to the un-transformed "bounds" of the view. If the view happens to have no zoom or scale transform this is okay, but that rarely if ever happens.
  • B) an oversized view with big black borders that match the transformed "frame" of the view. I'm assuming this happens due to some weirdness with snapshotting a scaled view's frame.

TL;DR: Can't snapshot / render a UIView after it has been scaled and zoomed by a UIScrollView, despite attempts to transform or redraw before snapshotting.

Edit: see below for an example of this frustrating behavior.

  1. The result of snapshotting the view (actual result) UIView Snapshot Result
  2. The expected result (unable to reproduce using CoreGraphics) Expected result

Solution

  • To draw the complete content view of a scroll view you could use something like:

    @IBAction func onClear(_ sender: Any) {
        self.targetImageView.image = nil
    }
    
    @IBAction func onSnapshot(_ sender: Any) {
        UIGraphicsBeginImageContextWithOptions(self.contentView.bounds.size, false, UIScreen.main.scale)
        if let context = UIGraphicsGetCurrentContext() {
            self.contentView.layer.render(in: context)
            let image = UIGraphicsGetImageFromCurrentImageContext()
            self.targetImageView.image = image
        } else {
            self.targetImageView.image = nil
        }
        UIGraphicsEndImageContext()
    }
    

    Test

    In the upper area there is a scroll view with activated zoom (maximumZoomScale = 5). In the lower area there is only a simple UIImageView. If one taps on the button with the label "snap", onSnapshot is called and the lower UIImageView is filled with the snapshot image. With the button 'clear' UIImageView's image is set to nil.

    snapshot demo