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 ani
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.
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];
}
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!
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];
}
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