Any ideas as to how Apple implemented the "slide to unlock" (also, "slide to power off" is another identical example) animation?
I thought about some sort of animating mask - but masking is not available on the iPhone OS for performance reasons.
Is there some private API effect (like SuckEffect) that they might have used? A spotlight type of effect? Some Core Animation thing?
Edit:
It's definitely not a series of stills. I've seen examples of being edit a plist value or something and customize the string on jailbroken iphones.
It can be easilly done by using Core Animation, animating a mask layer on the layer displaying the text.
Try this in any plain UIViewController (you can start with a new Xcode project based on the View-based application project template), or grab my Xcode project here:
Note that the CALayer.mask property is only available in iPhone OS 3.0 and later.
- (void)viewDidLoad
{
self.view.layer.backgroundColor = [[UIColor blackColor] CGColor];
UIImage *textImage = [UIImage imageNamed:#"SlideToUnlock.png"];
CGFloat textWidth = textImage.size.width;
CGFloat textHeight = textImage.size.height;
CALayer *textLayer = [CALayer layer];
textLayer.contents = (id)[textImage CGImage];
textLayer.frame = CGRectMake(10.0f, 215.0f, textWidth, textHeight);
CALayer *maskLayer = [CALayer layer];
// Mask image ends with 0.15 opacity on both sides. Set the background color of the layer
// to the same value so the layer can extend the mask image.
maskLayer.backgroundColor = [[UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.15f] CGColor];
maskLayer.contents = (id)[[UIImage imageNamed:#"Mask.png"] CGImage];
// Center the mask image on twice the width of the text layer, so it starts to the left
// of the text layer and moves to its right when we translate it by width.
maskLayer.contentsGravity = kCAGravityCenter;
maskLayer.frame = CGRectMake(-textWidth, 0.0f, textWidth * 2, textHeight);
// Animate the mask layer's horizontal position
CABasicAnimation *maskAnim = [CABasicAnimation animationWithKeyPath:#"position.x"];
maskAnim.byValue = [NSNumber numberWithFloat:textWidth];
maskAnim.repeatCount = HUGE_VALF;
maskAnim.duration = 1.0f;
[maskLayer addAnimation:maskAnim forKey:#"slideAnim"];
textLayer.mask = maskLayer;
[self.view.layer addSublayer:textLayer];
[super viewDidLoad];
}
The images used by this code are:
Yet another solution using a layer mask, but instead draws the gradient by hand and does not require images. View is the view with the animation, transparency is a float from 0 - 1 defining the amount of transparency (1 = no transparency which is pointless), and gradientWidth is the desired width of the gradient.
CAGradientLayer *gradientMask = [CAGradientLayer layer];
gradientMask.frame = view.bounds;
CGFloat gradientSize = gradientWidth / view.frame.size.width;
UIColor *gradient = [UIColor colorWithWhite:1.0f alpha:transparency];
NSArray *startLocations = #[[NSNumber numberWithFloat:0.0f], [NSNumber numberWithFloat:(gradientSize / 2)], [NSNumber numberWithFloat:gradientSize]];
NSArray *endLocations = #[[NSNumber numberWithFloat:(1.0f - gradientSize)], [NSNumber numberWithFloat:(1.0f -(gradientSize / 2))], [NSNumber numberWithFloat:1.0f]];
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"locations"];
gradientMask.colors = #[(id)gradient.CGColor, (id)[UIColor whiteColor].CGColor, (id)gradient.CGColor];
gradientMask.locations = startLocations;
gradientMask.startPoint = CGPointMake(0 - (gradientSize * 2), .5);
gradientMask.endPoint = CGPointMake(1 + gradientSize, .5);
view.layer.mask = gradientMask;
animation.fromValue = startLocations;
animation.toValue = endLocations;
animation.repeatCount = HUGE_VALF;
animation.duration = 3.0f;
[gradientMask addAnimation:animation forKey:#"animateGradient"];
SWIFT VERSION:
let transparency:CGFloat = 0.5
let gradientWidth: CGFloat = 40
let gradientMask = CAGradientLayer()
gradientMask.frame = swipeView.bounds
let gradientSize = gradientWidth/swipeView.frame.size.width
let gradient = UIColor(white: 1, alpha: transparency)
let startLocations = [0, gradientSize/2, gradientSize]
let endLocations = [(1 - gradientSize), (1 - gradientSize/2), 1]
let animation = CABasicAnimation(keyPath: "locations")
gradientMask.colors = [gradient.CGColor, UIColor.whiteColor().CGColor, gradient.CGColor]
gradientMask.locations = startLocations
gradientMask.startPoint = CGPointMake(0 - (gradientSize*2), 0.5)
gradientMask.endPoint = CGPointMake(1 + gradientSize, 0.5)
swipeView.layer.mask = gradientMask
animation.fromValue = startLocations
animation.toValue = endLocations
animation.repeatCount = HUGE
animation.duration = 3
gradientMask.addAnimation(animation, forKey: "animateGradient")
Swift 3
fileprivate func addGradientMaskToView(view:UIView, transparency:CGFloat = 0.5, gradientWidth:CGFloat = 40.0) {
let gradientMask = CAGradientLayer()
gradientMask.frame = view.bounds
let gradientSize = gradientWidth/view.frame.size.width
let gradientColor = UIColor(white: 1, alpha: transparency)
let startLocations = [0, gradientSize/2, gradientSize]
let endLocations = [(1 - gradientSize), (1 - gradientSize/2), 1]
let animation = CABasicAnimation(keyPath: "locations")
gradientMask.colors = [gradientColor.cgColor, UIColor.white.cgColor, gradientColor.cgColor]
gradientMask.locations = startLocations as [NSNumber]?
gradientMask.startPoint = CGPoint(x:0 - (gradientSize * 2), y: 0.5)
gradientMask.endPoint = CGPoint(x:1 + gradientSize, y: 0.5)
view.layer.mask = gradientMask
animation.fromValue = startLocations
animation.toValue = endLocations
animation.repeatCount = HUGE
animation.duration = 3
gradientMask.add(animation, forKey: nil)
}
You can use the kCGTextClip drawing mode to set the clipping path and then fill with a gradient.
// Get Context
CGContextRef context = UIGraphicsGetCurrentContext();
// Set Font
CGContextSelectFont(context, "Helvetica", 24.0, kCGEncodingMacRoman);
// Set Text Matrix
CGAffineTransform xform = CGAffineTransformMake(1.0, 0.0,
0.0, -1.0,
0.0, 0.0);
CGContextSetTextMatrix(context, xform);
// Set Drawing Mode to set clipping path
CGContextSetTextDrawingMode (context, kCGTextClip);
// Draw Text
CGContextShowTextAtPoint (context, 0, 20, "Gradient", strlen("Gradient"));
// Calculate Text width
CGPoint textEnd = CGContextGetTextPosition(context);
// Generate Gradient locations & colors
size_t num_locations = 3;
CGFloat locations[3] = { 0.3, 0.5, 0.6 };
CGFloat components[12] = {
1.0, 1.0, 1.0, 0.5,
1.0, 1.0, 1.0, 1.0,
1.0, 1.0, 1.0, 0.5,
};
// Load Colorspace
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
// Create Gradient
CGGradientRef gradient = CGGradientCreateWithColorComponents (colorspace, components,
locations, num_locations);
// Draw Gradient (using clipping path
CGContextDrawLinearGradient (context, gradient, rect.origin, textEnd, 0);
// Cleanup (exercise for reader)
Setup an NSTimer and vary the values in locations, or use CoreAnimation to do the same.
I added the code provided above by Pascal as a category on UILabel so you can animate any UILabel in this fashion. Here's the code. Some params might need to be changed for your background colors, etc. It uses the same mask image that Pascal has embedded in his answer.
//UILabel+FSHighlightAnimationAdditions.m
#import "UILabel+FSHighlightAnimationAdditions.h"
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#implementation UILabel (FSHighlightAnimationAdditions)
- (void)setTextWithChangeAnimation:(NSString*)text
{
NSLog(#"value changing");
self.text = text;
CALayer *maskLayer = [CALayer layer];
// Mask image ends with 0.15 opacity on both sides. Set the background color of the layer
// to the same value so the layer can extend the mask image.
maskLayer.backgroundColor = [[UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.15f] CGColor];
maskLayer.contents = (id)[[UIImage imageNamed:#"Mask.png"] CGImage];
// Center the mask image on twice the width of the text layer, so it starts to the left
// of the text layer and moves to its right when we translate it by width.
maskLayer.contentsGravity = kCAGravityCenter;
maskLayer.frame = CGRectMake(self.frame.size.width * -1, 0.0f, self.frame.size.width * 2, self.frame.size.height);
// Animate the mask layer's horizontal position
CABasicAnimation *maskAnim = [CABasicAnimation animationWithKeyPath:#"position.x"];
maskAnim.byValue = [NSNumber numberWithFloat:self.frame.size.width];
maskAnim.repeatCount = 1e100f;
maskAnim.duration = 2.0f;
[maskLayer addAnimation:maskAnim forKey:#"slideAnim"];
self.layer.mask = maskLayer;
}
#end
//UILabel+FSHighlightAnimationAdditions.h
#import <Foundation/Foundation.h>
#interface UILabel (FSHighlightAnimationAdditions)
- (void)setTextWithChangeAnimation:(NSString*)text;
#end
not so fresh...but maybe it'll be useful
#define MM_TEXT_TO_DISPLAY #"default"
#define MM_FONT [UIFont systemFontOfSize:MM_FONT_SIZE]
#define MM_FONT_SIZE 25
#define MM_FONT_COLOR [[UIColor darkGrayColor] colorWithAlphaComponent:0.75f];
#define MM_SHADOW_ENABLED NO
#define MM_SHADOW_COLOR [UIColor grayColor]
#define MM_SHADOW_OFFSET CGSizeMake(-1,-1)
#define MM_CONTENT_EDGE_INSETS_TOP 0
#define MM_CONTENT_EDGE_INSETS_LEFT 10
#define MM_CONTENT_EDGE_INSETS_BOTTON 0
#define MM_CONTENT_EDGE_INSETS_RIGHT 10
#define MM_CONTENT_EDGE_INSETS UIEdgeInsetsMake(MM_CONTENT_EDGE_INSETS_TOP, MM_CONTENT_EDGE_INSETS_LEFT, MM_CONTENT_EDGE_INSETS_BOTTON, MM_CONTENT_EDGE_INSETS_RIGHT)
#define MM_TEXT_ALIGNMENT UITextAlignmentCenter
#define MM_BACKGROUND_COLOR [UIColor clearColor]
#define MM_TIMER_INTERVAL 0.05f
#define MM_HORIZONTAL_SPAN 5
#interface MMAnimatedGradientLabel : UILabel {
NSString *textToDisplay;
int text_length;
CGGradientRef gradient;
int current_position_x;
NSTimer *timer;
CGPoint alignment;
CGGlyph *_glyphs;
}
- (id)initWithString:(NSString *)_string;
- (void)startAnimation;
- (void)toggle;
- (BOOL)isAnimating;
#end
#define RGB_COMPONENTS(r, g, b, a) (r) / 255.0f, (g) / 255.0f, (b) / 255.0f, (a)
#interface MMAnimatedGradientLabel (Private)
- (CGRect)calculateFrame;
#end
#implementation MMAnimatedGradientLabel
// Missing in standard headers.
extern void CGFontGetGlyphsForUnichars(CGFontRef, const UniChar[], const CGGlyph[], size_t);
- (id)init {
textToDisplay = MM_TEXT_TO_DISPLAY;
return [self initWithFrame:[self calculateFrame]];
}
- (id)initWithString:(NSString *)_string {
textToDisplay = _string;
return [self initWithFrame:[self calculateFrame]];
}
-(id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// set default values
//
self.textAlignment = MM_TEXT_ALIGNMENT;
self.backgroundColor = MM_BACKGROUND_COLOR;
self.font = MM_FONT;
self.text = textToDisplay;
self.textColor = MM_FONT_COLOR;
if (MM_SHADOW_ENABLED) {
self.shadowColor = MM_SHADOW_COLOR;
self.shadowOffset = MM_SHADOW_OFFSET;
}
text_length = -1;
CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
CGFloat colors[] =
{
RGB_COMPONENTS(255.0, 255.0, 255.0, 0.00),
// RGB_COMPONENTS(255.0, 255.0, 255.0, 0.15),
RGB_COMPONENTS(255.0, 255.0, 255.0, 0.95),
// RGB_COMPONENTS(255.0, 255.0, 255.0, 0.15),
RGB_COMPONENTS(255.0, 255.0, 255.0, 0.00)
};
gradient = CGGradientCreateWithColorComponents(rgb, colors, NULL, sizeof(colors)/(sizeof(colors[0])*4));
CGColorSpaceRelease(rgb);
current_position_x = -(frame.size.width/2);// - MM_CONTENT_EDGE_INSETS.left - MM_CONTENT_EDGE_INSETS.right);
}
return self;
}
- (CGRect)calculateFrame {
CGSize size = [textToDisplay sizeWithFont:MM_FONT];
NSLog(#"size: %f, %f", size.width, size.height);
return CGRectMake(0, 0, size.width + MM_CONTENT_EDGE_INSETS.left + MM_CONTENT_EDGE_INSETS.right, size.height + MM_CONTENT_EDGE_INSETS.top + MM_CONTENT_EDGE_INSETS.bottom);
}
- (void)tick:(NSTimer*)theTimer {
if (current_position_x < self.frame.size.width)
current_position_x = current_position_x + MM_HORIZONTAL_SPAN;
else
current_position_x = -(self.frame.size.width/2); // - MM_CONTENT_EDGE_INSETS.left - MM_CONTENT_EDGE_INSETS.right);
[self setNeedsDisplay];
}
- (void)startAnimation {
timer = [[NSTimer alloc] initWithFireDate:[NSDate date]
interval:MM_TIMER_INTERVAL
target:self
selector:#selector(tick:)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
}
- (void)toggle {
if (!timer) {
timer = [[NSTimer alloc] initWithFireDate:[NSDate date]
interval:MM_TIMER_INTERVAL
target:self
selector:#selector(tick:)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
} else {
[timer invalidate];
[timer release];
timer = nil;
current_position_x = -(self.frame.size.width/2);
[self setNeedsDisplay];
}
}
- (BOOL)isAnimating {
if (timer)
return YES;
else
return NO;
}
- (void)drawRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext();
// Get drawing font.
CGFontRef font = CGFontCreateWithFontName((CFStringRef)[[self font] fontName]);
CGContextSetFont(ctx, font);
CGContextSetFontSize(ctx, [[self font] pointSize]);
// Calculate text drawing point only first time
//
if (text_length == -1) {
// Transform text characters to unicode glyphs.
text_length = [[self text] length];
unichar chars[text_length];
[[self text] getCharacters:chars range:NSMakeRange(0, text_length)];
_glyphs = malloc(sizeof(CGGlyph) * text_length);
for (int i=0; i<text_length;i ++)
_glyphs[i] = chars[i] - 29;
// Measure text dimensions.
CGContextSetTextDrawingMode(ctx, kCGTextInvisible);
CGContextSetTextPosition(ctx, 0, 0);
CGContextShowGlyphs(ctx, _glyphs, text_length);
CGPoint textEnd = CGContextGetTextPosition(ctx);
// Calculate text drawing point.
CGPoint anchor = CGPointMake(textEnd.x * (-0.5), [[self font] pointSize] * (-0.25));
CGPoint p = CGPointApplyAffineTransform(anchor, CGAffineTransformMake(1, 0, 0, -1, 0, 1));
if ([self textAlignment] == UITextAlignmentCenter)
alignment.x = [self bounds].size.width * 0.5 + p.x;
else if ([self textAlignment] == UITextAlignmentLeft)
alignment.x = 0;
else
alignment.x = [self bounds].size.width - textEnd.x;
alignment.y = [self bounds].size.height * 0.5 + p.y;
}
// Flip back mirrored text.
CGContextSetTextMatrix(ctx, CGAffineTransformMakeScale(1, -1));
// Draw shadow.
CGContextSaveGState(ctx);
CGContextSetTextDrawingMode(ctx, kCGTextFill);
CGContextSetFillColorWithColor(ctx, [[self textColor] CGColor]);
CGContextSetShadowWithColor(ctx, [self shadowOffset], 0, [[self shadowColor] CGColor]);
CGContextShowGlyphsAtPoint(ctx, alignment.x, alignment.y, _glyphs, text_length);
CGContextRestoreGState(ctx);
// Draw text clipping path.
CGContextSetTextDrawingMode(ctx, kCGTextClip);
CGContextShowGlyphsAtPoint(ctx, alignment.x, alignment.y, _glyphs, text_length);
// Restore text mirroring.
CGContextSetTextMatrix(ctx, CGAffineTransformIdentity);
if ([self isAnimating]) {
// Fill text clipping path with gradient.
CGPoint start = CGPointMake(rect.origin.x + current_position_x, rect.origin.y);
CGPoint end = CGPointMake(rect.size.width/3*2 + current_position_x, rect.origin.y);
CGContextDrawLinearGradient(ctx, gradient, start, end, 0);
}
}
- (void) dealloc {
free(_glyphs);
[timer invalidate];
[timer release];
CGGradientRelease(gradient);
[super dealloc];
}
Thanks to rpetrich for the clipping gradient recipe. I am a newbie iPhone and Cocoa developer, so I was real happy to find it.
I've implemented a decent-looking Slide to Cancel UIViewController using rpetrich's method. You can download the Xcode project of my implementation from here.
My implementation uses a repeating NSTimer. I was unable to figure out how use Core (or Gore) Animation to have the iPhone's graphics engine continuously move the highlighting. I think that could be done on OS X with CALayer mask layers, but mask layers are not supported on iPhone OS.
When I play with Apple's "Slide to Unlock" slider on my iPhone's home screen, I occasionally see the animation freeze. So I think Apple may be using a timer as well.
If anyone can figure out how to do a non-timer based implementation using CA or OpenGL, I would love to see it.
Thanks for the help!
I know, I am a bit late with the answer, but Facebook has great library Shimmer that implements exactly that effect.
I took the best from above solutions and created a neat method that does all for you:
- (void)createSlideToUnlockViewWithText:(NSString *)text
{
UILabel *label = [[UILabel alloc] init];
label.text = text;
[label sizeToFit];
label.textColor = [UIColor whiteColor];
//Create an image from the label
UIGraphicsBeginImageContextWithOptions(label.bounds.size, NO, 0.0);
[[label layer] renderInContext:UIGraphicsGetCurrentContext()];
UIImage *textImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGFloat textWidth = textImage.size.width;
CGFloat textHeight = textImage.size.height;
CALayer *textLayer = [CALayer layer];
textLayer.contents = (id)[textImage CGImage];
textLayer.frame = CGRectMake(self.view.frame.size.width / 2 - textWidth / 2, self.view.frame.size.height / 2 - textHeight / 2, textWidth, textHeight);
UIImage *maskImage = [UIImage imageNamed:#"Mask.png"];
CALayer *maskLayer = [CALayer layer];
maskLayer.backgroundColor = [[UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.15] CGColor];
maskLayer.contents = (id)maskImage.CGImage;
maskLayer.contentsGravity = kCAGravityCenter;
maskLayer.frame = CGRectMake(-textWidth - maskImage.size.width, 0.0, (textWidth * 2) + maskImage.size.width, textHeight);
CABasicAnimation *maskAnimation = [CABasicAnimation animationWithKeyPath:#"position.x"];
maskAnimation.byValue = [NSNumber numberWithFloat:textWidth + maskImage.size.width];
maskAnimation.repeatCount = HUGE_VALF;
maskAnimation.duration = 2.0;
maskAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
[maskLayer addAnimation:maskAnimation forKey:#"slideAnimation"];
textLayer.mask = maskLayer;
self.slideToUnlockLayer = textLayer;
[self.view.layer addSublayer:self.slideToUnlockLayer];
}
First, a HUGE thank you to marcio for his solution. This worked almost perfectly, saved me hours of effort, and made a huge splash in my app. My boss loved it. I owe you beer. Or several.
One small correction for iPhone 4 only. I mean the hardware itself, not just iOS 4. They changed the system font on the iPhone 4 from Helvetica (iPhone 3Gs and below) to Helvetic Neue. This caused the translation you're doing from character to glyphs to be off by exactly 4 spots. For example the string "fg" would appear as "bc". I fixed this by explicitly setting the font to "Helvetica" rather than using "systemFontofSize". Now it works like a charm.
Again...THANK YOU!
I uploaded to GitHub a mini project who helps with the “slide to unlock” animation.
https://github.com/GabrielMassana/GM_FSHighlightAnimationAdditions
The project have LTR, RTL, Up to Down and Down to Up animations and it is based in the posts:
Pascal Bourque: https://stackoverflow.com/a/2778232/1381708
cberkley: https://stackoverflow.com/a/5710097/1381708
Cheers
Here's a SwiftUI version:
public struct Shimmer: AnimatableModifier {
private let gradient: Gradient
#State private var position: CGFloat = 0
public var animatableData: CGFloat {
get { position }
set { position = newValue }
}
private let animation = Animation
.linear(duration: 2)
.delay(1)
.repeatForever(autoreverses: false)
init(sideColor: Color = Color(white: 0.25), middleColor: Color = .white) {
gradient = Gradient(colors: [sideColor, middleColor, sideColor])
}
public func body(content: Content) -> some View {
LinearGradient(
gradient: gradient,
startPoint: .init(x: position - 0.2 * (1 - position), y: 0.5),
endPoint: .init(x: position + 0.2 * position, y: 0.5)
)
.mask(content)
.onAppear {
withAnimation(animation) {
position = 1
}
}
}
}
Use it like this:
Text("slide to unlock")
.modifier(Shimmer())
Maybe it's just a rendered-out animation - you know, a series of stills played one after another. Not necessarily a dynamic effect.
Update: Never mind, the video DrJokepu posted proved it's dynamically generated.
Top: UILabel with opaque background and clear text
Clear text is rendered in drawRect: func through complicated masking process
Middle: Worker View that is performing a repeating animation moving an image behind the top label
Bottom: a UIView that you add the middle and top subview to in that order. Can be whatever color you want the text to be
An example can be seen here https://github.com/jhurray/AnimatedLabelExample
Related
my code of making CAShapeLayer is
UIBezierPath *circle = [UIBezierPath bezierPathWithArcCenter:CGPointMake(75, 125)
radius:50
startAngle:0
endAngle:1.8 * M_PI
clockwise:YES];
CAShapeLayer *circleLayer = [CAShapeLayer layer];
[circleLayer setFrame:CGRectMake(200 - 50, 300 - 50, 100, 100)];
circleLayer.path = circle.CGPath;
circleLayer.bounds = CGPathGetBoundingBox(circle.CGPath);
circleLayer.strokeColor = [UIColor colorWithRed:0.4 green:1.0 blue:0.2 alpha:0.5].CGColor;
[circleLayer setFillColor:[UIColor clearColor].CGColor];
circleLayer.lineWidth = 3.0;
if ([circleLayer animationForKey:#"SpinAnimation"] == nil) {
CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
animation.fromValue = [NSNumber numberWithFloat:0.0f];
animation.toValue = [NSNumber numberWithFloat: 2 * M_PI];
animation.duration = 2.0f;
animation.repeatCount = INFINITY;
animation.removedOnCompletion = NO;
[circleLayer addAnimation:animation forKey:#"SpinAnimation"];
}
[self.view.layer addSublayer:circleLayer];
I want to make CAGradientLayer instead of CAShapeLayer.
REASON: I need to use gradient color in layer, which is not possible in CAShapeLayer.
I want to use yellow to black gradient upon Cirle.
Image :
In my required output, color is fetched from background image, and put some black at the end, or opacity 0 within layer.
Any Idea, suggestion.
Thanks.
One quick and dirty trick is adding a gradient layer as a sublayer then put some more layers with the background colors same as the superview (in the original question, it's white).
For example:
Add a gradient layer (and set proper cornerRadius).
Add a smaller layer with white background color at the center of the gradient layer (this will create a ring).
Create a small segment of arc with white background color.
However, this may not be suitable views with background images.
To solve this, you can create a custom subclass of UIView and override the drawRect method.
Suppose there are instance variables: startColor, endColor, radius, strokeWidth, and mirrored.
The variable mirrored is used to control the position of the gap (cut off left side or right side) and the rotation direction of the animation (clockwise or counter-clockwise).
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetBlendMode(ctx, kCGBlendModeNormal);
CGFloat gradientColors[4 * 2];
extractColorComponents(_startColor, gradientColors, 0);
extractColorComponents(_endColor, gradientColors, 4);
CGColorSpaceRef baseSpace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColorComponents(baseSpace, gradientColors, NULL, 2);
CGColorSpaceRelease(baseSpace);
baseSpace = nil;
CGPoint startPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect));
CGPoint endPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect));
CGContextDrawLinearGradient(ctx, gradient, startPoint, endPoint, 0);
CGGradientRelease(gradient);
gradient = nil;
// fill 'clear' color
CGContextSetFillColorWithColor(ctx, [UIColor clearColor].CGColor);
CGContextSetStrokeColorWithColor(ctx, [UIColor clearColor].CGColor);
CGContextSetBlendMode(ctx, kCGBlendModeClear);
CGFloat innerCircleRadius = _radius - _strokeWidth;
// fill an 'empty' hole inside the gradient part
CGContextFillEllipseInRect(ctx, (CGRect){
{_strokeWidth, _strokeWidth},
{innerCircleRadius * 2, innerCircleRadius * 2}
});
// fill an 'empty' segment of arc
CGFloat startAngle = _mirrored ? (0.9 * M_PI) : (-0.1 * M_PI);
CGFloat endAngle = startAngle + 0.2 * M_PI;
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:(CGPoint){_radius, _radius}
radius:_radius - _strokeWidth * 0.5
startAngle:startAngle
endAngle:endAngle
clockwise:YES];
path.lineWidth = _strokeWidth;
CGContextAddPath(ctx, path.CGPath);
CGContextSetLineWidth(ctx, _strokeWidth + 2);
CGContextStrokePath(ctx);
CGContextSetBlendMode(ctx, kCGBlendModeNormal);
}
Here's the complete implementation of the view.
Final result:
Two rings:
You can add two rings in your viewDidLoad or somewhere else with the following code to achieve the desired effect:
- (void)viewDidLoad
{
[super viewDidLoad];
UIColor *startColor = [UIColor colorWithHue:0.02 saturation:0.74 brightness:0.91 alpha:1];
UIColor *endColor = [UIColor colorWithHue:0.57 saturation:0.76 brightness:0.86 alpha:1];
CGPoint center = CGPointMake(160, 200);
Spinner *outerSpinner = [[Spinner alloc] initWithCenter:center
radius:50
strokeWidth:3
startColor:startColor
endColor:endColor
mirrored:NO];
Spinner *innerSpinner = [[Spinner alloc] initWithCenter:center
radius:40
strokeWidth:3
startColor:startColor
endColor:endColor
mirrored:YES];
[self.view addSubview:outerSpinner];
[self.view addSubview:innerSpinner];
[outerSpinner startAnimating];
[innerSpinner startAnimating];
}
Result:
The mask property on CALayer uses the alpha component of the mask layer to determine what should be visible and not. Since both of your colors (black and white) are fully opaque (no transparency) the mask has no effect. To fix this you should change one of the colors to a clear color:
gradient.colors = [NSArray arrayWithObjects:(id)[[UIColor blackColor] CGColor],
(id)[[UIColor clearColor] CGColor], nil];
EDIT
class Colors {
let colorTop = UIColor(red: 192.0/255.0, green: 38.0/255.0, blue: 42.0/255.0, alpha: 1.0).CGColor
let colorBottom = UIColor(red: 35.0/255.0, green: 2.0/255.0, blue: 2.0/255.0, alpha: 1.0).CGColor
let gl: CAGradientLayer
init() {
gl = CAGradientLayer()
gl.colors = [ colorTop, colorBottom]
gl.locations = [ 0.0, 1.0]
}
}
I drew the line that turn like clock.
This is the line's moving of my ideal
but my code moves like this
How can I make a line like the second picture's line?
Here is my code
LineLayer.m
- (void)drawInContext:(CGContextRef)ctx
{
CGMutablePathRef path = CGPathCreateMutable();
CGPoint points[] = {
CGPointMake(160, 220),
CGPointMake(160, 120),
};
int numPoints = sizeof(points)/sizeof(points[0]);
CGPathAddLines(path, NULL, points, numPoints);
CGContextAddPath(ctx, path);
CGPathRelease(path);
UIColor *lineColor = [UIColor colorWithWhite:1.0 alpha:1.0];
CGContextSetStrokeColorWithColor(ctx, lineColor.CGColor);
CGContextSetLineWidth(ctx, 5.0);
CGContextDrawPath(ctx, kCGPathStroke);
}
ViewController.h
- (void)setup
{
LineLayer *linelayer = [LineLayer layer];
linelayer.frame = CGRectInset(self.view.bounds,0.0,0.0);
linelayer.anchorPoint = CGPointMake(0.5, 0.5);
[self.view.layer addSublayer:linelayer];
[linelayer setNeedsDisplay];
CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
animation.toValue = [NSNumber numberWithFloat:M_PI / 2.0];
animation.duration = 0.5;
animation.repeatCount = MAXFLOAT;
animation.cumulative = YES;
[linelayer addAnimation:animation forKey:#"ImageViewRotation"];
}
you need a clock seconds needle or something like that??
#define DEGREES_TO_RADIANS(angle) ((angle) / 180.0 * M_PI)
NSTimer *timer1;//global declaration
CALayer *layerLine;//global declaration
in viewDidLoad()
layerLine = [CALayer layer];
[layerLine setFrame:CGRectMake(0, 0,2, 90)];
[layerLine setBackgroundColor:[UIColor yellowColor].CGColor];
[self.view.layer addSublayer:layerLine];
CGPoint centerPoint = CGPointMake([[UIScreen mainScreen] bounds].size.width/2, ([[UIScreen mainScreen] bounds].size.height/2)-(layerLine.frame.size.height/4));
layerLine.anchorPoint = CGPointMake(0, 0);
layerLine.position = centerPoint;
layerLine.transform = CATransform3DMakeRotation(M_PI_2*2, 0.0, 0.0, 1.0);
if (!timer)
{
timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(onTickTick) userInfo:nil repeats:YES];
}
timer invoking method
-(void)onTickTick
{
static int i = 180;
if (i==540)//if u change the value for i, it may affect this condition
{
i=180;
}
i=i+6;//u can replace 6 with 30 or any other value as u require
layerLine.transform = CATransform3DMakeRotation(DEGREES_TO_RADIANS(i), 0.0, 0.0, 1);
}
I want to draw the Circular progress view with gradient as image :
Design Image
I tried the below code but the result color is not correct. This is my code:
//draw gradient
CGFloat colors [] = {
1.0, 0.7,
0.2, 0.4
};
CGFloat locations[] = { 0.0, 1.0 };
CGColorSpaceRef baseSpace = CGColorSpaceCreateDeviceGray(); // gray colors want gray color space
CGGradientRef gradient = CGGradientCreateWithColorComponents(baseSpace, colors, locations, 2);
CGColorSpaceRelease(baseSpace), baseSpace = NULL;
CGContextSaveGState(context);
CGContextAddPath(context, progressPath);
CGContextClip(context);
CGRect boundingBox = CGPathGetBoundingBox(progressPath);
CGPoint gradientStart = CGPointMake(0.0, CGRectGetMinY(boundingBox));
CGPoint gradientEnd = CGPointMake(1.0, CGRectGetMaxY(boundingBox));
CGContextDrawLinearGradient(context, gradient, gradientStart, gradientEnd, 0);
CGGradientRelease(gradient), gradient = NULL;
CGContextRestoreGState(context);
The progressPath is the progress line with color :
[UIColor colorWithRed:21/255.0f green:182/255.0f blue:217/255.0f alpha:1.0]
UPDATE:
This is an image my code produced: Result Image
The result image is not the same the design, the color is not the same. I dont know why.
Please help me to correct this. Thanks in advance.
The kind of gradient you are looking for, where the color varies as a function of the angle around a center point, is called an angle gradient. Unfortunately, iOS does not have a built-in way to draw angle gradients, but you can do it using an open-source library such as paiv/AngleGradientLayer.
Working code:
- (void) setGradientWithFrame:(CGRect)frame
{
//create the gradient
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = frame;
//this example is white-black gradient, change it to the color you want..
gradient.colors = [NSArray arrayWithObjects:(id)[[UIColor whiteColor] CGColor], (id)[[UIColor blackColor] CGColor], nil];
gradient.cornerRadius = frame.size.height/2.0f;
[self.view.layer insertSublayer:gradient atIndex:0];
CALayer *whiteCircle = [CALayer layer];
whiteCircle.frame = CGRectMake(frame.origin.x + 10, frame.origin.y + 10, frame.size.width - 20, frame.size.height - 20);
whiteCircle.backgroundColor = [[UIColor whiteColor] CGColor];
whiteCircle.cornerRadius = whiteCircle.frame.size.width/2.0f;
[self.view.layer insertSublayer:whiteCircle atIndex:1];
CALayer *whitesquare = [CALayer layer];
whitesquare.frame = CGRectMake(50, frame.size.height - 20, 25, 25);
whitesquare.backgroundColor = [[UIColor whiteColor] CGColor];
whitesquare.transform = CATransform3DMakeRotation(-20.0 / 180.0 * M_PI, 0.0, 0.0, 1.0);
[self.view.layer insertSublayer:whitesquare atIndex:2];
}
to call the method:
[self setGradientWithFrame:CGRectMake(0, 0, 100, 100)];
PS. change the color to the color of gradient to the color you want:
I am trying to apply an alpha gradient to a UIView but can't get it to work at all. My current drawRect: method looks like this -
- (void)drawRect:(CGRect)rect
{
CGContextRef currentContext = UIGraphicsGetCurrentContext();
CGGradientRef alphaGradient;
CGColorSpaceRef rgbColorSpace;
size_t num_locations = 2;
CGFloat locations[2] = { 0.0, 1.0 };
CGFloat components[8] = { 1.0, 1.0, 1.0, 1.0, // Start color
1.0, 1.0, 1.0, 0.0 }; // End color
rgbColorSpace = CGColorSpaceCreateDeviceRGB();
alphaGradient = CGGradientCreateWithColorComponents(rgbColorSpace, components, locations, num_locations);
CGRect currentBounds = self.bounds;
CGPoint bottomCenter = CGPointMake(CGRectGetMidX(currentBounds), CGRectGetMaxY(currentBounds));
CGPoint midCenter = CGPointMake(CGRectGetMidX(currentBounds), CGRectGetMidY(currentBounds));
CGContextDrawLinearGradient(currentContext, alphaGradient, midCenter, bottomCenter, 0);
CGGradientRelease(alphaGradient);
CGColorSpaceRelease(rgbColorSpace);
}
I am sure there is something I am missing here. I have tried changing the RGB values to 0.0 instead of 1.0, changing the color space to gray scale, etc.
Any help pointing me in the right direction would be much appreciated. Thanks
I resolved the issue by making sure the view's background color was set to clear, then applying the view's layer as a mask on the image view.
I use CAGradient:
UIView * viewToGradient = // view to add gradient
UIColor * color1 = [UIColor lightGrayColor]; // gradient color 1
UIColor * color2 = [UIColor clearColor]; // gradient color 2
CAGradientLayer * gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = viewToGradient.bounds;
gradientLayer.colors = [NSArray arrayWithObjects:(id)color1.CGColor, (id)color2.CGColor, nil];
gradientLayer.opacity = .6;
[viewToGradient.layer addSublayer:gradientLayer];
I have a for loop inside the drawRect method that draws a number of circles to fill the screen. I'm trying to make it so each circle has a new random stroke. For some reason nothing is showing up. Here is my randomColor method:
-(UIColor *) randomColor
{
int red, green, blue, alpha;
red = arc4random_uniform(255);
green = arc4random_uniform(255);
blue = arc4random_uniform(255);
alpha = arc4random_uniform(255);
UIColor *colorToReturn = [[UIColor alloc] initWithRed:red green:green blue:blue alpha:alpha];
return colorToReturn;
}
and I try implementing it here:
-(void) drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGRect bounds = [self bounds];
// Firgure out the center of the bounds rectangle
CGPoint center;
center.x = bounds.origin.x + bounds.size.width / 2.0;
center.y = bounds.origin.y + bounds.size.height / 2.0;
// The radius of the circle should be nearly as big as the view
float maxRadius = hypot(bounds.size.width, bounds.size.height) / 2.0;
// The thickness of the line should be 10 points wide
CGContextSetLineWidth(ctx, 10);
// The color of the line should be gray (red/green/blue = 0.6, alpha = 1.0)
// CGContextSetRGBStrokeColor(ctx, 0.6, 0.6, 0.6, 1.0);
// The same as
// [[UIColor colorWithRed:0.6 green:0.6 blue:0.6 alpha:1.0] setStroke];
// The same as
// [[UIColor redColor] setStroke];
// Draw concentric circles from the outside in
for (float currentRadius = maxRadius; currentRadius > 0; currentRadius -= 20) {
// Add a path to the context
CGContextAddArc(ctx, center.x, center.y, currentRadius, 0.0, M_PI * 2.0, YES);
[[self randomColor] setStroke];
// Perform drawing instructions; removes path
CGContextStrokePath(ctx);
}
UIColor takes a float between 0 and 1 as a value for its RGB components:
UIColor *colorToReturn = [[UIColor alloc] initWithRed:red/255.0 green:green/255.0 blue:blue/255.0 alpha:alpha];
I use the two macros below to get a random color. The first one is a straightforward macro that I often use while setting colors. The second one returns a random color using it:
#define _RGB(r,g,b,a) [UIColor colorWithRed:r/255.0 green:g/255.0 blue:b/255.0 alpha:a]
#define kCLR_RANDOM_COLOR _RGB(arc4random()%255, arc4random()%255, arc4random()%255, 1)