One option - may or may not be suitable:
- generate an oval image
- blur that image
- use the resulting image in an image view or maybe as the content of a layer
Here's an attempt - note: I work with iOS, so lots of hard-coded values and possibly (likely) incorrect ways to do this:
import Cocoa
class ViewController: NSViewController {
let cyanView = NSView()
let shadowView = NSImageView()
override func viewDidLoad() {
super.viewDidLoad()
view.wantsLayer = true
if let myLayer = view.layer {
myLayer.backgroundColor = NSColor.gray.cgColor
}
cyanView.wantsLayer = true
if let myLayer = cyanView.layer {
myLayer.backgroundColor = NSColor.cyan.cgColor
}
// let's use constraints
[shadowView, cyanView].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
}
NSLayoutConstraint.activate([
cyanView.widthAnchor.constraint(equalToConstant: 400.0),
cyanView.heightAnchor.constraint(equalToConstant: 200.0),
cyanView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
cyanView.topAnchor.constraint(equalTo: view.topAnchor, constant: 80.0),
shadowView.widthAnchor.constraint(equalTo: cyanView.widthAnchor, multiplier: 1.5),
shadowView.heightAnchor.constraint(equalToConstant: 80.0),
shadowView.topAnchor.constraint(equalTo: cyanView.bottomAnchor, constant: 0.0),
shadowView.centerXAnchor.constraint(equalTo: cyanView.centerXAnchor),
])
let recognizer = NSClickGestureRecognizer(target: self, action: #selector(clickView(_:)))
view.addGestureRecognizer(recognizer)
}
override func viewDidLayout() {
super.viewDidLayout()
// create a blurred oval image for the shadowView
let img = NSImage.init(color: .black, size: shadowView.frame.size).oval()
guard let tiffRep = img.tiffRepresentation,
let blurFilter = CIFilter(name: "CIGaussianBlur")
else { return }
let inputImage = CIImage(data: tiffRep)
blurFilter.setDefaults()
blurFilter.setValue(inputImage, forKey: kCIInputImageKey)
blurFilter.setValue(NSNumber(value: 40.0), forKey: "inputRadius")
guard let outputImage = blurFilter.value(forKey: kCIOutputImageKey) as? CIImage else { return }
let outputImageRect = NSRectFromCGRect(outputImage.extent)
let blurredImage = NSImage(size: outputImageRect.size)
blurredImage.lockFocus()
outputImage.draw(at: .zero, from: outputImageRect, operation: .copy, fraction: 1.0)
blurredImage.unlockFocus()
shadowView.image = blurredImage.resize(to: shadowView.bounds.size)
}
@objc func clickView(_ sender: NSClickGestureRecognizer) {
let img = view.imageRepresentation()
// do something with the image
print("clicked")
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
extension NSView {
func imageRepresentation() -> NSImage? {
if let bitRep = self.bitmapImageRepForCachingDisplay(in: self.bounds) {
bitRep.size = self.bounds.size
self.cacheDisplay(in: self.bounds, to: bitRep)
let image = NSImage(size: self.bounds.size)
image.addRepresentation(bitRep)
return image
}
return nil
}
}
extension NSImage {
func resize(to size: NSSize) -> NSImage {
return NSImage(size: size, flipped: false, drawingHandler: {
self.draw(in: $0)
return true
})
}
convenience init(color: NSColor, size: NSSize) {
self.init(size: size)
lockFocus()
color.drawSwatch(in: NSRect(origin: .zero, size: size))
unlockFocus()
}
func oval(in rect: CGRect) -> NSImage {
let image = NSImage(size: size)
image.lockFocus()
NSGraphicsContext.current?.imageInterpolation = .high
NSBezierPath(ovalIn: rect).addClip()
draw(at: rect.origin, from: rect, operation: .sourceOver, fraction: 1)
image.unlockFocus()
return image
}
func oval() -> NSImage {
return oval(in: NSRect(origin: .zero, size: size))
}
}
Output when running:

Result of let img = view.imageRepresentation()
:
