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
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
There are several ways you could do this. Here's one way:
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).
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.
For each view that should participate in the common gradient, turn on clipsToBounds
.
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
.