Activity indicator with custom image

I am loading a UIWebView and in the meantime I wan't to show a blank page with this activity indicator spinning (siri activity indicator). From what I have understand you can not change the image, but can't I use that image and create an animation with it rotating 360° and looping? Or will that drain the battery?

something like this?:

- (void)webViewDidStartLoad:(UIWebView *)webView {
    //set up animation        
    [self.view addSubview:self.loadingImage];
    //start animation
}

- (void)webViewDidFinishLoad:(UIWebView *)webView
{   
    //stop animation
    [self.loadingImage removeFromSuperview];
}

What should I do?

Thanks in advance!

Answers


Most of this is found in Stack Overflow. Let me summarize:

Create an UIImageView which will serve as an activity indicator (inside storyboard scene, NIB, code ... wherever you wish). Let's call it _activityIndicatorImage

Load your image: _activityIndicatorImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"activity_indicator"]];

You need to use animation to rotate it. Here is the method I use:

+ (void)rotateLayerInfinite:(CALayer *)layer
{
    CABasicAnimation *rotation;
    rotation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
    rotation.fromValue = [NSNumber numberWithFloat:0];
    rotation.toValue = [NSNumber numberWithFloat:(2 * M_PI)];
    rotation.duration = 0.7f; // Speed
    rotation.repeatCount = HUGE_VALF; // Repeat forever. Can be a finite number.
    [layer removeAllAnimations];
    [layer addAnimation:rotation forKey:@"Spin"];
}

Inside my layoutSubviews method I initiate rotation. You could place this in your webViewDidStartLoad and webViewDidFinishLoad if this is better for your case:

- (void)layoutSubviews
{
    [super layoutSubviews];

    // some other code 

    [Utils rotateLayerInfinite:_activityIndicatorImage.layer];
}

You could always always stop rotation using [_activityIndicatorImage.layer removeAllAnimations];


You may use this beautiful loader inspired from Tumblr app: Asich/AMTumblrHud


SWIFT 4 Sweet And Simply just put extension UIView{}

Modified answer of @gandhi Mena

if you want to create your own custom Loading indicator

Create a UIView extension which create and customize your brand logo as a custom indicator put this code in you global declaration file.

extension UIView{
func customActivityIndicator(view: UIView, widthView: CGFloat?,backgroundColor: UIColor?, textColor:UIColor?, message: String?) -> UIView{

    //Config UIView
    self.backgroundColor = backgroundColor //Background color of your view which you want to set

    var selfWidth = view.frame.width
    if widthView != nil{
        selfWidth = widthView ?? selfWidth
    }

    let selfHeigh = view.frame.height
    let loopImages = UIImageView()

    let imageListArray = ["image1", "image2"] // Put your desired array of images in a specific order the way you want to display animation.

    loopImages.animationImages = imageListArray
    loopImages.animationDuration = TimeInterval(0.8)
    loopImages.startAnimating()

    let imageFrameX = (selfWidth / 2) - 30
    let imageFrameY = (selfHeigh / 2) - 60
    var imageWidth = CGFloat(60)
    var imageHeight = CGFloat(60)

    if widthView != nil{
        imageWidth = widthView ?? imageWidth
        imageHeight = widthView ?? imageHeight
    }

    //ConfigureLabel
    let label = UILabel()
    label.textAlignment = .center
    label.textColor = .gray
    label.font = UIFont(name: "SFUIDisplay-Regular", size: 17.0)! // Your Desired UIFont Style and Size
    label.numberOfLines = 0
    label.text = message ?? ""
    label.textColor = textColor ?? UIColor.clear

    //Config frame of label
    let labelFrameX = (selfWidth / 2) - 100
    let labelFrameY = (selfHeigh / 2) - 10
    let labelWidth = CGFloat(200)
    let labelHeight = CGFloat(70)

    // Define UIView frame
    self.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width , height: UIScreen.main.bounds.size.height)


    //ImageFrame
    loopImages.frame = CGRect(x: imageFrameX, y: imageFrameY, width: imageWidth, height: imageHeight)

    //LabelFrame
    label.frame = CGRect(x: labelFrameX, y: labelFrameY, width: labelWidth, height: labelHeight)

    //add loading and label to customView
    self.addSubview(loopImages)
    self.addSubview(label)
    return self }}

Hide an indicator something like this you can remove subview at the top from the subview stack. put this code in the same globally declared swift file.

func hideLoader(removeFrom : UIView){
removeFrom.subviews.last?.removeFromSuperview()
}

