swiftgrand-central-dispatchuiactivityindicatorviewdispatch-queue

How Do I Fix My Activity Indicator From Not Appearing/Freezing?


@IBOutlet weak var Input: UITextField!
@IBOutlet weak var Heads: UILabel!
@IBOutlet weak var working: UIActivityIndicatorView!

@IBAction func Toss(_ sender: Any) {
    
    DispatchQueue.global().sync {
        //set up variables and method
        var input = 0
        func integer(from textField: UITextField) -> Int {
            guard let text = textField.text, let number = Int(text) else {
                return 0
            }
            return number
        }
        var runningTotal = 0
        
        //collect input
        input = integer(from: Input)
        
        //start loading symbol
        DispatchQueue.main.async() { [self] in
            working.startAnimating()
        }
        
        //do math
        for _ in 0..<input {
            let currentTrial = Int.random(in: 0...1)
            if(currentTrial == 1) {
            runningTotal += 1
            }
        }

        DispatchQueue.main.async { [self] in
            
            //set output
            Heads.text = String(runningTotal)
            
            //stop loading symbol
            working.stopAnimating()
        }
    }
}

This program calculates the number of heads flipped when conducting x coin flips (specified by the user). My activity spinner does not show up at all, even though it is set up to appear when animated. Any help would be appreciated!


Solution

  • So, start by going and having a look at the documentation for DispatchQueue

    The sync function reads...

    func sync(execute: () -> Void)
    Submits a block object for execution and returns after that block finishes executing.

    which is not what you want, this will block the main thread and prevent the UI from been updated.

    Instead, what you want to use is one of the async variants, for example...

    func async(group: DispatchGroup?, qos: DispatchQoS, flags: DispatchWorkItemFlags, execute: () -> Void)
    Schedules a block asynchronously for execution and optionally associates it with a dispatch group.

    You should also get the input value before you run the background thread, as you should avoid accessing components outside of the context of the main thread.

    Runnable example...

    class ViewController: UIViewController {
        
        @IBOutlet weak var textField: UITextField!
        @IBOutlet weak var working: UIActivityIndicatorView!
        
        @IBOutlet weak var headsLabel: UILabel!
        
        override func viewDidLoad() {
            super.viewDidLoad()
            headsLabel.isHidden = true
        }
        
        @IBAction func doStuff(_ sender: Any) {
            var input = 0
            func integer(from textField: UITextField) -> Int {
                guard let text = textField.text, let number = Int(text) else {
                    return 0
                }
                return number
            }
            input = integer(from: textField)
            
            working.startAnimating()
            
            DispatchQueue.global(qos: .userInitiated).async {
                //set up variables and method
                var runningTotal = 0
                
                //do math
                for _ in 0..<input {
                    let currentTrial = Int.random(in: 0...1)
                    if(currentTrial == 1) {
                        runningTotal += 1
                    }
                }
                
                DispatchQueue.main.async { [self] in
                    
                    headsLabel.isHidden = false
                    //set output
                    headsLabel.text = String(runningTotal)
                    
                    //stop loading symbol
                    working.stopAnimating()
                }
            }
        }
        
        
    }