user9816630
user9816630

Reputation: 11

Problems : live updating labels/progress bars using Swift

I'm trying to do a small iOS app to visually prove the big number's law.

How it is supposed to work?


A function finds a random number (based on an interval given by the user, using the UIStepper) and check if it is equal to 1, for a number of times also given by the user (using the UISlider). If it is equal to 1, it then increments a variable. At the end, it gives the percentage of 1 after the whole loop is processed.

What's the problem then?


First of all, the UIProgressBar isn't moving through the process, and goes all the way up to 100% at the end. The main label is supposed to show the value in real time if the Switch is on, but it doesn't (the switch state does nothing). And - last but not least - if you select the maximum value of the UISlider, after the process is finished, you can't start again. You can check its User Interface to get a better idea of it.

This is the code:

class ViewController: UIViewController {

    //Outlets :
    @IBOutlet weak var refreshButton: UIBarButtonItem!
    @IBOutlet weak var mainButton: UIBarButtonItem!
    @IBOutlet weak var processingSwitch: UISwitch!
    @IBOutlet weak var probStepper: UIStepper!
    @IBOutlet weak var probSlider: UISlider!
    @IBOutlet weak var progressBar: UIProgressView!
    @IBOutlet weak var mainLabel: UILabel!
    @IBOutlet weak var labelMax: UILabel!
    @IBOutlet weak var labelPossibilties: UILabel!

    //Actions :
    @IBAction func refreshValues(_ sender: UIBarButtonItem) {
        probSlider.setValue(1, animated: true)
        probStepper.value = 2
        labelMax.text = String(probSlider.value)
        labelPossibilties.text = String(probStepper.value)
    }
    @IBAction func probaSlider(_ sender: UISlider) {labelMax.text = String(Double(Int(probSlider.value)))}
    @IBAction func probaStepper(_ sender: UIStepper) {labelPossibilties.text = String(UInt32(probStepper.value))}
    @IBAction func mainButton(_ sender: UIBarButtonItem) {bigNumbersLaw(processingSwitch.isOn, UInt32(probStepper.value), Double(Int(probSlider.value)))}

    //Variables :
    var repeatNumber:Double = 0
    var randomNumber:UInt32 = 0
    var probability:Double = 0
    var timesFound:Double = 0

    //Functions :
    func bigNumbersLaw(_ liveShow: Bool, _ poss: UInt32, _ max: Double) {

        while repeatNumber < max {
            repeatNumber += 1
            randomNumber = arc4random_uniform(poss)
            progressBar.progress = Float(repeatNumber/max)
            if randomNumber == 1 {
                timesFound += 1
            }
            probability = (timesFound/repeatNumber)
            if liveShow {mainLabel.text = String(probability)}
        }
        mainLabel.text = String(probability)
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        probStepper.maximumValue = 1000000
        probStepper.minimumValue = 2
        probSlider.maximumValue = 1000000
        probSlider.minimumValue = 1
    }
}

Thank you some much to anyone that can help me, and thanks for reading.

Upvotes: 1

Views: 784

Answers (2)

Losiowaty
Losiowaty

Reputation: 8006

Your problem lies with the fact, that there is no layout/drawing pass during the execution of your bigNumbersLaw method. Since this method is called synchronously, there are no UI updates until it executes itself, and when the next frame is being drawn it uses the latest values set.

What you need to do, is to "break down" this execution into steps, and update the UI between them.

//Functions :
func bigNumbersLaw(_ liveShow: Bool, _ poss: UInt32, _ max: Double) { // #3

    if repeatNumber < max { // #1
        repeatNumber += 1
        randomNumber = arc4random_uniform(poss)
        progressBar.progress = Float(repeatNumber/max)
        if randomNumber == 1 {
            timesFound += 1
        }
        probability = (timesFound/repeatNumber)
        mainLabel.text = String(probability)
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .milliseconds(1)) {
            bigNumbersLaw(liveShow, poss, max) // #2
        }
    }
}

Bear in mind - I haven't tested this code, but it should work. Two main things happened here :

  1. The loop got changed for a simple if check.
  2. The "loop" is now executed here. We dispatch the next "turn of the loop" to be executed 1 milisecond in the future - this will allow the system to update the UI.

I've removed the liveShow variable from the code, since I couldn't find an elegant solution to fit it - this is left as an exercise for the reader.

Another note - you should probably block the UI while this is being executed.

Upvotes: 1

palme
palme

Reputation: 2609

Have you tried putting your changes of the GUI on the main thread?

DispatchQueue.main.async() {
   // your UI update code
}

Changes of the GUI are should always be called on the main thread (GCD). This would apply in all your IBActions and when you want the slider to animate/move.

A very good read about it Grand Central Dispatch - Joyce Matos on Medium

Upvotes: 0

Related Questions