Spinner fails to appear

I am trying to start the spinner before a slow complex calculation, but it does not appear!


@IBAction func Save(sender: AnyObject) {

activityIndicator = UIActivityIndicatorView(frame: CGRectMake(0, 0, 50, 50))

activityIndicator.center = self.view.center

activityIndicator.hidesWhenStopped = true

activityIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.Gray

view.addSubview(activityIndicator)

activityIndicator.startAnimating()

let myCIimage = CIImage(image: imageIn.image!)

let myCGimage = convertCIImageToCGImage(myCIimage!)

imageOut.image = manipulatePixel(myCGimage)

UIImageWriteToSavedPhotosAlbum(imageOut.image!, self, "image:didFinishSavingWithError:contextInfo:", nil)

activityIndicator.stopAnimating()

}

If you're doing all that slow complex calculation in that same method on the main thread, that would explain it. You need to start the activity indicator, start your heavy lifting on a background thread, then when the background work completes, it should dispatch back to the main thread to stop the activity indicator and update the UI with the result of the calculation.

How do I do this?

Or maybe it is enough to call activityIndicator.stopAnimating() in UIImageWriteToSavedPhotosAlbum's completion method: "image:didFinishSavingWithError:contextInfo:"?


If image manipulation is slow, use for example GCD(Grand Central Dispatch) like junkpile said.

How do I do this?

The approach I recommend is outlined in Technote 2109 Simple and Reliable Threading with NSOperation and its associated ListAdder sample code.

Share and Enjoy

Quinn "The Eskimo!"
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Neither color change nor spinner works.



@IBAction func Save(sender: AnyObject) {

SaveButton.tintColor = UIColor.grayColor()

activityIndicator = UIActivityIndicatorView(frame: CGRectMake(0, 0, 50, 50))

activityIndicator.center = self.view.center

activityIndicator.hidesWhenStopped = true

activityIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.Gray

view.addSubview(activityIndicator)

activityIndicator.startAnimating()

var isDone = false

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {

print("start")

let myCIimage = CIImage(image: self.imageIn.image!)

let myCGimage = convertCIImageToCGImage(myCIimage!)

self.imageOut.image = manipulatePixel(myCGimage)

isDone = true

print("Done")

}

while isDone == false {

}

UIImageWriteToSavedPhotosAlbum(imageOut.image!, self, "image:didFinishSavingWithError:contextInfo:", nil)

SaveButton.tintColor = UIColor.redColor()

activityIndicator.stopAnimating()

}

Finally found something that works. But why is this so difficult?

Why didn't this work without the dispatch_async?


@IBAction func Save(sender: AnyObject) {

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {

self.SaveButton.tintColor = UIColor.grayColor()

self.activityIndicator = UIActivityIndicatorView(frame: CGRectMake(0, 0, 50, 50))

self.activityIndicator.center = self.view.center

self.activityIndicator.hidesWhenStopped = true

self.activityIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.Gray

self.view.addSubview(self.activityIndicator)

self.activityIndicator.startAnimating()

}

var isDone = false

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {

print("start")

let myCIimage = CIImage(image: self.imageIn.image!)

let myCGimage = convertCIImageToCGImage(myCIimage!)

self.imageOut.image = manipulatePixel(myCGimage)

isDone = true

print("Done")

}

while isDone == false {

}

UIImageWriteToSavedPhotosAlbum(imageOut.image!, self, "image:didFinishSavingWithError:contextInfo:", nil)

SaveButton.tintColor = UIColor.redColor()

activityIndicator.stopAnimating()

}

That code is not valid. You should never do any UIKit calls on a background thread. Setting tint color, adding subviews etc. must be done on the main thread. Only the computation should be dispatched to your background queue.


And a busy wait (while isDone == false { } ) is a horrible idea. The code should look like this.


// on main thread
self.activityIndicator = ...
self.view.addSubview(blah blah blah)...

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
   // do your heavy lifting on a background thread
   self.someMethodThatTakesAReallyLongTimeAndDoesntTouchAnyUIObjects()

    // perform any UI updates by dispatching back to the main thread
   dispatch_async(dispatch_get_main_queue() {
       self.activityIndicator.stopAnimating()
       self.activityIndicator.removeFromSuperview()
       self.someLabel.text = "Done!"
   }
}
Spinner fails to appear
 
 
Q