Local Notification is displayed without didFinishLaunchingWithOptions: - cocoa

I have quite an understanding issue with displaying local notifications.
As far as I was reading in other threads, one has first create and schedule the local notification with the application.
For displaying that notification, one has to use the delegates didFinishLaunchingWithOptions: (if app is in background operation) and didReceiveLocalNotification: (if app is in foreground).
Now even though I did NOT change the didFinishLaunchingWithOptions: method, the notification is already getting viewed when my app is in the background.
It wouldn't be that big of a problem if the didFinishLaunchingWithOptions: would at least be used when I specify it at all. But it doesn't.
So my problem is, that even though I haven't used the didFinishLaunchingWithOptions: method, the notification is getting displayed. When the user clicks on the notification, the app gets to foreground and the didReceiveLocalNotification: method is triggered and the notification is displayed again.
What I originally wanted to do is to cancelAllLocalNotifications on execution of didFinishLaunchingWithOptions:, but since it is not getting executed, I'm kinda stuck here.
Ok, there might be a workaround with applicationWillEnterForeground: but honestly, I'd like to understand, why the notification is getting displayed even without having specified that in didFinishLaunchingWithOptions:.
All of your help is really appreaciated!! Thanks!!
//
// myNotificationsClass.m
//
#import "myNotificationsClass.h"
#implementation myNotificationsClass
//Sets up a Local Notification with Message, TimeFromNow, BadgeNumber and UserInfo
//no Class Instance for calling this method needed!!
+ (void)setupLocalNotificationsWithMessage: (NSString *) message andTimeFromNow: (NSTimeInterval) seconds andAlertAction: (NSString *) alertAction andBadgeNumber: (NSInteger) badgeNumber andUserInfo: (NSDictionary *) infoDict {
//[[UIApplication sharedApplication] cancelAllLocalNotifications];
UILocalNotification *localNotification = [[UILocalNotification alloc] init];
// create date/time information
localNotification.fireDate = [NSDate dateWithTimeIntervalSinceNow:seconds];
localNotification.timeZone = [NSTimeZone defaultTimeZone];
//setup Appearence and Message
localNotification.alertBody = message; //#"Time to get up!";
localNotification.alertAction = alertAction;
localNotification.soundName = UILocalNotificationDefaultSoundName;
localNotification.applicationIconBadgeNumber = badgeNumber;
localNotification.userInfo = infoDict;
[[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
}
#end
//overwrites the viewWillAppear: Method from the primary Class to display a Test Notification
#implementation UIViewController (localNotification)
- (void)viewWillAppear:(BOOL)animated {
[myNotificationsClass setupLocalNotificationsWithMessage:#"First Test after 2 Seconds" andTimeFromNow:2 andAlertAction:#"GoTo iSnah" andBadgeNumber:7 andUserInfo:nil];
}
#end
//receive Local Notifications even if the App is in Foreground
//overwrites the primery method
#implementation UIResponder (localNotificationForeground)
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:[[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleName"]
message:notification.alertBody
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alertView show];
//reset Badge
application.applicationIconBadgeNumber = 0;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//Because I don't want the Notification to be displayed twice
[[UIApplication sharedApplication] cancelAllLocalNotifications];
UILocalNotification *notification = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
if (notification) {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:[[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleName"]
message:notification.alertBody
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alertView show];
//reset Badge
application.applicationIconBadgeNumber = 0;
}
return YES;
}
#end

You are very close. If the app is running in the background, the user responding to the notification will not cause the app to "launch". "Launch" means the app is not running at all (foreground or background). So only put code in there that you want to execute when the app launches or starts as a result of the user responding to your local notification.
So what you need for your didReceiveLocalNotification method is something to check the application state to see if it was in the foreground or in the background when the user responded to the local notification.
You might do something like the following to differentiate your logic between foreground and background:
- (void)application:(UIApplication *)application didReceiveLocalNotification: (UILocalNotification *)notification
if ( application.applicationState == UIApplicationStateActive ){
NSLog(#"Was already in the foreground");
}
else{
NSLog(#"Was in the background");
}
}
Minor point, however. Documentation states that the didReceiveLocalNotification method is invoked after application:didFinishLaunchingWithOptions: (if that method is implemented). So if you have both methods implemented you'll need to make sure the combination of the 2 work correctly in a "launch" situation.

Related

Receiving Apple Push Notifications when app is in background

I have implemented push notifications in my iOS8 app. I am trying to play an audio file once the notification is received.
The code is playing the audio when the app is in the foreground, but when the app is in the background, nothing happens.
I have tried regenerating the certificates and provisioning profiles. And I have made sure that the app is running in the background, i.e. the user has not swiped up to remove it. In Background modes, I have enabled Remote Notifications, Background Fetch and Audio & Airplay.
I have added code snippets from my AppDelegate.m file:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// More code here ---------------------------------------------------
if (launchOptions) {
NSDictionary *userInfo = [launchOptions valueForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
NSDictionary *apsInfo = [userInfo objectForKey:#"aps"];
if (apsInfo) { //apsInfo is not nil
[self performSelector:#selector(playCarAlarmAudio)
withObject:nil
afterDelay:1];
}
}
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0) {
[[UIApplication sharedApplication] registerForRemoteNotifications];
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeBadge
|UIUserNotificationTypeSound
|UIUserNotificationTypeAlert) categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:settings];
}
// More code here ---------------------------------------------------
}
The delegate methods to handle push notifications:
-(void) application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
NSLog(#"Failed to register for push");
}
-(void) application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {
}
-(void) application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
[self respondToEventNotification:userInfo];
}
-(void) application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
// [self respondToEventNotification:userInfo];
[self playAlarmAudio];
}
-(void) respondToEventNotification : (NSDictionary *) userInfo {
if ([[UIApplication sharedApplication] applicationState] == UIApplicationStateBackground) {
UILocalNotification *localNotification = [[UILocalNotification alloc] init];
[localNotification setSoundName:#"alarm.mp3"];
[localNotification setFireDate:[NSDate date]];
[[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
}
else if ([[UIApplication sharedApplication] applicationState] == UIApplicationStateActive) {
[self playAlarmAudio];
}
}
And to play the Alarm:
-(void) playAlarmAudio {
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"alarm" ofType:#"mp3"];
NSURL *fileUrl = [NSURL fileURLWithPath:filePath];
self.audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:fileUrl error:nil];
self.audioPlayer.numberOfLoops = 1;
[self.audioPlayer play];
}
According to the following Apple documentation, the notification sound to be played is specified inside the notification payload dictionary (https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/ApplePushService.html):
The Notification Payload
Each remote notification includes a payload. The payload contains information about how the system should alert the user as well as any custom data you provide. In iOS 8 and later, the maximum size allowed for a notification payload is 2 kilobytes; Apple Push Notification service refuses any notification that exceeds this limit. (Prior to iOS 8 and in OS X, the maximum payload size is 256 bytes.)
For each notification, compose a JSON dictionary object (as defined by RFC 4627). This dictionary must contain another dictionary identified by the key aps. The aps dictionary can contain one or more properties that specify the following user notification types:
An alert message to display to the user
A number to badge the app icon with
A sound to play

Open notification even application inactive or terminate

From my apps, it can receive the notification even the apps is inactive or terminate. But,when i click on the notification to open it, it will open the main page instead of going "Journal Page" as i wish.
This will work only if the apps are active or in background. Can anyone tell me something about this problem? I have try to put the UIApplicationState for all three(Active, Inactive, Background) in didReceiveRemoteNotification method, but every notification will go through the in background state even i open the notification during the apps is active or in background.
Can anyone give me any idea to solve this problem?
This is method for remote notification:
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
if([userInfo valueForKey:#"app"]) {
NSString *action_app = [userInfo valueForKey:#"app"];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:action_app forKey:#"app"];
NSLog(#"detect app value from UA ====> %#",action_app);
SampleViewController *sample=[[SampleViewController alloc]init];
[sample viewDidAppear:YES];
}else if([userInfo valueForKey:#"url"]){
NSString *action_url = [userInfo valueForKey:#"url"];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:action_url forKey:#"url"];
NSLog(#"detect url value from UA ====> %#",action_url);
SampleViewController *sample=[[SampleViewController alloc]init];
[sample viewDidAppear:YES];
}else{
NSLog(#"---nothing to read---");
}
// Send the alert to UA so that it can be handled and tracked as a direct response. This call
// is required.
[[UAPush shared] handleNotification:userInfo applicationState:application.applicationState];
// Reset the badge after a push received (optional)
[[UAPush shared] resetBadge];
// Open inboxData when receive notification
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
self.blankviewController = [[SampleViewController alloc] initWithNibName:#"SampleViewController" bundle:nil];
self.navigationController = [[UINavigationController alloc] initWithRootViewController:self.blankviewController];
self.window.rootViewController = self.navigationController;
[self.window makeKeyAndVisible];
}
didReceiveRemoteNotification: is only called if the notification is tapped when the app is in the background, or if the app is already running.
If the app is not running when the notification arrives, then when you tap on the notification the application:didFinishLaunchingWithOptions: method is called and the notification payload is passed to it.

Compling Xcode Project Application Windows are expected to have a root view controller

How do i fix this error? iphone simulator works but when I use ipad i get this in console.. 2013-05-25 20:59:17.556 YourRSS[2717:c07] Application windows are expected to have a root view controller at the end of application launch
//
// AppDelegate.m
// YourRSS
//
// Created by Mohammad Komeili on 5/17/13.
// Copyright (c) 2013 MOMEKS. All rights reserved.
//
#import "AppDelegate.h"
#import "ViewController.h"
#import "ViewController_iPad2.h"
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] ;
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
self.viewController = [[ViewController alloc] initWithNibName:#"ViewController_iPhone" bundle:nil];
self.navigationController = [[UINavigationController alloc] initWithRootViewController:self.viewController];
}
/* }else{
self.viewController = [[ViewController alloc] initWithNibName:#"ViewController_iPad2" bundle:nil];
self.navigationController = [[UINavigationController alloc] initWithRootViewController:self.viewController];
}
*/
self.navigationController.navigationBarHidden = YES;
self.window.rootViewController = self.navigationController;
[self.window makeKeyAndVisible];
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application
{
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
- (void)applicationWillTerminate:(UIApplication *)application
{
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
#end
The part of the code that is commented out was what would have executed if you were not on an iPhone (see: UIUserInterfaceIdiomPhone). Since you're skipping over the creation of the navigation controller when you're on an iPad, it stays nil...which gives you the obvious error.

Error Message Using Facebook Deprecated Headers

I have an application in Xcode 4.5 which integrates Facebook. I am using ARC. The application uses
native dialogs and therefore deprecated headers (ie Facebook.h). Apparently doing so leads to the following
error when closing/cleaning the active session:
"An instance 0xa089350 of class
FBSessionManualTokenCachingStrategy was deallocated while key value observers were still registered
with it. Observation info was leaked, and may even become mistakenly attached to some other object."
Upon researching this error, I assume that the issue is that when I init an instance of self.Facebook in the app delegate, Facebook
adds two observers rather than one, and then only one is cleaned up during logout. I have made numerous attempts to remove both instances, including every solution on this
page regarding this very error: facebook ios sdk log : strange message
None of these solutions have worked for me, including one solution I saw which involved deallocation which I cannot use as I am using ARC. Here is my code for opening and closing a session for reference:
- (void)sessionStateChanged:(FBSession *)session
state:(FBSessionState) state
error:(NSError *)error
{
switch (state) {
case FBSessionStateOpen:
if (!error) {
// We have a valid session
// Initiate a Facebook instance
self.facebook = [[Facebook alloc]
initWithAppId:FBSession.activeSession.appID
andDelegate:nil];
// Store the Facebook session information
self.facebook.accessToken = FBSession.activeSession.accessToken;
self.facebook.expirationDate = FBSession.activeSession.expirationDate;
}
break;
case FBSessionStateClosed:
case FBSessionStateClosedLoginFailed:
[FBSession.activeSession closeAndClearTokenInformation];
// Clear out the Facebook instance
self.facebook = nil;
break;
default:
break;
}
[[NSNotificationCenter defaultCenter]
postNotificationName:FBSessionStateChangedNotification
object:session];
if (error) {
UIAlertView *alertView = [[UIAlertView alloc]
initWithTitle:#"Error"
message:error.localizedDescription
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alertView show];
}
}
/*
* Opens a Facebook session and optionally shows the login UX.
*/
- (BOOL)openSessionWithAllowLoginUI:(BOOL)allowLoginUI //do I need this method?
{
NSArray *permissions = [[NSArray alloc] initWithObjects:
#"user_about_me",
#"read_friendlists",
#"read_stream",
#"friends_likes",
#"user_likes",
#"friends_photos",
#"user_photos",
nil];
return [FBSession openActiveSessionWithReadPermissions:permissions
allowLoginUI:allowLoginUI
completionHandler:^(FBSession *session,
FBSessionState state,
NSError *error) {
[self sessionStateChanged:session state:state
error:error]; }];
}
/*
* If we have a valid session at the time of openURL call, we handle
* Facebook transitions by passing the url argument to handleOpenURL
*/
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation {
// attempt to extract a token from the url
return [FBSession.activeSession handleOpenURL:url];
}
- (void) closeSession {
[FBSession.activeSession closeAndClearTokenInformation];
}
If anyone can offer any guidance as to how to solve this I would greatly appreciate it.

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