具有透明内部矩形的 UIBezierPath 阴影 Objective c

UIBezierPath Shadow with transparent internal rect Objective c

我在使用 UIbezierPath 添加阴影时遇到问题。我的密码是

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);

我正在尝试使用此代码添加红色阴影。问题是我正在设置视图的透明背景色。因此,添加的红色阴影层在背景上变得可见,而不是父背景颜色。喜欢下图

但我希望它是这样的

如有解决方法请指导

您可以通过

  • 添加一个 CAShapeLayer 作为子层
  • 给它一个rounded-rect路径
  • 给路径一个白色填充颜色
  • 然后使用“从中心切出矩形的蒙版”

这是一个快速示例视图子类和演示它的控制器:

自定义UIView子类

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
        
    }
    
}

示例视图控制器

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),
            
        ])
    }
}

外观:


编辑 - 我应该已经注意到 Objective-C 实施的需要(这些天请求越来越少)。

所以,这里和上面一样,但是在Obj-C(默认headers):

自定义UIView子类

#import "ShadowPathView.h"

@interface ShadowPathView ()
{
    CAShapeLayer *shadowLayer;
    CAShapeLayer *maskLayer;
    CGFloat radius;
}
@end

@implementation ShadowPathView

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self commonInit];
    }
    return self;
}

- (void) commonInit {
    radius = 10;
    shadowLayer = [CAShapeLayer new];
    maskLayer = [CAShapeLayer new];
    
    self.backgroundColor = [UIColor clearColor];
    
    [self.layer addSublayer:shadowLayer];
    
    shadowLayer.fillColor = [UIColor whiteColor].CGColor;
    shadowLayer.shadowColor = [UIColor redColor].CGColor;
    shadowLayer.shadowOpacity = 1.0;
    shadowLayer.shadowOffset = CGSizeZero;
    
    // set the layer mask
    shadowLayer.mask = maskLayer;
}

- (void)layoutSubviews {
    [super layoutSubviews];
    
    shadowLayer.frame = self.bounds;
    shadowLayer.path = [UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:radius].CGPath;
    
    // create a rect bezier path, large enough to exceed the shadow bounds
    UIBezierPath *bez = [UIBezierPath bezierPathWithRect:CGRectInset(self.bounds, -radius, -radius)];
    
    // create a path for the "hole" in the layer
    UIBezierPath *holePath = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(self.bounds, 0, 0) cornerRadius:radius];

    // this "cuts a hole" in the path
    [bez appendPath:holePath];
    bez.usesEvenOddFillRule = YES;
    maskLayer.fillRule = kCAFillRuleEvenOdd;
    
    // set the path of the mask layer
    maskLayer.path = bez.CGPath;
    
    CGFloat shadowWidth = 5;
    // make the shadow rect larger than bounds
    CGRect shadowRect =  CGRectInset(self.bounds, -shadowWidth, -shadowWidth);
    // set the shadow path
    //  make the corner radius larger to make the curves look correct
    shadowLayer.shadowPath = [UIBezierPath bezierPathWithRoundedRect:shadowRect cornerRadius:radius + shadowWidth].CGPath;
}

@end

示例视图控制器

#import "ViewController.h"
#import "ShadowPathView.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor colorWithRed:0.8 green:0.92 blue:0.97 alpha:1.0];
    
    // two of our custom ShadowPathView
    ShadowPathView *v1 = [ShadowPathView new];
    ShadowPathView *v2 = [ShadowPathView new];

    // a label to put UNDER the second view
    UILabel *underLabel = [UILabel new];

    // a label to add as a SUVBVIEW of the second view
    UILabel *subLabel = [UILabel new];

    for (UILabel *v in @[underLabel, subLabel]) {
        v.textAlignment = NSTextAlignmentCenter;
        v.backgroundColor = [UIColor greenColor];
    }
    for (UIView *v in @[v1, v2, underLabel, subLabel]) {
        v.translatesAutoresizingMaskIntoConstraints = NO;
    }
    for (UIView *v in @[v1, underLabel, v2]) {
        [self.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;

    UILayoutGuide *g = self.view.safeAreaLayoutGuide;
    
    [NSLayoutConstraint activateConstraints:@[
        
        [v1.topAnchor constraintEqualToAnchor:g.topAnchor constant:40.0],
        [v1.leadingAnchor constraintEqualToAnchor:g.leadingAnchor constant:40.0],
        [v1.trailingAnchor constraintEqualToAnchor:g.trailingAnchor constant:-40.0],
        [v1.heightAnchor constraintEqualToConstant:120.0],
        
        [v2.topAnchor constraintEqualToAnchor:v1.bottomAnchor constant:80.0],
        [v2.leadingAnchor constraintEqualToAnchor:g.leadingAnchor constant:40.0],
        [v2.trailingAnchor constraintEqualToAnchor:g.trailingAnchor constant:-40.0],
        [v2.heightAnchor constraintEqualToConstant:160.0],
        
        [underLabel.leadingAnchor constraintEqualToAnchor:v2.leadingAnchor constant:-20.0],
        [underLabel.topAnchor constraintEqualToAnchor:v2.topAnchor constant:-20.0],
        [underLabel.heightAnchor constraintEqualToConstant:80.0],
        
        [subLabel.bottomAnchor constraintEqualToAnchor:v2.bottomAnchor constant:-12.0],
        [subLabel.trailingAnchor constraintEqualToAnchor:v2.trailingAnchor constant:-40.0],
        [subLabel.widthAnchor constraintEqualToConstant:120.0],
        
    ]];
    
}


@end

谢谢@DonMag!我从他的 class ShadowPathView 那里得到帮助,按如下方式编辑我的 Objective C 函数

- (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;
}