37

When I switch between my tabs it loads some seconds and I want to know that my data is loading. For that I decided to add an activity indicator.

I wrote a little function:

func showActivityIndicator() {
    dispatch_async(dispatch_get_main_queue()) {
        self.spinner = UIActivityIndicatorView(activityIndicatorStyle: .WhiteLarge)
        self.spinner.frame = CGRect(x: 0.0, y: 0.0, width: 80.0, height: 80.0)
        self.spinner.center = CGPoint(x:self.loadingView.bounds.size.width / 2, y:self.loadingView.bounds.size.height / 2)

        self.loadingView.addSubview(self.spinner)
        self.view.addSubview(self.loadingView)
        self.spinner.startAnimating()
    }
}

that will show my indicator. And try to use it when I tapped from my infoController to button:

@IBAction func goToMainFromInfo(sender: AnyObject) {
        self.showActivityIndicator()
        self.performSegueWithIdentifier("fromMainToInfoWActivity", sender: nil)
        self.hideActivityIndicator()
    }
}

I show it before segue perform and hide after. It doesn't help me. When I did try to use sync:

@IBAction func goToMainFromInfo(sender: AnyObject) {
    dispatch_async(dispatch_get_main_queue()) {
        self.showActivityIndicator()
        self.performSegueWithIdentifier("fromMainToInfoWActivity", sender: nil)
        self.hideActivityIndicator()
    }
}

But it doesn't help too. When I press to tab it opacity becomes 0.5 and I wait while it loading. But I do not see my activity indicator.

What is the problem?

Orkhan Alizade
  • 7,379
  • 14
  • 40
  • 79

12 Answers12

59

Just try this:

var indicator = UIActivityIndicatorView()

func activityIndicator() {
    indicator = UIActivityIndicatorView(frame: CGRectMake(0, 0, 40, 40))
    indicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.Gray
    indicator.center = self.view.center
    self.view.addSubview(indicator)    
}

And where you want to start animating

indicator.startAnimating()
indicator.backgroundColor = UIColor.whiteColor()

For stop:

indicator.stopAnimating()
indicator.hidesWhenStopped = true

Note: Avoid the calling of start and stop at the same time. Just give some conditions.

SWIFT : 4.2 Just try this:

var indicator = UIActivityIndicatorView()

func activityIndicator() {
    indicator = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
    indicator.style = UIActivityIndicatorView.Style.gray
    indicator.center = self.view.center
    self.view.addSubview(indicator)   
}

And where you want to start animating

activityIndicator()
indicator.startAnimating()
indicator.backgroundColor = .white

For stop:

indicator.stopAnimating()
indicator.hidesWhenStopped = true
Lydia
  • 2,017
  • 2
  • 18
  • 34
19

Swift 3.0

// UIView Extension

