This works perfectly fine. Unfortunately, the counterpart – where you increase the size – only goes up to the original size of your imageView.

To my surprise, all of the code in this article works without imageView.needsDisplay = true – I will update this post if I find out whether one should use it (to be a good citizen) or avoid it (to simplify matters).

1) Using CGAffineTransform

Layer transformations

a) On iOS, you get a .transform property on UIView for free. On macOS, this property is missing – macOS views are not, by default, layer-backed, so you need to add that layer first.

As you can see, while the ‘zoom’ part functions nicely, the image view expands to take up all available space in the window. Since I have stacked
let zoom = CGAffineTransform(scaleX: 2, y: 2)
let translate = CGAffineTransform(translationX: -256, y: -256)
this should be no great surprise.

a) Comment out the translation (this was to achieve the effect of moving into a specific point in the picture), and set the height and width for your image view. Build and run. It will behave as before and expand to take up all available space.
b) Set the content hugging priority to 1000. This should, in theory, stop the imageView from expanding. Spoiler: it won’t.
c) Delete the height and width constraints, and pin the view to the edges of its superview, leaving ample space (50-100pts). Whenever I try this in Xcode 8/10.11, my window acquires epic proportions – about twice as wide as my screen – and the view grows beyond all expectations, and that’s _with_ the imageView’s scaling mode set to ‘propotionally down’. Right now, I’m cautiously filing this as ‘autolayout happens’.
Testing this with a 100x100px image, it appears that setting the outer constraints for the image view make it display the image at its full size. Since I wasn’t scaling down the picture above for display, this is the result. This isn’t expected (or wanted) behaviour, but right now, there seems to be no way to stop it other than not using autolayout. (The ‘scale’ settings – including ‘none’ make no difference: when autolayout calculates the size of an image view, it seems to always use the original image size and force the window to obey it. I do not remember this from previous apps, but I may never have used these partiular seetings – autolayout constraints to top, bottom, leading, trailing in combination with an oversized image. Will test on 10.12 and the latest Xcode before filing a bug.)

Remove the constraints. Even just setting height and width on the image view and setting an additional constraint, priority 1000, to the top of the view does not have the desired effect – the imageView still spills over.

6) Embed the imageView in a customView Editor->Embed in ->CustomView) and create an outlet for it in the viewController

@IBOutlet weak var outerView: NSView!

Set the size of the outerView to be the same as the imageView, and create constraints pinning it into place for the leading, trailing, top and bottom space to the superview.

Build and run: Rejoice.
Now the view stays where it is supposed to be, while the contents get transformed.

Alternatively, you can use NSScrollView.

Using ScrollViews

7) For a different behaviour, embed your image view in a scoll view (Editor->Embed in -> Scroll view)

a) Build and run.
The size of the view now remains consistent, but the moment you actually try to scroll, the zoom factor jumps back to 1.

When you set ‘allowMagnification’ in IB, the behaviour persists; if you zoom in manually (gestures/scroll view), the scroll view behaves as expected and lets you scroll around the zoomed-in image.

b) create an outlet for your scroll view, comment out the transformation code, and replace the zoom function with

For some reason, the scrollView’s magnification persists between application runs, so you might want to do some housekeeping in viewDidLoad to reset the magnification to

scrollView.magnify(toFit: scrollView.bounds)

c) try to break it: set the magification factor in the zoomOut function to 0. It will zoom out to the minimum factor you’ve set, whatever that is. (Default 0.4, but you can play with the values.)

And more…

Usage Example

I’ve worked this out because I wanted to create the illusion that the user is moving into an image (as a low-key solution rather than using animation).
Magnifying a scrollView, on the other hand, it a basic function of drawing applications.

Alternatives

Whether you use CGAffineTransform or manually magnify a scrollView depends on what you want to achieve.
Depending on what you are trying to achieve, you might wish to look into SpriteKit as an alternative framework for manipulating individual interface elements.

Extensions

For scrollViews, you might want to provide different content once the user has zoomed to a certain point (think maps: they are not magnified indefinitely, but get replaced by a more general or more detailed map.
The CGAffineTransforms discussed here are only the tip of the iceberg that is CGLayer, with all the potential for animation and content filters that layers offer.