如何添加跨越两个视图的渐变?

How can I add a gradient that spans two views?

我知道如何做 (1) 但我该怎么做 (2)?

UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 50)];
CAGradientLayer *gradient = [CAGradientLayer layer];

gradient.frame = view.bounds;
gradient.colors = @[(id)[UIColor blueColor].CGColor, (id)[UIColor redColor].CGColor];

[view.layer insertSublayer:gradient atIndex:0];

您可以通过在具有渐变的视图顶部添加一个视图,然后通过使用 UIBezierPath 制作遮罩来剪切形状,然后将其添加到顶部视图(让我们称之为 topView):

    let yourPath: UIBezierPath = //create the desired bezier path for your shapes
    let mask = CAShapeLayer()
    mask.path = yourPath.cgPath
    topView.layer.mask = mask

有几种方法可以做到这一点。这是一种方法:

  1. 创建一个名为GradientViewUIView子类来管理渐变图层。这很有用,因为它意味着您可以使用普通的 UIKit 技术来管理渐变布局(自动布局约束、自动调整大小蒙版、UIKit 动画)。

  2. 对于每个应该参与公共渐变的视图,添加一个GradientView子视图。相同地设置每个 GradientView 的颜色、位置以及起点和终点。

  3. 对于每个应该参与公共渐变的视图,打开clipsToBounds

  4. 使用自动布局约束使每个 GradientView 跨越所有参与的超级视图。 (了解约束可以跨越 superview/subview 边界很重要)。

使用这种方法,自动布局会负责使渐变覆盖所有视图,即使它们改变大小或四处移动也是如此。例如,当用户旋转设备时,您无需执行任何特殊操作即可使渐变动画效果很好。

因此,对于您的双视图示例,我建议您设置这样的视图层次结构:

在上面的视图调试器屏幕截图中,我禁用了裁剪。可以看到两个渐变视图的渐变完全相同,共享同一个屏幕space。 topGradienttopView 的子视图,bottomGradientbottomView 的子视图。

如果我们打开裁剪,您只会看到 topGradient 适合 topView 边界的部分,并且您只会看到 bottomGradient 的部分适合 bottomView 的边界。这是启用裁剪后的样子:

下面是我在模拟器中的测试程序的屏幕截图:

这是 GradientView 的源代码:

@interface GradientView: UIView
@property (nonatomic, strong, readonly) CAGradientLayer *gradientLayer;
@end

@implementation GradientView
+ (Class)layerClass { return CAGradientLayer.class; }
- (CAGradientLayer *)gradientLayer { return (CAGradientLayer *)self.layer; }
@end

这是我用来创建所有视图的代码:

- (void)viewDidLoad {
    [super viewDidLoad];

    UIView *topView = [[UIView alloc] initWithFrame:CGRectMake(20, 20, 100, 50)];
    topView.layer.cornerRadius = 10;
    topView.clipsToBounds = YES;
    UIView *topGradient = [self newGradientView];
    [topView addSubview:topGradient];
    [self.view addSubview:topView];

    UIView *bottomView = [[UIView alloc] initWithFrame:CGRectMake(20, 90, 100, 50)];
    bottomView.layer.cornerRadius = 10;
    bottomView.clipsToBounds = YES;
    UIView *bottomGradient = [self newGradientView];
    [bottomView addSubview:bottomGradient];
    [self.view addSubview:bottomView];

    [self constrainView:topGradient toCoverViews:@[topView, bottomView]];
    [self constrainView:bottomGradient toCoverViews:@[topView, bottomView]];
}

- (GradientView *)newGradientView {
    GradientView *gv = [[GradientView alloc] initWithFrame:CGRectZero];
    gv.translatesAutoresizingMaskIntoConstraints = NO;
    gv.gradientLayer.colors = @[(__bridge id)UIColor.blueColor.CGColor, (__bridge id)UIColor.redColor.CGColor];
    return gv;
}

下面是我如何创建使 GradientView(或任何视图)覆盖一组视图的约束:

- (void)constrainView:(UIView *)coverer toCoverViews:(NSArray<UIView *> *)coverees {
    for (UIView *coveree in coverees) {
        NSArray<NSLayoutConstraint *> *cs;

        cs = @[
               [coverer.leftAnchor constraintLessThanOrEqualToAnchor:coveree.leftAnchor],
               [coverer.rightAnchor constraintGreaterThanOrEqualToAnchor:coveree.rightAnchor],
               [coverer.topAnchor constraintLessThanOrEqualToAnchor:coveree.topAnchor],
               [coverer.bottomAnchor constraintGreaterThanOrEqualToAnchor:coveree.bottomAnchor]];
        [NSLayoutConstraint activateConstraints:cs];

        cs = @[
               [coverer.leftAnchor constraintEqualToAnchor:coveree.leftAnchor],
               [coverer.rightAnchor constraintEqualToAnchor:coveree.rightAnchor],
               [coverer.topAnchor constraintEqualToAnchor:coveree.topAnchor],
               [coverer.bottomAnchor constraintEqualToAnchor:coveree.bottomAnchor]];
        for (NSLayoutConstraint *c in cs) { c.priority = UILayoutPriorityDefaultHigh; }
        [NSLayoutConstraint activateConstraints:cs];
    }
}

greaterThanOrEqual/lessThanOrEqual 约束(默认情况下)具有所需的优先级,确保 coverer 覆盖每个 coveree 的整个帧。具有较低优先级的 equal 约束,然后确保 coverer 占据覆盖每个 coveree.

所需的最小 space