ᔕᖺᘎᕊ ᔕᖺᘎᕊ - 3 months ago 15
Swift Question

Activity Indicator view not showing

I have this code:

let x = (self.view.frame.width / 2)
let y = (self.view.frame.height / 2) - (self.navigationController?.navigationBar.frame.height)!

let activityView = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.WhiteLarge)
activityView.frame = CGRect(x: 200, y: 120, width: 200, height: 200)
activityView.center = CGPoint(x: x, y: y)
activityView.color = UIColor.blueColor()
activityView.layer.zPosition = 1000
activityView.startAnimating()
activityView.hidesWhenStopped = false
activityView.hidden = false
self.view.addSubview(activityView)
self.view.bringSubviewToFront(activityView)


to create and add an indicator view to my app, but it just doesn't show on the screen.

There is a (long) JSON request in between this and the stopAnimating() call.

This is on top of a video preview layer, which is why I've tried to change the z index and the
bringSubviewToFront()
, but it still doesn't show.

Here's what I get when debugging the view hierarchy:


enter image description here


The indicator view is showing there, so it must be being added to the main view. (the blue rectangle is also a separate UIView.)

Here's what I see on the app (no indicator view visible!):


enter image description here


How can I fix this?

Rob Rob
Answer

There are a couple of possible problems:

  1. Make sure you add this activity indicator view from the main thread. If this code is running on a background thread, make sure to dispatch the activity indicator related code back to the main queue.

  2. Make sure you perform the time consuming JSON request asynchronously (e.g. using NSURLSession or equivalent third party library). You must make sure that you do not block the main thread because that will interfere with the activity indicator view.

    So if you have code that is doing something equivalent to NSData(contentsOfURL:) or NSURLConnection.sendSynchronousRequest(), replace it with NSURLSession or equivalent third party library).

    It might look something like:

    func performNetworkRequest(request: NSURLRequest, completionHandler: (String?, ErrorType?) -> ()) {
        let spinner = startActivityIndicatorView()
    
        let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
            // regardless of what happens, stop activity indicator view
    
            defer { self.stopActivityIndicatorView(spinner) }
    
            // your parsing code here ...
    
            // when all done, call completion handler
    
            if successful {    
                completionHandler(..., nil)
            } else {
                completionHandler(nil, someError)
            }
        }
        task.resume()
    }
    
    // I've simplified this a little bit
    
    func startActivityIndicatorView() -> UIActivityIndicatorView {
        let x = (self.view.frame.width / 2)
        let y = (self.view.frame.height / 2) - (self.navigationController?.navigationBar.frame.height)!
    
        let activityView = UIActivityIndicatorView(activityIndicatorStyle: .WhiteLarge)
        activityView.frame = CGRect(x: 200, y: 120, width: 200, height: 200)
        activityView.center = CGPoint(x: x, y: y)
        activityView.color = .blueColor()
        activityView.startAnimating()
        self.view.addSubview(activityView)
    
        return activityView
    }
    
    func stopActivityIndicatorView(activityView: UIActivityIndicatorView) {
        dispatch_async(dispatch_get_main_queue()) {
            activityView.removeFromSuperview()
        }
    }
    

    And you'd use it like so:

    performNetworkRequest(request) { responseObject, error in
        // use responseObject/error here
    }
    
    // but not here
    

    Now, clearly that first parameter of the completion handler will match whatever what's being returned by your API (I used a String? but often it would be a dictionary or some other rich structure), but hopefully this illustrates the basic idea: Start activity indicator on main thread, perform the request and parsing asynchronously, and only when the request is done, remove the activity indicator.

Comments