Now you can shoot at the mark by this code To display activity indicator in your view controller put this code when you want to display.

 self.view.addSubview(UIView().customActivityIndicator(view: self.view, widthView: nil, backgroundColor:"Desired color", textColor: "Desired color", message: "Loading something"))

To hide animating loader you can user above function you defined in the globally. In your ViewController.swift where you want to hide put this line of code.

hideLoader(removeFrom: self.view)

imageListArray looks like this.


Without Image , you can use third party library

for objective C (also support in iOS 6) https://github.com/shebinkoshy/UIControllsRepo

for swift https://github.com/shebinkoshy/Activity-Indicator-Swift

Advantages

-> Able to set colors for spinner

-> Available in different sizes like tiny, small, medium, large, very large

-> Able to set Title (center and bottom) for medium, large, very large sizes


You can set an images to your activityIndicator. I created a function for add custom image to activityIndicator. Here is what I created.

public func showProgressView(view: UIView) -> UIImageView {
    let containerView = UIView()
    let progressView = UIView()
    var activityIndicatorImageView = UIImageView()

    if let statusImage = UIImage(named: Constants.ActivityIndicatorImageName1) {
        let activityImageView = UIImageView(image: statusImage)
        containerView.frame = view.frame
        containerView.backgroundColor = UIColor(hex: 0xffffff, alpha: 0.3)
        progressView.frame = CGRectMake(0, 0, 80, 80)
        progressView.center = CGPointMake(view.bounds.width / 2, view.bounds.height / 2)
        progressView.backgroundColor = UIColor(hex: 0x18bda3, alpha: 0.7)
        progressView.clipsToBounds = true
        progressView.layer.cornerRadius = 10
        activityImageView.animationImages = [UIImage(named: Constants.ActivityIndicatorImageName1)!,
            UIImage(named: Constants.ActivityIndicatorImageName2)!,
            UIImage(named: Constants.ActivityIndicatorImageName3)!,
            UIImage(named: Constants.ActivityIndicatorImageName4)!,
            UIImage(named: Constants.ActivityIndicatorImageName5)!]
        activityImageView.animationDuration = 0.8;
        activityImageView.frame = CGRectMake(view.frame.size.width / 2 - statusImage.size.width / 2, view.frame.size.height / 2 - statusImage.size.height / 2, 40.0, 48.0)
        activityImageView.center = CGPointMake(progressView.bounds.width / 2, progressView.bounds.height / 2)
        dispatch_async(dispatch_get_main_queue()) {
            progressView.addSubview(activityImageView)
            containerView.addSubview(progressView)
            view.addSubview(containerView)
            activityIndicatorImageView = activityImageView
        }
    }
    return activityIndicatorImageView
}

You can call this method everywhere in your code. And just call the startAnimating method. If you want to hide just call the stopAnimating method.


it works in both SWITF 3 and 4

var activityIndicator = UIActivityIndicatorView()
var myView : UIView = UIView()

func viewDidLoad() {
    spinnerCreation()
}

func spinnerCreation() {

    activityIndicator.activityIndicatorViewStyle =  .whiteLarge

    let label = UILabel.init(frame: CGRect(x: 5, y: 60, width: 90, height: 20))
    label.textColor = UIColor.white
    label.font = UIFont.boldSystemFont(ofSize: 14.0)
    label.textAlignment = NSTextAlignment.center
    label.text = "Please wait...."

    myView.frame = CGRect(x: (UIScreen.main.bounds.size.width - 100)/2, y: (UIScreen.main.bounds.size.height - 100)/2, width: 100, height: 100)

    myView.backgroundColor = UIColor.init(white: 0.0, alpha: 0.7)
    myView.layer.cornerRadius = 5
    activityIndicator.center = CGPoint(x: myView.frame.size.width/2, y:  myView.frame.size.height/2 - 10)
    myView.addSubview(activityIndicator)
    myView.addSubview(label)

    myView.isHidden = true
    self.window?.addSubview(myView)
}

@IBAction func activityIndicatorStart(_ sender: Any) {
    myView.isHidden = false
    self.activityIndicator.startAnimating()
    self.view.isUserInteractionEnabled = false
    self.view.bringSubview(toFront: myView)
}

@IBAction func activityIndicatorStop(_ sender: Any)() {
    myView.isHidden = true
    self.activityIndicator.stopAnimating()
    self.view.isUserInteractionEnabled = true
}

You can create your custom activity Indicator with this in Swift 3 & 4:

Create a new file with name: UIViewExtension.Swift and copy this code and paste in your new file file:

import UIkit

