I have a problem by adding shadow using UIbezierPath. My Code is
CGRect f = view.bounds;
view.layer.shadowColor = [UIColor redColor].CGColor;
view.layer.shadowOpacity = 1;
view.layer.shadowRadius = 10;
CGFloat shadowWidth = 5;
CGRect shadowRect = CGRectMake(-shadowWidth, -shadowWidth, f.size.width (shadowWidth*2), f.size.height (shadowWidth*2));
CGFloat shadowRadius = radius;
view.layer.shadowPath = [UIBezierPath bezierPathWithRoundedRect:shadowRect byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(shadowRadius, shadowRadius)].CGPath;
view.layer.shadowOffset = CGSizeMake(0, 0);
I am trying to add red shadow using this code. The problem is I am setting transparent background color of my view. Due to this the added red shadow layer becomes visible on background, rather the parent background color. like following image
But I want it to be like this
If there is any solution for the problem please guide.
CodePudding user response:
You can do this by
- adding a
CAShapeLayer
as a sublayer - give it a rounded-rect path
- give the path a White fill color
- then use a "mask with a rectangle cut out of the center"
Here's a quick example view subclass and a controller demonstrating it:
Custom UIView
subclass
class ShadowPathView: UIView {
let radius: CGFloat = 10
let shadowLayer = CAShapeLayer()
let maskLayer = CAShapeLayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
// these properties don't change
backgroundColor = .clear
layer.addSublayer(shadowLayer)
shadowLayer.fillColor = UIColor.white.cgColor
shadowLayer.shadowColor = UIColor.red.cgColor
shadowLayer.shadowOpacity = 1.0
shadowLayer.shadowOffset = .zero
// set the layer mask
shadowLayer.mask = maskLayer
}
override func layoutSubviews() {
super.layoutSubviews()
shadowLayer.frame = bounds
shadowLayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: radius).cgPath
// create a rect bezier path, large enough to exceed the shadow bounds
let bez = UIBezierPath(rect: bounds.insetBy(dx: -radius * 2.0, dy: -radius * 2.0))
// create a path for the "hole" in the layer
let holePath = UIBezierPath(rect: bounds.insetBy(dx: radius, dy: radius))
// this "cuts a hole" in the path
bez.append(holePath)
bez.usesEvenOddFillRule = true
maskLayer.fillRule = .evenOdd
// set the path of the mask layer
maskLayer.path = bez.cgPath
let w: CGFloat = 5
// make the shadow rect larger than bounds
let shadowRect = bounds.insetBy(dx: -w, dy: -w)
// set the shadow path
// make the corner radius larger to make the curves look correct
shadowLayer.shadowPath = UIBezierPath(roundedRect: shadowRect, cornerRadius: radius w).cgPath
}
}
Example view controller
class ShadowPathVC: UIViewController {
// two of our custom ShadowPathView
let v1 = ShadowPathView()
let v2 = ShadowPathView()
// a label to put UNDER the second view
let underLabel = UILabel()
// a label to add as a SUVBVIEW of the second view
let subLabel = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor(red: 0.8, green: 0.92, blue: 0.97, alpha: 1.0)
[underLabel, subLabel].forEach { v in
v.textAlignment = .center
v.backgroundColor = .green
}
[v1, v2, underLabel, subLabel].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
}
[v1, underLabel, v2].forEach { v in
view.addSubview(v)
}
v2.addSubview(subLabel)
underLabel.text = "This label is Under the shadow view"
subLabel.text = "This label is a subview of the shadow view"
subLabel.numberOfLines = 0
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
v1.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
v1.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
v1.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
v1.heightAnchor.constraint(equalToConstant: 120.0),
v2.topAnchor.constraint(equalTo: v1.bottomAnchor, constant: 80.0),
v2.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
v2.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
v2.heightAnchor.constraint(equalToConstant: 160.0),
underLabel.leadingAnchor.constraint(equalTo: v2.leadingAnchor, constant: -20.0),
underLabel.topAnchor.constraint(equalTo: v2.topAnchor, constant: -20.0),
underLabel.heightAnchor.constraint(equalToConstant: 80.0),
subLabel.bottomAnchor.constraint(equalTo: v2.bottomAnchor, constant: -12.0),
subLabel.trailingAnchor.constraint(equalTo: v2.trailingAnchor, constant: -40.0),
subLabel.widthAnchor.constraint(equalToConstant: 120.0),
])
}
}
How it looks:
CodePudding user response:
Thanks @DonMag! I got help from his class ShadowPathView to edit my Objective C function as follows
- (void)setOnView:(UIView *)view {
CGFloat radius = 10;
CAShapeLayer *maskLayer = [CAShapeLayer layer];
CAShapeLayer *shadowLayer = [CAShapeLayer layer];
[view.layer addSublayer:shadowLayer];
shadowLayer.shadowColor = [UIColor redColor].CGColor;
shadowLayer.shadowOpacity = 1;
shadowLayer.shadowOffset = CGSizeMake(0, 0);
shadowLayer.mask = maskLayer;
shadowLayer.frame = view.bounds;
shadowLayer.path = [UIBezierPath bezierPathWithRoundedRect:view.bounds cornerRadius:radius].CGPath;
UIBezierPath *bez = [UIBezierPath bezierPathWithRect:CGRectInset(view.bounds, -radius, -radius)];
UIBezierPath *holePath = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(view.bounds, 0, 0) cornerRadius:radius];
[bez appendPath:holePath];
bez.usesEvenOddFillRule = YES;
maskLayer.fillRule = kCAFillRuleEvenOdd;
maskLayer.path = bez.CGPath;
CGFloat shadowWidth = 5;
CGRect shadowRect = CGRectInset(view.bounds, -shadowWidth, -shadowWidth);
shadowLayer.shadowPath = [UIBezierPath bezierPathWithRoundedRect:shadowRect cornerRadius:radius].CGPath;
}