I'm just trying to add a shadow to the image, which is not just rectangular, I'd like to apply the same mask effect as image has. On stack overflow I've found the same question, but the answer is suggested on objective-c language - iOS SDK - drop shadow on masked image Can you please help me wit swift-code on this question. Thanks in advance.
class drawView: UIView {
override func draw(_ rect: CGRect) {
super.draw(rect)
guard let context = UIGraphicsGetCurrentContext() else {
return
}
self.drawCircle(context)
}
private func drawCircle(_ context: CGContext){
context.setStrokeColor(UIColor.black.cgColor)
let newMask = CAShapeLayer()
let circlePath = UIBezierPath(ovalIn: .init(x: 0, y: 0, width: 50, height: 50))
circlePath.stroke()
newMask.path = circlePath.cgPath
self.layer.mask = newMask //if I apply this, then shadow disappears
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOpacity = 0.15
self.layer.shadowOffset = .init(width: 10, height: -10)
self.layer.shadowRadius = 2
}
}
There are various ways to approach this.
I would suggest using a custom UIView
with a UIImageView
subview:
We mask the imageView, and then apply the layer shadow to the custom view itself:
class ShadowMaskImageView: UIView {
let theImageView = UIImageView()
let maskShape = CAShapeLayer()
var image: UIImage = UIImage() {
didSet {
theImageView.image = image
}
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
theImageView.translatesAutoresizingMaskIntoConstraints = false
addSubview(theImageView)
NSLayoutConstraint.activate([
theImageView.topAnchor.constraint(equalTo: topAnchor),
theImageView.leadingAnchor.constraint(equalTo: leadingAnchor),
theImageView.trailingAnchor.constraint(equalTo: trailingAnchor),
theImageView.bottomAnchor.constraint(equalTo: bottomAnchor),
])
theImageView.layer.mask = maskShape
layer.shadowOffset = CGSize(width: 10.0, height: -10.0)
layer.shadowColor = UIColor.black.cgColor
layer.shadowRadius = 2
layer.shadowOpacity = 0.15
}
override func layoutSubviews() {
super.layoutSubviews()
maskShape.frame = bounds
// let's inset the image frame by 10-pts on all sides
// so our 10 x -10 shadow will fit inside the bounds
let r = bounds.insetBy(dx: 10.0, dy: 10.0)
// oval path
let pth = UIBezierPath(ovalIn: r)
maskShape.path = pth.cgPath
}
}
Here's how it looks (red outline rectangle is just showing the view frame)...
First, without the mask OR shadow:
Now, with just the mask:
and, with the mask AND shadow:
and finally, without the red border showing the frame:
Here's an example view controller:
class MaskShadowViewController: UIViewController {
let testView = ShadowMaskImageView()
override func viewDidLoad() {
super.viewDidLoad()
// make sure we can load the image
guard let img = UIImage(named: "sample") else {
fatalError("Could not load sample image!!!")
}
// set the image
testView.image = img
// add testView and set its height equal to the ratio of the original image
testView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(testView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
testView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
testView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
testView.heightAnchor.constraint(equalTo: testView.widthAnchor, multiplier: img.size.height / img.size.width),
testView.centerYAnchor.constraint(equalTo: g.centerYAnchor),
])
// un-comment the following if we want to see the frame of our custom view
//let frameView = UIView()
//frameView.translatesAutoresizingMaskIntoConstraints = false
//view.addSubview(frameView)
//NSLayoutConstraint.activate([
// frameView.topAnchor.constraint(equalTo: testView.topAnchor, constant: 0.0),
// frameView.leadingAnchor.constraint(equalTo: testView.leadingAnchor, constant: 0.0),
// frameView.trailingAnchor.constraint(equalTo: testView.trailingAnchor, constant: 0.0),
// frameView.bottomAnchor.constraint(equalTo: testView.bottomAnchor, constant: 0.0),
//])
//
//frameView.layer.borderWidth = 1
//frameView.layer.borderColor = UIColor.red.cgColor
}
}