extension UIView{
   func customActivityIndicator(view: UIView, widthView: CGFloat? = nil,backgroundColor: UIColor? = nil, message: String? = nil,colorMessage:UIColor? = nil ) -> UIView{

    //Config UIView
    self.backgroundColor = backgroundColor ?? UIColor.clear
    self.layer.cornerRadius = 10


    var selfWidth = view.frame.width - 100
    if widthView != nil{
        selfWidth = widthView ?? selfWidth
    }

    let selfHeigh = CGFloat(100)
    let selfFrameX = (view.frame.width / 2) - (selfWidth / 2)
    let selfFrameY = (view.frame.height / 2) - (selfHeigh / 2)
    let loopImages = UIImageView()

    //ConfigCustomLoading with secuence images
    let imageListArray = [UIImage(named:""),UIImage(named:""), UIImage(named:"")]
    loopImages.animationImages = imageListArray
    loopImages.animationDuration = TimeInterval(1.3)
    loopImages.startAnimating()
    let imageFrameX = (selfWidth / 2) - 17
    let imageFrameY = (selfHeigh / 2) - 35
    var imageWidth = CGFloat(35)
    var imageHeight = CGFloat(35)

    if widthView != nil{
        imageWidth = widthView ?? imageWidth
        imageHeight = widthView ?? imageHeight
    }

    //ConfigureLabel
    let label = UILabel()
    label.textAlignment = .center
    label.textColor = .gray
    label.font = UIFont.boldSystemFont(ofSize: 17)
    label.numberOfLines = 0
    label.text = message ?? ""
    label.textColor = colorMessage ?? UIColor.clear

    //Config frame of label
    let labelFrameX = (selfWidth / 2) - 100
    let labelFrameY = (selfHeigh / 2) - 10
    let labelWidth = CGFloat(200)
    let labelHeight = CGFloat(70)

    //add loading and label to customView
    self.addSubview(loopImages)
    self.addSubview(label)

    //Define frames
    //UIViewFrame
    self.frame = CGRect(x: selfFrameX, y: selfFrameY, width: selfWidth , height: selfHeigh)

    //ImageFrame
    loopImages.frame = CGRect(x: imageFrameX, y: imageFrameY, width: imageWidth, height: imageHeight)

    //LabelFrame
    label.frame = CGRect(x: labelFrameX, y: labelFrameY, width: labelWidth, height: labelHeight)

    return self

}

}

And then you can use it in your ViewController like this:

import UIKit


class ExampleViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        self.view.addSubview(UIView().customActivityIndicator(view: self.view,backgroundColor: UIColor.green))

    }

   //function for stop and desappear loading
   func deseappearLoading(){
      self.view.subviews.last?.removeFromSuperview()
   }
}

Don't forget replace [UIImage(named:" "),UIImage(named:" "), UIImage(named:" ")] with your names of images and adjust the TimeInterval(1.3). Enjoy it.


I've faced a similar issue lately. And this is my solution. Basically, it's what topic starter initially wanted: blank page with custom activity indicator on it. I have partly used @Azharhussain Shaikh answer but I've implemented auto-layout instead of using frames and added a few other refinements with the intention to make usage as simple as possible.

So, it's an extension for UIView with two methods: addActivityIndicator() and removeActivityIndicator()

extension UIView {

func addActivityIndicator() {
    //    creating a view (let's call it "loading" view) which will be added on top of the view you want to have activity indicator on (parent view)
    let view = UIView()
    //    setting up a background for a view so it would make content under it look like not active
    view.backgroundColor = UIColor.white.withAlphaComponent(0.7)

    //    adding "loading" view to a parent view
    //    setting up auto-layout anchors so it would cover whole parent view
    self.addSubview(view)
    view.translatesAutoresizingMaskIntoConstraints = false
    view.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
    view.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
    view.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
    view.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true

    //    creating array with images, which will be animated
    //    in my case I have 30 images with names activity0.png ... activity29.png
    var imagesArray = [UIImage(named: "activity\(0)")!]
    for i in 1..<30 {
        imagesArray.append(UIImage(named: "activity\(i)")!)
    }

    //    creating UIImageView with array of images
    //    setting up animation duration and starting animation
    let activityImage = UIImageView()
    activityImage.animationImages = imagesArray
    activityImage.animationDuration = TimeInterval(0.7)
    activityImage.startAnimating()

    //    adding UIImageView on "loading" view
    //    setting up auto-layout anchors so it would be in center of "loading" view with 30x30 size
    view.addSubview(activityImage)
    activityImage.translatesAutoresizingMaskIntoConstraints = false
    activityImage.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
    activityImage.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
    activityImage.widthAnchor.constraint(equalToConstant: 30).isActive = true
    activityImage.heightAnchor.constraint(equalToConstant: 30).isActive = true
}

func removeActivityIndicator() {
    //    checking if a view has subviews on it
    guard let lastSubView = self.subviews.last else { return }
    //    removing last subview with an assumption that last view is a "loading" view
    lastSubView.removeFromSuperview()
} }

