Bindings AND target/action? - objective-c

I currently have a color well which keeps track of a color that gets saved in the NSUserDefaults. It is bound to an NSUserDefaultsController. However, I also want to listen for changes to the color so I can update my views accordingly. Therefore, in addition to the binding, I added a target/action to the color well to my preferences controller that posts a notification with the color.
1) How safe is having both target/action and bindings? Is there a possibility that one might lag or they may be out of sync and report different values?
2) When I am getting the color in my IBAction method, should I get it from the user defaults or from the color well?
Here is my colorChanged: action:
- (IBAction)colorChanged:(id)sender
{
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:[colorWell color] forKey:#"color"];
[notificationCenter postNotificationName:#"ColorChangedNotification" object:self userInfo:userInfo];
}
So should I be doing this:
[NSKeyedUnarchiver unarchiveObjectWithData:[[NSUserDefaults standardUserDefaults] objectForKey:#"color"]];
or:
[colorWell color];
Thanks!

1) How safe is having both
target/action and bindings? Is there a
possibility that one might lag or they
may be out of sync and report
different values?
I think for the most part, it should be OK. The best way to tell is to test it out.
2) When I am getting the color in my IBAction method, should I get it from the user defaults or from the color well?
You should definitely, definitely get it directly from the color well. Why? There could be a lag when saving to the user defaults. Heck, the defaults could even save only once right before the application terminates, and it would still be alright. (OK, this isn't entirely true, but still) The defaults' main purpose is to persist data in between application launches, not during the lifespan of the app.

It is safe to have both target/action and bindings. If you post notifications with an NSNotificationCenter, then the notifications are delivered synchronously to the observers. (With the obvious caveat that it is not magic--if observer A sends a message to observer B when it gets the notification, observer B will not have received the notification yet. Multiple threads add further complexity.) This is called out in the documentation for NSNotificationCenter.
Reading the color directly from the color well is fast, and probably fine from an IBAction. If you're running code when the application is starting it is best to read from the user defaults because the color well's bindings might not have been updated yet.

Related

NSManagedObjectContextDidSaveNotification best way to update the UI?

In my code i have an mainManagedObjectContext and a backgroundManagedObjectContext and its working great.
I moved all my save code to the backgroundManagedObjectContext and merging the differences between the contexts via NSManagedObjectContextDidSaveNotification.
Now I want to update my UI after NSManagedObjectContextDidSaveNotification. What is the best approach beside of a NSFetchedResultController to do this?
The changes in my object is visible via the debugger and I could use KVO for this, but IMHO it's a terrible idea. In my abstraction i got a Model to handle the database calls and it would be great when my Model also handling changes after merging the context.
What is the best approach to do this?
As has been pointed out, for table and collection views, the best bet is NSFetchedResultsControllerDelegate.
Another mechanism is to register for this (or your custom) notification NSNotificationCenter, e.g. for the original notification:
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(updateUI:)
name:NSManagedObjectContextDidChangeNotification
object:nil];
Best do this in viewDidAppear. Don't forget to remove the observer in viewWillDisappear. Note that following the comment I am using the change notification rather than the save notification.
In your non-table view controllers you should isolate the UI setup, similar to the boilerplate code for the fetched results controller delegate, which implements a method like configureCell:atIndexPath:. You can then simply call this setup routine when you get the notification without duplicating any code.

Using `valueForKey` to access view in UIBarButtonItem, private API violation?

Since UIBarButtonItem doesn't subclass UIView, it's impossible to get at the normal characteristics like its frame.
One way to do this is [barButtonItem valueForKey:#"view"]
This works perfectly, and allows you to add a GestureRecognizer (for instance) to the underlying UIView.
However, is this a private UIKit API violation?
This is not private in terms of immediate rejection upon validation, but it's private enough to be considered fragile (that is, new iOS version can break your existing app in the app store that's using the code).
I can say, that a similar code (fetching backgroundView ivar of UIToolbar via KVC) has passed app store validation and is being used in production.
In case of possible bad things, you must wrap the method in #try { ... } #catch, so that you intercept KVC possibly failing in newer iOS release.
Five Pieces of Evidence for "It's Not Private"
It's a property that you can get to in other ways. Try this and one of those views is, in fact, the _view ivar of the UIBarButtonItem in question. This indicates that access to this UIView is not prohibited itself, though the KVO way in might be questionable (but I doubt it).
NSArray *array = self.toolBar.subviews;
for (UIView *view in array) {
view.backgroundColor = UIColor.greenColor;
}
They actually trigger the KVO for this property. ivars do not have to trigger the KVO API, right?
#Farcaller mentions a similar case which is for sale in the App Store. Since he/she answered within the first 20 minutes of the question being up there, it's reasonable (but not safe!) to assume that there might be thousands of apps in the App Store that do this.
This UIView gets subbed out each time the button is pressed, so you cannot just, for example, set a gesture recognizer on it and be done. You can, however, keep setting the same gesture recognizer every time the view gets replaced. To me, this is actually more evidence that it's not a private API thing, but rather you have to be very careful when using it (and use KVO to make sure you have the latest one).
My app is for sale in the App Store and does this.

Using NSUndoManager with prepareWithInvocationTarget, Gestures and Objects

I have a Drawing App of sorts. I want to Implement Undo/Redo. Though I'm running into difficulty with storing the Original and New Values for the Undo/Redo.
With Gestures I need to store a Few things: transform, Center, for the Properties Dialog, I need to store many more, Color, Font, Font-Size, Outline, Outline Color, the Text It self, etc.
I've created a NSMutableDictionary of the Attributes that the user can change in a Single Gesture/Properties Popover.
I wanted to use Rob's Answer for NSUndoManager and Rotation Gesture though using his Solution works with CGAffineTransform, which when sent as a parameter for the prepareWithInvocationTarget, its not an object and just puts a copy of the CGAffineTransform struct on the Undo/Redo stack.
Though when using the prepareWithInvocationTarget with my NSMutableDictionary the Dictionaries I'm passing in (OriginalAttribs, newAttribs) are not retained. I cannot have them as local iVars as they will change with each action on a drawing object.
Seems like I want to use retainArguments as part of NSInvocation though I don't really want to retain them. I need a copy of them.
Its the Gestures that make this Difficult as I can't keep setting the OrigianlCenter, OriginalTransform as it changes while the gesture is active.
UPDATE
I found this link and it seems to be similar to what I want to do.
I setup my NSUndoManager like this:
//Needed to get access to UndoManager
NSUndoManager * undoManager = [(IoScreenEditorViewController * )UIAppDelegate.ioMainViewController.currentViewController undoManager];
//Need to Store our Center as a NSValue
[undoManager prepareWithInvocationTarget:self];
[undoManager forwardInvocation:anInvocation ];
I then get a runtime error:
-[NSUndoManager undoAttributesWithOriginalAttributes:newAttributes:]: unrecognized selector sent to instance 0xeedcbd0
undoAttributesWithOriginalAttributes:newAttributes: is the selector that I setup for my NSInvocation Object. According to the Documentation here, it says that it should pass it along to self (the Target), not the undo manager self?

Difference between NSNotification and NSEvent

I don't really understand the difference that cocoa makes between a notification and an event.
For instance I could have code like this:
-(void)mouseMoved:(NSEvent*)event { … }
but not
-(void)windowMoved:(NSEvent*)event { … }
For the second one I'd have to use NSNotification – why?
The difference is, that NSEvent is used to encapsulate input events. Mouse down, key down etc.
However, NSNotification is used to notify observers about a change of a state or an object (eg. when the network reachability changed, new data became available or that a window moved).
In your case: A window move isn't some kind of input, but a change of the windows position. Thus you get an NSNotification rather an NSEvent.

3 notifications instead of one

I'm developing simple MVC app in Cocoa/Objective-C. I have a strange issue (or misunderstanding) with notifications and KVO.
I have AppController object in MainMenu.xib, hence I implement awakeFromNib method where I register for NSImageView changing its image property. I add self as an observer in the following way:
// options:3 equals to new/old passed values in changeDictionary
[backgroundImageView addObserver:self
forKeyPath:#"image"
options:3
context:NULL];
The backgroundImageView is an IBOutlet in AppController connected to NSImageView.
In standard observeValueForKeyPath:ofObject:change:context method I just log the received notification.
Problem is - when i change the image value of NSImageView I get 3 notifications instead of one. Can you help me with this? Maybe I'm overlooking something in options or in generally registering observer?
UPDATE: backgroundImageView is the instance of BackgroundImageView class which is sublcass of NSImageView. I subclassed the latter one for handling drag and drop operations as drag destination. When performDragOperation: is called (the last 'state' of the dragging) it changes the value for image property with setImage between willChangeValueForKey and didChangeValueForKey.
… it changes the value for image property with setImage between willChangeValueForKey and didChangeValueForKey.
When you send an accessor message, you get KVO notifications for free with it. You should remove the {will,did}ChangeValueForKey: messages, because they're the cause of at least one of the extraneous change notifications.
Is your AppController the File's Owner of two other nibs? If so, it'll receive an awakeFromNib message for each of those, too. MainMenu plus two makes three awakeFromNib messages, which means you'll add yourself as an observer three times.
There does not seem to be any obvious problem with setting of the observer.
Have a look at how you update the image that you observe, maybe it's being modified 3 times?