Create notification with reply field - cocoa

How do i get input from a notification with a reply field. I have not found anything in the documentation
Here is my current code, what do I need to get a response from the user?
#import "AppDelegate.h"
#implementation AppDelegate
#synthesize nTitle;
#synthesize nMessage;
- (IBAction)showNotification:(id)sender{
NSUserNotification *notification = [[NSUserNotification alloc] init];
notification.title = [nTitle stringValue];
notification.informativeText = [nMessage stringValue];
notification.soundName = NSUserNotificationDefaultSoundName;
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
[[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:self];
}
- (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center shouldPresentNotification:(NSUserNotification *)notification{
return YES;
}
#end
Where can i find more recent information and documentation for the notification center?

You can find the latest documentation in headers as usual. First of all, make sure you are using OSX SDK 10.9. There are few new fields with description.
NSUserNotification.h:
// Set to YES if the notification has a reply button. The default value is NO.
// If both this and hasActionButton are YES, the reply button will be shown.
#property BOOL hasReplyButton NS_AVAILABLE(10_9, NA);
// Optional placeholder for inline reply field.
#property (copy) NSString *responsePlaceholder NS_AVAILABLE(10_9, NA);
// When a notification has been responded to, the NSUserNotificationCenter delegate
// didActivateNotification: will be called with the notification with the activationType
// set to NSUserNotificationActivationTypeReplied and the response set on the response property
#property (readonly) NSAttributedString *response NS_AVAILABLE(10_9, NA);
Let's do it:
- (IBAction)showNotification:(id)sender{
NSUserNotification *notification = [[NSUserNotification alloc] init];
...
notification.responsePlaceholder = #"Reply";
notification.hasReplyButton = true;
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
}
- (void)userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification
{
if (notification.activationType == NSUserNotificationActivationTypeReplied){
NSString* userResponse = notification.response.string;
}
}
Notice that the reply button is hidden until mouse is outside a notification window and the reply field will be shown after button click.

You can find all the documentation you need in the inline documentation of Xcode 5 if you search "NSUserNotificationCenter", "NSUserNotification" and "NSUserNotificationCenterDelegate Protocol Reference".
There's no reply field in a user notification but you can add an action button and test if the user clicked this button or the default button to dismiss the notification. However, if in the Notification Center preferences, the user choose to receive banners instead of alerts, you'll never be informed of the response because there are no buttons in banners.

Related

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.

Local Notification is displayed without didFinishLaunchingWithOptions:

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.

How to deal with a Toggle NSButton?

My application contains a PLAY/PAUSE button that is set to type Toggle in Interface Builder. I use it - as the name reveals - to play back my assets or to pause them.
Further, I am listening to the SPACE key to enable the same functionality via the keyboard shortcut. Therefore, I use keyDown: from NSResponderin my application. This is done in another subview. The button itself is not visible at this time.
I store the current state of playback in a Singleton.
How would you update the title/alternative title for the toogle button while taking into account that its state could have been altered by the keyboard shortcut? Can I use bindings?
I managed to implement the continuous update of the button title as follows. I added a programmatic binding for the state (in the example buttonTitle). Notice, that the IBAction toggleButtonTitle: does not directly change the button title! Instead the updateButtonTitle method is responsible for this task. Since self.setButtonTitle is called the aforementioned binding gets updated immediately.
The following example shows what I tried to describe.
// BindThisAppDelegate.h
#import <Cocoa/Cocoa.h>
#interface BindThisAppDelegate : NSObject<NSApplicationDelegate> {
NSWindow* m_window;
NSButton* m_button;
NSString* m_buttonTitle;
NSUInteger m_hitCount;
}
#property (readwrite, assign) IBOutlet NSWindow* window;
#property (readwrite, assign) IBOutlet NSButton* button;
#property (readwrite, assign) NSString* buttonTitle;
- (IBAction)toggleButtonTitle:(id)sender;
#end
And the implementation file:
// BindThisAppDelegate.m
#import "BindThisAppDelegate.h"
#interface BindThisAppDelegate()
- (void)updateButtonTitle;
#end
#implementation BindThisAppDelegate
- (id)init {
self = [super init];
if (self) {
m_hitCount = 0;
[self updateButtonTitle];
}
return self;
}
#synthesize window = m_window;
#synthesize button = m_button;
#synthesize buttonTitle = m_buttonTitle;
- (void)applicationDidFinishLaunching:(NSNotification*)notification {
[self.button bind:#"title" toObject:self withKeyPath:#"buttonTitle" options:nil];
}
- (IBAction)toggleButtonTitle:(id)sender {
m_hitCount++;
[self updateButtonTitle];
}
- (void)updateButtonTitle {
self.buttonTitle = (m_hitCount % 2 == 0) ? #"Even" : #"Uneven";
}
#end
If you store your state in an enum or integer a custom NSValueTransformer will help you to translate a state into its button title equivalent. You can add the NSValueTransformer to the binding options.
NSDictionary* options = [NSDictionary dictionaryWithObject:[[CustomValueTransformer alloc] init] forKey:NSValueTransformerBindingOption];
[self.button bind:#"title" toObject:self withKeyPath:#"buttonTitle" options:options];

Doing something after NSOpenPanel closes

I have an NSOpenPanel and I want to do some validation of the selection after the user has clicked OK. My code is simple:
void (^openPanelHandler)(NSInteger) = ^(NSInteger returnCode) {
if (returnCode == NSFileHandlingPanelOKButton) {
// do my validation
[self presentError:error]; // uh oh, something bad happened
}
}
[openPanel beginSheetModalForWindow:[self window]
completionHandler:openPanelHandler];
[self window] is an application-modal window. The panel opens as a sheet. So far so good.
Apple's docs say that the completion handler is supposed to be called "after the user has closed the panel." But in my case, it's called immediately upon the "OK/Cancel" button press, not upon the panel having closed. The effect of this is that the error alert opens above the open panel, not after the panel has closed. It still works, but it's not Mac-like.
What I would prefer is for the user to click OK, the open panel sheet to fold up, then the alert sheet to appear.
I guess I could present the alert using a delayed selector, but that seems like a hack.
Since the panel completion handler is invoked before the panel has effectively been closed,1 one solution is to observe NSWindowDidEndSheetNotification on your modal window:
Declare an instance variable/property in your class to hold the validation error;
Declare a method that will be executed when the panel is effectively closed. Define it so that if presents the error on the current window;
Have your class listen to NSWindowDidEndSheetNotification on [self window], executing the method declared above when the notification is sent;
In the panel completion handler, if the validation fails then assign the error to the instance variable/property declared above.
By doing this, the completion handler will only set the validation error. Soon after the handler is invoked, the open panel is closed and the notification will be sent to your object, which in turn presents the validation error that has been set by the completion handler.
For example:
In your class declaration, add:
#property (retain) NSError *validationError;
- (void)openPanelDidClose:(NSNotification *)notification;
In your class implementation, add:
#synthesize validationError;
- (void)dealloc {
[validationError release];
[super dealloc];
}
- (void)openPanelDidClose:(NSNotification *)notification {
if (self.validationError) [self presentError:error];
// or [[self window] presentError:error];
// Clear validationError so that further notifications
// don't show the error unless a new error has been set
self.validationError = nil;
// If [self window] presents other sheets, you don't
// want this method to be fired for them
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSWindowDidEndSheetNotification
object:[self window]];
}
// Assuming an action fires the open panel
- (IBAction)showOpenPanel:(id)sender {
NSOpenPanel *openPanel = [NSOpenPanel openPanel];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(openPanelDidClose:)
name:NSWindowDidEndSheetNotification
object:[self window]];
void (^openPanelHandler)(NSInteger) = ^(NSInteger returnCode) {
if (returnCode == NSFileHandlingPanelOKButton) {
// do my validation
// uh oh, something bad happened
self.validationError = error;
}
};
[openPanel beginSheetModalForWindow:[self window]
completionHandler:openPanelHandler];
}
1If you think this behaviour is wrong, consider filing a bug report with Apple. I don’t really remember whether an error should be presented over an open/save panel.

Can't handle button action due to "Program received signal: “EXC_BAD_ACCESS”."

Tried to deal with this problem for two days now.
My app has a text field and a button. when the text field is empty, pressing the button gives me a stable run and i receive an alert after pressing the button.
However, if i am typing something in the text field, then pressing the button - it crashes.
All i can see in the debug console is:
"Program received signal: “EXC_BAD_ACCESS”."
There is no stack detail or anything. My relevant code:
the header file:
#import <UIKit/UIKit.h>
#interface SpyTextViewController : UIViewController {
int sliderSpeed;
IBOutlet UITextField *textInput;
}
#property (nonatomic, retain) IBOutlet UITextField *textInput;
- (IBAction)sliderChanged:(id)sender;//speed of text show changed
- (IBAction)textFieldDoneEditing:(id)sender;//dor 'DONE' on keyboard
- (IBAction)backgroundTap:(id)sender;//for handling tapping on background
- (IBAction)textEmButtonPressed:(id)sender;
#end
------ the .m file:
#import "SpyTextViewController.h"
#import "txtViewController.h"
#implementation SpyTextViewController
#synthesize textInput;
- (IBAction)sliderChanged:(id)sender
{
UISlider *slider = (UISlider *)sender;
sliderSpeed = (int)(slider.value + 0.5f);//setting the speed determinned by the usr in slider
}
- (IBAction)textFieldDoneEditing:(id)sender
{
[sender resignFirstResponder];
NSLog(#"our text input is %#", textInput.text);
}
- (IBAction)backgroundTap:(id)sender
{
[textInput resignFirstResponder];
}
- (IBAction)textEmButtonPressed:(id)sender
{
NSLog(#"our text input length is %#", [textInput.text length]);
/*
if ([textInput.text length])
{
NSLog(#" inside the tvc init ");
//create the sub MVC
txtViewController *tvc = [[txtViewController alloc] init];
tvc.scrollSpeed = sliderSpeed;
tvc.scrollTxt = textInput.text;
[self.navigationController pushViewController:tvc animated:YES];
[tvc release];
//run text using speed;
}
else */
{
//tell 'em to input text with some pop-up
NSString *msg = nil;
msg = #"Write text to transmit";
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Forgot something?"
message:msg
delegate:self
cancelButtonTitle:#"Back"
otherButtonTitles:nil];
[alert show];
[alert release];
[msg release];
}
}
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload {
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[textInput release];
[super dealloc];
}
#end
Some detail on the .Xib file:
the action method is attached to file's owner through "touch-up inside".
the text field has its outlet connected to it also (i also printed the contents of the text, it works fine).I also changed the view's class identity to be UICpntrol so i could support the event of tapping on the view while typing txt in the text field so that the keyboard will be exited...
What am i doing wrong?
msg string is constant, and releasing it may have caused the exception.
Run under the debugger ("Build and Debug").
The debugger window (cmd-shift-Y) will show the traceback of where the problem occurred, and let you examine variables at that point.

Resources