Display Model's Data in NSMenuItem - objective-c

I'd like to display some data of my model within a status bar menu. So, I bound my entity-object to the title of an NSMenuItem:
[self.statusMenu setAutoenablesItems:NO];
NSMenuItem * exportMenuItem = [[NSMenuItem alloc] init];
[exportMenuItem bind:#"title" toObject:expo withKeyPath:#"menuItemTitle" options:nil];
[exportMenuItem setEnabled:NO];
[self.statusMenu insertItem:exportMenuItem atIndex:3];
It works fine so far from init. But when I update my Model it does not updates the title of the NSMenuItem.

For reference, the canonical solution to this issue is to implement a class method like this:
+ (NSSet *)keyPathsForValuesAffectingMenuItemTitle
{
return [NSSet setWithObjects: #"propertyMenuItemTitleDependsOn1",
#"propertyMenuItemTitleDependsOn2",
nil];
}
If you implement a method like this, then the framework will handle calling willChangeValueForKey: and didChangeValueForKey: for the key menuItemTitle any time any of the other properties are changed.

Okay I got it:
menuItemTitle is a dynamic getter method which combines two actual properties of expo. So the reason for NSMenuItem's title to not get updated is probably, that menuItemTitle probably never gets actually set.
So how do I tell, that menuItemTitle was changed, when one of my properties was set?
Overriding expo's setters to add [self willChangeValueForKey:#"menuItemTitle"]; and [self didChangeValueForKey:#"menuItemTitle"]; does not work as it causes an endless loop in calling the setter itself again and again.
So here is my solution: I overrode [NSManagedObject setValue:(id)value forKey:(NSString *)key]:
- (void)setValue:(id)value forKey:(NSString *)key {
[self willChangeValueForKey:#"menuItemTitle"];
[super setValue:value forKey:key];
[self didChangeValueForKey:#"menuItemTitle"];
}

Related

NSPopover view controller - Communicate with parent via block or delegate?

Say I have a NSViewController which will be displayed in a popover using something similar to this:
- (void) createAndShowPopover
{
TargetViewController * targetViewContoller = [[TargetViewController alloc] init];
NSPopover * targetPopover = [[NSPopover alloc] init];
targetPopover.contentViewController = targetViewContoller;
targetPopover.delegate = self;
[targetPopover setAppearance: NSPopoverAppearanceMinimal];
[targetPopover setAnimates: NO];
[targetPopover setBehavior: NSPopoverBehaviorTransient];
[targetPopover showRelativeToRect: ...];
}
Now TargetViewContoller contains a NSTextView and NSButton. When the button is clicked, I would like the parent controller (the view controller which owns targetPopover) to perform an action based on the contents of targetViews textView, then close the popover.
There are three ways I can think of doing this.
A block property on targetViewController such as typedef void (^TextRestoreBlock) (NSString * textToRestore); and adding the following code to the createAndShowPopover code:
__unsafe_unretained typeof(self) weakSelf = self;
targetController.restoreBlock =
^(NSString * textToRestore)
{
// Establish the strong self reference
__strong typeof(self) strongSelf = weakSelf;
[strongSelf.textView setString: textToRestore];
[strongSelf.targetPopover close];
}; // Set our string
Using a delegate and instead having createAndShowPopover add:
targetController.restoreDelegate = self;
And adding the code from the previous block to a delegate method.
I think I much prefer method #2 (delegate) as it seems neater. My overall question would be, is there a preferred style? Have I missed the mark completely?
Note that in my specific case, this is for mac development, but I believe the same concept would apply to iOS and uipopovercontroller.
I agree method #2 has always worked for me. Although there aren't any overwhelming computational benefits to doing one or the other. #2 is cleaner and a bit easier to edit if you decide to add more functionality. Go for it.

How to name Undo menu entries for Core Data add/remove items via bindings and NSArrayController?

I have a NSTableView populated by a Core Data entity and Add Item / Remove Item buttons all wired with a NSArrayController and bindings in Interface Builder.
The Undo/Redo menu items can undo or redo the add / remove item actions.
But the menu entries are called only „Undo“ resp. „Redo“.
How can i name them like „Undo Add Item“, „Undo Remove Item“, etc.
(I am aware, something similar was asked before, but the accepted answers are either a single, now rotten link or the advice to subclass NSManagedObject and override a method that Apples documentation says about: "Important: You must not override this method.“)
Add a subclass of NSArrayController as a file in your project. In the xib, in the Identity Inspector of the array controller, change the Class from NSArrayController to your new subclass.
Override the - newObject method.
- (id)newObject
{
id newObj = [super newObject];
NSUndoManager *undoManager = [[[NSApp delegate] window] undoManager];
[undoManager setActionName:#"Add Item"];
return newObj;
}
Also the - remove:sender method.
- (void)remove:(id)sender
{
[super remove:sender];
NSUndoManager *undoManager = [[[NSApp delegate] window] undoManager];
[undoManager setActionName:#"Remove Item"];
}
Register for NSManagedObjectContextObjectsDidChangeNotification:
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(mocDidChangeNotification:)
name:NSManagedObjectContextObjectsDidChangeNotification
object: nil];
And parse the userInfo dictionary in the corresponding method:
- (void)mocDidChangeNotification:(NSNotification *)notification
{
NSManagedObjectContext* savedContext = [notification object];
// Ignore change notifications for anything but the mainQueue MOC
if (savedContext != self.managedObjectContext) {
return;
}
// Ignore updates -- lots of noise from maintaining user-irrelevant data
// Set actionName for insertion
for (NSManagedObject* insertedObject in
[notification.userInfo valueForKeyPath:NSInsertedObjectsKey])
{
NSString* objectClass = NSStringFromClass([insertedObject class]);
savedContext.undoManager.actionName = savedContext.undoManager.isUndoing ?
[NSString stringWithFormat:#"Delete %#", objectClass] :
[NSString stringWithFormat:#"Insert %#", objectClass];
}
// Set actionName for deletion
for (NSManagedObject* deletedObject in
[notification.userInfo valueForKeyPath:NSDeletedObjectsKey])
{
NSString* objectClass = NSStringFromClass([deletedObject class]);
savedContext.undoManager.actionName = savedContext.undoManager.isUndoing ?
[NSString stringWithFormat:#"Insert %#", objectClass] :
[NSString stringWithFormat:#"Delete %#", objectClass];
}
}
I've tested this in my own code-- it's rough. Can spend a lot more time making the actionName nicer. I deleted parsing of updates because: 1) insertions and deletions of objects in to-many relationships generate updates of other objects 2) I don't care to figure out how to discover what properties changed at this time
I also have class names that aren't user-friendly, so this is a great time to implement the description function for all entities, and use that rather than the class name.
But this at least works for all object controllers in a project, and easily enough for insert and delete.
[edit] Updated with mikeD's suggestion to cover redo having an inverse name. Thanks!

Pass index back to parent view controller

The NSMutableArray detailsDataSource and int detailIndex is passed on to next View Controller from
MainDetailViewController.m:
#import "UsersDetailViewController.h"
...
- (void)swipeDetectedUp:(UISwipeGestureRecognizer *)sender
{
UsersDetailViewController *usersController = [[self storyboard] instantiateViewControllerWithIdentifier:#"UsersController"];
[self.navigationController pushViewController:usersController animated:NO];
usersController.usersDataSource = [[NSMutableArray alloc] initWithArray:detailsDataSource];
usersController.userDetailIndex = detailIndex;
}
Swipe through the index in UserDetailViewController.m:
- (void)swipeDetectedRight:(UISwipeGestureRecognizer *)sender
{
if (userDetailIndex != 0)
userDetailIndex--;
}
When swipeDetectedDown to pop back, MainDataViewController needs to know which object at index to display:
- (void)swipeDetectedDown:(UISwipeGestureRecognizer *)sender
{
//jump to correct object at index, same as current object at index in this view
[self.navigationController popViewControllerAnimated:NO];
}
Code suggestions?
Use NSNotificationCenter to send an object back to the MainDataViewController...
Example:
In UsersDetailViewController populate an NSDictionary with a key=>value pair then send it over to where you want it to go.
NSArray *key = [NSArray arrayWithObject:#"myIndex"];
NSArray *object = [NSArray arrayWithObject:detailIndex];
NSDictionary *dictionary = [NSDictionary dictionaryWithObjects:object forKeys:key];
[[NSNotificationCenter defaultCenter] postNotificationName:#"MainDataViewController" object:self userInfo:dictionary];
Note: You need to setup an identifier on MainDataViewController called MainDataViewController or whatever you want to call it. Using the VC name keeps it simpler.
Then on MainDataViewController do this in the viewDidLoad() method.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receiveNotification:) name:#"MainDataViewController" object:nil];
And then receive the notification by using the following method:
- (void)receiveNotification:(NSNotification *) notification
{
if([[notification name] isEqualToString:#"MainDataViewController"])
{
NSDictionary *dictionary = [NSDictionary dictionaryWithDictionary:[notification userInfo]];
if([dictionary valueForKey:#"myIndex"])
{
// do whatever you need to do with the passed object here. In your case grab the detailIndex and use it for something...
}
}
}
The easy part is to put the UsersDetailViewController pointer into a property of MainDetailViewController so it can access self.usersController.usersDataSource & self.usersController.userDetailIndex later. Then the only trick is to have it know when the UsersDetailViewController was popped.
In code I used to write, I often tried something like making MainDetailViewController be a delegate of UsersDetailViewController, and having a delegate method in MainDetailViewController be called when the UsersDetailViewController want to close programmatically, and in that do both the popViewControllerAnimated: and update the MainDetailViewController's state. In other words, always have the parent's code pop the child off. This works, but not in the case where you have the child view controller pop automatically via the navigation controller's back button say, so overall I'd argue against that technique.
I think there's better solutions for having the parent's code get called when its child is popped. Perhaps implement a viewWillAppear method and if self.usersController is set there, then you know you're coming back from the UsersDetailViewController, at that point access the other controller's properties and finally clear self.usersController.

NSMutableDictionary KVO

I'm trying to observe changes in dictionary using KVO.
Example:
dictionary = [NSMutableDictionary new];
[dictionary setObject:#"test1" forKey:#"key1"];
[dictionary setObject:#"test2" forKey:#"key2"];
[dictionary setObject:#"test3" forKey:#"key1"];
I'd love to be able to hook an observer for whenever a value is added to the dictionary. removed, or replaced (ie in the above cases, whenever any of the setObject methods are called)
So in conclusion:
I want a function to have
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
called when I ADD a any new entry to a dictionary, or Remove any entry, or REPLACE any entry.
NOT: I do NOT want to have to specify which keys I'm observing for. (eg observe only when #"key1" is added) as this solution doesn't scale.
Subclassing NSMutableDictionary is a bit annoying, due to the fact that NSDictionary and its friends are class clusters. It's certainly doable, and if you have to pass the object itself to another set of classes, then you may want to do exactly that. Otherwise, it might be easier to create a composite class which has the same basic API and uses NSMutableDictionary object internally for storage. There's a pretty good write-up as CocoaWithLove.com, Ordered Dictionary Subclassing, which goes into doing this.
However, that doesn't completely solve your problem. What I would suggest is that you begin with a subclass or decorator class such as the one above, then add support explicitly for -(NSArray*)allKeys, which is a standard accessor in NSDictionary itself. Then, you can add support to pass along change messages for allKeys, which will make it observable.
This can be done by adding the following code around the -setObject:forKey: and -removeObjectForKey: methods.
- (void)setObject:(id)anObject forKey:(id)aKey
{
BOOL addKey=NO;
if (![dictionary objectForKey: aKey]) {
addKey=YES;
[self willChangeValueForKey: #"allKeys"];
}
[dictionary setObject:anObject forKey:aKey];
if (addKey)
[self didChangeValueForKey: #"allKeys"];
}
- (void)removeObjectForKey:(id)aKey
{
[self willChangeValueForKey: #"allKeys"];
[dictionary removeObjectForKey:aKey];
[self didChangeValueForKey: #"allKeys"];
}
What is being done here is that we're adding explicit KVO notification to the class when the dictionary's keys are changed to mark a change in the array.
This will take care of adds and removes. If you want changes to be notified on the same basis, you can remove the if statements, and just have allKeys notify on either set or remove, like this:
- (void)setObject:(id)anObject forKey:(id)aKey
{
[self willChangeValueForKey: #"allKeys"];
[dictionary setObject:anObject forKey:aKey];
[self didChangeValueForKey: #"allKeys"];
}
Then, in your code, you put in a single observer for the key #"allKeys" on this object and you'll be receiving notifications whenever an item changes.
I solved a similar problem by adding an observer to the mutable dictionary "translator" in this way:
[self addObserver:self forKeyPath:#"translator.#count" options:0 context:NULL];
My app manages the data in a the classical way, using a tableview, a controller and the dictionary as KVO property "translator".
The dictionary is bound to a NSDictionaryController in my XIB, and a tableview content is bound to the controller.
This are the connections of the tableview:
Now, in any of the following cases I catch the change :
adding a key-value pair
removing a key-value pair
changing a key
changing a value
Remark: unfortunately, this approach does not work with NSMutableArrays.
Changes are not recognized
Can't you subclass NSMutableDictionary and override the various setters? For instance, overriding setObject:forKey: by calling super, then immediately calling addObserver...
You can also write a wrapper for NSMutableDictionary where you force yourself to use custom setters to manipulate the underlying NSMutableDictionary.
Maybe I need more context to any of your limitations or scalability intents.
I hope this will be helpful
- (void)addObserver:(id)observer {
for (id key in grid)
[self addObserver:observer
forKeyPath:[key description]
options:0
context:key];
}
I think another way to do this is using the below override, incase you are observing NSMutableDictionary "allRecentCurrencyData" whose values are dependent on recentBrazilReals, recentEuEuro, recentUkPounds, recentJapanYen, the observer will get called, but the drawback is you need to know the keys before hand to do this.
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key
{
NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
if ([key isEqualToString:#"allRecentCurrencyData"]) {
NSArray *affectingKeys = #[#"recentBrazilReals", #"recentEuEuro",#"recentUkPounds",#"recentJapanYen"];
keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
}
return keyPaths;
}

NSDictionary class changing if held in external Singleton?

I am watching a somewhat cruel behaviour momentarily: I have a ViewController for building a View programmatically. For this purpose I have stored the names of the UILabels that will be displayed in a NSDictionary that is held in an external class which is a singleton.
Unfortunately the NSDictionary is not accessible if I want to use the values in loadView. So I made some tests: The NSDictionary and its contents are availbale in init and the class is, of course, NSCFDictionary. If I have a look at it in loadView the class sometimes is NSCFDictionary and sometimes also CALayer or NSString?! I absolutely don't know what is happening??? This is the code I use:
- (id) init
{
self = [super initWithNibName:nil bundle:nil];
if (self)
{
UITabBarItem *tbi = [self tabBarItem];
[tbi setTitle:#"xxx"];
}
NSEnumerator *num = [[[ValueDispatcher dispatcher] labelDic] keyEnumerator];
NSLog(#"Class(init): %#", [[[ValueDispatcher dispatcher] labelDic] class]);
NSLog(#"No: %i", [[[ValueDispatcher dispatcher] labelDic] count]);
for (id key in num)
{
NSLog(#"Key %# Value %#", key, [[[ValueDispatcher dispatcher] labelDic] valueForKey:key]);
}
return self;
}
- (void)loadView
{
NSLog(#"Class(loadview)1: %#", [[[ValueDispatcher dispatcher] labelDic] class]);
NSLog(#"No: %i", [[[ValueDispatcher dispatcher] labelDic] count]);
NSEnumerator *num = [[[ValueDispatcher dispatcher] labelDic] keyEnumerator];
for (id key in num)
{
NSLog(#"Key34 %# Value %#", key, [[[ValueDispatcher dispatcher] labelDic] valueForKey:key]);
}
...
At which point between init and loadView can or will a NSDictionary be changed?
Btw, another info that might be important: If I use the above code and the NSDictionary is filled by an external service everything works fine. But if I fill the NSDictionary from a stored plist during startup it fails and I watch the described behaviour...
If I have a look at it in loadView the class sometimes is NSCFDictionary and sometimes also CALayer or NSString?
this (typically) means you have a reference count issue or you have not unregistered your observers correctly -- assuming you never set the dictionary. run instruments with zombies enabled.
At which point between init and loadView can or will a NSDictionary be changed?
a lot can happen in that time beyond the code you posted.
You'll need to retain that dictionary for as long your singleton needs it.
If you're using ARC, just make sure that ivar and/ or property are strong.
If you aren't using ARC, and you have a property setter to manage this for you, make sure you are actually using that setter.
And if no ARC, and you're setting your ivar directly, just make sure to retain the dictionary (and release the old one, if any)