adding fading color & transparency to UIView - iphone

I know how to create and animate a view like the one in the Share sub view of the new app store app that comes with iOS 6+ (see attached screenshot), but I don't know how to add that nice coloring effect with transparency on this view.
anyone can provide a code sample to make a UIView looks exactly the one in the screenshot?
P.S. the alpha property alone of UIView does not do such thing.

You can add this method to a UIView category and reuse as needed.
It applies a linear black gradient from "theColor" to transparent to the given view.
You should have QuartzCore.framework in your project in order to use the CAGradientLayer object.
+ (void)addLinearGradientToView:(UIView *)theView withColor:(UIColor *)theColor transparentToOpaque:(BOOL)transparentToOpaque
{
CAGradientLayer *gradient = [CAGradientLayer layer];
//the gradient layer must be positioned at the origin of the view
CGRect gradientFrame = theView.frame;
gradientFrame.origin.x = 0;
gradientFrame.origin.y = 0;
gradient.frame = gradientFrame;
//build the colors array for the gradient
NSArray *colors = [NSArray arrayWithObjects:
(id)[theColor CGColor],
(id)[[theColor colorWithAlphaComponent:0.9f] CGColor],
(id)[[theColor colorWithAlphaComponent:0.6f] CGColor],
(id)[[theColor colorWithAlphaComponent:0.4f] CGColor],
(id)[[theColor colorWithAlphaComponent:0.3f] CGColor],
(id)[[theColor colorWithAlphaComponent:0.1f] CGColor],
(id)[[UIColor clearColor] CGColor],
nil];
//reverse the color array if needed
if(transparentToOpaque)
{
colors = [[colors reverseObjectEnumerator] allObjects];
}
//apply the colors and the gradient to the view
gradient.colors = colors;
[theView.layer insertSublayer:gradient atIndex:0];
}
Please note that you should have the backgroundColor of theView set to clearColor so that it doesn't interfere with the gradient.
Also, for the results shown in the screenshot, the transparentToOpaque flag should be YES.

Here is PedroSilva's solution translated to Swift if anyone needs it (added vertical option as well)
class func addLinearGradientToView(view: UIView, colour: UIColor, transparntToOpaque: Bool, vertical: Bool)
{
let gradient = CAGradientLayer()
let gradientFrame = CGRectMake(0, 0, view.frame.size.width, view.frame.size.height)
gradient.frame = gradientFrame
var colours = [
colour.CGColor,
colour.colorWithAlphaComponent(0.9).CGColor,
colour.colorWithAlphaComponent(0.8).CGColor,
colour.colorWithAlphaComponent(0.7).CGColor,
colour.colorWithAlphaComponent(0.6).CGColor,
colour.colorWithAlphaComponent(0.5).CGColor,
colour.colorWithAlphaComponent(0.4).CGColor,
colour.colorWithAlphaComponent(0.3).CGColor,
colour.colorWithAlphaComponent(0.2).CGColor,
colour.colorWithAlphaComponent(0.1).CGColor,
UIColor.clearColor().CGColor
]
if transparntToOpaque == true
{
colours = colours.reverse()
}
if vertical == true
{
gradient.startPoint = CGPointMake(0, 0.5)
gradient.endPoint = CGPointMake(1, 0.5)
}
gradient.colors = colours
view.layer.insertSublayer(gradient, atIndex: 0)
}

CAGradientLayer *layer = [CAGradientLayer layer];
layer.frame = yourView.bounds;
UIColor *blackColor = [UIColor colorWithWhite:0.42f alpha:1.0f];
UIColor *clearColor = [UIColor colorWithWhite:0.42f alpha:0.0f];
layer.colors = [NSArray arrayWithObjects:(id)clearColor.CGColor, (id)blackColor.CGColor, nil];
[myView.layer insertSublayer:layer atIndex:0];
Where layer is the layer of your view.

You can simply use a semi transparent png as a background to get the effect like this. Make sure you set the UIView's opaque to NO and you will be fine.

There is no magic needed for this effect to be achieved.
You should do it like Apple does it. UIImageViews with small, stretched images.
This view (the bottom-half square that includes the icons) uses an image that stretches and produces this effect.
You should follow this method too. Your views will be very light and it will just cost you a few tests in Photoshop to achieve the correct effect.
As I have noticed, Apple never uses gradient layers on the iPhone. It must be significantly more GPU consuming because it would have saved them a lot of images...
Here's a similar image to test with.
Hope it helps.

You just need to set the view's background color's alpha value to your desired value.
See attached zip code. Go to AlphaViewController's nib file and see it there
I have only set the alpha value for the subview to 70%. You can do it according to your requirements.

