View/Window won't update until #IBAction is complete - swift

I have a button linked to an IBAction that takes around 5-10 seconds to complete.
While the IBAction function is running I want to print progress messages in my view/window.
The only message that prints is the last one, and it prints after the IBAction has completed (ie the button is not blue).
When I step through the function in the debugger, no messages are visible until I'm out of the function (ie button is not blue).
Any idea on how to update the window while the callback is being executed?

embed the IBAction logic inside a background queue:
DispatchQueue.global(qos: .background).async {
// background code
}
and anytime you need to print the progress in your view/window embed that part inside:
DispatchQueue.main.async {
// user interface code
}
generally you may want to try this:
DispatchQueue.global(qos: .background).async {
//background code
DispatchQueue.main.async {
//your main thread
}
}

Related

Why is it that after showing an NSAlert nothing works?

Why is it that after showing an NSAlert nothing works until I close the NSAlert?
I was trying to print a statement after the display of an NSAlert but print is not working.
Below I have attached my code:
let alert: NSAlert = NSAlert()
alert.messageText = "Hello I am Message text"
alert.informativeText = "i am information"
alert.addButton(withTitle: "OK") // First Button
alert.addButton(withTitle: "Cancel") // 2nd Button
alert.alertStyle = NSAlert.Style.warning
alert.delegate = self
if alert.runModal() == .alertFirstButtonReturn {
print("First Button clicked")
} else {
print("Cancel button clicked")
}
print("after NSAlert >>>>>>> ")
My question is why.
Notice how runModal returns the result of the modal as a NSModalResponse. Code after the line alert.runModal() must be able to access the value that it returns, e.g.
let result = alert.runModal()
print(result)
If the code after runModal were run as soon as the modal is displayed, what would result be? The user has not clicked any buttons on the modal yet, so no one knows!
This is why when runModal is called, code execution kind of just pauses there, at that line, until the user chooses one of the options. runModal is synchronous and blocking.
Compare this with alert.beginSheetModal, which accepts a completionHandler closure, and the modal response is not returned, but passed to the completionHandler. This allows the code after the call to continue to run while the modal is presented, because the code after the call does not have access to the modal response. Only the code in the completionHandler does. beginSheetModal is asynchronous.
If you have something you want to print as soon as the alert is displayed, write it before the runModal call, and (optionally) wrap it in a DispatchQueue.asyncAfter/DispatchQueue.async call, so that your print is asynchronous.
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) {
print("Hello")
}
alert.runModal()
if alert.runModal()
This is executed in application-wide modal session
Here is from doc:
Summary
Runs the alert as an app-modal dialog and returns the constant that
identifies the button clicked. Declaration
open func runModal() -> NSApplication.ModalResponse

How to update UI at main queue in Swift?

I want to create a splash screen application that I also have a Class named EXClass.
for some reasons the EXClass must initialize in main thread and update some UI.
please reference following swift code.
func applicationDidFinishLaunching(aNotification: NSNotification) {
self.windowController = ......
self.windowController.showWindow(self)
// In order to show window at the very first time, I create a main queue
dispatch_async(dispatch_get_main_queue()) {
self.ex = EXClass() // this class must init in main thread
}
}
If I init EXClass in dispatch_async(dispatch_get_main_queue()) that init class is ok but update UI is no effect. why?

SWIFT Updating screen in between steps of while loop

I'm building an app that simulates Conway's Game Of Life. I'm trying to run an infinite animation when a RUN button is pressed. Here's my code:
//When RUN button is clicked, call run repeat
#IBAction func run(sender: AnyObject) {
UIView.animateWithDuration(3, delay: 2, options: [.Repeat], animations: {self.runrepeat()}, completion: nil)
}
//Run repeat calls run, calculating next generation of the board
func runrepeat() {
board.run()
//Update the appearance of all the buttons based on their new values
for cellButton in self.cellButtons {
cellButton.setTitle("\(cellButton.getLabelText())",
forState: .Normal)
}
}
When the RUN UI button is pressed, I want run() to be called, which should continuously call runrepeat() every 3 seconds. board.run() runs algorithms to determine the configuration of the next generation of cells, and the for cellButton{} loop updates that appearances of all the cells.
However, as is, runrepeat() is only called once, so the next generation appears on the board and the animation stops, without any delays. My RUN button is correctly executing runrepeat(), but only once. I want it to repeat forever.
I tried this too:
//Run repeat calls run, calculating next generation of the board
func runrepeat() {
while(true){
board.run()
//Update the appearance of all the buttons based on their new values
for cellButton in self.cellButtons {
cellButton.setTitle("\(cellButton.getLabelText())",
forState: .Normal)
}
}
}
but the infinite while loop just causes my program to freeze up. The updating of the screen never happens.
Can someone please help me with executing a continuous function call, screen update loop?