"Rotating" effect is achieved by those 30 images you've put in imagesArray. Each image is a new frame of a rotating indicator like this.

Usage. In your view controller for showing an activity indicator simply put:

    view.addActivityIndicator()

For removing an activity indicator:

    view.removeActivityIndicator()

For example, in case of using it with table view (like I do) it can be used like this:

func setLoadingScreen() {
    view.addActivityIndicator()
    tableView.isScrollEnabled = false
}

func removeLoadingScreen() {
    view.removeActivityIndicator()
    tableView.isScrollEnabled = true
}

It works in Swift 4.


Swift 5

Another answer working perfect

Step 1.

Create swift file "CustomLoader.swift" and put this code in that file

import UIKit
import CoreGraphics
import QuartzCore

class CustomLoader: UIView
{
    //MARK:- NOT ACCESSABLE OUT SIDE

    fileprivate var duration : CFTimeInterval! = 1
    fileprivate var isAnimating :Bool = false
    fileprivate var backgroundView : UIView!

    //MARK:- ACCESS INSTANCE ONLY AND CHANGE ACCORDING TO YOUR NEEDS   *******
    let colors : [UIColor] = [.red,  .blue,  .orange, .purple]
    var defaultColor : UIColor = UIColor.red
    var isUsrInteractionEnable : Bool = false
    var defaultbgColor: UIColor = UIColor.white
    var loaderSize : CGFloat = 80.0
    /// **************** ******************  ////////// **************

    //MARK:- MAKE SHARED INSTANCE
    private static var Instance : CustomLoader!
    static let sharedInstance : CustomLoader = {

        if Instance == nil
        {
            Instance = CustomLoader()
        }

        return Instance
    }()

    //MARK:- DESTROY TO SHARED INSTANCE
    @objc fileprivate func destroyShardInstance()
    {
        CustomLoader.Instance = nil
    }

    //MARK:- SET YOUR LOADER INITIALIZER FRAME ELSE DEFAULT IS CENTER
    func startAnimation()
    {
        let win = UIApplication.shared.keyWindow

        backgroundView = UIView()
        backgroundView.frame = (UIApplication.shared.keyWindow?.frame)!
        backgroundView.backgroundColor = UIColor.init(white: 0, alpha: 0.4)
        win?.addSubview(backgroundView)

        self.frame = CGRect.init(x: ((UIScreen.main.bounds.width) - loaderSize)/2, y: ((UIScreen.main.bounds.height) - loaderSize)/2, width: loaderSize, height: loaderSize)

        self.addCenterImage()
        self.isHidden = false
        self.backgroundView.addSubview(self)

        self.layer.cornerRadius = loaderSize/2
        self.layer.masksToBounds = true
        backgroundView.accessibilityIdentifier = "CustomLoader"

        NotificationCenter.default.removeObserver(self, name: NSNotification.Name.NSExtensionHostDidBecomeActive, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(CustomLoader.ResumeLoader), name: NSNotification.Name.NSExtensionHostDidBecomeActive, object: nil)

        self.layoutSubviews()
    }

    //MARK:- AVOID STUCKING LOADER WHEN CAME BACK FROM BACKGROUND
    @objc fileprivate func ResumeLoader()
    {
        if isAnimating
        {
            self.stopAnimation()
            self.AnimationStart()
        }
    }

    override func layoutSubviews()
    {
        super.layoutSubviews()

        self.backgroundColor = defaultbgColor
        UIApplication.shared.keyWindow?.isUserInteractionEnabled = isUsrInteractionEnable
        self.AnimationStart()
    }

    @objc fileprivate func addCenterImage()
    {
        /// add image in center
        let centerImage = UIImage(named: "Logo")
        let imageSize = loaderSize/2.5

        let centerImgView = UIImageView(image: centerImage)
        centerImgView.frame = CGRect(
            x: (self.bounds.width - imageSize) / 2 ,
            y: (self.bounds.height - imageSize) / 2,
            width: imageSize,
            height: imageSize
        )

        centerImgView.contentMode = .scaleAspectFit
        centerImgView.layer.cornerRadius = imageSize/2
        centerImgView.clipsToBounds = true
        self.addSubview(centerImgView)

    }


