Run loop doesn't proceed events - cocoa

I'm testing with run loops in standard (created by XCode) App. My App has 2 buttons:
Start Loop - starts runloop in some mode (see code below);
Stop Loop - change self.stop flag to stop runloop.
`
- (IBAction)stopLoop:(id)sender
{
self.stop = YES;
}
- (IBAction)startLoop:(id)sender
{
self.stop = NO;
do
{
[[NSRunLoop currentRunLoop] runMode:runLoopMode beforeDate:runLoopLimitDate];
if (self.stop)
{
break;
}
} while (YES);
}
`
where:
1. runLoopMode is one of the predefined modes (I try each, default, event tracking, modal, connection).
2. runLoopLimitDate [NSDate distantFuture], or [NSDate distantPast], or close feature.
3. self.stop flag is installed in other method, which called by button.
That's all, my App hasn't any other code.
AFAIU, runloop mode is a set of event sources. So, if I run runloop in some mode, runloop will be proceed those event sources, whose are associated with this mode.
By default Cocoa runs runloop in default mode and all events are proceeds greatly. But when user press startLoop button, my App is freezing: .
startLoop method is never break this infinity cycle. Application doesn't send any event to me, therefore UI freezing and user can't press stopLoop button. The same problem if I run Core Foundation counterparts.
But, when I try to receive events through NSApplication (of NSWindow) method nextEventMatchingMask:untilDate:inMode:dequeue: and pass the same mode, I receive UI events.
- (IBAction)startLoop:(id)sender
{
self.stop = NO;
do
{
NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:nil inMode:NSEventTrackingRunLoopMode dequeue:YES];
if (event == nil)
{
break;
}
[NSApp sendEvent:event];
if (self.stop)
{
break;
}
} while (YES);
}
There are question: "Why if I run default run loop mode, or some other, in this way, I can't receive events?"
Thanks for your advice.

