Double-click action for menu bar icon in Mac OSX - objective-c

I'm writing a small Mac OSX app that displays a menu bar icon. When clicked, a menu pops up.
I'd like to have a "default" action for the menu bar icon. Basically, to execute an action when double-clicking it, without having to select the action from the menu.
I looked over the Apple docs and there's is such a thing in NSStatusItem called doubleAction, but it's soft deprecated and does not (seem to) work. More over, the docs it says to use the button property, but trying to do so results in the compiler error shown below:
Any code or guidance are much appreciated, thanks!

The situation as it stands today (Xcode 7.3.1, OSX 10.11.4):
the doubleAction of NSStatusItem is deprecated (and NOT actually working).
Apple tells you to use the button property - but there's no header for doubleAction (I wonder if the implementation exists). Oh, it's also read-only.
there are no other options regarding left/right/double click in any of the NSStatusItem's properties.
The workaround: create a category for NSButton (the exact same that Apple was talking about) and implement a custom click handler that posts a notification when a double click was detected, like the following:
#implementation NSButton (CustomClick)
- (void)mouseDown:(NSEvent *)event {
if (self.tag != kActivateCustomClick) {
[super mouseDown:event];
return;
}
switch (event.clickCount) {
case 1: {
[self performSelector:#selector(callMouseDownSuper:) withObject:event afterDelay:[NSEvent doubleClickInterval]];
break;
}
case 2: {
[NSRunLoop cancelPreviousPerformRequestsWithTarget:self];
[[NSNotificationCenter defaultCenter] postNotificationName:#"double_click_event" object:nil];
break;
}
}
}
- (void)callMouseDownSuper:(NSEvent *)event {
[super mouseDown:event];
}
#end
As you can see, this handler only handles NSButton instances that have a specific tag value.
When a click is detected, I defer the call to super for handling by the system's double-click interval. If within that time I receive another click, I cancel the call to super and treat it as a double-click.
Hope it helps!

You can use NSApp.currentEvent
self.statusItem.button.target = self;
self.statusItem.button.action = #selector(clickOnStatusItem:);
...
- (void)clickOnStatusItem:(id)sender {
if (NSApp.currentEvent.clickCount == 2) {
// Double click on status item
}
}
also, you can process a right mouse button on the status bar item

Related

What action is invoked when today widget center is being closed?

I need to find the way to respond to notification centre being closed/hidden. The reason is that I have a pop up NSMenu in that widget, and if you open it and then close entire notification centre, pop up menu remains on the screen.
I have already tried to implement NSWindowDelegate but there is no such event that defines closing of today widget centre. The closest things I found are -windowDidMiniaturize: and
-windowWillClose:. But when side bar closes they are not invoked.
Finally I have found needed method in NSWindowDelegate protocol.
First step is to add self (in that case it is viewController) as an observer for desired method: windowDidResignKey:
-(void)viewWillAppear {
//set this view controller delegate for selector windowDidResignKey
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(windowDidResignKey:) name:NSWindowDidResignKeyNotification object:self.view.window];
}
And second is to implement this method:
- (void)windowDidResignKey:(NSNotification *)notification {
//If window did resign key (close today center) - close menus
if(_sourceLanguageMenu)
[ _sourceLanguageMenu cancelTracking];
if(_targetLanguageMenu)
[ _targetLanguageMenu cancelTracking];
}

iOS UIActionSheet for NSDataDetector and double modal view

I am having an issue with the detected numbers in iOS 7 and iPhone, when the user long tap the number detected by the OS, it prompts an UIActionSheet with the options: "Call", "Send Message", "Add to Contacts", "Copy" and "Cancel". The problem I am facing is, when the option "Send Message" or "Add to Contacts" is tapped, the OS creates an modal view on top of my current modal view, which leads to having the navigation bar of the second modal view not being displayed correctly.
With that in mind, I am not able to assert at which moment the user has tapped which button, because it is not me who created the UIActionSheet (iOS does itself), then I can not receive any kind of delegate methods. The only message sent to the UIViewController is:
-(BOOL)textView:(UITextView *)textView
shouldInteractWithURL:(NSURL *)URL
inRange:(NSRange)characterRange
Which tells me what kind of data was tapped once by the user (but not long tapped). I tried as well with the method call:
-(void)viewWillDisappear:(BOOL)animated
-(void)viewDidDisappear:(BOOL)animated
Unfortunately, they are never invoked on iOS7, whereas iOS8 does. Which drives me to the conclusion that, so far, this issue is only iOS7 related, I am using an iPhone 4 with iOS 7.1.2. When I tried the same case in iOS8, the second modal view renders correctly, being placed on top of my current view.
I hope someone has more info or other ideas.
Thanks!!
The solution that I ended up applying was to completely avoid the long press gesture on the title number that was generating the action sheet to appear. I did it checking on the gesture recognizer list for the UITextView that was inside the cell, then look up for one that was of the class UILongPressGestureRecognizer, inside I would just disable the friend gesture. Snippet of code is:
RestrictedTextView.h
#import <UIKit/UIKit.h>
#interface RestrictedTextView : UITextView
#end
RestrictedTextView.m
#import "RestrictedTextView.h"
NSString *const kFriendsStringInGesture = #"friends";
#implementation RestrictedTextView
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
if ([[gestureRecognizer class] isEqual:[UILongPressGestureRecognizer class]])
{
UILongPressGestureRecognizer *longPress = (UILongPressGestureRecognizer *)gestureRecognizer;
longPress.enabled = YES;
if ([longPress valueForKey:kFriendsStringInGesture] != nil)
{
UILongPressGestureRecognizer *friendLongPress = (UILongPressGestureRecognizer *)[[longPress valueForKey:#"friends"] anyObject];
friendLongPress.enabled = NO;
}
}
return YES;
}
#end

NSWindow, press key ENTER: how to limit the key listening to the focused NSControl?

I have an NSWindow with a main "OK" button. This button has as "key equivalent" property in interface builder, the key ENTER i.e ↵.
It works good, but now I have a new NSComboBox, which is supposed to invoke a method when the user selects a list item, or he preses Enter / ↵.
However, when I press Enter, the main Button receive the notification and the window close. How to prevent this?
This is the normal behavior what you are getting, but you can hack a bit, by removing and adding the key-equivalent.
Add following delegates of NSComboBox:
- (void)comboBoxWillPopUp:(NSNotification *)notification;{
[self.closeButton setKeyEquivalent:#""];
}
- (void)comboBoxWillDismiss:(NSNotification *)notification;{
[self.closeButton setKeyEquivalent:#"\r"];
}
One way you can workaround for prevent enter notification is like that below:-
//Connect this action method to your combobbox and inside that set one BOOL flag to yes
- (IBAction)comBoxItm:(id)sender
{
self.isEnterCalled=YES;
}
//Now check this flag to your some method where close window is called
-(void)someMethod
{
//Check the flag value if it is yes then just ignore it
if (!self.isEnterCalled)
{
//Close window logic
}
self.isEnterCalled=NO;
}
Ran into the same problem. Had "hot key" which I'd like to switch off while editing some text fields. I found solution for myself. There's no need in override lots of NSTextField base methods.
Firstly, I removed all the "key equivalents". I used to detect Enter key down with the + (void)addLocalMonitorForEventsMatchingMask:(NSEventMask)mask handler:(NSEvent *(^)(NSEvent *))block class method of NSEvent. You pass block as a parameter, where you can check for some conditions. The first parameter is the event mask. For your task it would be NSKeyDownMask, look for other masks at the NSEvent Reference Page
The parameter block will perform each time the user pushes the button. You should check if it is right button pushed, and - generally - if the current window first responder isn't some editable control. For that purposes we need NSWindow category class just not to implement this code each time we deal with NSKeyDownMasked local monitors.
NSWindow+Responders class listing:
#interface NSWindow (Responders)
- (BOOL)isEditableFirstResponder;
#end
#implementation NSWindow (Responders)
- (BOOL)isEditableFirstResponder
{
if (!self.firstResponder)
return NO; // no first responder at all
if ([self.firstResponder isKindOfClass:[NSTextField class]]) // NSComboBox is NSTextField subclass
{
NSTextField *field=(NSTextField *)self.firstResponder;
return field.isEditable;
}
if ([self.firstResponder isKindOfClass:[NSButton class]]) // yep, buttons may be responders
return YES;
return NO; // the first responder is not NSTextField or NSButton subclass - not editable
}
#end
Don't know if there's another way to check if we are now editing some text field or combo box. So, there's at least the part you add the local monitor somewhere in your class (NSWindow, NSView, some controller etc.).
- (void)someMethod
{
id monitor=[NSEvent addLocalMonitorForEventsMatchingMask:NSKeyDownMask handler:(NSEvent *)^(NSEvent *theEvent){
if (theEvent.keyCode==/*Enter key code*/ && ![self.window.isEditableFirstResponder]) // you should check the key modifiers too
{
// your code here
}
return theEvent; // you may return the event to pass the key to the receiver
}];
}
Local monitors is safe remedy about the Apple rules. It works only inside your application. For global key down events you may use addGlobalMonitor but Apple may reject your app from the AppStore.
And don't forget to remove the monitor when there's no need in it.
- (void)viewControllerShutdownMethod
{
[NSEvent removeMonitor:monitor];
}
Good luck.

Menu Bar App Never Becomes Reactivated

I'm building a Mac app that only sits in the menu bar with no dock item and no key window and no main menu (it's LSUIElement in the info.plist is set to YES). When I first launch the app, applicationDidBecomeActive: is called, as I expect. However, once another app gains focus, applicationDidBecomeActive: is never called again.
This prevents a text field I have within my app from becoming the first responder. When I first open the app, the text field is editable:
But after another app comes to the foreground, the text field is not editable:
What I've tried:
When the menu is opened, menuWillOpen: is called on the NSMenu's delegate. I've tried placing the following with no success:
[NSApp unhide];
[NSApp arrangeInFront:self];
[NSApp activateIgnoringOtherApps:YES];
[NSApp requestUserAttention:NSCriticalRequest];
[[NSRunningApplication currentApplication] activateWithOptions:NSApplicationActivateIgnoringOtherApps];
[[NSRunningApplication currentApplication] unhide];
I think the issue is probably related to not having any windows to bring to the front. I feel like I'm grasping at straws here. Any help would be greatly appreciated.
I think the issue is with that how the runloop operates when a NSMenu is open, so you should try activating the app before you display the menu. If you're having the NSStatusItem display it, I'd suggest doing it yourself like this:
- (void)toggleMenu:(id)sender
{
// App might already be active
if ([NSApp isActive]) {
[self.statusItem popUpStatusItemMenu:self.menu];
} else {
[NSApp activateIgnoringOtherApps:YES];
}
}
- (void)applicationDidBecomeActive:(NSNotification *)notification
{
[self.statusItem popUpStatusItemMenu:self.menu];
}
That should work, but I think though in general you'll have better luck with an actual window instead of a menu.
You probably need to allow your input to -becomeFirstResponder, maybe by overriding -canBecomeFirstResponder or by calling the become method yourself.
You'd likely have to implement/call these methods for whatever view is housing your text input, or maybe tell your input view to become the first responder.
Either way, it smells like a responder chain issue.
Try calling -makeFirstResponder: on your window. NSWindow is usually the start of the NSResponder chain.
- (void)menuWillOpen:(NSMenu *)menu {
[[NSApp mainWindow] makeFirstResponder:yourTextInputField];
}
I'm assuming your text field already accepts first responder since you said your app launches initially with it as the first responder. If not, make sure your text field overrides -acceptsFirstResponder: to return YES
- (BOOL)acceptsFirstResponder {
return YES;
}
Edit: Ah, see that you don't have a key window. It looks like NSMenu actually has a window associated with it though, and it's safe to call -makeFirstResponder:. Some discussion here suggests overriding -viewDidMoveToWindow: on your view containing your text field in the NSMenu like so:
- (void)viewDidMoveToWindow {
[super viewDidMoveToWindow];
[[self window] makeFirstResponder:yourTextInputField];
}

Suspending keyboard when user press on home button?

I am developing an application were everything is working fine, except one i.e. when user press on home while keyboard is in active and again opens my application the view frame bounds are changing and moving out of bounds. My expected result is keyboard should get suspended or the view should stay in the same position when it is come back from background to foreground with keyboard in-active state.
I hope people understand my scenario and reply ASAP.
Thanks.
I have found the solution to my question, i hope people can use my solution. Below is the code what I have done,
Add the below line of code in your RootViewController file (i.e. which view is coming at first when you open your APP).
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receivedNotification:) name:UIApplicationDidEnterBackgroundNotification object:nil];
And then add a private method as below
- (void) receivedNotification:(NSNotification *) notification
{
if ([username isFirstResponder])
{
[username resignFirstResponder];
}
else if ([password isFirstResponder])
{
[password resignFirstResponder];
}
}
I hope it help some body,Thank u.
Further assistance please see the mentioned link,
there is a method in the app delegate
- (void)applicationDidEnterBackground:(UIApplication *)application
this method is fired when you press the home button.
do the necessary changes(textField resignFirstResponder) in this method and it should work fine i guess.
EDIT here's the code
in the class where you have your textfield create a method
-(void)performWhenHomeBtnprssed
{
[MytextField resignFirstResponder];
}
then in
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[myClassObj performWhenHomeBtnprssed];
}
also i agree with #valexa you should find the root cause of the problem
In software development it is always better to address the root causes than to patch the effect, in your case there are problems with the positioning of your views and you should address that, foreground/background cycling should not affect the views positioning.