How can I add a gradient that spans two views?

后端 未结 2 448
盖世英雄少女心
盖世英雄少女心 2020-12-21 16:05

I know how to do (1) but how can I do (2)?

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


        
相关标签:
2条回答
  • 2020-12-21 16:43

    You can do this by adding a view on top of the view with the gradient, then cutting out the shapes by making a mask out of a UIBezierPath, then adding that to the view on top (let's call it topView):

        let yourPath: UIBezierPath = //create the desired bezier path for your shapes
        let mask = CAShapeLayer()
        mask.path = yourPath.cgPath
        topView.layer.mask = mask
    
    0 讨论(0)
  • 2020-12-21 16:51

    There are several ways you could do this. Here's one way:

    1. Create a UIView subclass named GradientView to manage the gradient layer. This is helpful because it means you can use the normal UIKit techniques to manage the gradient layout (auto layout constraints, autoresizing masks, UIKit animations).

    2. For each view that should participate in the common gradient, add a GradientView subview. Set up each GradientView's colors, locations, and start and end points identically.

    3. For each view that should participate in the common gradient, turn on clipsToBounds.

    4. Use auto layout constraints to make each GradientView span all of the participating superviews. (It's important to understand that constraints can cross superview/subview boundaries).

    With this approach, auto layout takes care of making the gradient cover all of the views even if they change size or move around. For example, you won't have to do anything special to make the gradients animate nicely when the user rotates the device.

    Thus, for your two-view example, I'm proposing that you set up a view hierarchy like this:

    In the view debugger screenshot above, I disabled clipping. You can see that the two gradient views have identical gradients and share the same screen space. The topGradient is a subview of topView and bottomGradient is a subview of bottomView.

    If we turn clipping on, you'll only see the part of topGradient that fits inside topView's bounds, and you'll only see the part of bottomGradient that fits inside bottomView's bounds. Here's what it looks like with clipping enabled:

    And here's a screen shot of my test program in the simulator:

    Here's the source code for 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
    

    Here's the code I used to create all of the views:

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

    And here's how I create the constraints that make a GradientView (or any view) cover a set of views:

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

    The greaterThanOrEqual/lessThanOrEqual constraints, which (by default) have required priority, ensure that coverer covers the entire frame of each coveree. The equal constraints, which have lower priority, then ensure that coverer occupies the minimum space required to cover each coveree.

    0 讨论(0)
提交回复
热议问题