You are assuming that running the NSRunLoop in the NSDefaultRunMode processes user input events such as key presses or mouse clicks. I don't think that's the case.
NSApplication fetches events from the event with nextEventMatchingMask:untilDate:inMode:dequeue:. By running the run loop like that your not really fetching any event of the event queue.
Cloud you try something like this:
- (IBAction)startLoop:(id)sender
{
do
{
NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask];
[NSApp sendEvent:event];
if (self.stop)
{
break;
}
} while (YES);
}
(Haven't tested this).

What happens if you substitute the following code in your app in place of the -startLoop: method?
- (NSString *) debugLogRunLoopInfo(BOOL didRun)
{
NSLog (#"didRun? %#, runMode: %#, dateNow: %#, limitDate: %#",
didRun ? #"YES" : #"NO",
runLoopMode,
[NSDate date],
limitDate);
}
- (IBAction)startLoop:(id)sender
{
BOOL didRun = NO;
do
{
didRun = [[NSRunLoop currentRunLoop] runMode:runLoopMode beforeDate:runLoopLimitDate];
[self debugLogRunLoopInfo:didRun];
if (self.stop)
{
break;
}
} while (YES);
}

The -[NSRunLoop runUntilDate:] method spins the runloop in NSDefaultRunLoopMode. Since you're wanting to experiment with runloop modes, you could try the code below.
I've implemented a -myRunLoopUntilDate:runMode: method that does what runUntilDate: is documented to do, but allows you to specify a runloop mode.
All of this is compiled in my text editor (i.e. not compiled at all), so caveat emptor.
- (NSString *) debugLogRunLoopInfo(BOOL didRun)
{
NSLog (#"didRun? %#, runMode: %#, dateNow: %#, limitDate: %#",
didRun ? #"YES" : #"NO",
runLoopMode,
[NSDate date],
limitDate);
}
- (void) myRunLoopUntilDate:(NSDate *)limitDate runMode:(NSString *)runLoopMode
{
BOOL didRun = NO;
do {
didRun = [[NSRunLoop currentRunLoop] runMode:runLoopMode beforeDate:limitDate];
[self debugLogRunLoopInfo:didRun];
} while (didRun && ([limitDate timeIntervalSinceNow] > 0));
}
- (IBAction)startLoop:(id)sender
{
BOOL didRun = NO;
do
{
[self myRunLoopUntilDate:runLimitDate runMode:runLoopMode];
if (self.stop)
{
break;
}
} while (YES);
}

Related

objective c WatchKit WKInterfaceController openParentApplication call blocks indefinitely

I'm using the following code to "simply" determine the application state of the parent application from my watch app:
WatchKit Extension:
[WKInterfaceController openParentApplication:[NSDictionary dictionary] reply:^(NSDictionary *replyInfo, NSError *error)
{
UIApplicationState appState = UIApplicationStateBackground;
if(nil != replyInfo)
appState = (UIApplicationState)[((NSNumber*)[replyInfo objectForKey:kAppStateKey]) integerValue];
//handle app state
}];
Main App:
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *replyInfo))reply
{
__block UIBackgroundTaskIdentifier realBackgroundTask;
realBackgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
reply([NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:[[UIApplication sharedApplication] applicationState]], kAppStateKey, nil]);
[[UIApplication sharedApplication] endBackgroundTask:realBackgroundTask];
}];
reply([NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:[[UIApplication sharedApplication] applicationState]], kAppStateKey, nil]);
[[UIApplication sharedApplication] endBackgroundTask:realBackgroundTask];
}
When the app is in the foreground this works 100% of the time. When the app is "minimized" or "terminated" this maybe works 50% of the time (maybe less). When it doesn't work it appears to be blocking indefinitely. If after 1 minute, for example, I launch the parent app, the call (openParentApplication) immediately returns with the state "UIApplicationStateBackground" (the state it was before I launched the app as clearly the app isn't in the background state if I launched it).
BTW: I'm testing with real hardware.
What am I doing wrong? Why is iOS putting my main app to sleep immediately after receiving the call even though I create a background task? This is a complete show-stopper.
Any thoughts or suggestions would be greatly appreciated!!
After some research it looks to be a known issue. For example, the following link identifies this issue and provides a solution:
http://www.fiveminutewatchkit.com/blog/2015/3/11/one-weird-trick-to-fix-openparentapplicationreply
However, this solution did not work for me. As a result I implemented the following solution (its a little sloppy, but this is intentional to help condense the solution):
//start the timeout timer
timeoutTimer = [NSTimer scheduledTimerWithTimeInterval:kTimeOutTime target:self selector:#selector(onTimeout) userInfo:nil repeats:NO];
//make the call
messageSent = [WKInterfaceController openParentApplication:[NSDictionary dictionary] reply:^(NSDictionary *replyInfo, NSError *error)
{
if(nil != _stateDelegate)
{
UIApplicationState appState = UIApplicationStateBackground;
if(nil != replyInfo)
appState = (UIApplicationState)[((NSNumber*)[replyInfo objectForKey:kAppStateKey]) integerValue];
[_stateDelegate onOperationComplete:self timeout:false applicationState:appState];
_stateDelegate = nil;
}
}];
//if the message wasn't sent, then this ends now
if(!messageSent)
{
if(nil != _stateDelegate)
{
//just report that the main application is inactive
[_stateDelegate onOperationComplete:self timeout:false applicationState:UIApplicationStateInactive];
}
_stateDelegate = nil;
}
-(void)onTimeout
{
timeoutTimer = nil;
if(nil != _stateDelegate)
{
[_stateDelegate onOperationComplete:self timeout:true applicationState:UIApplicationStateInactive];
}
_stateDelegate = nil;
}
In a nutshell, if the timer fires before I hear back from the main app I will basically assume that the main app has been put to sleep. Keep in mind that all pending calls will succeed at some point (e.g. app state is restored to active) and, thus, you will need to handle this scenario (if necessary).

how to stop runaway flashing NSButton / NSTimer (IOS or OSX ok)

I'm trying to make an NSButton (but I could use the technique for iOS too, so any answers are welcome).
I have a button where hiding/enabling is turned on and off:
-(void)textDidChange:(NSNotification *)notification
{
timerStatus = 0;
timerTest = [NSTimer scheduledTimerWithTimeInterval:2.0
target:self
selector:#selector(changeButtonState:)
userInfo:nil
repeats:YES];
}
-(void) changeButtonState:(id) sender {
NSLog(#"%s", __FUNCTION__);
if (timerStatus == 2) return;
if (timerStatus == 0) {
timerStatus = 1;
saveButton.enabled = YES;
saveButton.hidden = NO;
} else {
timerStatus = 0;
saveButton.enabled = NO;
saveButton.hidden = YES;
}
}
The button blinks ok, but after it's associated method is fired (in this case a save action), I want the timer to stop and the button to stop blinking. It's this last part that's giving me a headache.. Any help appreciated.
- (IBAction)saveItemNotes:(id)sender
{
NSLog(#"%s", __FUNCTION__);
<do my save stuff here>
timerStatus = 2;
[timerTest invalidate];
timerTest = nil;
}
Just invalidate your timer in that same method where you change the button state.
Better yet, don't do it this way.
Use Core Animation instead if you want to draw attention to an interface item.

XCode View Controller Not Updating in Do Loop

I have a do loop that I want to execute a command every 1 second while a SWITCH is on.
The Code works fine ONCE, when I don't have the DO LOOP.
However, as soon as I add the LOOP, none of the labels in the view controller are updated, the back button for the storyboard doesn't work, and the SWITCH will not toggle off. Essentially, the DO LOOP keeps looping, but nothing on the screen will work, nor can I back out.
I know I'm doing it wrong. But, I don't now what. Any thoughts would be appreciated.
I attached the code that gets me in trouble.
Thanks,
- (IBAction)roaming:(id)sender {
UISwitch *roamingswitch = (UISwitch *)sender;
BOOL isOn = roamingswitch.isOn;
if (isOn) {
last=[NSDate date];
while (isOn)
{
current = [NSDate date];
interval = [current timeIntervalSinceDate:last];
if (interval>10) {
TheCommand.text=#"ON";
[self Combo:sendcommand];
last=current;
}
}
}
else
{
TheCommand.text=#"OFF";
}
}
iOS and OSX are event based systems and you cannot use loops like this in the main (UI) thread to do what you want to do, otherwise you don't allow the run loop to run and events stop being processed.
See: Mac App Programming Guide section "The App’s Main Event Loop Drives Interactions".
What you need to do is set-up a timer (NSTimer) which will fire every second:
.h file:
#interface MyClass : NSView // Or whatever the base class is
{
NSTimer *_timer;
}
#end
.m file:
#implementation MyClass
- (id)initWithFrame:(NSRect)frame // Or whatever the designated initializier is for your class
{
self = [super initInitWithFrame:frame];
if (self != nil)
{
_timer = [NSTimer timerWithTimeInterval:1.0
target:self
selector:#selector(timerFired:)
userInfo:nil
repeats:YES];
}
return self;
}
- (void)dealloc
{
[_timer invalidate];
// If using MRR ONLY!
[super dealloc];
}
- (void)timerFired:(NSTimer*)timer
{
if (roamingswitch.isOn)
{
TheCommand.text=#"ON";
[self Combo:sendcommand];
}
}
#end
Give your processor enough time to update your view controller and not be interrupted by other processes. I give it 0.5 second before and after the view controller update signal.
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5]];
self.CrashingTime.text = [NSString stringWithFormat: #"Crash Time = %f ms", outputOfCrashTime];
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5]];

