Update View Multiple Times Within Function

What is the best/accepted way to relay the changing status to a user?

Dialog?

TextField inside the window?


I have a function that reads and processes a bunch of user data.

I want to show the status of the process, not just show an Activity Indicator.

Depending on the amount of data a user has accumulated, the process could take 20 seconds.


Here is my function (in general)


@IBOutlet weak var statusMessageLabel: NSTextField!

func ProcessData()
{
    statusText.stringValue = “Message 1”
    read_data1()
    statusText.stringValue = “Message 2”
    read_data2()
    statusText.stringValue = “Message 3”
    check_for_stuff()
    statusText.stringValue = “Message 4”
    check_for_stuff2()
    statusText.stringValue = "Done"
}

As expected, the only thing shown is "Done".


I need the UI to update before the function is complete, but I don’t think that’s possible.

Accepted Reply

NSProgressIndicator object exist in IB. It is easy to implement.


Insert a ProgressBar in IB (Determinate ProgressBar)

Set minimum to 0 and maximum to 4, step 1

Connect to an IBOutlet

    @IBOutlet weak var progressBar: NSProgressIndicator!


Then, instead or in addition to self.statusText.stringValue, set the value of progress indicator, like here:


    func processData() {
        DispatchQueue.global(qos: .userInitiated).async {
            DispatchQueue.main.async { //  as we call UI function
                self.statusText.stringValue = "Message 1"
                self.progressBar.doubleValue = 0.0
            }
            self.readData()
            DispatchQueue.main.async {
                self.statusText.stringValue = "Message 2"
                self.progressBar.doubleValue = 1.0
            }
            self.readData()
            DispatchQueue.main.async {
                self.statusText.stringValue = "Message 3"
                self.progressBar.doubleValue = 2.0
            }
            self.readData()
            DispatchQueue.main.async {
                self.statusText.stringValue = "Message 4"
                self.progressBar.doubleValue = 3.0
            }
            self.readData()
            DispatchQueue.main.async { 
                self.statusText.stringValue = "Done"
                self.progressBar.doubleValue = 4.0
            }
        }
    }



Don't forget to mark the thread as closed after your test and good continuation.

Replies

You need to execute porcessData in another thread


// Where you call ProcessData():

        DispatchQueue.global(qos: .userInitiated).async {
                self.ProcessData()
          }

Now, as we call UI functions in processData, they must be called from main thread


    func processData() {
        DispatchQueue.main.async { //  as we call UI function
            self.statusText.stringValue = "Message 1"
        }
        read_data1()
        DispatchQueue.main.async {
            self.statusText.stringValue = "Message 2"
        }
        read_data2()
        DispatchQueue.main.async {
            self.statusText.stringValue = "Message 3"
        }
        read_data3()
        DispatchQueue.main.async {
            self.statusText.stringValue = "Message 4"
        }
        read_data4()
        DispatchQueue.main.async { 
            self.statusText.stringValue = "Done"
        }
    }


Note:

- names of func should start with lowercase

- convention is to use camelCase more than underscore: read_data1 -> readData1

- don't forget to prefix with self in Dispatch blocks

- I assumed the name of IBOutlet is statusText, not statusMessageLabel

     @IBOutlet weak var statusMessageLabel: NSTextField!


You could also Dispatch in the func itself


If so, just call processData() in code directly, without enclosing in a Dispatch.


change func as:


    func ProcessData() {
        DispatchQueue.global(qos: .userInitiated).async {
            DispatchQueue.main.async { //  as we call UI function
                self.statusText.stringValue = "Message 1"
            }
            self.read_data1()
            DispatchQueue.main.async {
                self.statusText.stringValue = "Message 2"
            }
            self.read_data2()
            DispatchQueue.main.async {
                self.statusText.stringValue = "Message 3"
            }
            self.read_data3()
            DispatchQueue.main.async {
                self.statusText.stringValue = "Message 4"
            }
            self.read_data4()
            DispatchQueue.main.async { 
                self.statusText.stringValue = "Done"
            }
        }
    }



I tested, with a dummy read, it works OK.

If it works for you, don't forget to close the thread.

And to answer your first question:


What is the best/accepted way to relay the changing status to a user?

Dialog?

TextField inside the window?


HIG make it clear that you should not abuse of alerts:

Minimize alerts. Alerts disrupt the user experience and should only be used in important situations like confirming purchases and destructive actions (such as deletions), or notifying people about problems. The infrequency of alerts helps ensure that people take them seriously. Ensure that each alert offers critical information and useful choices.


So, in your case, TextField seems most appropriate (as you said you don't want to use progress bar: why ?).

Thank you for the response, Claude. I will be testing your suggestions tonight.


I wouldn't mind a progress bar. I've never used one, so I know I should learn.

I didn't want to use a "spinning thing" because if a user is unsure how long it should take, how long would they wait until they Force Quit?


Even if the updates are fairly quickly, I want the user to see activity.

NSProgressIndicator object exist in IB. It is easy to implement.


Insert a ProgressBar in IB (Determinate ProgressBar)

Set minimum to 0 and maximum to 4, step 1

Connect to an IBOutlet

    @IBOutlet weak var progressBar: NSProgressIndicator!


Then, instead or in addition to self.statusText.stringValue, set the value of progress indicator, like here:


    func processData() {
        DispatchQueue.global(qos: .userInitiated).async {
            DispatchQueue.main.async { //  as we call UI function
                self.statusText.stringValue = "Message 1"
                self.progressBar.doubleValue = 0.0
            }
            self.readData()
            DispatchQueue.main.async {
                self.statusText.stringValue = "Message 2"
                self.progressBar.doubleValue = 1.0
            }
            self.readData()
            DispatchQueue.main.async {
                self.statusText.stringValue = "Message 3"
                self.progressBar.doubleValue = 2.0
            }
            self.readData()
            DispatchQueue.main.async {
                self.statusText.stringValue = "Message 4"
                self.progressBar.doubleValue = 3.0
            }
            self.readData()
            DispatchQueue.main.async { 
                self.statusText.stringValue = "Done"
                self.progressBar.doubleValue = 4.0
            }
        }
    }



Don't forget to mark the thread as closed after your test and good continuation.

Excellent! Status and ProgressBar implemented and working.


Thanks Claude!