In Case if anyone need Swift 3.X
func addLinearGradientToView(view: UIView, colour: UIColor, transparntToOpaque: Bool, vertical: Bool)
{
let gradient = CAGradientLayer()
let gradientFrame = CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height)
gradient.frame = gradientFrame
var colours = [
colour.cgColor,
colour.withAlphaComponent(0.9).cgColor,
colour.withAlphaComponent(0.8).cgColor,
colour.withAlphaComponent(0.7).cgColor,
colour.withAlphaComponent(0.6).cgColor,
colour.withAlphaComponent(0.5).cgColor,
colour.withAlphaComponent(0.4).cgColor,
colour.withAlphaComponent(0.3).cgColor,
colour.withAlphaComponent(0.2).cgColor,
colour.withAlphaComponent(0.1).cgColor,
UIColor.clear.cgColor
]
if transparntToOpaque == true
{
colours = colours.reversed()
}
if vertical == true
{
gradient.startPoint = CGPoint(x: 0, y: 0.5)
gradient.endPoint = CGPoint(x: 0, y: 0.5)
}
gradient.colors = colours
view.layer.insertSublayer(gradient, at: 0)
}
Other way in Swift 3.x
let gradient = CAGradientLayer()
gradient.frame = view.bounds
gradient.colors = [UIColor.blue.cgColor, UIColor.white.cgColor]
view.layer.insertSublayer(gradient, at: 0)

Related

How to create a top fade effect using UIScrollView?