How do I use the return value of a sheet to decide whether or not to close a window?

I want to use windowShouldClose: in my NSWindowController subclass to pop up a sheet asking if the user wants to save changes before closing with Save, Cancel, and Don't Save buttons.
The issue I'm running in to is that beginSheetModalForWindow:... uses a delegate instead of a return value.
I can return NO in windowShouldClose:, but then when I send [self close] to the controller in the panel's delegate nothing happens.
Can somebody explain to me how to do this or point me in the direction of some sample code?
The basic solution is to put a boolean flag on the window that states whether or not the window has warned about unsaved changes. Before calling [self close], set this flag to true.
Finally, in the windowShouldClose method, return the value of the flag.
This is the code I ended up using.
windowShouldCloseAfterSaveSheet_ is an instance variable in my controller class.
Remember to set the window outlet for the controller in IB.
- (BOOL)windowShouldClose:(id)window {
if (windowShouldCloseAfterSaveSheet_) {
// User has already gone through save sheet and choosen to close the window
windowShouldCloseAfterSaveSheet_ = NO; // Reset value just in case
return YES;
}
if ([properties_ settingsChanged]) {
NSAlert *saveAlert = [[NSAlert alloc] init];
[saveAlert addButtonWithTitle:#"OK"];
[saveAlert addButtonWithTitle:#"Cancel"];
[saveAlert addButtonWithTitle:#"Don't Save"];
[saveAlert setMessageText:#"Save changes to preferences?"];
[saveAlert setInformativeText:#"If you don't save the changes, they will be lost"];
[saveAlert beginSheetModalForWindow:window
modalDelegate:self
didEndSelector:#selector(alertDidEnd:returnCode:contextInfo:)
contextInfo:nil];
return NO;
}
// Settings haven't been changed.
return YES;
}
// This is the method that gets called when a user selected a choice from the
// do you want to save preferences sheet.
- (void)alertDidEnd:(NSAlert *)alert
returnCode:(int)returnCode
contextInfo:(void *)contextInfo {
switch (returnCode) {
case NSAlertFirstButtonReturn:
// Save button
if (![properties_ saveToFile]) {
NSAlert *saveFailedAlert = [NSAlert alertWithMessageText:#"Save Failed"
defaultButton:#"OK"
alternateButton:nil
otherButton:nil
informativeTextWithFormat:#"Failed to save preferences to disk"];
[saveFailedAlert runModal];
}
[[alert window] orderOut:self];
windowShouldCloseAfterSaveSheet_ = YES;
[[self window] performClose:self];
break;
case NSAlertSecondButtonReturn:
// Cancel button
// Do nothing
break;
case NSAlertThirdButtonReturn:
// Don't Save button
[[alert window] orderOut:self];
windowShouldCloseAfterSaveSheet_ = YES;
[[self window] performClose:self];
break;
default:
NSAssert1(NO, #"Unknown button return: %i", returnCode);
break;
}
}

Wait for [NSAlert beginSheetModalForWindow:...];

When I display an NSAlert like this, I get the response straight away:
int response;
NSAlert *alert = [NSAlert alertWithMessageText:... ...];
response = [alert runModal];
The problem is that this is application-modal and my application is document based. I display the alert in the current document's window by using sheets, like this:
int response;
NSAlert *alert = [NSAlert alertWithMessageText:... ...];
[alert beginSheetModalForWindow:aWindow
modalDelegate:self
didEndSelector:#selector(alertDidEnd:returnCode:contextInfo:)
contextInfo:&response];
//elsewhere
- (void) alertDidEnd:(NSAlert *) alert returnCode:(int) returnCode contextInfo:(int *) contextInfo
{
*contextInfo = returnCode;
}
The only issue with this is that beginSheetModalForWindow: returns straight away so I cannot reliably ask the user a question and wait for a response. This wouldn't be a big deal if I could split the task into two areas but I can't.
I have a loop that processes about 40 different objects (that are in a tree). If one object fails, I want the alert to show and ask the user whether to continue or abort (continue processing at the current branch), but since my application is document based, the Apple Human Interface Guidelines dictate to use sheets when the alert is specific to a document.
How can I display the alert sheet and wait for a response?
We created a category on NSAlert to run alerts synchronously, just like application-modal dialogs:
NSInteger result;
// Run the alert as a sheet on the main window
result = [alert runModalSheet];
// Run the alert as a sheet on some other window
result = [alert runModalSheetForWindow:window];
The code is available via GitHub, and the current version posted below for completeness.
Header file NSAlert+SynchronousSheet.h:
#import <Cocoa/Cocoa.h>
#interface NSAlert (SynchronousSheet)
-(NSInteger) runModalSheetForWindow:(NSWindow *)aWindow;
-(NSInteger) runModalSheet;
#end
Implementation file NSAlert+SynchronousSheet.m:
#import "NSAlert+SynchronousSheet.h"
// Private methods -- use prefixes to avoid collisions with Apple's methods
#interface NSAlert ()
-(IBAction) BE_stopSynchronousSheet:(id)sender; // hide sheet & stop modal
-(void) BE_beginSheetModalForWindow:(NSWindow *)aWindow;
#end
#implementation NSAlert (SynchronousSheet)
-(NSInteger) runModalSheetForWindow:(NSWindow *)aWindow {
// Set ourselves as the target for button clicks
for (NSButton *button in [self buttons]) {
[button setTarget:self];
[button setAction:#selector(BE_stopSynchronousSheet:)];
}
// Bring up the sheet and wait until stopSynchronousSheet is triggered by a button click
[self performSelectorOnMainThread:#selector(BE_beginSheetModalForWindow:) withObject:aWindow waitUntilDone:YES];
NSInteger modalCode = [NSApp runModalForWindow:[self window]];
// This is called only after stopSynchronousSheet is called (that is,
// one of the buttons is clicked)
[NSApp performSelectorOnMainThread:#selector(endSheet:) withObject:[self window] waitUntilDone:YES];
// Remove the sheet from the screen
[[self window] performSelectorOnMainThread:#selector(orderOut:) withObject:self waitUntilDone:YES];
return modalCode;
}
-(NSInteger) runModalSheet {
return [self runModalSheetForWindow:[NSApp mainWindow]];
}
#pragma mark Private methods
-(IBAction) BE_stopSynchronousSheet:(id)sender {
// See which of the buttons was clicked
NSUInteger clickedButtonIndex = [[self buttons] indexOfObject:sender];
// Be consistent with Apple's documentation (see NSAlert's addButtonWithTitle) so that
// the fourth button is numbered NSAlertThirdButtonReturn + 1, and so on
NSInteger modalCode = 0;
if (clickedButtonIndex == NSAlertFirstButtonReturn)
modalCode = NSAlertFirstButtonReturn;
else if (clickedButtonIndex == NSAlertSecondButtonReturn)
modalCode = NSAlertSecondButtonReturn;
else if (clickedButtonIndex == NSAlertThirdButtonReturn)
modalCode = NSAlertThirdButtonReturn;
else
modalCode = NSAlertThirdButtonReturn + (clickedButtonIndex - 2);
[NSApp stopModalWithCode:modalCode];
}
-(void) BE_beginSheetModalForWindow:(NSWindow *)aWindow {
[self beginSheetModalForWindow:aWindow modalDelegate:nil didEndSelector:nil contextInfo:nil];
}
#end
The solution is to call
[NSApp runModalForWindow:alert];
after beginSheetModalForWindow. Also, you need to implement a delegate that catches the "dialog has closed" action, and calls [NSApp stopModal] in response.
Here is a NSAlert category that solves the issue (as suggested by Philipp with the solution proposed by Frederick and improved by Laurent P.: I use a code block instead of a delegate, so it is simplified once again).
#implementation NSAlert (Cat)
-(NSInteger) runModalSheetForWindow:(NSWindow *)aWindow
{
[self beginSheetModalForWindow:aWindow completionHandler:^(NSModalResponse returnCode)
{ [NSApp stopModalWithCode:returnCode]; } ];
NSInteger modalCode = [NSApp runModalForWindow:[self window]];
return modalCode;
}
-(NSInteger) runModalSheet {
return [self runModalSheetForWindow:[NSApp mainWindow]];
}
#end
Just in case anyone comes looking for this (I did), I solved this with the following:
#interface AlertSync: NSObject {
NSInteger returnCode;
}
- (id) initWithAlert: (NSAlert*) alert asSheetForWindow: (NSWindow*) window;
- (NSInteger) run;
#end
#implementation AlertSync
- (id) initWithAlert: (NSAlert*) alert asSheetForWindow: (NSWindow*) window {
self = [super init];
[alert beginSheetModalForWindow: window
modalDelegate: self didEndSelector: #selector(alertDidEnd:returnCode:) contextInfo: NULL];
return self;
}
- (NSInteger) run {
[[NSApplication sharedApplication] run];
return returnCode;
}
- (void) alertDidEnd: (NSAlert*) alert returnCode: (NSInteger) aReturnCode {
returnCode = aReturnCode;
[[NSApplication sharedApplication] stopModal];
}
#end
Then running an NSAlert synchronously is as simple as:
AlertSync* sync = [[AlertSync alloc] initWithAlert: alert asSheetForWindow: window];
int returnCode = [sync run];
[sync release];
Note there is potential for re-entrancy issues as discussed, so be careful if doing this.
Unfortunately, there is not much you can do here. You basically have to make a decision: re-architect your application so that it can process the object in an asynchronous manner or use the non-approved, deprecated architecture of presenting application modal alerts.
Without knowing any information about your actual design and how you processes these objects, it's hard to give any further information. Off the top of my head, though, a couple of thoughts might be:
Process the objects in another thread that communicates with the main thread through some kind of run loop signal or queue. If the window's object tree gets interrupted, it signals the main thread that it was interrupted and waits on a signal from the main thread with information about what to do (continue this branch or abort). The main thread then presents the document-modal window and signals the process thread after the user chooses what to do.
This may be really over-complicated for what you need, however. In that case, my recommendation would be to just go with the deprecated usage, but it really depends on your user requirements.
Swift 5:
extension NSAlert {
/// Runs this alert as a sheet.
/// - Parameter sheetWindow: Parent window for the sheet.
func runSheetModal(for sheetWindow: NSWindow) -> NSApplication.ModalResponse {
beginSheetModal(for: sheetWindow, completionHandler: NSApp.stopModal(withCode:))
return NSApp.runModal(for: sheetWindow)
}
}
here is my answer:
Create a global class variable 'NSInteger alertReturnStatus'
- (void)alertDidEndSheet:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo
{
[[sheet window] orderOut:self];
// make the returnCode publicly available after closing the sheet
alertReturnStatus = returnCode;
}
- (BOOL)testSomething
{
if(2 != 3) {
// Init the return value
alertReturnStatus = -1;
NSAlert *alert = [[[NSAlert alloc] init] autorelease];
[alert addButtonWithTitle:#"OK"];
[alert addButtonWithTitle:#"Cancel"];
[alert setMessageText:NSLocalizedString(#"Warning", #"warning")];
[alert setInformativeText:#"Press OK for OK"];
[alert setAlertStyle:NSWarningAlertStyle];
[alert setShowsHelp:NO];
[alert setShowsSuppressionButton:NO];
[alert beginSheetModalForWindow:[self window] modalDelegate:self didEndSelector:#selector(alertDidEndSheet:returnCode:contextInfo:) contextInfo:nil];
// wait for the sheet
NSModalSession session = [NSApp beginModalSessionForWindow:[alert window]];
for (;;) {
// alertReturnStatus will be set in alertDidEndSheet:returnCode:contextInfo:
if(alertReturnStatus != -1)
break;
// Execute code on DefaultRunLoop
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[NSDate distantFuture]];
// Break the run loop if sheet was closed
if ([NSApp runModalSession:session] != NSRunContinuesResponse
|| ![[alert window] isVisible])
break;
// Execute code on DefaultRunLoop
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[NSDate distantFuture]];
}
[NSApp endModalSession:session];
[NSApp endSheet:[alert window]];
// Check the returnCode by using the global variable alertReturnStatus
if(alertReturnStatus == NSAlertFirstButtonReturn) {
return YES;
}
return NO;
}
return YES;
}
Hope it'll be of some help,
Cheers
--Hans
This is the version of Laurent, et al., above, translated into Swift 1.2 for Xcode 6.4 (latest working version as of today) and tested in my app. Thanks to all those who contributed to make this work! The standard documentation from Apple gave me no clues as to how go about this, at least not anywhere that I could find.
One mystery remains to me: why I had to use the double exclamation point in the final function. NSApplication.mainWindow is supposed to be just an optional NSWindow (NSWindow?), right? But the compiler gave the error shown until I used the second '!'.
extension NSAlert {
func runModalSheetForWindow( aWindow: NSWindow ) -> Int {
self.beginSheetModalForWindow(aWindow) { returnCode in
NSApp.stopModalWithCode(returnCode)
}
let modalCode = NSApp.runModalForWindow(self.window as! NSWindow)
return modalCode
}
func runModalSheet() -> Int {
// Swift 1.2 gives the following error if only using one '!' below:
// Value of optional type 'NSWindow?' not unwrapped; did you mean to use '!' or '?'?
return runModalSheetForWindow(NSApp.mainWindow!!)
}
}
Unlike Windows I don't believe there's a way to block on modal dialogs. The input (e.g. the user clicking a button) will be processed on your main thread so there's no way of blocking.
For your task you will either have to pass the message up the stack and then continue where you left off.
When one object fails, stop processing the objects in the tree, make a note of which object failed (assuming that there is an order and you can pick up where you left off), and throw up the sheet. When the user dismisses the sheet, have the didEndSelector: method start processing again from the object that it left off with, or don't, depending on the returnCode.
- (bool) windowShouldClose: (id) sender
{// printf("windowShouldClose..........\n");
NSAlert *alert=[[NSAlert alloc ]init];
[alert setMessageText:#"save file before closing?"];
[alert setInformativeText:#"voorkom verlies van laatste wijzigingen"];
[alert addButtonWithTitle:#"save"];
[alert addButtonWithTitle:#"Quit"];
[alert addButtonWithTitle:#"cancel"];
[alert beginSheetModalForWindow: _window modalDelegate: self
didEndSelector: #selector(alertDidEnd: returnCode: contextInfo:)
contextInfo: nil];
return false;
}
You can use dispatch_group_wait(group, DISPATCH_TIME_FOREVER);:
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:#"alertMessage"];
[alert addButtonWithTitle:#"Cancel"];
[alert addButtonWithTitle:#"Ok"];
dispatch_async(dispatch_get_main_queue(), ^{
[alert beginSheetModalForWindow:progressController.window completionHandler:^(NSModalResponse returnCode) {
if (returnCode == NSAlertSecondButtonReturn) {
// do something when the user clicks Ok
} else {
// do something when the user clicks Cancel
}
dispatch_group_leave(group);
}];
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
//you can continue your code here
Hope that helps.

Resources