Shimmer Like Facebook in Swift 3.0

Posted in software by Christopher R. Wirz on Tue Jun 06 2017



Facebook Shimmer came out 4 years ago for iOS 6. Though it has withstood the test of time, it would be nice if the design parameters were exposed and you didn't have to include the cocoa pod.

Note: This example adds inspectable properties to UIView - and therefore basically any UI Control that inherits from UIView.

While properties can't be added through an extension directly, get set properties that are backed by a private struct are allowed. In order to be inspectable, CGColor, which is used by the gradient, must be translated into a UIColor.

The file UIView.Extensions.Shimmer.swift could be created as follows:


import Foundation
import UIKit
import Dispatch

/*
 It is convenient if anything that can simmer conforms to this protocol
 */
protocol Shimmerable {
    func startShimmering(count: Int) -> Void
    func stopShimmering() -> Void
    var lightShimmerColor: UIColor {get set}
    var darkShimmerColor: UIColor {get set}
    var isShimmering: Bool {get}
}


@IBDesignable
extension UIView: Shimmerable {
    
    /*
     A way to store the custom properties
     */
    private struct UIViewCustomShimmerProperties {
        static let shimmerKey:String = "shimmer"
        static var lightShimmerColor:CGColor = UIColor.white.withAlphaComponent(0.1).cgColor
        static var darkShimmerColor:CGColor = UIColor.black.withAlphaComponent(1).cgColor
        static var isShimmering:Bool = false
        static var gradient:CAGradientLayer = CAGradientLayer()
        static var animation:CABasicAnimation = CABasicAnimation(keyPath: "locations")
    }
    
    /*
     Set shimmer properties
     */
    @IBInspectable 
	var lightShimmerColor: UIColor {
        get {
            return UIColor(cgColor: UIViewCustomShimmerProperties.lightShimmerColor)
        }
        set {
            UIViewCustomShimmerProperties.lightShimmerColor = newValue.cgColor
        }
    }
    @IBInspectable 
	var darkShimmerColor: UIColor {
        get {
            return UIColor(cgColor: UIViewCustomShimmerProperties.darkShimmerColor)
        }
        set {
            UIViewCustomShimmerProperties.darkShimmerColor = newValue.cgColor
        }
    }
    
    var isShimmering: Bool {
        get {
            return UIViewCustomShimmerProperties.isShimmering
        }
    }
    
    func stopShimmering() {
        guard UIViewCustomShimmerProperties.isShimmering else {return}
        self.layer.mask?.removeAnimation(forKey: UIViewCustomShimmerProperties.shimmerKey)
        self.layer.mask = nil
        UIViewCustomShimmerProperties.isShimmering = false
        self.layer.setNeedsDisplay()
    }

    func startShimmering(count: Int = 3) {
        guard !UIViewCustomShimmerProperties.isShimmering else {return}
        
        CATransaction.begin()       
        CATransaction.setCompletionBlock({
            self.stopShimmering()
        })
        
        UIViewCustomShimmerProperties.isShimmering = true

        UIViewCustomShimmerProperties.gradient.colors = [UIViewCustomShimmerProperties.darkShimmerColor, UIViewCustomShimmerProperties.lightShimmerColor, UIViewCustomShimmerProperties.darkShimmerColor];
        UIViewCustomShimmerProperties.gradient.frame = CGRect(x: CGFloat(-2*self.bounds.size.width), y: CGFloat(0.0), width: CGFloat(4*self.bounds.size.width), height: CGFloat(self.bounds.size.height))
        UIViewCustomShimmerProperties.gradient.startPoint = CGPoint(x: Double(0.0), y: Double(0.5));
        UIViewCustomShimmerProperties.gradient.endPoint = CGPoint(x: Double(1.0), y: Double(0.625));
        UIViewCustomShimmerProperties.gradient.locations = [0.4, 0.5, 0.6];
        
        UIViewCustomShimmerProperties.animation.duration = 1.0
        UIViewCustomShimmerProperties.animation.repeatCount = (count > 0) ? Float(count) : .infinity
        UIViewCustomShimmerProperties.animation.fromValue = [0.0, 0.12, 0.3]
        UIViewCustomShimmerProperties.animation.toValue = [0.6, 0.86, 1.0]
        
        UIViewCustomShimmerProperties.gradient.add(UIViewCustomShimmerProperties.animation, forKey: UIViewCustomShimmerProperties.shimmerKey)
        self.layer.mask = UIViewCustomShimmerProperties.gradient;
        
        CATransaction.commit()
    }
}

So what next? How about we try it on a UIViewController...

Note: The code below has only been tested in iOS Swift 4.1.

import UIKit

class ShimmerDemoViewController: UIViewController {

    var imageLogoView: UIImageView!

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view, like setup the
        imageLogoView = UIImageView(image: UIImage(named: "MyLogo"))
        imageLogoView.frame = CGRect(x: (UIScreen.main.bounds.width - 250.0) / 2, y: 32.0, width: 250.0, height: 250.0)
        view.addSubview(imageLogoView)
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        // Start Shimmering
        imageLogoView.startShimmering()
    }
}

There you have it. When the view loads your logo will start shimmering!