How to observe a key path like "object.attribute" - cocoa

greetings , I'm a cocoa beginner and this is my first post:P
I'm trying to make a very simple rhythm game but get stuck , here's what I got:
/**** TouchView.h/m ****/
#property AVAudioPlayer *audioPlayer;
[self addObserver:self
forKeyPath:#"audioPlayer.currentTime"
options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld)
context:NULL];
//audioPlayer.currentTime's type is NSTimeinterval (double)
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change (NSDictionary *)change context:(void *)context
{
NSLog(#"action triggered!")
}
which doesn't work (I have initialized audioPlayer properly,it can play sound but just can't be caught when its currentTime value changes)
I test these code with another property "double testNumber" , set it as the argument of "keyPath" , increase it by one when I touch the screen , then that works well. But what should I do to make audioPlayer.currentTime can be observed , I just want to get notified when this value changed , any other advice will also be appreciated. I'm counting on you , please help me ,thanks :)

How about:
[audioPlayer addObserver:self
forKeyPath:#"currentTime"
options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld)
context:NULL];
However if you really want to make your object KVC-compliant (which is what you're attempting to use), you need to follow Apple's guide on the subject:
In order for a class to be considered KVC compliant for a specific
property, it must implement the methods required for valueForKey: and
setValue:forKey: to work for that property.

Related

The Idea? creating sorted array from NSDictionary key values (with objects)

I know there are a whole bunch of questions that have been asked and answered in stackoverflow about the challenge of getting keys in an NSDictionary sorted by putting those keys into sort order in an array. I understand that objects are not stored in sort order within the actual dictionary and that is, I think, for reasons of efficiency or maybe memory management on the part of Foundation code.
I have been working on trying out examples from several answers out here and in apple documentation and blogs (some I can get to work, others not!) , but I can't seem to find an example that solves my confusion.
I think my confusion is that the examples I'm encountering both here, in apple documentation and in the different helpful blogs, all seem to have examples where there is just a key value pair and the second value is not an object - it's more like just a value. (However isn't it really an object at some level? I would think it is)
One example, that I couldn't get to work (Sorting an NSArray by an NSDictionary value ) , uses this idea
[array sortedArrayUsingComparator:^(NSDictionary *item1, NSDictionary *item2) {
NSString *age1 = [item1 objectForKey:#"age"];
NSString *age2 = [item2 objectForKey:#"age"];
return [age1 compare:age2 options:NSNumericSearch];
}];
I thought maybe this idea, specifying the key in a more specific manner, might be my problem.
I wonder if maybe I'm not communicating to the compiler what the key is, and what the object is, and that is why I'm getting an "unrecognized selector sent to instance" error.
..... Code Snips Follow .....
1)
I have a class called "Dog". A given dog object has several properties, including an NSString key.
My key is "licenseString" is an alphanumeric key - I'm also wondering if I should use decimalNumberWithString but that's not the question here
#property (strong,nonatomic) NSString *licenseString;
#property (strong, nonatomic) NSString *dogName;
#property (strong, nonatomic) NSString *whatMakesDogSpecial;
#property (strong, nonatomic) UIImage *dogPhoto;
2) I have an NSDictionary
#property (nonatomic, strong) NSDictionary *dogDictionary;
I hardcode information into the dogDictionary in this not very sophisticated way,
Dog *aDog;
// Dog one
aDog = [[Dog alloc]init] ;
aDog.licenseString = #"1";
aDog.dogName = #"Oscar";
aDog.whatMakesDogSpecial = #"This animal was found at the Mid-Penn humane society. He is super friendly, plays well with other dogs and likes people too. He enjoys fetching balls and frisbees too. He also goes to the park daily." ;
aDog.dogPhoto = [UIImage imageNamed:#"webVuMiniAppDogOscar.jpg"];
[self.dogDictionary setValue:aDog forKey:aDog.licenseString];
3) Then once I have several dog objects in my dogDictionary, I want to sort on the license tag values, so that I can populate a table view with dog names, but by order of their license tags.
BTW it seems that the compiler does recognize "vars.dogDictionary" which appears in the code snip below, because when I look at the debugger I can see that two valid instances are coming up from my dog dictionary. The debugger output is in an attachment
So, using ideas from a stackoverflow answer and the apple documentation, I write this
NSArray *sortedKeys = [vars.dogDictionary keysSortedByValueUsingComparator:
^NSComparisonResult(id obj1, id obj2) {
return [obj1 compare:obj2];
}];
NSLog(#" The sorted array is %#", sortedKeys);
And that's where my problem happens. I recognize that 0x1182f740 refers to "obj1" as shown in the debugger attachment
2013-08-06 15:13:58.276 SortDogLIcenseTags[3876:11303] -[Dog compare:]: unrecognized selector sent to instance 0x1182f740
(lldb)
Attachment is a picture showing debugger values - they don't like to paste very well
Here's how I resolved this challenge. It works and was pretty straightforward to integrate into my Master/Detail project
I know I found a tutorial on the web somewhere that led me to this solution , I'm sorry I can't find it now.
Note that sortedDogDictionaryArray and dogDictionaryArray are declared as properties in the .h file.
self.dogDictionaryArray = [vars.dogDictionary allValues];
// Sort
NSSortDescriptor *sortDescriptorDog =
[[NSSortDescriptor alloc] initWithKey:#"licenseString" ascending:YES];
NSArray *sortDescriptorsDogs =
[NSArray arrayWithObject:sortDescriptorDog];
self.sortedDogDictionaryArray =
[self.dogDictionaryArray sortedArrayUsingDescriptors:sortDescriptorsDogs];
NSLog(#"%#",self.sortedDogDictionaryArray );
int doggie;
Dog *someDogName;
NSLog(#"Sorted Order is...");
for (doggie = 0; doggie < [self.sortedDogDictionaryArray count]; doggie++) {
//NSLog(#"%#", [sortedArray objectAtIndex:i]);
//NSLog(#"%#", [sortedArrayDogs objectAtIndex:doggie]);
someDogName = [self.sortedDogDictionaryArray objectAtIndex:doggie];
//NSLog(#"name is %#", someDogName.dogName);
NSLog(#"name is %# tag is %#", someDogName.dogName, someDogName.licenseString);
}

NSPrintInfo printSettings not KVO compliant despite comment saying so in the header

the background: I'm adding a print panel accessory view to a print dialog (using addAccessoryController:), with controls binded to NSPrintInfo printSettings values so the values are saved in the print presets. I'm having troubles observing printSettings changes. I'm building with SDK 10.6, testing on OS X 10.7.
Here is a code sample that should work in my understanding, but observeValueForKeyPath: is never called:
- (void)testKVO
{
NSPrintInfo *printInfo = [NSPrintInfo sharedPrintInfo];
[printInfo addObserver:self forKeyPath:#"printSettings.foo" options:0 context:NULL];
[printInfo setValue:#"bar" forKeyPath:#"printSettings.foo"]; // observeValueForKeyPath:ofObject:change:context: not called
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
NSLog(#"%s %# :: %#", _cmd, keyPath, object);
}
I also tried observing printSettings directly, with no more success, the observer method is not called either (the printSettings returned by NSPrintInfo is in fact of class NSPrintInfoDictionaryProxy):
- (void)testKVO
{
NSMutableDictionary *printSettings = [[NSPrintInfo sharedPrintInfo] printSettings];
[printSettings addObserver:self forKeyPath:#"foo" options:0 context:NULL];
[printSettings setValue:#"bar" forKey:#"foo"]; // observeValueForKeyPath:ofObject:change:context: not called
}
I double checked that my KVO system works in normal conditions and calls the observer method:
- (void)testKVO
{
NSMutableDictionary *printSettings = [NSMutableDictionary dictionary];
[printSettings addObserver:self forKeyPath:#"foo" options:0 context:NULL];
[printSettings setValue:#"bar" forKey:#"foo"]; // observeValueForKeyPath:ofObject:change:context: called at last!
}
So the question is: how can I observe printSettings modifications, especially to know when the user has chosen a print preset?
I'd also like the preview to be updated automatically with
- (NSSet *)keyPathsForValuesAffectingPreview
{
return [NSSet setWithObjects:
#"representedObject.printSettings.foo",
nil];
}
there is an easy workaround for the preview update: adding an indirection level by redeclaring the properties directly on the NSViewController itself. But for the print preset change I have no clue.
In the end, here is the comment in NSPrintInfo.h:
- (NSMutableDictionary *)printSettings;
The print info's print settings. You can put values in this dictionary to store them in any preset that the user creates while editing this print info with a print panel. Such values must be property list objects. This class is key-value coding (KVC) and key-value observing (KVO) compliant for "printSettings" so you can often bind controls in print panel accessory views directly to entries in this dictionary. You can also use this dictionary to get values that have been set by other parts of the printing system, like a printer driver's print dialog extension (the same sort of values that are returned by the Carbon Printing Manager's PMPrintSettingsGetValue() function). Other parts of the printing system often use key strings like "com.apple.print.PrintSettings.PMColorSyncProfileID" but dots like those in key strings wouldn't work well with KVC, so those dots are replaced with underscores in keys that appear in this dictionary, as in "com_apple_print_PrintSettings_PMColorSyncProfileID". You should use the same convention when adding entries to this dictionary.
Any help appreciated
Thanks
Well I found a way. There is an undocumented notification that is being sent when selecting a print preset or changing paper format, all that you have to do is add an observer:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(printInfoDidChange:) name:#"NSPrintInfoDidChange" object:nil];
That is not as straightforward as binding to printSettings keypaths, and I really don't like using an undocumented notification (almost as bad as using private API in term of maintainability) but that's the only way I could figure out to do the job.

How to get the frame (origin, Size) of every visible windows on the active space?

I'm trying to figure out how to get the frame of all visible windows.
I tried the following code, but it only works for the app itself other windows report {0,0,0,0}
NSArray *windowArray = [NSWindow windowNumbersWithOptions:NSWindowNumberListAllApplications | NSWindowNumberListAllSpaces];
for(NSNumber *number in windowArray){
NSLog(#"Window number: %#", number);
NSWindow *window = [[NSApplication sharedApplication] windowWithWindowNumber:[number intValue]];
NSLog(#"Window: %#", NSStringFromRect( [[window contentView] frame]));
}
Sample code is appreciated.
I figured it out:
NSMutableArray *windows = (__bridge NSMutableArray *)CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, kCGNullWindowID);
for (NSDictionary *window in windows) {
NSString *name = [window objectForKey:#"kCGWindowName" ];
CGRect bounds;
CGRectMakeWithDictionaryRepresentation((CFDictionaryRef)[window objectForKey:#"kCGWindowBounds"], &bounds);
NSLog(#"%#: %#",name,NSStringFromRect(bounds));
}
You can't create an NSWindow for a window of another application. In general, you can't access the objects of other applications except through an interface that they cooperate with, like scripting.
You can get what you're looking for using the Quartz Window Services (a.k.a. CGWindowList) API.
I'm not at all sure that the window numbers returned by Cocoa are the same as the window numbers used by that API. In fact, the docs for -[NSWindow windowNumber] specifically say "note that this isn’t the same as the global window number assigned by the window server". I'm note sure to what use you can put the window numbers returned by +[NSWindow windowNumbersWithOptions:] which are not for your application's windows.

Keeping window on top when switching spaces

I have created a window using -[NSWindow setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces].
It only does half of what I want, though: when I switch spaces, the window also switches spaces (as expected), but my window moves to the back behind all other windows in that space. This is especially bad because my app is active but its window is below all other apps' windows. I tried changing the level to NSFloatingWindowLevel, and that does keep it on top, but then it loses key status (focus) when switching spaces.
I tried NSWindowCollectionBehaviorMoveToActiveSpace for the collection behaviour but it's definitely not what I'm looking for.
Is there hope? I know there are almost no other APIs that relate to Spaces.
Spaces is a pain. My solution was to register for a change notification like so:
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self
selector:#selector(activeSpaceDidChange:)
name:NSWorkspaceActiveSpaceDidChangeNotification
object:nil];
Then in my WindowController class:
- (void) activeSpaceDidChange:(NSNotification *)aNotification {
if ([NSApp isActive]) [[self window] orderFront:self];
}
For borderless windows (created with NSBorderlessWindowMask), I banged my head until I came up with the following modification to Francis':
- (void) activeSpaceDidChange:(NSNotification *)aNotification {
if ([NSApp isActive])
{
NSRect windowRect = [[self window] frame];
[[self window] setStyleMask:NSTitledWindowMask];
[[self window] setStyleMask:NSBorderlessWindowMask];
[[self window] setFrame:windowRect display:YES];
[[NSApplication sharedApplication] activateIgnoringOtherApps : YES];
}
}
I saw others stating that borderless windows have issues which led me to the idea to trick it momentarily into not seeing it as a borderless window. First I had setting the style mask to "Titled" followed by "activateIgnoringOtherApps" and then setting the "borderless" style back, which seemed to be a more logical solution. Yet, just to see what minimal solution was required for it to function, I ended up seeing the above works. Be great if somebody could fill in what is exactly happening that allows this to work.
Swift 5 solution of #francis-mcgrew's answer ⤵︎
// Quick Solution: Place this in the AppDelegate in applicationDidFinishLaunching
NSWorkspace.shared.notificationCenter.addObserver(
self,
selector: #selector(notifySpaceChanged),
name: NSWorkspace.activeSpaceDidChangeNotification,
object: nil
)
// and this in the same class
#objc func notifySpaceChanged() {
window.orderFrontRegardless()
}

Placing an NSTimer in a separate thread

Note: It's probably worth scrolling down to read my edit.
I'm trying to setup an NSTimer in a separate thread so that it continues to fire when users interact with the UI of my application. This seems to work, but Leaks reports a number of issues - and I believe I've narrowed it down to my timer code.
Currently what's happening is that updateTimer tries to access an NSArrayController (timersController) which is bound to an NSTableView in my applications interface. From there, I grab the first selected row and alter its timeSpent column. Note: the contents of timersController is a collection of managed objects generated via Core Data.
From reading around, I believe what I should be trying to do is execute the updateTimer function on the main thread, rather than in my timers secondary thread.
I'm posting here in the hopes that someone with more experience can tell me if that's the only thing I'm doing wrong. Having read Apple's documentation on Threading, I've found it an overwhelmingly large subject area.
NSThread *timerThread = [[[NSThread alloc] initWithTarget:self selector:#selector(startTimerThread) object:nil] autorelease];
[timerThread start];
-(void)startTimerThread
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
activeTimer = [[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(updateTimer:) userInfo:nil repeats:YES] retain];
[runLoop run];
[pool release];
}
-(void)updateTimer:(NSTimer *)timer
{
NSArray *selectedTimers = [timersController selectedObjects];
id selectedTimer = [selectedTimers objectAtIndex:0];
NSNumber *currentTimeSpent = [selectedTimer timeSpent];
[selectedTimer setValue:[NSNumber numberWithInt:[currentTimeSpent intValue]+1] forKey:#"timeSpent"];
}
-(void)stopTimer
{
[activeTimer invalidate];
[activeTimer release];
}
UPDATE
I'm still totally lost with regards to this leak. I know I'm obviously doing something wrong, but I've stripped my application down to its bare bones and still can't seem to find it. For simplicities sake, I've uploaded my applications controller code to: a small pastebin. Note that I've now removed the timer thread code and instead opted to run the timer in a separate runloop (as suggested here).
If I set the Leaks Call Tree to hide both Missing Symbols and System Libraries, I'm shown the following output:
EDIT: Links to screenshots broken and therefor removed.
If the only reason you are spawning a new thread is to allow your timer to run while the user is interacting with the UI you can just add it in different runloop modes:
NSTimer *uiTimer = [NSTimer timerWithTimeInterval:(1.0 / 5.0) target:self selector:#selector(uiTimerFired:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:uiTimer forMode:NSRunLoopCommonModes];
As an addendum to this answer it is now possible to schedule timers using Grand Central Dispatch and blocks:
// Update the UI 5 times per second on the main queue
// Keep a strong reference to _timer in ARC
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(_timer, DISPATCH_TIME_NOW, (1.0 / 5.0) * NSEC_PER_SEC, 0.25 * NSEC_PER_SEC);
dispatch_source_set_event_handler(_timer, ^{
// Perform a periodic action
});
// Start the timer
dispatch_resume(_timer);
Later when the timer is no longer needed:
dispatch_source_cancel(_timer);

Resources