I've got a UIScrollView in my app and I have seen in some other apps that when the user scrolls, the top section fades out on scroll rather than just dissapearing out.
I really love this effect and want to achieve it. Any ideas how it can be done?
Simple 2020 solution:
import UIKit
class FadeTail: UIIView {
private lazy var gradientLayer: CAGradientLayer = {
let l = CAGradientLayer()
l.startPoint = CGPoint(x: 0.5, y: 0)
l.endPoint = CGPoint(x: 0.5, y: 1)
let baseColor = UIColor.white // for example
l.colors = [
baseColor.withAlphaComponent(0),
baseColor.withAlphaComponent(1),
].map{$0.cgColor}
layer.addSublayer(l)
return l
}()
override func layoutSubviews() {
super.layoutSubviews()
gradientLayer.frame = bounds
}
}
Simply
autolayout that view in storyboard,
any shape or size you wish
on top of the view (text, image, webview, anything) you wish to be faded.
Easy.
Tip - gradients, circles, etc
If you need crazy circular/banded/etc fades, use the techniques here: https://stackoverflow.com/a/61174086/294884
EDIT: I've put this code up on github, see here.
See my answer to a similar question.
My solution is to subclass UIScrollView, and create a mask layer in the layoutSubviews method.
#import "FadingScrollView.h"
#import <QuartzCore/QuartzCore.h>
static float const fadePercentage = 0.2;
#implementation FadingScrollView
// ...
- (void)layoutSubviews
{
[super layoutSubviews];
NSObject * transparent = (NSObject *) [[UIColor colorWithWhite:0 alpha:0] CGColor];
NSObject * opaque = (NSObject *) [[UIColor colorWithWhite:0 alpha:1] CGColor];
CALayer * maskLayer = [CALayer layer];
maskLayer.frame = self.bounds;
CAGradientLayer * gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = CGRectMake(self.bounds.origin.x, 0,
self.bounds.size.width, self.bounds.size.height);
gradientLayer.colors = [NSArray arrayWithObjects: transparent, opaque,
opaque, transparent, nil];
// Set percentage of scrollview that fades at top & bottom
gradientLayer.locations = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0],
[NSNumber numberWithFloat:fadePercentage],
[NSNumber numberWithFloat:1.0 - fadePercentage],
[NSNumber numberWithFloat:1], nil];
[maskLayer addSublayer:gradientLayer];
self.layer.mask = maskLayer;
}
#end
The code above fades the top and bottom of the UIScrollView from the background colour to transparent, but this can be easily changed to fade the top only (or fade to any colour you want).
Change this line to fade the top only:
// Fade top of scrollview only
gradientLayer.locations = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0],
[NSNumber numberWithFloat:fadePercentage],
[NSNumber numberWithFloat:1],
[NSNumber numberWithFloat:1], nil];
EDIT 2:
Or fade the top only by changing these two lines:
// Fade top of scrollview only
gradientLayer.colors = [NSArray arrayWithObjects: transparent, opaque, nil];
gradientLayer.locations = [NSArray arrayWithObjects: [NSNumber numberWithFloat:0],
[NSNumber numberWithFloat:fadePercentage], nil];
Or, fade the bottom only:
// Fade bottom of scrollview only
gradientLayer.colors = [NSArray arrayWithObjects: opaque, transparent, nil];
gradientLayer.locations = [NSArray arrayWithObjects: [NSNumber numberWithFloat:1.0 - fadePercentage],
[NSNumber numberWithFloat:1], nil];
You can use a CAGradientLayer by
Adding the QuartzCore.framework to your project (see Linking to Library or Framework).
Add #import of the QuartzCore headers:
#import <QuartzCore/QuartzCore.h>
And then use CAGradientLayer:
- (void)addGradientMaskToView:(UIView *)view
{
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = view.bounds;
gradient.colors = #[(id)[[UIColor clearColor] CGColor], (id)[[UIColor whiteColor] CGColor]];
gradient.startPoint = CGPointMake(0.5, 0.0); // this is the default value, so this line is not needed
gradient.endPoint = CGPointMake(0.5, 0.20);
[view.layer setMask:gradient];
}
Note, this CAGradientLayer is a gradient from a color with alpha of 0.0 (e.g. clearColor) to a color to a color with alpha of 1.0 (e.g. whiteColor), not just from black to white. You can adjust the startPoint (the default value is probably fine) and the endPoint to adjust where you want the gradient to be applied.
And generally, when doing this with a UIScrollView, unless you want the gradient to scroll with you, you make the UIScrollView a subview of some other UIView and apply this gradient to that container view, not the scroll view itself.
Thanks to Fattie's answer I created the following UIView extension, in Swift, that takes care of the gradient fading and provides more styles (bottom, top, left right, vertical and horizontal) as well as fade percentage.
For any comments/recommendations, please let me know at the gist I've created, I try to keep both that and this answer up to date with any changes I add.
The Extension:
extension UIView {
enum UIViewFadeStyle {
case bottom
case top
case left
case right
case vertical
case horizontal
}
func fadeView(style: UIViewFadeStyle = .bottom, percentage: Double = 0.07) {
let gradient = CAGradientLayer()
gradient.frame = bounds
gradient.colors = [UIColor.white.cgColor, UIColor.clear.cgColor]
let startLocation = percentage
let endLocation = 1 - percentage
switch style {
case .bottom:
gradient.startPoint = CGPoint(x: 0.5, y: endLocation)
gradient.endPoint = CGPoint(x: 0.5, y: 1)
case .top:
gradient.startPoint = CGPoint(x: 0.5, y: startLocation)
gradient.endPoint = CGPoint(x: 0.5, y: 0.0)
case .vertical:
gradient.startPoint = CGPoint(x: 0.5, y: 0.0)
gradient.endPoint = CGPoint(x: 0.5, y: 1.0)
gradient.colors = [UIColor.clear.cgColor, UIColor.white.cgColor, UIColor.white.cgColor, UIColor.clear.cgColor]
gradient.locations = [0.0, startLocation, endLocation, 1.0] as [NSNumber]
case .left:
gradient.startPoint = CGPoint(x: startLocation, y: 0.5)
gradient.endPoint = CGPoint(x: 0.0, y: 0.5)
case .right:
gradient.startPoint = CGPoint(x: endLocation, y: 0.5)
gradient.endPoint = CGPoint(x: 1, y: 0.5)
case .horizontal:
gradient.startPoint = CGPoint(x: 0.0, y: 0.5)
gradient.endPoint = CGPoint(x: 1.0, y: 0.5)
gradient.colors = [UIColor.clear.cgColor, UIColor.white.cgColor, UIColor.white.cgColor, UIColor.clear.cgColor]
gradient.locations = [0.0, startLocation, endLocation, 1.0] as [NSNumber]
}
layer.mask = gradient
}
}
You add an alpha mask layer to a view containing your scroll view like this:
CALayer *mask = [CALayer layer];
CGImageRef maskRef = [UIImage imageNamed:#"scrollMask"].CGImage;
CGImageRef maskImage = CGImageMaskCreate(CGImageGetWidth(maskRef),
CGImageGetHeight(maskRef),
CGImageGetBitsPerComponent(maskRef),
CGImageGetBitsPerPixel(maskRef),
CGImageGetBytesPerRow(maskRef),
CGImageGetDataProvider(maskRef), NULL, false);
mask.contents = (__bridge id)maskImage;
mask.frame = CGRectMake(0, 0, view.bounds.size.width, view.bounds.size.height);
view.layer.mask = mask;
view.layer.masksToBounds = YES;
CGImageRelease(maskImage);
where "scrollMask" is a grayscale image defining mask region: white == fully masked, black == not masked at all and gray == partially masked.
To create the effect you're looking for, the mask image would be black with a white gradient at the top like this:
For more details, take a look at the documentation for CGImageMaskCreate.
We built onto Steph Sharp's code to only fade when necessary. Our code is setup as a static utility method instead of a subclass so that we could reuse the method in our subclasses of UIScrollView and UITableView. Here is our static utility method:
#define kScrollViewFadeColorLight [UIColor colorWithRed:0.56 green:0.56 blue:0.56 alpha:0.0]
#define kScrollViewFadeColorDark [UIColor colorWithRed:0.56 green:0.56 blue:0.56 alpha:1.0]
#define kScrollViewScrollBarWidth 7.0
#define kScrollViewFadingEdgeLength 40.0
+ (void) applyFadeToScrollView:(UIScrollView*) scrollView {
CGFloat topOffset = -scrollView.contentInset.top;
CGFloat bottomOffset = scrollView.contentSize.height + scrollView.contentInset.bottom - scrollView.bounds.size.height;
CGFloat distanceFromTop = scrollView.contentOffset.y - topOffset;
CGFloat distanceFromBottom = bottomOffset - scrollView.contentOffset.y;
BOOL isAtTop = distanceFromTop < 1.0;
BOOL isAtBottom = distanceFromBottom < 1.0;
if (isAtTop && isAtBottom) {
// There is no scrolling to be done here, so don't fade anything!
scrollView.layer.mask = nil;
return;
}
NSObject* transparent = (NSObject*)[kScrollViewFadeColorLight CGColor];
NSObject* opaque = (NSObject*)[kScrollViewFadeColorDark CGColor];
CALayer* maskLayer = [CALayer layer];
maskLayer.frame = scrollView.bounds;
CALayer* scrollGutterLayer = [CALayer layer];
scrollGutterLayer.frame = CGRectMake(scrollView.bounds.size.width - kScrollViewScrollBarWidth, 0.0,
kScrollViewScrollBarWidth, scrollView.bounds.size.height);
scrollGutterLayer.backgroundColor = (__bridge CGColorRef)(opaque);
[maskLayer addSublayer:scrollGutterLayer];
CAGradientLayer* gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = CGRectMake(0.0, 0.0, scrollView.bounds.size.width, scrollView.bounds.size.height);
CGFloat fadePercentage = kScrollViewFadingEdgeLength / scrollView.bounds.size.height;
NSMutableArray* colors = [[NSMutableArray alloc] init];
NSMutableArray* locations = [[NSMutableArray alloc] init];
if (!isAtTop) {
[colors addObjectsFromArray:#[transparent, opaque]];
[locations addObjectsFromArray:#[#0.0, [NSNumber numberWithFloat:fadePercentage]]];
}
if (!isAtBottom) {
[colors addObjectsFromArray:#[opaque, transparent]];
[locations addObjectsFromArray:#[[NSNumber numberWithFloat:1.0 - fadePercentage], #1.0]];
}
gradientLayer.colors = colors;
gradientLayer.locations = locations;
[maskLayer addSublayer:gradientLayer];
scrollView.layer.mask = maskLayer;
}
Here is our usage of that method.
FadingScrollView.h
#interface FadingScrollView : UIScrollView
#end
FadingScrollView.m
#implementation FadingScrollView
- (void) layoutSubviews {
[super layoutSubviews];
[Utils applyFadeToScrollView:self];
}
#end
FadingTableView.h
#interface FadingTableView : UITableView
#end
FadingTableView.m
#implementation FadingTableView
- (void) layoutSubviews {
[super layoutSubviews];
[Utils applyFadeToScrollView:self];
}
#end

iPhone iOS how to add linear gradient to a UIButton under the button's text or image?

I'm working on creating fancy looking UIbuttons by adding linear gradient to the button. However I'm not sure at which index I need to add the gradient. The code that I have currently places the gradient over the image/text.
How can I insert a sublayer to a UIButton under the text/image sublayer? It is important for me to keep the text and the image of a button visible!
+(void)addLinearGradientToView:(UIView*)view TopColor:(UIColor*)topColor BottomColor:(UIColor*)bottomColor
{
for(CALayer* layer in view.layer.sublayers)
{
if ([layer isKindOfClass:[CAGradientLayer class]])
{
[layer removeFromSuperlayer];
}
}
CAGradientLayer* gradientLayer = [CAGradientLayer layer];
gradientLayer.startPoint = CGPointMake(0.5, 0);
gradientLayer.endPoint = CGPointMake(0.5,1);
gradientLayer.frame = view.bounds;
gradientLayer.colors = [NSArray arrayWithObjects:(id)[topColor CGColor], (id)[bottomColor CGColor], nil];
// [view.layer addSublayer:gradientLayer];
if(view.layer.sublayers.count>0)
{
[view.layer insertSublayer:gradientLayer atIndex:view.layer.sublayers.count-2];
}else {
[view.layer addSublayer:gradientLayer];
}
}
Add it to the layer of your custom button:
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = customButton.layer.bounds;
gradientLayer.colors = [NSArray arrayWithObjects:
(id)[UIColor colorWithWhite:1.0f alpha:0.1f].CGColor,
(id)[UIColor colorWithWhite:0.4f alpha:0.5f].CGColor,
nil];
gradientLayer.locations = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0.0f],
[NSNumber numberWithFloat:1.0f],
nil];
gradientLayer.cornerRadius = customButton.layer.cornerRadius;
[customButton.layer addSublayer:gradientLayer];
where customButton is your custom UIButton.
For what you are trying to achieve, always insert at index 0.
I wrote an article about this sort of thing recently that you may find interesting.
Swift 4
#IBOutlet weak var gButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
let topGradientColor = UIColor.red
let bottomGradientColor = UIColor.yellow
let gradientLayer = CAGradientLayer()
gradientLayer.frame = gButton.bounds
gradientLayer.colors = [topGradientColor.cgColor, bottomGradientColor.cgColor]
//Vertical
//gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.0)
//gradientLayer.endPoint = CGPoint(x: 0.0, y: 1.0)
//Horizontal
gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.0)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.0)
gButton.layer.insertSublayer(gradientLayer, at: 0)
}
Here is the code to achieve this
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = <#View Variable Name#>.bounds;
gradient.colors = [NSArray arrayWithObjects:(id)([UIColor colorWithRed:0.337 green:0.596 blue:1.000 alpha:1.000].CGColor),(id)([UIColor colorWithRed:0.757 green:0.459 blue:1.000 alpha:1.000].CGColor),(id)([UIColor colorWithRed:0.310 green:0.835 blue:1.000 alpha:1.000].CGColor),nil];
gradient.startPoint = CGPointMake(0.5,0.0);
gradient.endPoint = CGPointMake(0.5,1.0);
[<#View Variable name#>.layer insertSublayer:gradient atIndex:0];
I got this tool that makes it easy to select your colors and automatically output the code for the gradient. Very handy I use it everyday
itunes.apple.com/gb/app/gradient-creator/id1031070259?mt=12

Applying a Gradient to CAShapeLayer

Does anyone have any experience in applying a Gradient to a CAShapeLayer? CAShapeLayer is a fantastic layer class, but it appears to only support solid fill coloring, whereas I'd like it to have a gradient fill (actually an animatable gradient at that).
Everything else to do with CAShapeLayer (shadows, shapes, stroke color, animatable shape path) is fantastic.
I've tried placing a CAGradientLayer inside a CAShapeLayer, or indeed setting the CAShapeLayer as the mask of the GradientLayer and adding both to a container layer, but these don't have the right outcome.
Should I subclass CAShapeLayer, or is there a better way forward?
Thanks.
You could use the path of your shape to create a masking layer and apply that on the gradient layer, like this:
UIView *v = [[UIView alloc] initWithFrame:self.window.frame];
CAShapeLayer *gradientMask = [CAShapeLayer layer];
gradientMask.fillColor = [[UIColor clearColor] CGColor];
gradientMask.strokeColor = [[UIColor blackColor] CGColor];
gradientMask.lineWidth = 4;
gradientMask.frame = CGRectMake(0, 0, v.bounds.size.width, v.bounds.size.height);
CGMutablePathRef t = CGPathCreateMutable();
CGPathMoveToPoint(t, NULL, 0, 0);
CGPathAddLineToPoint(t, NULL, v.bounds.size.width, v.bounds.size.height);
gradientMask.path = t;
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
gradientLayer.startPoint = CGPointMake(0.5,1.0);
gradientLayer.endPoint = CGPointMake(0.5,0.0);
gradientLayer.frame = CGRectMake(0, 0, v.bounds.size.width, v.bounds.size.height);
NSMutableArray *colors = [NSMutableArray array];
for (int i = 0; i < 10; i++) {
[colors addObject:(id)[[UIColor colorWithHue:(0.1 * i) saturation:1 brightness:.8 alpha:1] CGColor]];
}
gradientLayer.colors = colors;
[gradientLayer setMask:gradientMask];
[v.layer addSublayer:gradientLayer];
If you want to also use the shadows, you would have to place a "duplicate" of the shape layer under the gradient layer, recycling the same path reference.
Many thanks to #Palimondo for a great answer!
In case someone is looking for Swift 4 + filling animation code of this solution:
let myView = UIView(frame: .init(x: 0, y: 0, width: 200, height: 150))
view.addSubview(myView)
myView.center = view.center
// Start and finish point
let startPoint = CGPoint(x: myView.bounds.minX, y: myView.bounds.midY)
let finishPoint = CGPoint(x: myView.bounds.maxX, y: myView.bounds.midY)
// Path
let path = UIBezierPath()
path.move(to: startPoint)
path.addLine(to: finishPoint)
// Gradient Mask
let gradientMask = CAShapeLayer()
let lineHeight = myView.frame.height
gradientMask.fillColor = UIColor.clear.cgColor
gradientMask.strokeColor = UIColor.black.cgColor
gradientMask.lineWidth = lineHeight
gradientMask.frame = myView.bounds
gradientMask.path = path.cgPath
// Gradient Layer
let gradientLayer = CAGradientLayer()
gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5)
// make sure to use .cgColor
gradientLayer.colors = [UIColor.red.cgColor, UIColor.green.cgColor]
gradientLayer.frame = myView.bounds
gradientLayer.mask = gradientMask
myView.layer.addSublayer(gradientLayer)
// Corner radius
myView.layer.cornerRadius = 10
myView.clipsToBounds = true
Extra. In case you also need a "filling animation", add this lines:
// Filling animation
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.fromValue = 0
animation.duration = 10
gradientMask.add(animation, forKey: "LineAnimation")
This is a great solution, but you might encounter unexpected problems if you're creating a category on CAShapeLayer where you don't immediately have the view.
See Setting correct frame of a newly created CAShapeLayer
Bottom line, get the bounds of the path then set the gradient mask's frame using the path bounds and translate as necessary. Good thing here is that by using the path's bounds rather than any other frame, the gradient will only fit within the path bounds (assuming that's what you want).
// Category on CAShapeLayer
CGRect pathBounds = CGPathGetBoundingBox(self.path);
CAShapeLayer *gradientMask = [CAShapeLayer layer];
gradientMask.fillColor = [[UIColor blackColor] CGColor];
gradientMask.frame = CGRectMake(0, 0, pathBounds.size.width, pathBounds.size.height);
gradientMask.path = self.path;
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
gradientLayer.startPoint = CGPointMake(0.5,1.0);
gradientLayer.endPoint = CGPointMake(0.5,0.0);
gradientLayer.frame = CGRectMake(0, 0, pathBounds.size.width, pathBounds.size.height);
NSMutableArray *colors = [NSMutableArray array];
for (int i = 0; i < 10; i++) {
[colors addObject:(id)[[UIColor colorWithHue:(0.1 * i) saturation:1 brightness:.8 alpha:1] CGColor]];
}
gradientLayer.colors = colors;
[gradientLayer setMask:gradientMask];
[self addSublayer:gradientLayer];

Can't add a corner radius and a shadow

I'm trying to draw a shadow and a corner radius on an image. I can add them separately, but I've got no way to add both effects at the same time. I'm adding a shadow with:
[layer setShadowOffset:CGSizeMake(0, 3)];
[layer setShadowOpacity:0.4];
[layer setShadowRadius:3.0f];
[layer setShouldRasterize:YES];
Here, layer is a CALayer of a UIView subclass. So this works whenever I set
[layer setMasksToBounds:NO];
Now to add a corner radius I do this:
[layer setCornerRadius:7.0f];
but I need to set MasksToBounds to YES in order for this to work:
[layer setMasksToBounds:YES];
Is there anyway I can get both of these effects to add?
Thanks for your time,
Denis
Yes, yes there is...
If you want both a corner radius and a drop shadow, you don't turn on -masksToBounds, but rather set the corner radius and set the bezier path of the shadow with a rounded rect. Keep the radius of the two the same:
[layer setShadowOffset:CGSizeMake(0, 3)];
[layer setShadowOpacity:0.4];
[layer setShadowRadius:3.0f];
[layer setShouldRasterize:YES];
[layer setCornerRadius:12.0f];
[layer setShadowPath:
[[UIBezierPath bezierPathWithRoundedRect:[self bounds]
cornerRadius:12.0f] CGPath]];
You might want to check your performance without the -shouldRasterize parameter set once you're setting the shadow path. Drawing performance tends to be very good once you've set a shadow path.
UPDATE
I hadn't looked at this problem in quite awhile, but it appears that you no longer need to set a shadowPath in order to get this to work. Simply setting the cornerRadius and shadowOpacity will work now. I think this has been the case since iOS5 (as far as I can tell). Providing this update is probably unnecessary since setting those parameters 'just works', but I'll provide it for posterity sake. To recap, this is now all you need:
[layer setShadowOpacity:0.4];
[layer setCornerRadius:12.0f];
If you still need better performance, you can go ahead and set the shouldRasterize parameter as well:
[layer setShouldRasterize:YES];
And speaking of performance, it's worth noting that if you are noticing sluggish animations, you will want to use the technique of setting the shadow path after all. This update was really just to point out that setting the path is no longer required to achieve the effect of displaying both a corner radius and a shadow at the same time. If performance is your priority, though, use a path.
UPDATE 2
Since people seem to be having trouble getting this to work in some instances, I'll post a more complete code snippet here from a sample project I created:
- (void)viewDidLoad
{
[super viewDidLoad];
CALayer *layer = [CALayer layer];
[layer setBounds:CGRectMake(0.0f, 0.0f, 100.0f, 200.0f)];
[layer setPosition:[[self view] center]];
[layer setBackgroundColor:[[UIColor lightGrayColor] CGColor]];
[layer setShadowOpacity:0.55f];
[layer setCornerRadius:8.0f];
[layer setBorderWidth:1.0f];
[[[self view] layer] addSublayer:layer];
[[[self testView] layer] setShadowOpacity:0.55f];
[[[self testView] layer] setShadowRadius:15.0f];
[[[self testView] layer] setCornerRadius:8.0f];
[[[self testView] layer] setBorderWidth:1.0f];
}
The testView is a UIView I added in Interface Builder and set an outlet on. This is to make sure it's working the same on both layers you add explicitly as well as the layers within subviews.
I've tested this on the simulators for iOS5 through iOS6.1. It gives this result for me in each of them:
Because I use a UIButton with a background image none of these answers worked for me. I keep getting either no shadow or no round edges on my buttons.
The easiest way in my scenario was to just add another view behind the button and add the shadow to it like so:
button.clipsToBounds=YES;
button.layer.cornerRadius = 25;
UIView *shadowView = [[UIView alloc]initWithFrame:button.frame];
shadowView.backgroundColor = [UIColor whiteColor];//needs this to cast shadow
shadowView.layer.cornerRadius = 25;
shadowView.clipsToBounds = YES;
shadowView.layer.masksToBounds = NO;
shadowView.layer.shadowOffset = CGSizeMake(0, 2);
shadowView.layer.shadowRadius = 1;
shadowView.layer.shadowOpacity = 0.2;
[[button superview]addSubview:shadowView];
[[button superview]bringSubviewToFront:button];
The following Swift 3 code shows how to draw a shadow and a corner radius on an image by using CAShapeLayer and CALayer.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// constants
let radius: CGFloat = 20, offset = 8
let rect = CGRect(x: 0, y: 0, width: 200, height: 200)
// roundedView
let roundedView = UIView()
view.addSubview(roundedView)
// shadow layer
let shadowLayer = CALayer()
shadowLayer.shadowColor = UIColor.darkGray.cgColor
shadowLayer.shadowPath = UIBezierPath(roundedRect: rect, cornerRadius: radius).cgPath
shadowLayer.shadowOffset = CGSize(width: offset, height: offset)
shadowLayer.shadowOpacity = 0.8
shadowLayer.shadowRadius = 2
roundedView.layer.addSublayer(shadowLayer)
// mask layer
let maskLayer = CAShapeLayer()
maskLayer.path = UIBezierPath(roundedRect: rect, cornerRadius: radius).cgPath
// image layer
let imageLayer = CALayer()
imageLayer.mask = maskLayer
imageLayer.frame = rect
imageLayer.contentsGravity = kCAGravityResizeAspectFill
imageLayer.contents = UIImage(named: "image")?.cgImage
roundedView.layer.addSublayer(imageLayer)
// auto layout
roundedView.translatesAutoresizingMaskIntoConstraints = false
roundedView.widthAnchor.constraint(equalToConstant: rect.width).isActive = true
roundedView.heightAnchor.constraint(equalToConstant: rect.height).isActive = true
roundedView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
roundedView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
}
This code generates the following display:
The previous code can be refactored into the following swift files:
CustomView.swift
import UIKit
class CustomView: UIView {
var imageLayer: CALayer!
var image: UIImage? {
didSet { refreshImage() }
}
override var intrinsicContentSize:
CGSize {
return CGSize(width: 200, height: 200)
}
func refreshImage() {
if let imageLayer = imageLayer, let image = image {
imageLayer.contents = image.cgImage
}
}
override func layoutSubviews() {
super.layoutSubviews()
if imageLayer == nil {
let radius: CGFloat = 20, offset: CGFloat = 8
let shadowLayer = CALayer()
shadowLayer.shadowColor = UIColor.darkGray.cgColor
shadowLayer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: radius).cgPath
shadowLayer.shadowOffset = CGSize(width: offset, height: offset)
shadowLayer.shadowOpacity = 0.8
shadowLayer.shadowRadius = 2
layer.addSublayer(shadowLayer)
let maskLayer = CAShapeLayer()
maskLayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: radius).cgPath
imageLayer = CALayer()
imageLayer.mask = maskLayer
imageLayer.frame = bounds
imageLayer.backgroundColor = UIColor.red.cgColor
imageLayer.contentsGravity = kCAGravityResizeAspectFill
layer.addSublayer(imageLayer)
}
refreshImage()
}
}
ViewController.swift
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let roundedView = CustomView()
roundedView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(roundedView)
// auto layout
let horizontalConstraint = roundedView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
let verticalConstraint = roundedView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
NSLayoutConstraint.activate([horizontalConstraint, verticalConstraint])
roundedView.image = UIImage(named: "image")
}
}
You can find more ways to combine images with rounded corners and shadow on this Github repo.
view.layer.cornerRadius=4;
[view.layer setMasksToBounds:YES];
[view.layer setShadowColor:SHADOW_COLOR];
[view.layer setShadowOpacity:4 ];
[view.layer setShadowRadius:4];
[view.layer setShadowOffset:0];
[view.layer setShadowPath: [[UIBezierPath bezierPathWithRoundedRect:[view bounds] cornerRadius:CORNER_RADIUS] CGPath]];
view.layer.borderColor=[[UIColor lightGrayColor] colorWithAlphaComponent:.5].CGColor;
view.layer.borderWidth=.4;

