3

A label in a XIB bound to an NSMutableString property does not seem to update when I change the string, but the label does update if the property is NSString.

In a test app, I have the default AppDelegate class and MainMenu.xib. I create two properties in AppDelegate, one NSString and one NSMutableString, and bind them to two labels in the XIB. I have two buttons to change the values of these strings from one set to another and back. The code is given below. The output of NSLog shows that the value of NSMutableString is changing, but is not being reflected in the GUI.

Not sure what I am missing.. any help will be appreciated!

PS: EDIT: I want to achieve this without creating a new mutable string

CODE:

@synthesize mutLabel, unmutLabel;

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    [self willChangeValueForKey:@"mutLabel"];
    mutLabel = [NSMutableString stringWithCapacity:10];
    [mutLabel setString:@"MutLabel 1"];
    [self didChangeValueForKey:@"mutLabel"];

    [self willChangeValueForKey:@"unmutLabel"];
     unmutLabel = @"UnMutLabel 1";
    [self didChangeValueForKey:@"unmutLabel"];

    [self addObserver:self forKeyPath:@"mutLabel" options:0 context:nil];
    [self addObserver:self forKeyPath:@"unmutLabel" options:0 context:nil];
}

- (IBAction)clkBtn1:(id)sender {

    [self willChangeValueForKey:@"mutLabel"];
    [mutLabel setString:@"MutLabel 1"];
    [self didChangeValueForKey:@"mutLabel"];

    [self willChangeValueForKey:@"unmutLabel"];
    unmutLabel = @"UnMutLabel 1";
    [self didChangeValueForKey:@"unmutLabel"];
}

- (IBAction)clkBtn2:(id)sender {

    [self willChangeValueForKey:@"mutLabel"];
    [mutLabel setString:@"MutLabel 2"];
    [self didChangeValueForKey:@"mutLabel"];

    [self willChangeValueForKey:@"unmutLabel"];
    unmutLabel = @"UnMutLabel 2";
    [self didChangeValueForKey:@"unmutLabel"];
}

-(void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    NSLog(@"Key change: Key: %@  Value: %@\n",keyPath, [self performSelector:NSSelectorFromString(keyPath)]  );
}
matlabGuy
  • 31
  • 2
  • Try biding to mutLabel.string, rather than mutLabel? KVC looks for setter/getter invocations, so setString/string are basically the accessors for the string "property" on the mutable string object. – stevesliva Jul 06 '14 at 05:15
  • @stevesliva : tried that, and I get a crash.. "Class is not key-value coding complaint" – matlabGuy Jul 06 '14 at 05:19
  • Huh. That's annoying. (Because no one class supports both the setter and getter?) Duplicate of [this question](http://stackoverflow.com/questions/3645018/how-do-i-bind-an-nsmutablestring-to-the-value-of-an-nstextview), BTW. Though I'm not sure I like the answers. You can get that error message when a binding is looking for a setter -- perhaps try mutLabel.string binding to a button's title or something's toolTip. A read-only binding might work. And if one-way binding works, there may be a way to get the value binding to never look for the setter. – stevesliva Jul 06 '14 at 16:20

2 Answers2

0

Remove above lines of code and just try below lines of code for setting the string in mutable and immutable object which is bound to the label.

    NSString * str=@"yourString";
    self.mutLabel=[str mutableString];
    self.unmutLabel=str;
Hussain Shabbir
  • 14,801
  • 5
  • 40
  • 56
  • 1
    Hussain, thanks! Your solution works, but isn't it creating a new mutable string every time? For example, if I declare a third property "pointToMutLabel", and define it as "pointToMutLabel = mutLabel", then the two strings do not track after a change. I wanted to achieve this without creating a new string – matlabGuy Jul 06 '14 at 05:57
  • If you have third property then just declared as self.pointToMutLabel=[str mutableString]; like for previous mutable string. So that it will make sure that one string variable is being used in all the mutable string. – Hussain Shabbir Jul 06 '14 at 06:02
  • THere's what I want to do: In my real app, AppDelegate starts by crunching some time variant data to generate a bunch of strings, and thus cannot be instantiated again. From time to time, based on socket interrupts, these strings are updated. I have a couple of XIB's that display these strings, and each XIB has its own NSWindowController that is instantiated in the AppDelegate. How do I bind the labels in these XIB's to the string properties in AppDelegate without instantiating AppDelegate as an object in the XIB?I thought the solution is the create NSMutableString properties and "equate them" – matlabGuy Jul 06 '14 at 06:17
  • Ok then update your NSString always with new values str=[self.mutLabel string]; and pass this string to the new one self.pointMutLabel=[str mutableString]; – Hussain Shabbir Jul 06 '14 at 07:39
0

According to the documentation, calling willChangeValueForKey: and didChangeValueForKey: where appropriate should be all that's needed in terms of KVO compliance in order to support bindings. However, as far as I can determine through experimentation*, the text content of AppKit text views (NSTextField and NSTextView), won't update if the property they're bound to refers to the same object before and after the update. The experiments suggested the observation happened, but was ignored; presumably, the views contain logic to avoid unnecessary updates.

In short, the bindings of NSTextView and NSTextField are incompatible with NSMutableString mutation. Unless you have another reason to use a mutable string, you might as well stick with the immutable versions.

* AppKit doesn't appear to be included among Apple's open source releases, so examining the sources wasn't an option.

outis
  • 75,655
  • 22
  • 151
  • 221