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.
Related
I have an NSPopupButton whose content is bound to an NSArray, let’s say the array is
#[
#"Option 1",
#"Option 2"
];
Its selected object is bound to User Defaults Controller, and is written to a preference file by the user defaults system.
In my code I check whether the preference is set to #"Option 1" or not, and perform actions accordingly.
This all worked well (though I did feel a little uneasy checking for what is essentially a UI value, but whatever...) until I needed to localize.
Because the value is the label, I’m having an issue.
If my user is in France, his preferences file will say #"L’option 1", which is not equal to #"Option 1". I need to abstract the presentation from the meaning and it's proving pretty difficult.
I split up the binding into two arrays, let's call them values and labels.
Let’s say they look like this:
values = #[
#"option_1",
#"option_2"
];
labels = #[
NSLocalizedString(#"Option 1", nil),
NSLocalizedString(#"Option 2", nil)
];
I’ve bound the NSPopUpButton’s Content binding to values and its Content Values binding to labels. However, the popup list is showing option_1 and option_2, it does not seem to want to use the labels array to label the items in the popup button.
How do I get the NSPopUpButton to use values internally and store that in the preferences file, but display labels to the user?
It doesn’t have to be architected this way, if you can think of a better solution. The point is I want to store and check one value, and have that value associated with a label that gets localized appropriately.
Cocoa bindings work very well with value transformers, because you can apply them directly in the bindings window, for example
#implementation LocalizeTransformer
+ (Class)transformedValueClass
{
return [NSArray class];
}
+ (BOOL)allowsReverseTransformation
{
return NO;
}
- (id)transformedValue:(id)value {
if (![value isKindOfClass:[NSArray class]]) return nil;
NSMutableArray *output = [NSMutableArray arrayWithCapacity:[value count]];
for (NSString *string in value) {
[output addObject:NSLocalizedString(string, nil)];
}
return [output copy];
}
#end
you have to register the transformer in awakeFromNib or better in +initialize
NSValueTransformer *localizeTransformer = [[LocalizeTransformer alloc] init];
[NSValueTransformer setValueTransformer:localizeTransformer
forName:#"LocalizeTransformer"];
then it appears in the popup menu of value transformers
Bind Selected Tag to your User Defaults Controller instead of Selected Object.
If the NSPopupButton choices are fixed add the NSMenuItems in Interface Builder and set their Tags. Otherwise bind an array of NSMenuItem, again with proper Tags.
Selected Index would also work but only until you change the order.
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.
in my project I use a NSmutablearray that I fill with UIImageView in .m (viewDidLoad)
arrayView = [[NSMutableArray alloc] initWithObjects: image1, image2, image3, nil];
but in method
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
when I write
int i = 1;
[[arrayView objectAtIndex:i] setImage:image];
there is an exception that say that my array is empty...why?
Please post the rest of viewDidLoad. I want to see how image1 is initialized. The line:
arrayView = [[NSMutableArray alloc] initWithObjects: image1, image2, image3, nil];
will return an empty array if image1 is nil. You should also protect your app from crashing by not making assumptions about the data source. You shouldn't crash if the array is empty, just display an empty tableView.
[EDIT]
I just read the last comment you made in the other answer. Sounds like your imageViews are created in IB. Make sure they are connected to the image1 etc outlets in IB.
Try this instead:
if (i < [arrayView count]) {
UIImageView *imageView = [arrayView objectAtIndex:i];
imageView.image = image;
}
Separating accessing the array element (by assigning it to an actual UIImageView object) and then assigning the new image may be helpful. I've seen cases where if you stack up too many operations things get confused, especially if you are dealing with objects of different types that may or may not have the selectors you're using.
Why your array is turning up empty is another issue. Initializing it in viewDidLoad seems right. You may need to add some "protection" (as above) in your table methods to avoid accessing an empty array. Then in method like viewWillAppear:, call reloadData.
Is there any way to prevent the NSTokenField to select everything when pressing the ENTER key or when making to the first responder maybe using the TAB key?
An NSTokenField is a subclass of NSTextField. There's no easy, direct way to directly manipulate the selection of these classes (aside from -selectText:, which selects all).
To do this when it becomes the first responder, you'll need to subclass NSTokenField (remember to set the class of the field in your XIB to that of your custom subclass) and override -becomeFirstResponder like so:
- (BOOL)becomeFirstResponder
{
if ([super becomeFirstResponder])
{
// If super became first responder, we can get the
// field editor and manipulate its selection directly
NSText * fieldEditor = [[self window] fieldEditor:YES forObject:self];
[fieldEditor setSelectedRange:NSMakeRange([[fieldEditor string] length], 0)];
return YES;
}
return NO;
}
This code first looks to see if super answers "yes" (and becomes the first responder). If it does, we know it will have a field editor (an NSText instance), whose selection we can directly manipulate. So we get its field editor and set its selected range (I put the insertion point at the end with a { lastchar, nolength } range).
To do this when the field is done editing (return, tabbing out, etc.), override -textDidEndEditing: like this:
- (void)textDidEndEditing:(NSNotification *)aNotification
{
[super textDidEndEditing:aNotification];
NSText * fieldEditor = [[self window] fieldEditor:YES forObject:self];
[fieldEditor setSelectedRange:NSMakeRange([[fieldEditor string] length], 0)];
}
In this case, when the user ends editing, this method lets super do its thing, then it looks to see if it's still the first responder. If it is, it does the same as above: puts the insertion carat at the end of the field.
Note, this behavior is not standard and is unexpected. Use sparingly.
I'm working on a Core Data document application that dynamically creates NSTableColumns. The data cell type may be a checkbox, slider, etc. Programmatically binding to all cell types works, except for NSTextFieldCell.
All NSTextFieldCells fail to bind, and after editing they return to their default value. This happens no matter if they're binding to a string, a number (with an NSNumberFormatter applied), or a date (NSDateFormatter applied). I'm using the following format to do all bindings:
NSDictionary *textFieldOpts = [NSDictionary dictionaryWithObjectsAndKeys:#"YES", NSContinuouslyUpdatesValueBindingOption, #"YES", NSValidatesImmediatelyBindingOption, nil];
[aCell bind:#"value" toObject:[[entryAC arrangedObjects] objectAtIndex:0] withKeyPath:#"numberData" options:textFieldOpts];
Again, these statements work if the cell type is anything but an NSTextFieldCell.
I threw in an -observeValueForKeyPath method to log when the value changes... and for other cell types (NSSliderCell for instance) I can see the value changing, but with the NSTextFieldCell, it never, ever updates.
Turns out that I needed to implement the NSTableView data source method setObjectValue to manually get the change from the NSTableView (View) and then manually set the data in the array controller (Model) similar to the code below:
- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex {
[[[entryAC arrangedObjects] objectAtIndex:rowIndex] setValue:(NSNumber *)anObject forKey:#"numberData"];
}
For some reason, the bindings to NSTextFieldCells, when set programmatically on a cell-by-cell basis, only work one way: to display the data only. Personally I would classify this as a bug...