I can't believe how many hours of searching and experimenting I had to do before I was able to simply flip an image horizontally in AppKit. I cannot upvote this question and mashers' answer enough.
Here is an updated version of my solution for swift to flip an image horizontally. This method is implemented in an NSImageView
subclass.
override func draw(_ dirtyRect: NSRect) {
// NSViews are not backed by CALayer by default in AppKit. Must request a layer
self.wantsLayer = true
if self.flippedHoriz {
// If a horizontal flip is desired, first multiple every X coordinate by -1. This flips the image, but does it around the origin (lower left), not the center
var trans = AffineTransform(scaledByX: -1, byY: 1)
// Add a transform that moves the image by the width so that its lower left is at the origin
trans.append(AffineTransform(translationByX: self.frame.size.width, byY: 0)
// AffineTransform is bridged to NSAffineTransform, but it seems only NSAffineTransform has the set() and concat() methods, so convert it and add the transform to the current graphics context
(trans as NSAffineTransform).concat()
}
// Don't be fooled by the Xcode placehoder. This must be *after* the above code
super.draw(dirtyRect)
}
The behavior of the transforms also took a bit of experimentation to understand. The help for NSAffineTransform.set()
explains:
it removes the existing transformation matrix, which is an accumulation of transformation matrices for the screen, window, and any superviews.
This will very likely break something. Since I wanted to still respect all the transformations applied by the window and superviews, the concat()
method is more appropriate.
concat()
multiplies the existing transform matrix by your custom transform. This is not exactly cumulative, though. Each time draw
is called, your transform is applied to the original transform for the view. So repeatedly calling draw doesn't continuously flip the image. Because of this, to not flip the image, simply don't apply the transform.