NSStatusItem appears briefly on launch, but promptly disappears - objective-c

I'm starting to get back into Cocoa development after not working on anything for a few months. Originally when I started I was using Snow Leopard and Xcode 3. I'm now running Lion with Xcode 4.2 and I'm running into some issues that I hadn't run into before.
I believe it's probably the fact that I've never used ARC before, so I'm sure I'm missing something.
I'm trying to create Statusbar application, without a main window, or dock icon. When I run the application the Statusbar icon for my application appears briefly, for about a second, but then disappears.
Heres my code.
QuickPlusAppDelegate.h
#import <Cocoa/Cocoa.h>
#interface QuickPlusAppDelegate : NSObject <NSApplicationDelegate>
#property (assign) IBOutlet NSWindow *window;
#property (assign) NSStatusItem *statusItem;
#property (weak) IBOutlet NSMenu *statusItemMenu;
#property (strong) NSImage *statusItemIcon;
#property (strong) NSImage *statusItemIconHighlighted;
#property (strong) NSImage *statusItemIconNewNotification;
#end
QuickPlusAppDelegate.m
#import "QuickPlusAppDelegate.h"
#implementation QuickPlusAppDelegate
#synthesize statusItemMenu = _statusItemMenu;
#synthesize window = _window, statusItem = _statusItem;
#synthesize statusItemIcon = _statusItemIcon,
statusItemIconHighlighted = _statusItemIconHighlighted,
statusItemIconNewNotification = _statusItemIconNewNotification;
- (void) awakeFromNib
{
NSBundle *appBundle = [NSBundle mainBundle];
_statusItemIcon = [[NSImage alloc] initWithContentsOfFile:[appBundle pathForResource:#"statusItemIcon" ofType:#"png"]];
_statusItemIconHighlighted = [[NSImage alloc] initWithContentsOfFile:[appBundle pathForResource:#"statusItemIconHighlighted" ofType:#"png"]];
_statusItemIconNewNotification = [[NSImage alloc] initWithContentsOfFile:[appBundle pathForResource:#"statusItemIconNewNotification" ofType:#"png"]];
_statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
[_statusItem setImage:_statusItemIcon];
[_statusItem setAlternateImage:_statusItemIconHighlighted];
[_statusItem setHighlightMode:YES];
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// empty
}
#end
Edit If you see anything wrong with my code please let me know. I definitely would some critique so I can get better.
Another Edit It seems as if the Statusbar icon disappears when the main window itself loads.

_statusItem will be autoreleased in this case.
_statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
This returns an autoreleased object. _statusItem is just an iVar. Not only that, but you declare the property as assign:
#property (assign) NSStatusItem *statusItem;
What you probably want to do here is make the property strong and then, instead of setting the ivar directly, use the property to set it. So like this:
#property (strong) NSStatusItem *statusItem;
and then:
self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
That will cause the statusItem be retained. I'm betting that what's happening now is that it gets released when the autorelease pool pops, and then your app crashes the next time anything tries to access it, thus causing it to disappear from the menu bar. Running it through the Zombies instrument would tell you for sure if that was what was happening. But in general, your app needs to have a strong reference to that object for it to stick around.

I was having this issue in Xamarin. For awhile it worked fine. Then I added extra code to the FinishedLaunching method and the StatusItem started vanishing. I had this code generating the StatusItem:
public override void AwakeFromNib ()
{
var statusItem = NSStatusBar.SystemStatusBar.CreateStatusItem (30);
statusItem.Menu = mainMenu;
statusItem.Image = NSImage.ImageNamed ("menuicon");
statusItem.AlternateImage = NSImage.ImageNamed ("menuicon_selected");
statusItem.HighlightMode = true;
}
Eventually, I found my problem. In my Xcode I had declared this property in my AppDelegate but I wasn't using it:
#property(nonatomic, retain) IBOutlet NSStatusItem *statusItem;
When I removed the var the StatusItem continued to show in its infinite glory :)
public override void AwakeFromNib ()
{
statusItem = NSStatusBar.SystemStatusBar.CreateStatusItem (30);
statusItem.Menu = mainMenu;
statusItem.Image = NSImage.ImageNamed ("menuicon");
statusItem.AlternateImage = NSImage.ImageNamed ("menuicon_selected");
statusItem.HighlightMode = true;
}
I didn't have to change it to (strong). In fact I tried but it didn't persist when copying back to Xamarin Studio.

Related

How to add a secondary window to a document-based app

I want to add a secondary window containing resources that can be dragged and dropped into NSDocuments.
My project contains:
1) ResourceWindow.xib
2) ViewController.xib
3) Main.storyboard
#interface AppDelegate ()
#property (nonatomic,strong)NSWindowController* wc;
#property (nonatomic, weak)NSWindow* resourceWindow;
#property (nonatomic, strong)ViewController* vc;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
self.wc = [[NSWindowController alloc]initWithWindowNibName:#"ResourceController.xib"];
self.resourceWindow = [self.wc window];
[self.wc showWindow:self];
self.vc = [[ViewController alloc]initWithNibName:nil bundle:nil];
[self.vc.view setFrame:[self.resourceWindow.contentView bounds]];
[self.resourceWindow.contentView addSubview:self.vc.view];
}
self.wc.window is nil immediately after allocating and initializing it.
Please set me straight on this.
Thanks
EDIT:
ResourceWindow.xib does not contain a window controller just a window . Is that the problem? Is the solution to drag and drop a custom object into the xib file and change it's class to NSWindowController?
In addition to the file name issue pointed to by Willeke, windows are nil until showWindow is called. Sigh. I started again by subclassing NSWindowManager and checking add xib file. Then, set the class of files owner to SourceWindowController and changed the code to:
#property (nonatomic,strong)SourceWindowController* wc;
#property (nonatomic, weak)NSWindow* resourceWindow;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
self.wc = [[SourceWindowController alloc]initWithWindowNibName:#"SourceWindowController"];
[self.wc showWindow:self.window];
self.resourceWindow = [self.wc window];

Objective-C NSPopover statusItem not opening

I'm having difficulty getting my NSPopover to function properly. The statusItem pops up in the status bar, and highlights on click, but the popover isn't displaying.
Here's the structure of my code.
#property (strong, nonatomic) NSStatusItem *statusItem;
#property (strong, nonatomic) NSEvent *popoverTransiencyMonitor;
#property (weak, nonatomic) IBOutlet NSPopover *popover;
#property (weak, nonatomic) IBOutlet NSView *popoverView;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
self.statusItem.highlightMode = YES;
[self.statusItem.image setTemplate:YES];
self.statusItem.action = #selector(itemClicked:);
}
-(void)itemClicked:(id)sender {
[[self popover] showRelativeToRect:[sender bounds] ofView:sender preferredEdge:NSMinYEdge];
if (self.popoverTransiencyMonitor == nil) {
self.popoverTransiencyMonitor = [NSEvent addGlobalMonitorForEventsMatchingMask:(NSLeftMouseDownMask | NSRightMouseDownMask | NSKeyUpMask) handler:^(NSEvent* event) {
[NSEvent removeMonitor:self.popoverTransiencyMonitor];
self.popoverTransiencyMonitor = nil;
[self.popover close];
}];
}
}
I got help from another person on StackOverflow, Gavin, and that help is located here. NSPopover transiency when popover is in status bar
I reached out to Gavin, and he managed to help me out by sending me his xCode project and it gave me some insight. But our code matches, mine doesn't work, and his does.
Any ideas?
This is a bit embarrassing, but it seems that the issue was that I created a project which uses Storyboards, and NSPopover wouldn't work with that for some reason.
Sorry for the clutter.

mac status bar application not working

I'm following this: http://lepture.com/en/2012/create-a-statusbar-app simple tutorial to get a Status Bar based Mac app working, I've referenced Apple's NSStatusItem class reference as well - And cannot figure out what I'm doing wrong?
It's just not working. My project uses ARC.
Here's FPAppDelete.h:
#import <Cocoa/Cocoa.h>
#interface FPAppDelegate : NSObject <NSApplicationDelegate>
#property (weak) IBOutlet NSMenu *statusMenu;
#property (strong, nonatomic) NSStatusItem *statusBar;
#end
Here's FPAppDelegate.m:
#import "FPAppDelegate.h"
#implementation FPAppDelegate
#synthesize statusBar = _statusBar;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
}
- (void) awakeFromNib {
self.statusBar = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
self.statusBar.title = #"G";
self.statusBar.menu = self.statusMenu;
self.statusBar.highlightMode = YES;
}
#end
I'm not expecting this at all, but I get this when I run the app, with nothing in my Status Bar
It looks like you are willing to listen. So I'll show you quickly how to run a status application.
(1) Add NSMenu to the side bar (or whatever you call). (See the screenshot below.) It's up to you to keep or remove 3 generic menu items.
(2) Add the following instance variables under AppDelegate.h.
#interface AppDelegate : NSObject <NSApplicationDelegate> {
IBOutlet NSMenu *statusMenu;
NSStatusItem *statusItem;
NSImage *statusImage;
}
#property (assign) IBOutlet NSWindow *window;
#property (strong) NSMenuItem *menuItem1; // show application
I'll also add a property as an example.
The following code is for AppDelegate.m
#implementation AppDelegate
#synthesize menuItem1;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
[self setMainStatus];
}
- (void)setMainStatus {
statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
statusImage = [NSImage imageNamed:#"statusImage"];
[statusItem setImage:statusImage];
[statusItem setMenu:statusMenu];
NSMutableString *menuname1 = [[NSMutableString alloc] initWithString:NSLocalizedString(#"statusMenuShowApplication", #"Show Application Window")];
menuItem1 = [[NSMenuItem alloc] initWithTitle:menuname1 action:#selector(statusApplicatinClicked:) keyEquivalent:#""];
[statusMenu addItem:menuItem1];
}
- (void)statusApplicatinClicked:(id)sender {
[self.window setIsVisible:YES];
}
#end
(3) Go back to Interface Builder and connect Status Menu to the NSMenu control you've added.
The setMainStatus method (or whatever you want to name) adds menu items to the status menu programatically. First, you need to create NSStatusbar, which takes NSImage. This NSImage is used to show an icon on the status menu. Next, add menu items and separators to the status menu (status Menu in my case). I have menuItem1 as a property so that the application could enable/disable it. That's just a quick example. You can add NSView to the status menu. If you want to add a separator, it can go as follows.
[statusMenu addItem:[NSMenuItem separatorItem]];
You don't need an application window to run a status menu application. You have to show the main application window for the first time if you are going to submit your status application to Apple's Mac App Store, though.
The status bar is the top-right part of your entire screen. It's where the date and time, Spotlight, etc live in the Menu Bar.
The screenshot you posted is of the title bar of your primary NSWindow.

How to change NSMenuItem Title (Login to Logout)

Im surprised that this hasn't already been asked:
But how does one go about changing the NSMenuItem title in a NSStatusBar menu. When a user logs in I want the menu item to say logout. I have tried creating an outlet to modify my NSMenuItem as a would a label or something.
AppDelegate.h
#property (retain) IBOutlet NSMenuItem *loginItem;
AppDelegate.m
[loginItem setTitle:#"Logout"];
But that didnt work.
The only thing that I was able to do was delete the old NSMenuItem, then add a new one, but it would just add it to the bottom. Is the only way to do this to remove every menu item then re-add them?? That seems very inefficient.
The method you describe should work, though, in general, keeping IBOutlets for all your menu items can be tedious. (If your solution isn't working, make sure the IBOutlet is actually connected in the nib file, and make sure that you're setting the title at an appropriate time. If you're trying to set it in your controller's init method, for example, that's too early on, and the outlets haven't yet been connected up: move the method to awakeFromNib or similar.
A better approach in the long run is to use the <NSMenuDelegate> protocol and NSMenuValidation (informal) protocol to update menu items dynamically (and lazily).
For example, define your controller class like the following:
#interface MDAppDelegate : NSObject <NSApplicationDelegate, NSMenuDelegate>
#property (strong) NSStatusItem *statusItem;
#property (weak) IBOutlet NSWindow *window;
#property (weak) IBOutlet NSMenu *statusItemMenu;
#property (weak) IBOutlet NSMenuItem *toggleLoginLogoutMenuItem;
#property (weak) IBOutlet NSTextField *statusField;
#property (weak) IBOutlet NSTextField *progressField;
#property (weak) IBOutlet NSProgressIndicator *progressIndicator;
#property (assign) BOOL loggedIn;
- (IBAction)toggleLoginLogout:(id)sender;
#end
In the nib file, the delegate outlet of the statusItemMenu is set to the MDAppDelegate controller class. That assures that the MDAppDelegate class is in the responder chain and allow it to work with validating the menu items.
Then you could implement your .m like the following:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
_statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
_statusItem.menu = _statusItemMenu;
_statusItem.title = NSLocalizedString(#"NSStatusItem", #"");
[self updateLoggedInStatus];
}
- (void)updateLoggedInStatus {
[self.statusField setStringValue:(self.loggedIn ? #"Logged in" : #"Logged out")];
}
- (IBAction)toggleLoginLogout:(id)sender {
[self performSelector:#selector(finishFakeLoginLogout:)
withObject:nil afterDelay:2.0];
}
- (void)finishFakeLoginLogout:(id)sender {
self.loggedIn = !self.loggedIn;
[self updateLoggedInStatus];
}
- (void)menuNeedsUpdate:(NSMenu *)menu {
#if MD_DEBUG
NSLog(#"[%# %#]", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
#endif
}
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
#if MD_DEBUG
NSLog(#"[%# %#]", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
#endif
SEL action = menuItem.action;
if (action == #selector(toggleLoginLogout:)) {
[menuItem setTitle:(self.loggedIn ? #"Logout" :#"Login")];
}
return YES;
}
Sample project: http://github.com/NSGod/NSStatusBarFinagler
You don't need to connect your menu item just try this..
NSMenuItem *menuItem = (NSMenuItem*) sender;
NSString *menuString = menuItem.title;
if ([menuString isEqualToString:#"Login"])
{
[menuItem setTitle:#"LogOut"];
}
NSMenuItem menuItem = (NSMenuItem) sender;
this line automatically collect the menu items in your app.

Setting the initial value of a UILABEL

I'm trying to create a simple Quiz app (I'm a beginner), when I launch the app I want a UILabel to show the first question (of an array of questions). I'm having some trouble with setting the initial value.
I've done a couple of attempts, whiteout success. I my QuizAppDelegate.h file I declare my UILabel like this:
IBOutlet UILabel * questionField;
In my main .m file I have tried the following:
- (id)init {
[super init];
questions = [[NSMutableArray alloc] init];
// Not working
questionField = [[UILabel alloc] init];
[questionField setText:#"Hello"];
// Working
NSLog(#"Hello");
[self defaultQuestions];
// [self showQuestion];
return self;
}
Another thing I have tried is the following in QuizAppDelegate:
#property (nonatomic, retain) IBOutlet UILabel *questionField;
- (void)changeTitle:(NSString *)toName;
And in the .m file:
#synthesize questionField;
- (id)init {
[super init];
questions = [[NSMutableArray alloc] init];
// Not working
[self changeTitle:#"Hello"];
// Working
NSLog(#"Hello");
[self defaultQuestions];
// [self showQuestion];
return self;
}
-(void)changeTitle:(NSString *)toName {
[questionField setText:toName];
}
Any tips on how to solve this would be great!
// Anders
Hopefully you're not actually putting code into main.m. On iOS, you rarely modify that file.
Since you're doing everything in the AppDelegate, let's keep it there (as opposed to creating a new UIViewController). Let's start with the basics.
Adding the Label as an instance variable
You're doing this correctly—inside the curly braces of the .h file, put the line
IBOutlet UILabel * questionField;
Then, declare the corresponding property, and make sure to synthesize it in the .m file.
#property (nonatomic, retain) IBOutlet UILabel *questionField;
#synthesize questionField // in the .m file
Adding the UILabel in Interface Builder
Open up MainWindow.xib. Drag a UILabel from the Library to the Window that represents your app's window. Then Control-Drag from the AppDelegate object (the third icon on the left in Xcode 4; it'll be labelled in the Document window in IB 3). You'll see a little black window come up—select the option called questionField to make the connection.
See this link for screenshots and how to make connections in IB. The same applies in Xcode 4.
Changing the text
You don't need a separate method to change the text—just modify the label's text property.
Pick a method that'll be called when the app launches (applicationDidFinishLaunching:WithOptions: is a good place to do it in), and put the following code:
questionField.text = #"Hello";
And that's it!
Code
QuizAppDelegate.h
#import <UIKit/UIKit.h>
#interface QuizAppDelegate : NSObject <UIApplicationDelegate> {
IBOutlet UILabel *questionField;
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain) IBOutlet UILabel *questionField;
#end
QuizAppDelegate.m
#import "QuizAppDelegate.h"
#implementation QuizAppDelegate
#synthesize window=_window;
#synthesize questionField;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
// Add the tab bar controller's current view as a subview of the window
[self.window addSubview:self.questionField];
[self.window makeKeyAndVisible];
self.questionField.text = #"Hello";
return YES;
}
- (void)dealloc
{
[_window release];
[questionField release];
[super dealloc];
}
#end
If you're creating the label programmatically, then you have to add the label to the view:
[self.view addSubview:questionField];
This assumes that you have a ViewController. If not, and you're doing this directly in the AppDelegate (a very bad idea, by the way), then do
[self.window addSubview:questionField];
If you're creating it in the IB, make sure you set up the connections.
You should not both add the UILabel in the IB and instantiate it programmatically. Only call alloc if you are creating it programmatically. Otherwise, if using the IB, skip that part. You created it already with the xib.
I suspect that you have either not created your Interface Builder layout properly - either you have missed the control out all together or more likely you have not connected that control to the questionField outlet in yout header file.
You need to drag a UILabel view into the main view and then connect it to the correct line in your header file.
You shouldn't be using your main.m like that at all. In fact, you should almost certainly never do anything with it. Try creating a UIViewController subclass and practicing your quiz with that. (Add the UILabel to the IB file and then connect the outlet.) Perhaps use the View-Based Application template while you are practicing.
This is a good answer:
"You're doing this correctly—inside the curly braces of the .h file, put the line
IBOutlet UILabel * questionField;"
I was trying to change the value of mylabel.text and the screen didn't update the label with this.value. I included the {IBOutlet UILabel * mylabel} and it works like a charm!
So this answer is valid to change the text of a label programmatically!
Thanks