Gradients on UIView and UILabels On iPhone [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Manually drawing a gradient in iPhone apps?
My application needs to display text in either a UIView or UILabel but the back ground must be a gradient as opposed to a true UIColor. Using a graphics program to create desired look is no good as the text may vary depending on data returned from a server.
Does anyone know the quickest way to tackle this?
Your thoughts are greatly appreciated.
I realize this is an older thread, but for future reference:
As of iPhone SDK 3.0, custom gradients can be implemented very easily, without subclassing or images, by using the new CAGradientLayer:
UIView *view = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 100)] autorelease];
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = view.bounds;
gradient.colors = [NSArray arrayWithObjects:(id)[[UIColor blackColor] CGColor], (id)[[UIColor whiteColor] CGColor], nil];
[view.layer insertSublayer:gradient atIndex:0];
Take a look at the CAGradientLayer docs. You can optionally specify start and end points (in case you don't want a linear gradient that goes straight from the top to the bottom), or even specific locations that map to each of the colors.
You can use Core Graphics to draw the gradient, as pointed to in Mike's response. As a more detailed example, you could create a UIView subclass to use as a background for your UILabel. In that UIView subclass, override the drawRect: method and insert code similar to the following:
- (void)drawRect:(CGRect)rect
{
CGContextRef currentContext = UIGraphicsGetCurrentContext();
CGGradientRef glossGradient;
CGColorSpaceRef rgbColorspace;
size_t num_locations = 2;
CGFloat locations[2] = { 0.0, 1.0 };
CGFloat components[8] = { 1.0, 1.0, 1.0, 0.35, // Start color
1.0, 1.0, 1.0, 0.06 }; // End color
rgbColorspace = CGColorSpaceCreateDeviceRGB();
glossGradient = CGGradientCreateWithColorComponents(rgbColorspace, components, locations, num_locations);
CGRect currentBounds = self.bounds;
CGPoint topCenter = CGPointMake(CGRectGetMidX(currentBounds), 0.0f);
CGPoint midCenter = CGPointMake(CGRectGetMidX(currentBounds), CGRectGetMidY(currentBounds));
CGContextDrawLinearGradient(currentContext, glossGradient, topCenter, midCenter, 0);
CGGradientRelease(glossGradient);
CGColorSpaceRelease(rgbColorspace);
}
This particular example creates a white, glossy-style gradient that is drawn from the top of the UIView to its vertical center. You can set the UIView's backgroundColor to whatever you like and this gloss will be drawn on top of that color. You can also draw a radial gradient using the CGContextDrawRadialGradient function.
You just need to size this UIView appropriately and add your UILabel as a subview of it to get the effect you desire.
EDIT (4/23/2009): Per St3fan's suggestion, I have replaced the view's frame with its bounds in the code. This corrects for the case when the view's origin is not (0,0).
Note: The results below apply to older versions of iOS, but when testing on iOS 13 the stepping doesn't occur. I don't know for which version of iOS the stepping was removed.
When using CAGradientLayer, as opposed to CGGradient, the gradient is not smooth, but has noticeable stepping to it. See :
To get more attractive results it is better to use CGGradient.
You could also use a graphic image one pixel wide as the gradient, and set the view property to expand the graphic to fill the view (assuming you are thinking of a simple linear gradient and not some kind of radial graphic).
Mirko Froehlich's answer worked for me, except when i wanted to use custom colors. The trick is to specify UI color with Hue, saturation and brightness instead of RGB.
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = myView.bounds;
UIColor *startColour = [UIColor colorWithHue:.580555 saturation:0.31 brightness:0.90 alpha:1.0];
UIColor *endColour = [UIColor colorWithHue:.58333 saturation:0.50 brightness:0.62 alpha:1.0];
gradient.colors = [NSArray arrayWithObjects:(id)[startColour CGColor], (id)[endColour CGColor], nil];
[myView.layer insertSublayer:gradient atIndex:0];
To get the Hue, Saturation and Brightness of a color, use the in built xcode color picker and go to the HSB tab. Hue is measured in degrees in this view, so divide the value by 360 to get the value you will want to enter in code.
This is what I got working- set UIButton in xCode's IB to transparent/clear, and no bg image.
UIColor *pinkDarkOp = [UIColor colorWithRed:0.9f green:0.53f blue:0.69f alpha:1.0];
UIColor *pinkLightOp = [UIColor colorWithRed:0.79f green:0.45f blue:0.57f alpha:1.0];
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = [[shareWordButton layer] bounds];
gradient.cornerRadius = 7;
gradient.colors = [NSArray arrayWithObjects:
(id)pinkDarkOp.CGColor,
(id)pinkLightOp.CGColor,
nil];
gradient.locations = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0.0f],
[NSNumber numberWithFloat:0.7],
nil];
[[recordButton layer] insertSublayer:gradient atIndex:0];
I achieve this in a view with a subview that is an UIImageView. The image the ImageView is pointing to is a gradient. Then I set a background color in the UIView, and I have a colored gradient view. Next I use the view as I need to and everything I draw will be under this gradient view. By adding a second view on top of the ImageView, you can have some options whether your drawing will be below or above the gradient...