Check if NSAlert is currently showing up

I am using an NSAlert to display error messages on the main screen of my app.
Basically, the NSAlert is a property of my main view controller
class ViewController: NSViewController {
var alert: NSAlert?
...
}
And when I receive some notifications, I display some messages
func operationDidFail(notification: NSNotification)
{
dispatch_async(dispatch_get_main_queue(), {
self.alert = NSAlert()
self.alert.messageText = "Operation failed"
alert.runModal();
})
}
Now, if I get several notifications, the alert shows up for every notification. I mean, it shows up with the first message, I click on "Ok", it disappears and then shows up again with the second message etc... Which is a normal behaviour.
What I would like to achieve is to avoid this sequence of error message. I actually only care about the first one.
Is there a way to know if my alert view is currently being displayed ?
Something like alert.isVisible as on iOS's UIAlertView ?
From your code, I suspect that notification is triggered in background thread. In this case, any checks that alert is visible right now will not help. Your code will not start subsequent block execution until first block will finish, because runModal method will block, running NSRunLoop in modal mode.
To fix your problem, you can introduce atomic bool property and check it before dispatch_async.
Objective-C solution:
- (void)operationDidFail:(NSNotification *)note {
if (!self.alertDispatched) {
self.alertDispatched = YES;
dispatch_async(dispatch_get_main_queue(), ^{
self.alert = [NSAlert new];
self.alert.messageText = #"Operation failed";
[self.alert runModal];
self.alertDispatched = NO;
});
}
}
Same code using Swift:
func operationDidFail(notification: NSNotification)
{
if !self.alertDispatched {
self.alertDispatched = true
dispatch_async(dispatch_get_main_queue(), {
self.alert = NSAlert()
self.alert.messageText = "Operation failed"
self.alert.runModal();
self.alertDispatched = false
})
}
}
Instead of run modal you could try
- beginSheetModalForWindow:completionHandler:
source: https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSAlert_Class/#//apple_ref/occ/instm/NSAlert/beginSheetModalForWindow:completionHandler:
In the completion handler set the alert property to nil.
And only show the alert if the alert property is nil ( which would be every first time after dismissing the alert).
EDIT : I don't see the documentation say anything about any kind of flag you look for.

Updating the main View from a background thread - Swift

When I press a button I start a loop of calculations and need to be able to update the main view as it progresses. As this is a background thread the screen doesn't update until the call has completed. How do I achieve this in Swift? This is my current, simplified, approach which doesn't work.
#IBAction func calculatePower(sender: AnyObject) {
for day in 1...365 {
dispatch_async(dispatch_get_main_queue()) {
self.dayLabel.text = "\(day)"
}
}
}
I typically add some 0s onto the loop otherwise it completes too quickly going from just 1 to 365.
In Cocoa I would use a statement like:
[self performSelectorOnMainThread:#selector(updateDisplayEnergySunEquator:) withObject:[NSNumber numberWithDouble:distanceFromSun] waitUntilDone:YES];
Any ideas?
#IBAction functions are called in the main thread when the button is pressed. What happens is that your closures are queued on the main thread, but your main thread is busy working on your loop. Basically, you are doing asynchronous work within one thread.
To run the loop on another thread, wrap it in a dispatch_async and use another queue.
#IBAction func calculatePower(sender: AnyObject) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
for day in 1...365 {
dispatch_async(dispatch_get_main_queue()) {
self.dayLabel.text = "\(day)"
}
}
}
}