To better understand KVO, I created a simple application with 1 button and 2 very basic model classes: Book and Author. I want to trigger the Book when the Author changes. For example, A simple KVO example, why doesn't this trigger the observer?
#import "AppDelegate.h"
#import "Book.h"
#import "Author.h"
#implementation AppDelegate {
Book *home;
Author *nancy;
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
NSLog(#"FunWithKVO");
nancy = [[Author alloc] init];
[nancy setFirstName:#"Nancy"];
[nancy setLastName:#"Drew"];
home = [[Book alloc] init];
[home addObserver:nancy forKeyPath:#"lastName" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:NULL];
[home setAuthor:#"Nancy Drew"];
}
- (IBAction)changeName:(id)sender {
NSLog(#"%#",[home author]);
[nancy setLastName:#"Martin"];
}
#end
NOW THIS SHOULD BE CALLED BUT ISN'T:
#import "Book.h"
#implementation Book
#synthesize author;
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
[author setValue:[NSString stringWithFormat:#"Nancy %#",[change value]]];
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
NSLog(#"name is now: %#",author);
}
#end
You mixed up the observing and the observed object in the registration.
nancy is the object to be observed by home, therefore it should be
[nancy addObserver:home forKeyPath:#"lastName" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:NULL];
Related
I am trying to make an observer to notify me whenever my laptop it automatically changes its wifi connection.
I have a "NetworkProperties" class where I store my connection properties:
#interface NetworkProperties : NSObject
{
#public
CWInterface *wfi;
#private
CWWiFiClient *wfc;
NSString *SSID;
NSString *BSSID;
NSString *phyMode;
NSString *hwAddr;
NSString *securityType;
}
#end
and a "GUIHandle" class where I try to handle my GUI. I also have the following functions to enable/disable the scaning:
#interface GUIHandle : NSObject
{
NetworkProperties *NP;
}
-(IBAction)startScan:(id)sender;
-(IBAction)stopScan:(id)sender;
#end
-(IBAction)startScan:(id)sender
{
done = 0;
NP = [[NetworkProperties alloc] init];
[NP scanNetworkProperties];
[NP->wfi addObserver:self forKeyPath:#"bssid" options:NSKeyValueObservingOptionNew context:nil];
}
-(IBAction)stopScan:(id)sender
{
[NP->wfi removeObserver:self forKeyPath:#"bssid"];
}
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSLog(#"We are here!\n");
if ([keyPath isEqual:#"bssid"]) {
NSLog(#"Value changed!\n");
}
}
However I do not get any notification if the "bssid" of my connection is changing. What am I doing wrong?
I'm not familiar with iOS but I'm trying to find when the default, built-in camera application is focusing. To do this I create my own separate Objective-C application and following this answer here [iPhone : camera autofocus observer? but I'm not getting anything from observeValueForKeyPath in the NSLog.
#import "ViewController.h"
#import "AVFoundation/AVCaptureDevice.h"
#import "AVFoundation/AVMediaFormat.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSLog(#"viewDidLoad");
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
// callback
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSLog(#"observeValueForKeyPath");
if( [keyPath isEqualToString:#"adjustingFocus"] ){
BOOL adjustingFocus = [ [change objectForKey:NSKeyValueChangeNewKey] isEqualToNumber:[NSNumber numberWithInt:1] ];
NSLog(#"Is adjusting focus? %#", adjustingFocus ? #"YES" : #"NO" );
NSLog(#"Change dictionary: %#", change);
}
if( [keyPath isEqualToString:#"focusMode"] ){
AVCaptureFocusMode focusMode = [ [change objectForKey:NSKeyValueChangeNewKey] isEqualToNumber:[NSNumber numberWithInt:1] ];
NSLog(#"focusMode? %ld", focusMode);
}
}
// register observer
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear: animated];
NSLog(#"viewWillAppear");
AVCaptureDevice *camDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
int flags = NSKeyValueObservingOptionNew;
[camDevice addObserver:self forKeyPath:#"adjustingFocus" options:flags context:nil];
[camDevice addObserver:self forKeyPath:#"focusMode" options:flags context:nil];
}
#end
Any help much appreciated.
For anyone who visits this question, the answer is what Bluewings wrote as a comment. I was trying to use KVO to observe one application from another which is not possible since only one lock on a capture device is possible at one time.
I just stumbled over a weird behaviour in Obj-C when observing a property which name "matches" its type in lowercase
Example: if the property is called foo and its type is Foo then the keyPath to observe is not #"foo" but #"Foo" which does not seem right. Or does it?
Note that I used the "out of the box" KVO with getters/setters and KVO event emitting handled automatically.
Example code
Foo.h
#import <Foundation/Foundation.h>
#interface Foo : NSObject
#end
Foo.m
#import "Foo.h"
#implementation Foo
#end
KVOTest.h
#import <Foundation/Foundation.h>
#import "Foo.h"
#interface KVOTest : NSObject
#property Foo *foo;
-(void)test;
#end
KVOTest.m
#import "KVOTest.h"
#import "Foo.h"
#implementation KVOTest
-(void)test {
// this won't work
[self addObserver:self forKeyPath:#"foo" options:0 context:nil];
// this does work
[self addObserver:self forKeyPath:#"Foo" options:0 context:nil];
self.foo = [[Foo alloc] init];
[self removeObserver:self forKeyPath:#"foo"];
[self removeObserver:self forKeyPath:#"Foo"];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(#"keypath: %#", keyPath);
}
#end
main.m
#import <Foundation/Foundation.h>
#import "KVOTest.h"
int main(int argc, const char * argv[]) {
#autoreleasepool {
KVOTest *kvo = [KVOTest new];
[kvo test];
}
return 0;
}
I checked Apples KVO documentation but did not find this case covered.
Also I did not find any references to this on SO so I wonder if this is a bug or a feature due to the potential "clash" between type and propery name?
I'm trying to do a very simple application, the purpose is listening an audio stream (AAC 64 kbps). To do so I'm using AVPlayer from the Apple AVFoundation has follow :
ViewController.m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
#synthesize playerItem, player;
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void) viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
playerItem = [AVPlayerItem playerItemWithURL:[NSURL URLWithString:#"http://stream.myjungly.fr/MYJUNGLY2"]];
[playerItem addObserver:self forKeyPath:#"timedMetadata" options:NSKeyValueObservingOptionNew context:nil];
player = [AVPlayer playerWithPlayerItem:playerItem];
[player play];
NSLog(#"player item error : %#", playerItem.error.description);
NSLog(#"player error : %#", player.error.description);
}
- (void) observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object
change:(NSDictionary*)change context:(void*)context {
if ([keyPath isEqualToString:#"timedMetadata"])
{
AVPlayerItem* _playerItem = object;
for (AVMetadataItem* metadata in _playerItem.timedMetadata)
{
NSLog(#"\nkey: %#\nkeySpace: %#\ncommonKey: %#\nvalue: %#", [metadata.key description], metadata.keySpace, metadata.commonKey, metadata.stringValue);
}
}
}
#end
My object player and playerItem are strong properties :
ViewController.h
#interface ViewController : UIViewController
#property (nonatomic, strong) AVPlayerItem* playerItem;
#property (nonatomic, strong) AVPlayer* player;
#end
The Key Value Observer is working great, here is my log :
2013-05-14 11:18:03.725 MusicAvPlayer[6494:907] player item error : (null)
2013-05-14 11:18:03.728 MusicAvPlayer[6494:907] player error : (null)
2013-05-14 11:18:08.140 MusicAvPlayer[6494:907]
key: title
keySpace: comn
commonKey: title
value: Alabama Shakes - Be Mine
But the audio is not played, I've go no sound ! Any idea why ?
EDIT: I already look at this questions :
No sound coming from AVPlayer
AVAudioPlayer, No Sound
AVAudioPlayer not playing any sound
That's why I'm using a strong property, so I guess my problem is not ARC related
I found problem : the iphone was in silent mode ... so no sound can go out on the speaker, the the sound was played when I was using the head phone.
But I've got a new question now : how can you play sound on the speaker when the phone is in silent mode ? (like the official Music application)
EDIT : ... and the answer is there :
Play sound on iPhone even in silent mode
// Init PlayerItem
playerItem = [AVPlayerItem playerItemWithURL:[NSURL URLWithString:#"http://stream.myjungly.fr/MYJUNGLY2"]];
// Init Player Obj
player = [AVPlayer playerWithPlayerItem:playerItem];
// Add objserver on Player
[player addObserver:self forKeyPath:#"status" options:0 context:nil];
Add Observer Method your Class
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context {
if (object == player && [keyPath isEqualToString:#"status"]) {
if (player.status == AVPlayerStatusReadyToPlay) {
// Start playing...
[player play];
} else if (player.status == AVPlayerStatusFailed) {
// something went wrong. player.error should contain some information
}
}
if ([keyPath isEqualToString:#"timedMetadata"])
{
AVPlayerItem* _playerItem = object;
for (AVMetadataItem* metadata in _playerItem.timedMetadata)
{
NSLog(#"\nkey: %#\nkeySpace: %#\ncommonKey: %#\nvalue: %#", [metadata.key description], metadata.keySpace, metadata.commonKey, metadata.stringValue);
}
}
}
I have now nearly figured out how to Filter a NSTreeController, to do this I have sub-classed NSManagedObject and added some code to my App Delegate, I have also bound my NSSearchField to the filterPredicate of my App Delegate but I think I need to connect my NSTreeController and NSSearchField in some way to make it work.
Below I have posted all the code I have used so far to try and make it work.
NSManagedObject Sub-Class Header File.
#interface Managed_Object_Sub_Class : NSManagedObject {
NSArray *filteredChildren; // this should fix the compiler error
}
- (NSArray *)filteredChildren;
#end
NSManagedObject Sub-Class Implementation File.
#implementation Managed_Object_Sub_Class
static char *FilteredChildrenObservationContext;
- (id)initWithEntity:(NSEntityDescription *)entity insertIntoManagedObjectContext:(NSManagedObjectContext *)context {
if (self = [super initWithEntity:entity insertIntoManagedObjectContext:context]) {
[[NSApp delegate] addObserver:self forKeyPath:#"filterPredicate" options:0 context:&FilteredChildrenObservationContext];
[self addObserver:self forKeyPath:#"subGroup" options:0 context:&FilteredChildrenObservationContext];
}
return self;
}
// use finalize with GC
- (void)dealloc {
[[NSApp delegate] removeObserver:self forKeyPath:#"filterPredicate"];
[self removeObserver:self forKeyPath:#"subGroup"];
[super dealloc];
}
- (NSArray *)filteredChildren {
if (filteredChildren == nil) {
NSPredicate *predicate = [[NSApp delegate] filterPredicate];
if (predicate)
filteredChildren = [[[self valueForKey:#"subGroup"] filteredArrayUsingPredicate:predicate] copy];
else
filteredChildren = [[self valueForKey:#"subGroup"] copy];
}
return filteredChildren;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context == &FilteredChildrenObservationContext) {
[self willChangeValueForKey:#"filteredChildren"];
[filteredChildren release];
filteredChildren = nil;
[self didChangeValueForKey:#"filteredChildren"];
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
#end
Code Added To App Delegate Header File
NSPredicate *filterPredicate;
Code Added To App Delegate Implementation File
- (NSPredicate *)filterPredicate {
return filterPredicate;
}
- (void)setFilterPredicate:(NSPredicate *)newFilterPredicate {
if (filterPredicate != newFilterPredicate) {
[filterPredicate release];
filterPredicate = [newFilterPredicate retain];
}
}
Search Field Binding
alt text http://snapplr.com/snap/vs9q
This doesn't work yet, and so that is why I am asking what I need to do from here to make it work, like I said I think I need to connect the NSSearchField and NSTreeController Together in some way.
Again I hav answered my own question, I also hope that this will help other people so they know how to Filter a NSTreeController.
To make it work from my post above do the following.
1.For your entity set the Class as your NSManagedObject Sub-Class in my Case JGManagedObject.
alt text http://dvlp.me/c3k
2.For your search field in IB set the predicate format to what you want to Filter ( The Property in your entity, for me it is name).
alt text http://dvlp.me/9k9rw