context menu based on NSTableView cell - objective-c

i would like to place a context menu onto a NSTableView. this part is done. what i would like to do is to show different menu entries based on the content of the right clicked cell, and do NOT show the context menu for specific columns.
this is:
column 0, and 1 no context menu
all other cells should have the context menu like this:
first entry: "delete " samerow.column1.value
second entry: "save " samecolumn.headertext
-EDIT-
the one on the right is how the context menu is supposed to look like for any given cell.

Theres a delegate for that! - No need to subclass
In IB if you drag an NSTableView onto your window/view you'll notice that theres a menu outlet for the table.
So a very easy way to implement the contextual menu is to connect that outlet to a stub menu and connect the delegate outlet of the menu to an object which implements the NSMenuDelegate protocol method - (void)menuNeedsUpdate:(NSMenu *)menu
Normally the delegate of the menu is the same object which provides the datasource/delegates to the table but it might also be the view controller which owns the table too.
Have a look at the docs for more info on this
Theres a bundle of clever stuff you can do in the protocol but a very simple implementation might be like below
#pragma mark tableview menu delegates
- (void)menuNeedsUpdate:(NSMenu *)menu
{
NSInteger clickedrow = [mytable clickedRow];
NSInteger clickedcol = [mytable clickedColumn];
if (clickedrow > -1 && clickedcol > -1) {
//construct a menu based on column and row
NSMenu *newmenu = [self constructMenuForRow:clickedrow andColumn:clickedcol];
//strip all the existing stuff
[menu removeAllItems];
//then repopulate with the menu that you just created
NSArray *itemarr = [NSArray arrayWithArray:[newmenu itemArray]];
for(NSMenuItem *item in itemarr)
{
[newmenu removeItem:[item retain]];
[menu addItem:item];
[item release];
}
}
}
And then a method to construct the menu.
-(NSMenu *)constructMenuForRow:(int)row andColumn:(int)col
{
NSMenu *contextMenu = [[[NSMenu alloc] initWithTitle:#"Context"] autorelease];
NSString *title1 = [NSString stringWithFormat:#"Delete %#",[self titleForRow:row]];
NSMenuItem *item1 = [[[NSMenuItem alloc] initWithTitle:title1 action:#selector(deleteObject:) keyEquivalent:#""] autorelease];
[contextMenu addItem:item1];
//
NSString *title2 = [NSString stringWithFormat:#"Save %#",[self titleForColumn:col]];
NSMenuItem *item2 = [[[NSMenuItem alloc] initWithTitle:title1 action:#selector(saveObject:) keyEquivalent:#""] autorelease];
[contextMenu addItem:item2];
return contextMenu;
}
How you choose to implement titleForRow: and titleForColumn: is up to you.
Note that NSMenuItem provides the property representedObject to allow you to bind an arbitrary object to the menu item and hence send information into your method (e.g deleteObject:)
EDIT
Watch out - implementing - (void)menuNeedsUpdate:(NSMenu *)menu in your NSDocument subclass will stop the Autosave/Versions menu that appears in the title bar appearing in 10.8.
It still works in in 10.7 so go figure. In any case the menu delegate will need to be something other than your NSDocument subclass.

Edit: The better way to do this than the below method is using delegate as shown in the accepted answer.
You can subclass your UITableView and implement menuForEvent: method:
-(NSMenu *)menuForEvent:(NSEvent *)event{
if (event.type==NSRightMouseDown) {
if (self.selectedColumn == 0 || self.selectedColumn ==1) {
return nil;
}else {
//create NSMenu programmatically or get a IBOutlet from one created in IB
NSMenu *menu=[[NSMenu alloc] initWithTitle:#"Custom"];
//code to set the menu items
//Instead of the following line get the value from your datasource array/dictionary
//I used this as I don't know how you have implemented your datasource, but this will also work
NSString *deleteValue = [[self preparedCellAtColumn:1 row:self.selectedRow] title];
NSString *deleteString = [NSString stringWithFormat:#"Delete %#",deleteValue];
NSMenuItem *deleteItem = [[NSMenuItem alloc] initWithTitle:deleteString action:#selector(deleteAction:) keyEquivalent:#""];
[menu addItem:deleteItem];
//save item
//similarly
[menu addItem:saveItem];
return menu;
}
}
return nil;
}
That should do it. I haven't tried out the code though. But this should give you an idea.

I also tried the solution posted by Warren Burton and it works fine.
But in my case I had to add the following to the menu items:
[item1 setTarget:self];
[item2 setTarget:self];
Setting no target explicitly causes the context menu to remain disabled.
Cheers!
Alex
PS: I would have posted this as a comment but I do not have enough reputation to do that :(

Warren Burton's answer is spot on. For those working in Swift, the following example fragment might save you the work of translating from Objective C. In my case I was adding the contextual menu to cells in an NSOutlineView rather than an NSTableView. In this example the menu constructor looks at the item and provides different options depending on item type and state. The delegate (set in IB) is a ViewController that manages an NSOutlineView.
func menuNeedsUpdate(menu: NSMenu) {
// get the row/column from the NSTableView (or a subclasse, as here, an NSOutlineView)
let row = outlineView.clickedRow
let col = outlineView.clickedColumn
if row < 0 || col < 0 {
return
}
let newItems = constructMenuForRow(row, andColumn: col)
menu.removeAllItems()
for item in newItems {
menu.addItem(item)
// target this object for handling the actions
item.target = self
}
}
func constructMenuForRow(row: Int, andColumn column: Int) -> [NSMenuItem]
{
let menuItemSeparator = NSMenuItem.separatorItem()
let menuItemRefresh = NSMenuItem(title: "Refresh", action: #selector(refresh), keyEquivalent: "")
let item = outlineView.itemAtRow(row)
if let block = item as? Block {
let menuItem1 = NSMenuItem(title: "Delete \(block.name)", action: #selector(deleteBlock), keyEquivalent: "")
let menuItem2 = NSMenuItem(title: "New List", action: #selector(addList), keyEquivalent: "")
return [menuItem1, menuItem2, menuItemSeparator, menuItemRefresh]
}
if let field = item as? Field {
let menuItem1 = NSMenuItem(title: "Delete \(field.name)", action: #selector(deleteField), keyEquivalent: "")
let menuItem2 = NSMenuItem(title: "New Field", action: #selector(addField), keyEquivalent: "")
return [menuItem1, menuItem2, menuItemSeparator, menuItemRefresh]
}
return [NSMenuItem]()
}

As TheGoonie mentioned, I also got the same experience- context menu items were remained disable. However the reason for items being disabled is 'Auto Enables Items' property.
Make 'Auto Enables Items' property to off. or set it programmatically to NO.
[mTableViewMenu setAutoenablesItems:NO];

Here is an example setting up an NSOutlineView programmatically within a view controller. This is all the plumbing you need to get the context menu up and running. No subclassing required.
I had previously subclassed NSOutlineView to override menu(for event: NSEvent), but came to a simpler set-up with the help of Graham's answer here and Warren's answer above.
class OutlineViewController: NSViewController
{
// ...
var outlineView: NSOutlineView!
var contextMenu: NSMenu!
override func viewDidLoad()
{
// ...
outlineView = NSOutlineView()
contextMenu = NSMenu()
contextMenu.delegate = self
outlineView.menu = contextMenu
}
}
extension OutlineViewController: NSMenuDelegate
{
func menuNeedsUpdate(_ menu: NSMenu) {
// clickedRow catches the right-click here
print("menuNeedsUpdate called. Clicked Row: \(outlineView.clickedRow)")
// ... Flesh out the context menu here
}
}

This is the easiest method for a custom/dynamic NSMenu I found, that also preserves the system look (the blue selection border). Subclass NSTableView and set your menu in menu(for:).
The important part is to set the menu on the table view, but return the menu from its super call.
override func menu(for event: NSEvent) -> NSMenu? {
let point = convert(event.locationInWindow, from: nil)
let clickedRow = self.row(at: point)
var menuRows = selectedRowIndexes
// The blue selection box should always reflect the
// returned row indexes.
if menuRows.isEmpty || !menuRows.contains(clickedRow) {
menuRows = [clickedRow]
}
// Build your custom menu based on the menuRows indexes
self.menu = <#myMenu#>
return super.menu(for: event)
}

Related

Contextual menu on only certain items in a "Source List"

I have a window with a Source List (NSOutlineView). My source list has just two levels. Level one is header and level two is data. I want to have a contextual menu on some of the data cells. Not all.
First, I try to attach a menu on the table cell view who represents the data cell -> nothing happens.
Second, I attach a menu on the Outline View in IB -> the contextual menu opens on each cells (header and data). I search for stopping the opening of the menu, but I don't find anything.
Do you have some ideas ?
Thank you
OS X 10.8.2 Lion, Xcode 4.5.2, SDK 10.8
If you subclass NSOutlineView, you can override menuForEvent: to return a menu only if the user clicked on the correct row. Here's an example:
- (NSMenu *)menuForEvent:(NSEvent *)event;
{
//The event has the mouse location in window space; convert it to our (the outline view's) space so we can find which row the user clicked on.
NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
NSInteger row = [self rowAtPoint:point];
//If the user did not click on a row, or is not exactly one level down from the top level of hierarchy, return nil—that is, no menu.
if ( row == -1 || [self levelForRow:row] != 1 )
return nil;
//Create and populate a menu.
NSMenu *menu = [[NSMenu alloc] init];
NSMenuItem *delete = [menu addItemWithTitle:NSLocalizedString( #"Delete", #"" ) action:#selector(delete:) keyEquivalent:#""];
[self selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
//Set the Delete menu item's represented object to the clicked-on item. If the user chooses this item, we'll retrieve its represented object so we know what to delete.
[delete setRepresentedObject:[self itemAtRow:row]];
return menu;
}
This assumes we're compiling with ARC, so you don't need to autorelease the menu object being created.
This extension + subclass (both NSOutlineView and NSTableView) does the sensible thing of seeing whether a menu is attached to a cell view or row view. Just a general, reusable subclass!
Set the menu on the cell view in outlineView:viewForTableColumn:item: – menu is a NSResponder property.
(Below is in Swift)
// An extension lets us both subclass NSTableView and NSOutlineView with the same functionality
extension NSTableView {
// Find a cell view, or a row view, that has a menu. (e.g. NSResponder’s menu: NSMenu?)
func burnt_menuForEventFromCellOrRowViews(event: NSEvent) -> NSMenu? {
let point = convertPoint(event.locationInWindow, fromView: nil)
let row = rowAtPoint(point)
if row != -1 {
if let rowView = rowViewAtRow(row, makeIfNecessary: true) as? NSTableRowView {
let column = columnAtPoint(point)
if column != -1 {
if let cellView = rowView.viewAtColumn(column) as? NSTableCellView {
if let cellMenu = cellView.menuForEvent(event) {
return cellMenu
}
}
}
if let rowMenu = rowView.menuForEvent(event) {
return rowMenu
}
}
}
return nil
}
}
class OutlineView: NSOutlineView {
override func menuForEvent(event: NSEvent) -> NSMenu? {
// Because of weird NSTableView/NSOutlineView behaviour, must set receiver’s menu otherwise the target cannot be found
self.menu = burnt_menuForEventFromCellOrRowViews(event)
return super.menuForEvent(event)
}
}
class TableView: NSTableView {
override func menuForEvent(event: NSEvent) -> NSMenu? {
// Because of weird NSTableView/NSOutlineView behaviour, must set receiver’s menu otherwise the target cannot be found
self.menu = burnt_menuForEventFromCellOrRowViews(event)
return super.menuForEvent(event)
}
}
It's not clear from your question whether your outline is view based or cell based. That's important.
If you're view based, then your view instances can implement
- (NSMenu *)menuForEvent:(NSEvent *)theEvent
and return the menu appropriate to that item -- or nil f you don't want a menu at all.
If you're cell based, or if you don't want to handle this in the view class for some reason, you'll need to subclass NSOutlineView and implement - (NSMenu *)menuForEvent:(NSEvent *)theEvent there. Again, you'll figure out which cell is hit or active, and decide from that what menu you want.
- (void)rightMouseDown:(NSEvent *)event
An NSView will not pass this to the next view, This method looks to see that the current class has a menuForEvent:, if it does then it is called. If it does not then it is finished and nothing else will happen. This is why you will not see an NSTableCellView respond to a menuForEvent: because the table view swallows the rightMouseDown:.
You may subclass the tableview and handle the rightMouseDown: event and call the NSTableCellView's rightMouseDown: and handle displaying your menu that you have constructed in your storyboard and hooked up to your NSTableViewCell.
Here is my solution in a subclassed NSTableView:
- (void)rightMouseDown:(NSEvent *)event
{
for (NSTableRowView *rowView in self.subviews) {
for (NSView *tableCellView in [rowView subviews]) {
if (tableCellView) {
NSPoint eventPoint = [event locationInWindow];
// NSLog(#"Window Point: %#", NSStringFromPoint(eventPoint));
eventPoint = [self convertPoint:eventPoint toView:nil];
eventPoint = [self convertPoint:eventPoint toView:self];
// NSLog(#"Table View Point: %#", NSStringFromPoint(eventPoint));
NSRect newRect = [tableCellView convertRect:[tableCellView bounds] toView:self];
// NSLog(#"Rect: %#", NSStringFromRect(newRect));
BOOL rightMouseDownInTableCellView = [tableCellView mouse:eventPoint inRect:newRect];
// NSLog(#"Mouse in view: %hhd", mouseInView);
if (rightMouseDownInTableCellView) {
if (tableCellView) {
// Lets be safe and make sure that the object is going to respond.
if ([tableCellView respondsToSelector:#selector(rightMouseDown:)]) {
[tableCellView rightMouseDown:event];
}
}
}
}
}
}
}
This will find where the right mouse event occurred, check to see if we have the correct view and pass the rightMouseDown: to that view.
Please let me know if this solution works for you.

NSTableView Right Clicked Row Index

I'm looking for a way to get right-clicked row index from NSTableView but I can't find any delegate methods or class attributes for it. Any suggestion is appreciated.
Use the NSTableView method - (NSInteger)clickedRow to get the index of the last clicked row. The returned NSInteger will be the index of the right clicked row.
You do not need to subclass NSTableView for this solution. clickedRow is also available on NSOutlineView.
While I haven't done this, I am pretty sure you can by overriding NSView's - (NSMenu*)menuForEvent:(NSEvent*)theEvent. The example in this link does a point conversion to determine the index.
-(NSMenu*)menuForEvent:(NSEvent*)theEvent
{
NSPoint mousePoint = [self convertPoint:[theEvent locationInWindow] fromView:nil];
int row = [self rowAtPoint:mousePoint];
// Produce the menu here or perform an action like selection of the row.
}
Updated Answer
If you want to get clicked row index on menu opening, the answer is NSTableView.clickedRow. Anyway this property is available only in specific moments, and usually just -1.
When is this index to be available? That's in NSMenuDelegate.menuWillOpen method. So you conform the delegate and implement the method on your class, and access the clickedRow property. It's done.
final class FileNavigatorViewController: NSViewController, NSMenuDelegate {
let ov = NSOutlineView() // Assumed you setup this properly.
let ctxm = NSMenu()
override func viewDidLoad() {
super.viewDidLoad()
ov.menu = ctxm
ctxm.delegate = self
}
func menuWillOpen(_ menu: NSMenu) {
print(outlineView.clickedRow)
}
}
Clicked row index is available until you click an item in the menu. So this also works.
final class FileNavigatorViewController: NSViewController {
let ov = NSOutlineView() // Assumed you setup this properly.
let ctxm = NSMenu()
let item1 = NSMenuItem()
override func viewDidLoad() {
super.viewDidLoad()
ov.menu = ctxm
ov.addItem(item1)
ov.target = self
ov.action = #selector(onClickItem1(_:))
}
#objc
func onClickItem1(_: NSObject?) {
print(outlineView.clickedRow)
}
}
I tested this on macOS Sierra (10.12.5).
Old Answer
Starting from OS X 10.11, Apple finally added a method to access clickedRow easily. Just subclass NSTableView and override this method and you'll get the clickedRow as far as I experienced.
func willOpenMenu(menu: NSMenu, withEvent event: NSEvent)
This needs subclassing, but anyway, the cleanest and simplest way to access clickedRow.
Also, there's a pairing method.
func didCloseMenu(menu: NSMenu, withEvent event: NSEvent?)
Just select row on right-click by implementing menuForEvent: in NSTableView subclass:
#implementation MyTableView
- (NSMenu *)menuForEvent:(NSEvent *)theEvent
{
int row = [self rowAtPoint:[self convertPoint:theEvent.locationInWindow fromView:nil]];
if (row == -1)
return nil;
if (row != self.selectedRow)
[self selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
return self.menu;
}
#end
if you dont need to open NSMenu but need to know "right click action with row number", i think most simple way is below. (Swift4 Code) Don't need any other connected Outer NSMenu class.
class SomeViewController: NSViewController, NSTableViewDataSource, NSTableViewDelegate {
#IBOutlet weak var tableView: NSTableView!
...
override func viewDidLoad() {
...
tableView.action = #selector(some method()) // left single click action
tableView.doubleAction = #selector(someMethod()) // left double click action
}
// right click action
override func rightMouseDown(with theEvent: NSEvent) {
let point = tableView.convert(theEvent.locationInWindow, from: nil)
let row = tableView.row(at: point)
print("right click")
print(row)
}
I had the same question but I also needed a solution that would work with multiple selected rows (because when multiple rows are selected and you right-click on one of them, NSTableView highlights all of them). Here's the property I added for this in a subclass of NSTableView:
var rightClickRowIndexes: IndexSet {
if clickedRow >= 0 {
return selectedRowIndexes.contains(clickedRow) ? selectedRowIndexes : IndexSet(integer: clickedRow)
} else {
return IndexSet()
}
}

How to create right click Menu in NSOutlineView?

I am new to this technology , I want to display different Context Menu on Right click of Parent Node and Child Node..
Subclass NSOutlineView and implement - (NSMenu *)menuForEvent:(NSEvent *)theEvent.
-(NSMenu*)menuForEvent:(NSEvent*)evt
{
NSLog(#"menuForEvent %# %#",self, [self delegate]);
NSPoint pt = [self convertPoint:[evt locationInWindow] fromView:nil];
int row=[self rowAtPoint:pt];
// create menu ...
return menu;
}
On Mac OS 10.5 and above, create NSMenu in nib and set delegate and implement:
-(void)menuNeedsUpdate:(NSMenu *)menu
Swift Version:
class SubclassOutlineView: NSOutlineView {
override func menu(for event: NSEvent) -> NSMenu? {
let point = convert(event.locationInWindow, from: nil)
let row = self.row(at: point)
let item = self.item(atRow: row)
let menu = NSMenu()
// ...
return menu
}
}
The bit I was missing was item(atRow:, which gives you the needed data source item. Found that at this related question:
How do you add context senstive menu to NSOutlineView (ie right click menu)

Popup menu implementation from NSButton

How should I go about it?
I was thinking about...
[NSMenu popUpContextMenu:menu withEvent:event forView:(NSButton *)sender];
Yup.
On button action call
[NSMenu popUpContextMenu:menu withEvent:event forView:(NSButton *)sender];
where
menu : menu you want to show
sender : button you clicked
event : a new NSEvent you create
When you create the new NSEvent, specify the location as to where you want the popup menu to be shown.
Swift version of accepted answer
#IBAction func actionOccurred(sender: NSButton) {
if let event = NSApplication.sharedApplication().currentEvent {
NSMenu.popUpContextMenu(sender.menu!, withEvent: event, forView: sender)
}
}
Updated answer
Add NSMenu to the NSViewController in the storyboard as seen in the image
Connect the Outlet
Right-click on the button in the storyboard and connect the menu outlet to the menu that we have just added to the storyboard(Where arrow points in the image)
Add IBAction
#IBAction func menuClicked(_ sender: NSButton) {
var location = NSEvent.mouseLocation
location.x -= 10; location.y -= 10 // Menu appears below the button
let menu = sender.menu!
menu.popUp(positioning: menu.item(at: 0), at: location, in: nil)
}
As I have commented I find the ButtonMadness example less than perfect.
My implementation seems to work better. The menu is shown on mouse down, the button remains depressed throughout, the menu position can be specified and the menu is dismissed without subsequent spurious display.
To be honest NSPopupButton is a better choice in the majority of situations. I use this code mainly because of the convenience of having one class for buttons and popups and because the menu does not contain the popup control image and title. I load the menu from a separate nib and reuse it as is elsewhere in the app as required.
Note that it is trivial to add additional support for say a popover as well as menu.
NSButton subclass:
- (void)mouseDown:(NSEvent *)theEvent {
// if a menu is defined let the cell handle its display
if (self.menu) {
if ([theEvent type] == NSLeftMouseDown) {
[[self cell] setMenu:[self menu]];
} else {
[[self cell] setMenu:nil];
}
}
[super mouseDown:theEvent];
}
NSButtonCell subclass:
- (BOOL)trackMouse:(NSEvent *)event inRect:(NSRect)cellFrame ofView:(NSView *)controlView untilMouseUp:(BOOL)untilMouseUp
{
// if menu defined show on left mouse
if ([event type] == NSLeftMouseDown && [self menu]) {
NSPoint result = [controlView convertPoint:NSMakePoint(NSMidX(cellFrame), NSMidY(cellFrame)) toView:nil];
NSEvent *newEvent = [NSEvent mouseEventWithType: [event type]
location: result
modifierFlags: [event modifierFlags]
timestamp: [event timestamp]
windowNumber: [event windowNumber]
context: [event context]
eventNumber: [event eventNumber]
clickCount: [event clickCount]
pressure: [event pressure]];
// need to generate a new event otherwise selection of button
// after menu display fails
[NSMenu popUpContextMenu:[self menu] withEvent:newEvent forView:controlView];
return YES;
}
return [super trackMouse:event inRect:cellFrame ofView:controlView untilMouseUp:untilMouseUp];
}
Recently, I was trying to implement it and I came, as I think, with a simpler solution
-(IBAction)buttonClick:(id)sender {
NSButton * b = (NSButton*)sender;
NSPoint l = [ self.window convertBaseToScreen:b.frame.origin ];
[ self.menu popUpMenuPositioningItem:nil atLocation:l inView:nil ];
}
Update
convertBaseToScreen is deprecated starting from 10.7, instead of it use convertRectToScreen in the following way:
NSPoint l = [self.window convertRectToScreen:b.frame].origin;
Using a context menu on the action call isn't a great way to do it because the menu doesn't show until mouseUp - you don't get the hold & drag menu behavior. Apple's ButtonMadness sample shows how to really do this in a subclass of NSButton, see DropDownButton. https://developer.apple.com/library/mac/samplecode/ButtonMadness/Introduction/Intro.html
Summarizing that subclass: create a NSPopUpButtonCell with pullsDown set to YES & preferredEdge to NSMaxYEdge, copy your menu to add a blank top item and set it as that cell's menu, on mouseDown call [thePopUpCell performClickWithFrame:self.bounds inView:self] and set self.needsDisplay
Do like that.
-(IBAction)onClickSourceAdd:(id)sender {
NSMenu *mainMenu = [NSApp mainMenu];
NSMenu *sourceMenu = [[mainMenu itemAtIndex:2] submenu];
NSMenu *addMenu = [[sourceMenu itemAtIndex:0] submenu];
[NSMenu popUpContextMenu:addMenu
withEvent:[NSApp currentEvent]
forView:(NSButton *)sender];
}

Save preference to show or hide NSStatusItem

I've got an application which runs as a normal app but also has a NSStausItem.
I wanted to implement the ability to set in the preferences a checkbox and when this checkbox is turned on the status item should be shown, but when the checkbox is off the status item should be removed or be invisible.
I found someone facing a similar problem in a forum here: How do you toggle the status item in the menubar on and off using a checkbox?
But the problem I have with this solution is that it does not work as expected. So I make this checkbox and all works fine, but when I open the application a second time the app does not recognize the choice I took at the first run. This is because the checkbox isn't bound to a BOOL or something, the checkbox only has an IBAction, which removes or adds the status item at runtime.
So my question is: how can I make a checkbox in the preferences which allows me to choose whether the status item should show up or not.
Ok actually i tried the following i copied the from the post i gave you the link
In AppDelegate.h :
NSStatusItem *item;
NSMenu *menu;
IBOutlet NSButton myStatusItemCheckbox;
and then in the Delegate.m :
- (BOOL)createStatusItem
{
NSStatusBar *bar = [NSStatusBar systemStatusBar];
//Replace NSVariableStatusItemLength with NSSquareStatusItemLength if you
//want the item to be square
item = [bar statusItemWithLength:NSVariableStatusItemLength];
if(!item)
return NO;
//As noted in the docs, the item must be retained as the receiver does not
//retain the item, so otherwise will be deallocated
[item retain];
//Set the properties of the item
[item setTitle:#"MenuItem"];
[item setHighlightMode:YES];
//If you want a menu to be shown when the user clicks on the item
[item setMenu:menu]; //Assuming 'menu' is a pointer to an NSMenu instance
return YES;
}
- (void)removeStatusItem
{
NSStatusBar *bar = [NSStatusBar systemStatusBar];
[bar removeStatusItem:item];
[item release];
}
- (IBAction)toggleStatusItem:(id)sender
{
BOOL checked = [sender state];
if(checked) {
BOOL createItem = [self createStatusItem];
if(!createItem) {
//Throw an error
[sender setState:NO];
}
}
else
[self removeStatusItem];
}
then in the IBaction i added this one :
[[NSUserDefaults standardUserDefaults] setInteger:[sender state]
forKey:#"MyApp_ShouldShowStatusItem"];
and in my awakefromnib i added this one : `
NSInteger statusItemState = [[NSUserDefaults standardUserDefaults] integerForKey:#"MyApp_ShouldShowStatusItem"];
[myStatusItemCheckbox setState:statusItemState];
Then in the interface builder i created a new checkbox connected it with "myStatusItemCheckbox" and added an IBaction also i clicked on the bindings inspector and set in the value the following bind to : NSUserDefaultController and as ModelKeyPath i set: MyApp_ShouldShowStatusItem.
Unfortunately this doesnt work at all what am i doing wrong ?
What you need to do is to use the User Defaults system. It makes it very easy to save and load preferences.
In the button's action, you will save its state:
- (IBAction)toggleStatusItem:(id)sender {
// Your existing code...
// A button's state is actually an NSInteger, not a BOOL, but
// you can save it that way if you prefer
[[NSUserDefaults standardUserDefaults] setInteger:[sender state]
forKey:#"MyApp_ShouldShowStatusItem"];
}
and in your app delegate's (or another appropriate object) awakeFromNib, you will read that value back out of the user defaults:
NSInteger statusItemState = [[NSUserDefaults standardUserDefaults] integerForKey:#"MyApp_ShouldShowStatusItem"];
[myStatusItemCheckbox setState:statusItemState];
and then make sure to call removeStatusItem if neccessary.
This procedure will apply to almost any preference you might want to save.