    //MARK:- CALL IT TO START THE LOADER , AFTER INITIALIZE THE LOADER
    @objc fileprivate func AnimationStart()
    {
        if isAnimating
        {
            return
        }

        let size = CGSize.init(width: loaderSize , height: loaderSize)

        let dotNum: CGFloat = 10
        let diameter: CGFloat = size.width / 5.5   //10

        let dot = CALayer()
        let frame = CGRect(
            x: (layer.bounds.width - diameter) / 2 + diameter * 2,
            y: (layer.bounds.height - diameter) / 2,
            width: diameter/1.3,
            height: diameter/1.3
        )

        dot.backgroundColor = colors[0].cgColor
        dot.cornerRadius = frame.width / 2
        dot.frame = frame

        let replicatorLayer = CAReplicatorLayer()
        replicatorLayer.frame = layer.bounds
        replicatorLayer.instanceCount = Int(dotNum)
        replicatorLayer.instanceDelay = 0.1

        let angle = (2.0 * M_PI) / Double(replicatorLayer.instanceCount)

        replicatorLayer.instanceTransform = CATransform3DMakeRotation(CGFloat(angle), 0.0, 0.0, 1.0)

        layer.addSublayer(replicatorLayer)
        replicatorLayer.addSublayer(dot)

        let scaleAnimation = CABasicAnimation(keyPath: "transform.scale")
        scaleAnimation.toValue = 0.4
        scaleAnimation.duration = 0.5
        scaleAnimation.autoreverses = true
        scaleAnimation.repeatCount = .infinity
        scaleAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
        dot.add(scaleAnimation, forKey: "scaleAnimation")

        let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation")
        rotationAnimation.toValue = -2.0 * Double.pi
        rotationAnimation.duration = 6.0
        rotationAnimation.repeatCount = .infinity
        rotationAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
        replicatorLayer.add(rotationAnimation, forKey: "rotationAnimation")

        if colors.count > 1 {

            var cgColors : [CGColor] = []
            for color in colors {
                cgColors.append(color.cgColor)
            }

            let colorAnimation = CAKeyframeAnimation(keyPath: "backgroundColor")
            colorAnimation.values = cgColors
            colorAnimation.duration = 2
            colorAnimation.repeatCount = .infinity
            colorAnimation.autoreverses = true
            dot.add(colorAnimation, forKey: "colorAnimation")

        }

        self.isAnimating = true
        self.isHidden = false

    }


    //MARK:- CALL IT TO STOP THE LOADER
    func stopAnimation()
    {
        if !isAnimating
        {
            return
        }
        UIApplication.shared.keyWindow?.isUserInteractionEnabled = true
        let winSubviews = UIApplication.shared.keyWindow?.subviews
        if (winSubviews?.count)! > 0
        {
            for viw in winSubviews!
            {
                if viw.accessibilityIdentifier == "CustomLoader"
                {
                    viw.removeFromSuperview()
                    //  break
                }
            }
        }

        layer.sublayers = nil

        isAnimating = false
        self.isHidden = true

        self.destroyShardInstance()
    }
    //MARK:- GETTING RANDOM COLOR , AND MANAGE YOUR OWN COLORS
    @objc fileprivate func randomColor()->UIColor
    {
        let randomRed:CGFloat = CGFloat(drand48())
        let randomGreen:CGFloat = CGFloat(drand48())
        let randomBlue:CGFloat = CGFloat(drand48())
        return UIColor(red: randomRed, green: randomGreen, blue: randomBlue, alpha: 1.0)
    }
    override func draw(_ rect: CGRect)
    {
    }
}

find the func name and "addCenterImage" and replace the image name with your custom image.

Step 2

Create the AppDelegate class instance out side of the AppDelegate class like this.

var AppInstance: AppDelegate!
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate
{    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
{
    AppInstance = self
}

Step 3.

put these two func in your AppDelegate

//MARK: - Activity Indicator -
    func showLoader()
    {
        CustomLoader.sharedInstance.startAnimation()
    }
    func hideLoader()
    {
        CustomLoader.sharedInstance.stopAnimation()
    }

Step 4. Use the functions like this whenever you want to animate your loader and stop.

AppInstance.showLoader()
AppInstance.hideLoader()

HAPPY LOADING...


Need Your Help

The project file has been moved renamed or is not on your computer

visual-studio tfs visual-studio-2008

I get this error when I try to load a VS 2008 project from TFS source control:

Broad-phase collision detection methods?

algorithm physics collision-detection broad-phase

I'm building a 2D physics engine and I want to add broad-phase collision detection, though I only know of 2 or 3 types: