Get a reference to status menu item and change its title - xcode

I have a Mac OS app with a status menu (made in Interface Builder). I want to change status menu item title when certain event happens. I can do it just fine inside the action handler, because I have a reference to the item there (sender):
- (IBAction)playPauseMusic:(id)sender {
// ...
[sender setTitle:#"New Title"];
}
But how to do it in other parts of my app? I don't know how to get a reference to menuItem in the following code:
- (void) someOtherMethod:(int)isPlaying {
menuItem = ...;
if(isPlaying) {
[menuItem setTitle:#"Pause"];
}
}
What to do to make the above work?
Update. Here's how I attach the status menu:
// MyAppDelegate.h:
#interface MyApp : NSApplication
#end
#interface MyAppDelegate : NSObject <NSApplicationDelegate>
{
NSMenu *statusMenu;
NSStatusItem *statusItem;
// ...
}
#property (strong) IBOutlet NSMenu *statusMenu;
// ...
#end
// MyAppDelegate.m:
#synthesize statusMenu;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
statusItem = [[NSStatusBar systemStatusBar]
statusItemWithLength:NSVariableStatusItemLength];
[statusItem setMenu:[self statusMenu]];
}

If you want to access an item within the menu, then set a tag on the menu item (in IB, for example) and use:
NSMenuItem *menuItem = [[statusItem menu] itemWithTag:100]; // 100 = example
menuItem.title = #"Something";
If the code that wants to set the title is not the same object that holds statusItem then you'll need to expose a setStatusMenuTitle:forItemWithTag: method which performs the above code.
I cannot tell you how to get access to that object without more details, however.

First, I created an outlet for the menu item in Xcode. I followed the "Create and connect a new outlet" video guide. I made a strong outlet for my menu item:
#property (strong) IBOutlet NSMenuItem *playMenuItem;
Then added it to the interface:
#interface MyAppDelegate ...
{
// ...
NSMenuItem *playMenuItem;
}
Then added #synthesize declaration in implementation:
#synthesize playMenuItem;
Finally, the following worked:
[playMenuItem setTitle:#"New Title"];
P.S. If anyone can show me how to make it simpler, I'll be grateful.

Related

Menu items don't appear in nibless StatusMenu app?

I've been working on an application port that must show a status menu and some dynamic items within. It's behaviour is similar to the apple WIFI menu which has an icon, some fixed items and some dynamic items in the center (the available WIFI networks).
For various reasons I decided to go nibless. I've managed to get the menu icon to appear, but I can't seem to get the items to show in the menu when I click the icon.
This is what I have so far:
AppDelegate.h
#import <Cocoa/Cocoa.h>
#interface AppDelegate : NSObject <NSApplicationDelegate>
// Strange, if these are not properties and not declared strong,
// the menu flashes momentarily and disappears. Could the dynamic menu items be related to object lifetime?
#property (strong, nonatomic) IBOutlet NSMenu *statusMenu;
#property (strong, nonatomic) NSStatusItem *statusItem;
#end
AppDelegate.m
import "AppDelegate.h"
#implementation AppDelegate
- (IBAction)loginClicked:(id)sender
{
NSLog(#"LoginClicked");
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
NSLog(#"AppDidFinishLaunching!");
self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
[self.statusItem setMenu:self.statusMenu];
NSImage *statusImage = [[NSImage alloc] initWithContentsOfFile:#"/tmp/applogo.png"];
//menuImage = [[NSImage alloc] initWithContentsOfFile:[bundle pathForResource:#"applogo" ofType:#"png"]];
[self.statusItem setImage:statusImage];
[self.statusItem setAlternateImage:statusImage];
//[self.statusItem setTitle:#"MyApp"];
[self.statusItem setHighlightMode:YES];
// Add login
NSMenuItem *login = [[NSMenuItem alloc] initWithTitle:#"Login" action:loginClicked keyEquivalent:#""];
[self.statusMenu addItem:login];
NSMenuItem *quit = [[NSMenuItem alloc] initWithTitle:#"Quit" action:nil keyEquivalent:#""];
[self.statusMenu addItem:quit];
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
main.m
#import <Cocoa/Cocoa.h>
#import "AppDelegate.h"
int main(int argc, const char* argv[])
{
#autoreleasepool { // Do I need this or is it on by default?
// make sure the application singleton has been instantiated
NSApplication * application = [NSApplication sharedApplication];
// instantiate our application delegate
AppDelegate * applicationDelegate = [[AppDelegate alloc] init];
// assign our delegate to the NSApplication
[application setDelegate:applicationDelegate];
// call the run method of our application
[application run];
}
// execution never gets here...
return 0;
}
After much mucking around, I tried something that now seems obvious.
I don't know why, but in examples of StatusBar Apps that use nib, IBMenu is not alloc'ed and inited. It appears to be done automatically somehow.
Adding the following seemed to fix it.
self.statusMenu = [[NSMenu alloc] init];
Also, noting my comment on lifetime. If I don't use properties, I have to set retain on statusItem when creating it. Then it doesn't get destroyed. I'm still learning about how objective-C managages object lifetime and still a bit confused about ARC here. But it least it now seems to show my menu items.

switching views with NSMenuItem IBAction does not work

In a document based Cocoa App I want to fill a NSBox in the Document.xib with a view,
by selecting the view with a NSMenuItem. However, the box is not updated with the view.
If I insert a button in the Document.xib, which is connected with the same IBAction as the NSMenuItem, the app works in the expected way.
I created the tree files:
- ViewController.h
- ViewController.m
- prettyView.xib
In ViewController.m the XIB File of the view is initialized.
// ViewController.m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (id)init
{
if(![super initWithNibName:#"prettyView" bundle:nil]){
return nil;
}
[self setTitle:#"Pretty View"];
return self;
}
#end
The Document.h contains outlets for the box and two buttons.
One button fills the box with the view, the other one clears the box.
// Document.h
#import <Cocoa/Cocoa.h>
#class ViewController;
#interface Document : NSDocument
#property (weak) IBOutlet NSBox *contentBox;
- (IBAction)fillBox:(id)sender;
- (IBAction)clearBox:(id)sender;
#property ViewController * myViewController;
#end
In Document.m the view controller is instantiated.
// Document.m
#import "Document.h"
#import "ViewController.h"
#interface Document ()
#end
#implementation Document
- (instancetype)init {
self = [super init];
if (self) {
_myViewController = [ViewController new];
}
return self;
}
The methods for the IBActions are implemented in Document.m too.
- (IBAction)fillBox:(id)sender {
NSLog(#"Fill Box selected from %#", [sender className]);
[self.contentBox setContentView:[self.myViewController view]];
}
- (IBAction)clearBox:(id)sender {
NSLog(#"Clear Box selected");
[self.contentBox setContentView:nil];
}
The method fillBox is connected to one of the both buttons as well as to the NSMenuItem.
Pressing the button, a message is written to the console and the view is shown in the box.
Selecting the NSMenuItem, a message is written too, but the view is not displayed in the box.
The IBActions must not be connected with Document but with First Responder of MainMenu.xib.

hide NSMenu from a NSButton inside a custom view

I have a simple cocoa coredata statusbar application with Xcode 4.6.2.
Here in my AppController.h i have
#interface AppController : NSObject
#property NSStatusItem *statusItem;
#property IBOutlet NSMenu *statusMenu;
In my AppController.m:
#synthesize statusItem = statusItem;
#synthesize statusMenu = statusMenu;
-(void)awakeFromNib{
statusItem = [[NSStatusBar systemStatusBar]statusItemWithLength:NSVariableStatusItemLength];
statusItem.menu = statusMenu;
}
then, in my AppDelegate.m there is a function:
#import "AppController.h"
-(IBAction)someAction:(id)sender{
//code to do something
AppController *x = [[AppController alloc]init];
[x.statusMenu cancelTracking];
}
I want to close the menu via a button that performs an action inside a custom view of a NSMenuItem (from Connection Inspector->Outlets->view ctrl+drag to the button). I cannot select 2 different sent actions for a NSButton, so i have to close the menu declared in AppController class from the IBAction someAction that is in AppDelegate class. How to do it? The code i've posted doesn't work.
Thanks in advance
Your code probably doesn't work because you are initializing a second AppController.
You need a reference to your original AppController in AppDelegate. This can be achieved by using cocoa bindings. In your .XIB file, drag a new blue Object from your Object Library, set it to be a AppController class using the inspector, then control-drag into your AppDelegate header to create a binding. Use that reference and remove the AppController *x = [[AppController alloc] init];.
Maybe try this:
[[NSStatusBar systemStatusBar] removeStatusItem:[GSAppDelegate alloc].statusMenu];

Creating status item - icon shows up, menu doesn't

In a document-based project I am trying to create a status menu. I have a singleton class that builds the status bar, and I am initiating it from an application delegate, as you can see. When I run this, I get no errors, but only an image of the status bar, but no menu drops down. I created the menu in IB. What am I messing up?
Delegate
#import "KBAppDelegate.h"
#import "KBStatusMenu.h"
#implementation KBAppDelegate
#synthesize window = _window;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
KBStatusMenu *aStatusItem = [[KBStatusMenu alloc] init];
aStatusItem = [[KBStatusMenu instance] buildStatusItem];
}
#end
.h
#import <Foundation/Foundation.h>
#interface KBStatusMenu : NSObject
{
NSStatusItem *myStatusItem;
NSImage *statusImage;
IBOutlet NSMenu *myStatusMenu;
}
+ (KBStatusMenu *)instance;
- (id)buildStatusItem;
#end
.m
#import "KBStatusMenu.h"
#implementation KBStatusMenu
static KBStatusMenu *gInstance = nil;
+ (KBStatusMenu *)instance
{
#synchronized(self) {
if (gInstance == nil)
gInstance = [[self alloc] init];
}
return(gInstance);
}
- (id)buildStatusItem
{
myStatusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain];
statusImage = [NSImage imageNamed:#"statusNormTemplate.png"];
[myStatusItem setImage:statusImage];
[myStatusItem setHighlightMode:YES];
[myStatusItem setMenu:myStatusMenu];
return myStatusItem;
}
#end
You declared myStatusMenu as an outlet, but never loaded a nib (or assigned anything to it yourself). An outlet cannot get objects out of nowhere; the outlet is set only when you load a nib that has the outlet connected to something (or assign something to the variable yourself, as if it weren't an outlet).
You can prove this by adding a line to buildStatusItem that logs the value of the myStatusMenu instance variable. I expect that it will be nil.
What you need to do is:
Create a nib to contain the status item's menu.
Set the class of the File's Owner to KBStatusMenu.
In KBStatusMenu, implement init to load the nib you just created.
Then, by the time you reach buildStatusItem, loading the nib will have set the outlet, and you will have a menu to give to your status item.
I would recommend only creating one KBStatusMenu instance. In this case, I recommend enforcing the singleton: init should test whether gInstance has already been set and, if so, return that; only if it hasn't should it initialize and return self.

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];

Resources