Home > other >  UIBezierPath Shadow with transparent internal rect Objective c
UIBezierPath Shadow with transparent internal rect Objective c

Time:04-06

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

Problem in setting shadow

But I want it to be like this

Expected output

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:

enter image description here

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;
}
  • Related