fileprivate var ActivityIndicatorViewAssociativeKey = "ActivityIndicatorViewAssociativeKey"
public extension UIView {
   var activityIndicatorView: UIActivityIndicatorView {
        get {
            if let activityIndicatorView = getAssociatedObject(&ActivityIndicatorViewAssociativeKey) as? UIActivityIndicatorView {
                return activityIndicatorView
            } else {
                let activityIndicatorView = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
                activityIndicatorView.activityIndicatorViewStyle = .gray
                activityIndicatorView.color = .gray
                activityIndicatorView.center = center
                activityIndicatorView.hidesWhenStopped = true
                addSubview(activityIndicatorView)

                setAssociatedObject(activityIndicatorView, associativeKey: &ActivityIndicatorViewAssociativeKey, policy: .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
                return activityIndicatorView
            }
        }

        set {
            addSubview(newValue)
            setAssociatedObject(newValue, associativeKey:&ActivityIndicatorViewAssociativeKey, policy: .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
}

// NSObject Extension

public extension NSObject {
    func setAssociatedObject(_ value: AnyObject?, associativeKey: UnsafeRawPointer, policy: objc_AssociationPolicy) {
        if let valueAsAnyObject = value {
            objc_setAssociatedObject(self, associativeKey, valueAsAnyObject, policy)
        }
    }

    func getAssociatedObject(_ associativeKey: UnsafeRawPointer) -> Any? {
        guard let valueAsType = objc_getAssociatedObject(self, associativeKey) else {
            return nil
        }
        return valueAsType
    }
}

start animation

tableView.activityIndicatorView.startAnimating()

stop animation

tableView.activityIndicatorView.stopAnimating()

You can find more code in Magic

broccoli
  • 343
  • 3
  • 7
14

Swift 2+

    class ViewController: UITableViewController {
        weak var activityIndicatorView: UIActivityIndicatorView!

        override func viewDidLoad() {
            super.viewDidLoad()
            let activityIndicatorView = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.Gray)
            tableView.backgroundView = activityIndicatorView
            self.activityIndicatorView = activityIndicatorView
            activityIndicatorView.startAnimating()

        }
        ...
    }
Ragul
  • 3,176
  • 3
  • 16
  • 32
  • For me the problem was that I added it to the view, which was hidden by my tableview cells, with the same background color as the tableview's background color. This solution does work as long as no cell's are covering the view (so while loading this is no problem), except if you have (as I did) a placeholder cell displaying some text. In that case it might be easier to add the loading indicator to that cell. –  Oct 14 '16 at 07:22
7

I use two extension methods to add an UIActivityIndicatorView as the backgroundView of the tableview.

extension UITableView {
    func showActivityIndicator() {
        DispatchQueue.main.async {
            let activityView = UIActivityIndicatorView(style: .medium)
            self.backgroundView = activityView
            activityView.startAnimating()
        }
    }

    func hideActivityIndicator() {
        DispatchQueue.main.async {
            self.backgroundView = nil
        }
    }
}

You can show/hide it like this.

tableView.showActivityIndicator()
tableView.hideActivityIndicator()
Isuru
  • 30,617
  • 60
  • 187
  • 303
2

This code can help you :)

   let indicator:UIActivityIndicatorView = UIActivityIndicatorView  (activityIndicatorStyle: UIActivityIndicatorViewStyle.Gray) 
   indicator.color = UIColor .magentaColor()
   indicator.frame = CGRectMake(0.0, 0.0, 10.0, 10.0)
   indicator.center = self.view.center
   self.view.addSubview(indicator)
   indicator.bringSubviewToFront(self.view)
   indicator.startAnimating()
Shanmugasundharam
  • 2,082
  • 23
  • 32
2

SWIFT

Place this below your class:

let indicator:UIActivityIndicatorView = UIActivityIndicatorView  (activityIndicatorStyle: UIActivityIndicatorViewStyle.Gray)

Place this in your loadView():

indicator.color = UIColor .magentaColor()
indicator.frame = CGRectMake(0.0, 0.0, 10.0, 10.0)
indicator.center = self.view.center
self.view.addSubview(indicator)
indicator.bringSubviewToFront(self.view)
indicator.startAnimating()

In my case, I am requesting json objects through a func request, so I placed this at the end of that function to remove the activity indicator once the data loads:

self.indicator.stopAnimating()
self.indicator.hidesWhenStopped = true
A.J. Hernandez
  • 969
  • 11
  • 13
2

Another approach, In my code I added an extension for UITableView (Swift 2.3) :

extension UITableView {
    func activityIndicator(center: CGPoint = CGPointZero, loadingInProgress: Bool) {
        let tag = 12093
        if loadingInProgress {
            var indicator = UIActivityIndicatorView()
            indicator = UIActivityIndicatorView(frame: CGRectMake(0, 0, 40, 40))
            indicator.tag = tag
            indicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.WhiteLarge
            indicator.color = //Your color here
            indicator.center = center
            indicator.startAnimating()
            indicator.hidesWhenStopped = true
            self.superview?.addSubview(indicator)
        }else {
             if let indicator = self.superview?.viewWithTag(tag) as? UIActivityIndicatorView { {
                indicator.stopAnimating()
                indicator.removeFromSuperview()
            }
        }
    }
}

Note : My tableview is embedded in a UIView (superview)

Ali Abbas
  • 4,247
  • 1
  • 22
  • 40
2

Update Swift 4.2:

1.call the activityIndicator function on viewDidLoad
eg:

var indicator = UIActivityIndicatorView()
override func viewDidLoad() {
    //Initializing the Activity Indicator
    activityIndicator()
    //Starting the Activity Indicator
    indicator.startAnimating()
    //Call Your WebService or API
    callAPI()
}

Here is the Code For Adding ActivityIndicator as Subview

func activityIndicator() {
            indicator = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
            indicator.style = UIActivityIndicatorView.Style.whiteLarge
            indicator.color = .red
            indicator.center = self.view.center
            self.view.addSubview(indicator)
    }

2. Do UI related Operations or API Call and stop activity indicator

    func callAPI() {     
        YourModelClass.fetchResult(someParams, 
        completionHandler: { (response) in 
        //Do necessary UIUpdates
        //And stop Activity Indicator
        self.indicator.stopAnimating()
        })
    }
BharathRao
  • 1,846
  • 1
  • 18
  • 28
1
 func setupSpinner(){
    spinner = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 40, height:40))
    spinner.color = UIColor(Colors.Accent)
    self.spinner.center = CGPoint(x:UIScreen.main.bounds.size.width / 2, y:UIScreen.main.bounds.size.height / 2)
    self.view.addSubview(spinner)
    spinner.hidesWhenStopped = true
}
Ahmed Lotfy
  • 3,806
  • 26
  • 28
0

Using "lazy var". It's better than function

fileprivate lazy var activityIndicatorView: UIActivityIndicatorView = {
    let activityIndicatorView = UIActivityIndicatorView(activityIndicatorStyle: .gray)
    activityIndicatorView.hidesWhenStopped = true

    // Set Center
    var center = self.view.center
    if let navigationBarFrame = self.navigationController?.navigationBar.frame {
        center.y -= (navigationBarFrame.origin.y + navigationBarFrame.size.height)
    }
    activityIndicatorView.center = center

    self.view.addSubview(activityIndicatorView)
    return activityIndicatorView
}()

Just start the spinner anywhere

like this

func requestData() {
  // Request something
  activityIndicatorView.startAnimating()
}
Den Jo
  • 413
  • 4
  • 10
0

@brocolli's answer for swift 4.0. You have to use objc_ before getting or setting associated objects. According to the documentation, The APIs of getting and setting the associated object in Swift are:

func objc_getAssociatedObject(object: AnyObject!,
                                 key: UnsafePointer<Void>
                             )  -> AnyObject!

func objc_setAssociatedObject(object: AnyObject!,
                                 key: UnsafePointer<Void>,
                               value: AnyObject!,
                              policy: objc_AssociationPolicy)

Implementation:

import UIKit

fileprivate var ActivityIndicatorViewAssociativeKey = "ActivityIndicatorViewAssociativeKey"

extension UIView {
    var activityIndicatorView: UIActivityIndicatorView {
        get {
            if let activityIndicatorView = objc_getAssociatedObject(self, &ActivityIndicatorViewAssociativeKey) as? UIActivityIndicatorView {
                return activityIndicatorView
            } else {
                let activityIndicatorView = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
                activityIndicatorView.activityIndicatorViewStyle = .gray
                activityIndicatorView.color = .gray
                activityIndicatorView.center = center
                activityIndicatorView.hidesWhenStopped = true
                addSubview(activityIndicatorView)

                objc_setAssociatedObject(self, &ActivityIndicatorViewAssociativeKey, activityIndicatorView, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
                return activityIndicatorView
            }
        }

        set {
            addSubview(newValue)
            objc_setAssociatedObject(self, &ActivityIndicatorViewAssociativeKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }

    }
}
  • Please provide some detail as to how this solves the problem. This answer is virtually useless to future users otherwise. – rst-2cv Jun 26 '18 at 10:34
0

In order to place the UIActivityIndicator in foreground, even over the eventual UITableViewController separators, I have adopted this solution.

  • I have add the UIActivityIndicator programmatically, and add it as a subview of my UINavigationController

    var activityIndicator: UIActivityIndicatorView!
    
    override func viewDidLoad() {
       super.viewDidLoad()
       // Code
       // .... omissis
    
       // Set activity indicator
       activityIndicator = UIActivityIndicatorView(style: .whiteLarge)
       activityIndicator.color = UIColor.darkGray
       activityIndicator.center = tableView.center
       activityIndicator.hidesWhenStopped = true
       activityIndicator.stopAnimating()
       self.navigationController?.view.addSubview(activityIndicator)
    }
    
  • I have just start & stop it when needed (in my case):

    func animateActivityIndicator(_ sender: Any ) {
       guard let vc = sender as? UIViewController else { return }
       if let v = vc as? MyTableViewController {
           if v.activityIndicator.isAnimating {
               v.activityIndicator.stopAnimating()
           } else {
               v.activityIndicator.startAnimating()
           }
       }
       // Others UIViewController or UITableViewController follows...
       // all of them exhibits an activityIndicator variable
       // implemented programmatically or with the storyboard
    }
    

PS. My environment is Xcode 10.0, iOS 12.0