Animación de iPhone para “deslizar para desbloquear”

Cualquier idea sobre cómo Apple implementó el “deslizamiento para desbloquear” (también, “deslizar para apagar” es otro ejemplo idéntico) de animación?

Pensé en algún tipo de máscara de animación, pero el enmascaramiento no está disponible en el iPhone OS por motivos de rendimiento.

¿Hay algún efecto de API privada (como SuckEffect) que puedan haber usado? ¿Un tipo de foco de efecto? ¿Alguna cosa de Core Animation?

Editar: Definitivamente no es una serie de imágenes fijas. He visto ejemplos de editar un valor de plist o algo así y personalizar la cadena en iPhones con jailbreak.

Se puede hacer fácilmente utilizando Core Animation, animando una capa de máscara en la capa que muestra el texto.

Prueba esto en cualquier UIViewController simple (puedes comenzar con un nuevo proyecto de Xcode basado en la plantilla de proyecto de la aplicación basada en View ), o hazte con mi proyecto de Xcode aquí :

Tenga en cuenta que la propiedad CALayer.mask solo está disponible en iPhone OS 3.0 y versiones posteriores.

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

Las imágenes utilizadas por este código son:

Capa de máscara Capa de texto

Otra solución más con una máscara de capa, pero en su lugar dibuja el degradado a mano y no requiere imágenes. Ver es la vista con la animación, la transparencia es un valor flotante de 0 a 1 que define la cantidad de transparencia (1 = sin transparencia que no tiene sentido), y gradientWidth es el ancho deseado del degradado.

 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"]; 

VERSIÓN SWIFT:

 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) } 

Puede usar el modo de dibujo kCGTextClip para establecer el trazado de recorte y luego rellenarlo con un degradado.

 // 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) 

Configure un NSTimer y varíe los valores en ubicaciones, o use CoreAnimation para hacer lo mismo.

Agregué el código proporcionado anteriormente por Pascal como una categoría en UILabel para que pueda animar cualquier UILabel de esta manera. Aquí está el código. Es posible que haya que cambiar algunos parámetros para los colores de fondo, etc. Utiliza la misma imagen de máscara que Pascal ha incorporado en su respuesta.

 //UILabel+FSHighlightAnimationAdditions.m #import "UILabel+FSHighlightAnimationAdditions.h" #import  #import  @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  @interface UILabel (FSHighlightAnimationAdditions) - (void)setTextWithChangeAnimation:(NSString*)text; @end 

no tan fresco … pero tal vez sea útil

 #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 

Gracias a rpetrich por la receta de gradiente de recorte. Soy un desarrollador de iPhone y Cocoa novato, así que estaba realmente feliz de encontrarlo.

Implementé Slide to Cancel UIViewController de aspecto decente usando el método de rpetrich. Puede descargar el proyecto Xcode de mi implementación desde aquí .

Mi implementación usa un NSTimer repetitivo. No pude averiguar cómo usar Animación Core (o Gore) para que el motor gráfico del iPhone mueva continuamente el resalte. Creo que se podría hacer en OS X con capas de máscara CALayer, pero las capas de máscara no son compatibles con iPhone OS.

Cuando juego con el control deslizante “Desbloquear para desbloquear” de Apple en la pantalla de inicio de mi iPhone, de vez en cuando veo la animación congelada. Así que creo que Apple también puede estar usando un temporizador.

Si alguien puede descubrir cómo hacer una implementación sin temporizador usando CA u OpenGL, me encantaría verla.

¡Gracias por la ayuda!

Lo sé, llegué un poco tarde con la respuesta, pero Facebook tiene una excelente biblioteca Shimmer que implementa exactamente ese efecto.

Tomé lo mejor de las soluciones anteriores y creé un método limpio que hace todo por ti:

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

Primero, un ENORME agradecimiento a Mario por su solución. Esto funcionó casi a la perfección, me ahorró horas de esfuerzo e hizo un gran revuelo en mi aplicación. Mi jefe lo amaba. Te debo cerveza. O varios.

Una pequeña corrección para iPhone 4 solamente. Me refiero al hardware en sí, no solo iOS 4. Cambiaron la fuente del sistema en el iPhone 4 de Helvetica (iPhone 3Gs y abajo) a Helvetic Neue. Esto causó que la traducción que estás haciendo desde el carácter hasta los glifos fuera exactamente 4 puntos. Por ejemplo, la cadena “fg” aparecerá como “bc”. Lo arreglé estableciendo explícitamente la fuente en “Helvetica” en lugar de usar “systemFontofSize”. Ahora funciona como un encanto!

¡Una vez más, gracias!

Cargué en GitHub un mini proyecto que ayuda con la animación “deslizar para desbloquear”.

https://github.com/GabrielMassana/GM_FSHighlightAnimationAdditions

El proyecto tiene LTR, RTL, animaciones ascendentes y descendentes y se basa en las publicaciones:

Pascal Bourque: https://stackoverflow.com/a/2778232/1381708

cberkley: https://stackoverflow.com/a/5710097/1381708

Aclamaciones

Tal vez es solo una animación renderizada, ya sabes, una serie de imágenes fijas reproducidas una tras otra. No necesariamente un efecto dynamic.

Actualización: No importa, el video que publicó DrJokepu demostró que se generó de manera dinámica.

  • Arriba: UILabel con fondo opaco y texto claro
    • El texto claro se representa en drawRect: func a través de un complicado proceso de enmascaramiento
  • Medio: vista de trabajador que realiza una animación repetitiva moviendo una imagen detrás de la etiqueta superior
  • Abajo: una UIView en la que agrega la subvista central y superior en ese orden. Puede ser del color que desee que sea el texto

Se puede ver un ejemplo aquí https://github.com/jhurray/AnimatedLabelExample