iPhone "slide to unlock" animation

IosObjective CCocoa TouchCore Animation

Ios Problem Overview


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.

Ios Solutions


Solution 1 - Ios

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:

Mask Layer Text Layer

Solution 2 - Ios

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

Solution 3 - Ios

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.

Solution 4 - Ios

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

Solution 5 - Ios

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

Solution 6 - Ios

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!

Solution 7 - Ios

I know, I am a bit late with the answer, but Facebook has great library Shimmer that implements exactly that effect.

Solution 8 - Ios

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

Solution 9 - Ios

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!

Solution 10 - Ios

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

Solution 11 - Ios

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

Solution 12 - Ios

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.

Solution 13 - Ios

  • 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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRussView Question on Stackoverflow
Solution 1 - IosPascal BourqueView Answer on Stackoverflow
Solution 2 - IosSamsiniteView Answer on Stackoverflow
Solution 3 - IosrpetrichView Answer on Stackoverflow
Solution 4 - IoscberkleyView Answer on Stackoverflow
Solution 5 - IosmarcioView Answer on Stackoverflow
Solution 6 - IosMyCatsNameIsBernieView Answer on Stackoverflow
Solution 7 - IosMaxim PavlovView Answer on Stackoverflow
Solution 8 - IosNico S.View Answer on Stackoverflow
Solution 9 - IosDave KlotzView Answer on Stackoverflow
Solution 10 - IosGabriel.MassanaView Answer on Stackoverflow
Solution 11 - IosXavier LowmillerView Answer on Stackoverflow
Solution 12 - IosbhollisView Answer on Stackoverflow
Solution 13 - IosjhurrayView Answer on Stackoverflow