Search code examples
swiftuitableviewuiactivityindicatorview

I am trying to get an UIActivityIndicatorView to show when I am loading a UITableView


This problem has been answered several times before on this site, I have tried them all and none work. The difference I think is that I have a UITableView inside my UIViewController. I have tried when loading the data within viewDidLoad, here the screen I am coming from show until all is complete and my new view appears. I have also tried within viewDidAppear, here I have a blank table showing before the final view comes up.

I have tried 4 methods all from this site, I call pauseApp(n) before I start the load and restartApp(n) when completed

    var spinner:UIActivityIndicatorView = UIActivityIndicatorView()
    var loadingView = UIView()
    var loadingLabel = UILabel()
    var indicator = UIActivityIndicatorView()
    @IBOutlet weak var tvTable: UITableView!

    func pauseApp() {
        tvTable.activityIndicatorView.startAnimating()
        tvTable.activityIndicatorView.bringSubviewToFront(aIV)
        UIApplication.shared.beginIgnoringInteractionEvents()
    }
    func pauseApp1() {
        spinner = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
        spinner.center = self.navBar.center
        spinner.hidesWhenStopped = true
        spinner.style = UIActivityIndicatorView.Style.gray
        self.navigationController?.view.addSubview(spinner)
        spinner.startAnimating()
        UIApplication.shared.beginIgnoringInteractionEvents()
    }
    func pauseApp2() {
        tvTable.activityIndicatorView.startAnimating()
        indicator.startAnimating()
        indicator.backgroundColor = UIColor.white
        UIApplication.shared.beginIgnoringInteractionEvents()
    }
    func pauseApp3() {
        setLoadingScreen()
        UIApplication.shared.beginIgnoringInteractionEvents()
    }
    func restartApp() {
    //        sleep(2)
        tvTable.activityIndicatorView.stopAnimating()
        UIApplication.shared.endIgnoringInteractionEvents()
    }
    func restartApp1() {
        spinner.stopAnimating()
        UIApplication.shared.endIgnoringInteractionEvents()
    }
    func restartApp2() {
    //        sleep(2)
        indicator.stopAnimating()
        indicator.hidesWhenStopped = true
        UIApplication.shared.endIgnoringInteractionEvents()
    }
    func restartApp3() {
    //       sleep(2)
        removeLoadingScreen()
        UIApplication.shared.endIgnoringInteractionEvents()
    }
    private func setLoadingScreen() {
        let width: CGFloat = 120
        let height: CGFloat = 30
        let x = (view.frame.width / 2) - (width / 2)
        let y = (view.frame.height / 2) - (height / 2) - 20
        loadingView.frame = CGRect(x: x, y: y, width: width, height: height)

        // Sets loading text
        loadingLabel.textColor = .gray
        loadingLabel.textAlignment = .center
        loadingLabel.text = "Loading..."
        loadingLabel.frame = CGRect(x: 0, y: 0, width: 140, height: 30)

        // Sets spinner
        spinner.style = .gray
        spinner.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
        spinner.startAnimating()

        // Adds text and spinner to the view
        loadingView.addSubview(spinner)
        loadingView.addSubview(loadingLabel)

        view.addSubview(loadingView)
        view.bringSubviewToFront(loadingView)

    }
    private func removeLoadingScreen() {
        spinner.stopAnimating()
        spinner.isHidden = true
        loadingLabel.isHidden = true
    }

    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)
    }
    fileprivate var ActivityIndicatorViewAssociativeKey = "ActivityIndicatorViewAssociativeKey"
    public var aIV: UIActivityIndicatorView = UIActivityIndicatorView()
    public extension UITableView {
        var activityIndicatorView: UIActivityIndicatorView {
            get {
                if let aIV = getAssociatedObject(&ActivityIndicatorViewAssociativeKey) as? UIActivityIndicatorView {
                return aIV
                } else {
                    let aIV = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
                    aIV.style = .gray
                    aIV.color = .gray
                    aIV.backgroundColor = UIColor.black
                    aIV.center = center
                    aIV.hidesWhenStopped = true
                    addSubview(aIV)

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

            set {
                addSubview(newValue)
                setAssociatedObject(newValue, associativeKey:&ActivityIndicatorViewAssociativeKey, policy: .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
            }
        }
    }
    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
        }
    }

Solution

  • Verify your Interface Builder file, specifically the order in which the components are defined. Components higher up in the hierarchy may be hidden by those defined below them. Thus it's quite possible that your tableview hides your activity view.

    You should be able to confirm this fairly quickly by hiding the table view and other other views that may be on top. Depending on your activity view settings, you may also need to do tvTable.activityIndicatorView.isHidden = false. Note that since UITableView implement a built-in scrollview, adding an activity view as a child to a UITableView may not be the the best course. You are better off defining it as a child of the tableView's superview; ref: enter image description here

    Your attempt with pauseApp1 could work with minor modifications, but only if your view controller is hosted inside a navigation controller. You should also always define any relationship only AFTER the view is added as a subview not before.

    Starting a brand new project from scratch, here's how you can display an activity indicator by code:

    class ViewController: UIViewController {
    
        override func viewDidAppear(_ animated: Bool) {
            super.viewDidAppear(animated)
    
            // We add some delay for fun, absolutely not required!
            DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
                self.showSpinner()
            }
        }
    
        private func showSpinner() {
            let spinner = UIActivityIndicatorView.init(style: .gray)
            self.view.addSubview(spinner)
            spinner.center = self.view.center
            spinner.startAnimating()
            spinner.isHidden = false
            spinner.hidesWhenStopped = true
        }
    }