Begin Process
Show a Progress Wheel
Do a LONG progress..... reading files, analying etc.
Hide Progress
Tell user we are done
End Process
Problem is the UI isn’t updated until ‘End Process’ … I’ve tried all kinds of nested Dispatch, and nothing works.
I would also like the ‘Long Process’ to update a textarea . This process is 2-5 minutes in length so a static display makes the user wonder “Well is it working?”
I’ve tended to use ‘DispatchQueue’ operations for background tasks, but you could write entire chapter of a book on the subject of threading operations with Swift.
Just a quick idea before I shut down for the night, as it’s getting very late here.
Something like this.
let filesList = [Any]()
// Display and start a UI NSProgressIndicator
DispatchQueue.global(qos: .background).async {
var progress = Int()
for file in self.filesList {
// Process File in fileList
progress += 1
DispatchQueue.main.async {
self.updateProgress(with: progress)
}
}
}
func updateProgress(with value: Int) {
// Update UI NSProgressIndicator with value
}
Not tested in real situation, just an idea.
The important thing is to always update the UI on the main thread.
And any references to objects or variables outside of the DispatchQueue block, has to be referenced by the objects owner, thats why I’ve used ‘self.object’, and not just the objects name.
Well if your using an NSProgressIndicator, I usually set it’s ‘isDisplayedWhenStopped:’ property to false.
Then you just use the ‘startAnimation()’ or ‘stopAnimation()’ functions, and the progress indicator automatically displays itself when started, and automatically hides itself when stopped.
Sorry my bad.
The start and stop animation technique which automatically displays or hides the NSProgressIndicator, only works with the indeterminate style of indicator.
With the determinate type of indicator, you where correct with using the isHidden function call.
It’s also worth using the ‘usesThreadedAnimation’ set to true when your window or main view loads, as this also helps to keep the UI more responsive.
I only ask this as I recently helped someone on Macscripter with some AppleScriptObjc code.
Just to find out that they where on a later MacOS to me, and some bits didn’t work for them.
I cannot get the GUI to update and show the progress wheel until the end of the execution. I have tried dozens of various dispatch scenarios, and none did what the articles indicated.
I don’t have access to “Big Sur” so that might be a problem, I can only guess, and not test.
Firstly make sure you have the progress wheel’s ‘usesThreadedAnimation’ set.
progressIndicator.usesThreadedAnimation = true
Is the progress wheel an indeterminate style ?
I’m guessing not as your using ‘isHidden’ instead of starting and stopping the animation.
Is it an @IBOutlet property in your class or code module ?
if it’s an @IBOutlet, then with respect, double check the connection, as I’ve been there before myself.
Secondly to check the NSProgressIndicator is actually working, comment out your file processing code.
and put ‘sleep(3)’ between the showing and hiding of the progress wheel, just to make sure it actually appears and disappears correctly.
As you need to make sure this is happening before processing any files.
OK Dave this definitely works for me on my “High Sierra” partition.
I have not checked on my “Mojave” or “Catalina” disks, but I’m sure it would work there as well.
This is just a brand new Xcode project, and all of the code is in the “AppDelegate”
import Cocoa
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
@IBOutlet weak var window: NSWindow!
@IBOutlet weak var progressIndicator: NSProgressIndicator!
func applicationWillFinishLaunching(_ notification: Notification) {
progressIndicator.usesThreadedAnimation = true
}
func applicationDidFinishLaunching(_ notification: Notification) {
progressIndicator.startAnimation(nil)
DispatchQueue.global(qos: .default).async {
self.doStuff()
self.doMoreStuff()
}
}
func doStuff() {
for i in 1...10 {
sleep(1)
}
}
func doMoreStuff() {
for i in 1...10 {
sleep(1)
}
DispatchQueue.main.async {
self.progressIndicator.stopAnimation(nil)
}
}
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
}
func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
return NSApplication.TerminateReply.terminateNow
}
}
Catalyst is (mostly) UIKit for the Mac. SwiftUI is another UI framework that also supports the Mac.
Catalyst is almost as fully-featured as UIKit on iOS, since it originated from code for running iOS apps on the Simulator. SwiftUI is not even close to be as capable as UIKit.
Catalyst can be used from Objective-C as well as Swift. If you have other language bindings, that should work as well. SwiftUI is Swift-only for now.
The point of SwiftUI is mainly Apple’s response to 3rd party reactive frameworks like RxSwift.
However that quote is from over a year ago, so I don’t think that is quite true anymore as SwiftUI has made enormous progress from what I see, but feel free to correct me on this.
Basically I see Catalyst as an interim solution that came out of the existing simulator tech, and SwiftUI as the future as it seems unlikely that Apple will maintain two UI kits.
It also works for me when I change the ‘.default’ thread to ‘.background’ thread as well.
The important thing to understand with my example, is that the ‘doStuff’ and ‘doMoreStuff’ functions are running on the default or background threads, depending on which one you use.
So that means any UI manipulation has to be called on the main thread, because that’s the OS rules.