multiple (redundant?) observeValueForKeyPath: messages received - cocoa

I have an obj-C Mac app with an object observing some property (bool) of standardUserDefaults.
The code is pretty standard:
[[NSUserDefaults standardUserDefaults] addObserver:self forKeyPath:#"property" options:NSKeyValueObservingOptionNew context:nil];
This standardUserDefaults' property is bound to an NSButton state.
When I click the button, the property changes as normal, but up to 8 messages in a row notifying a change in the property are received at each click! I have no idea why that is. The property is not bound to anything else but the button. I checked that the button is indeed clicked once. The value of the property is the same across notifications resulting from the same click.
This also occurs without binding if I manually change the property via
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:#"property"];
After that, 8 observeValueForKeyPath: messages are received in a row!
The only related issue I found was this one: https://developer.apple.com/forums/thread/47609.
Any idea?

Related

Button with IBAction causes "Could not connect action, target class NSNibExternalObjectPlaceholder does not respond to -action" error

I have a Storyboard scene which contains an NSTableView with an NSTableViewRow subclass. In this table view row, I have a button which I have wired up to an action in the view controller on the scene.
When the table loads, I am seeing this cryptic error from Xcode and the action never fires when I click the button:
Could not connect action, target class NSNibExternalObjectPlaceholder does not respond to -playVideo:
It looks like the file's owner never gets wired up.
Since these NSTableRowViews get instantiated programmatically, I checked to see if the owner is getting set, and indeed it is:
- (NSView *) tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
...
CombinedHistoryEntryTableCellView *cellView = [tableView makeViewWithIdentifier:#"deviceEvent" owner:self];
...
return cellView;
}
I believe I am experiencing a bug (and I will file a radar later) but I was able to work around the issue by wiring the button up to an action on the CombinedHistoryEntryTableCellView class in IB. Since the code needed to run the action was in the view controller I gave the cell view object a reference to the controller so that it could call it.
Still don't know why that was an issue, though, but if someone else experiences this that is a suitable workaround.

UIPopover button repeats action

I am creating the application for iPad with the UIPopover. I have a main view from which I am calling two popovers displaying different information. I was following the guidelines in How to Dismiss a Storyboard Popover thread and another threads, everything works fine except one thing. In my popover I have a button which triggers an action on the parent view. From time to time the action is triggered more than once even if the button was clicked just once and also popover was opened just once. My first assumption was that the popover was caching some data from several calls, but the problem seems to appear just randomly.
My configuration is: Mac OSx Snow Leopard with Xcode 4.2, iOS 5.0.
Tested in Simulator, iPad 5.1 and iPad 6.0 all the same results.
I have the main view View 1 and the popover view View 2
In my view 2 I have a method ProceedButtonClicked which sends the notification to the View 1
- (IBAction) ProceedButtonClicked{
[[NSNotificationCenter defaultCenter] postNotificationName:#"proceedButtonClicked" object:self];
}
The method is binded to the button in the popover view.
In view1 (parent view):
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(ButtonClicked:) name:#"proceedButtonClicked" object:nil];
}
- (void) ButtonClicked:(NSNotification *) notification {
NSLog(#"I'm here ...");
//dismiss popover
if (paramPopover)
[paramPopover dismissPopoverAnimated:YES];
}
I am pretty new to the iPad development, so maybe I am missing in my code something obvious, but searching until now resulted to nothing.
Any help would be appreciated.
Thank you.
When using notifications, you risk multiple instances of a class answering the same notification, so if you have 2 controllers alive for some reason (bad memory management?), then when pressing the button 2 controllers will answer the call and you will have a duplicate action.
Buttons can have a specific callback assigned to them, and it's very simple to set it up by code:
If your button is a UIButton, you can set your target action like this:
[button addTarget:self action:#selector(buttonAction:) forControlEvents:UIControlEventTouchUpInside];
If your button is a UIBarButtonItem, you set the target when you create it
[[UIBarButtonItem alloc] initWithTitle:#"Title" style:UIBarButtonItemStyleBordered target:self action:#selector(buttonAction:)];
Edit:
NSLog(#"I'm here ...");
That's creepy ...

UITableView Row Persistance - why does "didSelectRowAtIndexPath" crash

I have a UITableView that stores the selected row in User Defaults. The tableView is part of a menu structure that may be reloaded during the lifetime of the application, hence I want the persistence between loads. In viewDidLoad, this UserDefault is checked for existence, and I call
NSIndexPath *path = [NSIndexPath indexPathForRow:row inSection:0];
[self.tableView selectRowAtIndexPath:path animated:NO scrollPosition:(UITableViewScrollPositionMiddle)];
This works fine, as expected. However, it doesn't actually select the row, it just highlight's it. If I subsequently call
[self tableView:self.tableView didSelectRowAtIndexPath:path];
I get a crash - "unrecognised selector sent to instance". Why?
[self tableView:self.tableView didDeselectRowAtIndexPath:path] will call YOUR implementation of the deselection method (which is defined in UITableViewDelegate).
You get a crash since you didn't implement it in your delegate.
You should call:
[self.tableView deselectRowAtIndexPath:path animated:NO];
The UITableView works via two delegate protocols, UITableViewDelegate and UITableViewDataSource. The class that defines your UITableView should implement these protocols and you should set the delegate and datasource of the UITableView to "self". You should not call the protocol methods directly which is most likely the reason for your crash.
In order to select a particular row based on your data model (User Default), you will need to set the UITableViewCell selected property to "YES" in the tableView:cellForRowAtIndexPath: for the row that is being rendered.
I recommend going through this tutorial to help you understand UITableView's better.
http://www.iosdevnotes.com/2011/10/uitableview-tutorial/

Manual binding in Cocoa

I have an ImageView which shows a lock, informing if an opened file is locked or not. I have 2 images for locked and unlocked cases. I want synchronize the displayed image with boolean value of my object representing an opened file.
To do this I want my ViewController to change the image in my ImageView depending on lock state of object. So both object and ViewController have a property "isLocked".
How can I synchronize them? It is easy in IB but I don't know how to do it programmatically. I tried in initialize method of my ViewController to use:
[ViewController bind:#"value" toObject:[ArrayController selection] withKeyPath:#"isLocked" options:nil];
But it doesn't work. In documentation it is said that I have to expose my binding before using it.
I try to put the following code in initializer method of my object:
[self exposeBinding:#"isLocked"];
But Xcode doesn't recognize this method.
Does somebody have experience with this kind of bindings establishing?
As #nick says, you want Key-Value-Observing.
[arrayController addObserver:self
forKeyPath:#"selection.isLocked"
options:NSKeyValueObservingOptionNew
context:#"this_context"]
Then when isLocked changes the -observeValueForKeyPath:ofObject:change:context: method that you have added to your viewController will be called (as long as you only manipulate isLocked in a KVC compliant way).
The options parameter lets you optionally tweak exactly what conditions will trigger the notification and what data is sent along with the notification. The context parameter is there to help you distinguish between notifications that you registered to receive and notifications your superclass registered to receive. It is optional.
Bindings seem like they might be useful to keep two values in sync. However, this is not what they do at all.
Yes, lots of things seem to give the impression that this is what they do, and there isn't much saying that this isn't what they do, also lots of people believe that this is what they do - but no, you cannot use them for this.
Only a handful of classes support bindings (they are listed here) and then, and this is the important bit, those classes only support binding their named bindings, and these bindings are not instance variables. eg NSTextField has a 'fontFamilyName' binding yet NSTextField does not have a 'fontFamilyName' property or instance variable, even a derived one. NSTextField does have a 'isBordered' property but not a binding - so you cannot bind 'isBordered'.
It does not mean anything to 'bind' an arbitrary property of an arbitrary Class.
Yes, you can bind two arbitrary values, the following code works just fine:
#import <Cocoa/Cocoa.h>
#interface SomeObject : NSObject
#property (retain,nonatomic) id someValue;
#end
#implementation SomeObject
#end
int main()
{
SomeObject *source=[SomeObject new];
SomeObject *target=[SomeObject new];
[target bind:#"someValue" toObject:source withKeyPath:#"someValue" options:0];
[source bind:#"someValue" toObject:target withKeyPath:#"someValue" options:0];
[source setSomeValue:#(42)];
NSLog(#"target: %#",[target someValue]);
[target setSomeValue:#(22)];
NSLog(#"source: %#",[source someValue]);
return 0;
}
As far as I can tell, the problem is the bit [ArrayController selection]. The first problem is that ArrayController is (or should be) a class, and getting the class's selection is probably pointless. The other problem is that even if this were an instance, you would be binding to the selection at the time of the call, which is almost certainly not what you want. You want to track the current selection as it changes.
So what you want is probably something like the following:
[myViewController bind:#"value" toObject:myArrayController withKeyPath:#"selection.isLocked" options:nil];

Getting MAAttachedWindow (Really a view) to close when clicked outside of

I have a MAAttachedWindow that shows itself when a status bar item is clicked. I need to have it close when its clicked outside of. I found some other directions that say to set it as a delegate and use - (void)windowDidResignKey:(NSNotification *)notification to detect when the user exits the window. I've tried it many times but can't seem to get it working which is probably because I didn't correctly set the delegate. Whats the best way to set the delegate so it will respond to the notification? The code is available here.
Thanks in advance
I found out to get the notification you have to register for it.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(windowDidResignKey:)
name:NSWindowDidResignKeyNotification
object:self];

Resources