0
votes

I'm having a dilemma that I can't seem to solve. I've applied some KVO through a binding to a property on a model, however, because I'm not assigning through dot notation the KVO doesn't get fired. Instead, I'm appending like so:

[[self messagesString] appendAttributedString:attrVal];

messagesString is an NSMutableAttributedString. Of course this won't kick off the KVO notification so I instead do the following:

[self willChangeValueForKey:@"messagesString"];
[[self messagesString] appendAttributedString:attrVal];
[self didChangeValueForKey:@"messagesString"];

But I'm having no luck with this. If I do the following:

NSAttributedString *attrVal = [[NSAttributedString alloc] initWithString:str];
[self willChangeValueForKey:@"messagesString"];
[[self messagesString] appendAttributedString:attrVal];
[self didChangeValueForKey:@"messagesString"];
messagesString = [[NSMutableAttributedString alloc] initWithAttributedString:messagesString];

Then it works fine. However, if I remove the appending line, it doesn't work. It seems it has to be in this order, including these things, for it to work.

What am I missing that's so obvious for it to kick off the KVO notification?

EDIT

So, I've torn out any non-relevant stuff from this class declaration, but here's the main one I was referring to:

#import <Foundation/Foundation.h>

@interface Channel : NSObject {
    NSString* name;
    NSMutableAttributedString *messagesString;
}

@property (retain) NSString* name;
@property (retain) NSMutableAttributedString* messagesString;

- (id)initWithName:(NSString*)name;
- (void)appendString:(NSString*)str;

@end

And the implementation

#import "Channel.h"

@implementation Channel

@synthesize name;
@synthesize messagesString;

- (id)initWithName:(NSString *)channelName {
    self = [super init];

    if (self)
    {
        [self setName:channelName];
        messagesString = [[NSMutableAttributedString alloc] initWithString:@" "];
    }

    return self;
}


- (void)appendString:(NSString *)str {
     NSAttributedString *attrVal = [[NSAttributedString alloc] initWithString:str];
    [self willChangeValueForKey:@"messagesString"];
    [[self messagesString] appendAttributedString:attrVal];
    [self didChangeValueForKey:@"messagesString"];
    messagesString = [[NSMutableAttributedString alloc] initWithAttributedString:messagesString];
}

@end

I do initWithString for the NSMutableAttributedString because there's some oddities in the way you use an instance of this class if it doesn't already have an empty string (appendAttributedString has issues if no value was set upon instantiation supposedly).

This is how a string is appended to it in a completely separate class:

Channel *c = [channels valueForKey:@"server"];
[c appendString:val];

Finally, my UI has a binding for an NSTextView on the Attributed String property to go to self.currentChannel.messagesString. I'm not on my Mac at the moment so I can't show these bits.

The appendString method in my Channel class looks as it is because I was toying around with getting it to work. Very much play code.

1
What's the property declaration for messageString?Brian Palma
Can you post your observation setup call and the method watching for changes?Jesse Rusak
@BrianPalma my updated post shows this.Kezzer
@JesseRusak it's done through the UI tools, not explicitly. There's a binding on the Attributed String property of the NSTextVew.Kezzer

1 Answers

2
votes

I created a project and played around with it. If you decide to use manual KVO (using the automatically... class method declaration), then you will NOT get kvo when you use "setValue" on that ivar/property (so my original answer was not correct).

I created a test project, then used a mutable string and verified that yes, I did get KVO when I used the will/did messages wrapped around an appendString: to the mutable string. This verified that the code you show should in fact work. I then commented out that code and directly set the mutable string with another string and didn't get any KVO at all (as expected).

At this point I can only surmise that there is something very unusual about your property, your class, or your observeValueForKeypath: method watching for these changes.

EDIT: The problem is NSTextView. It maintains it own storage system, so actually when you want append data to it you will do it directly, and the binding to YOUR attributedString should update (not the other way around). The reason it worked when you made that final change was you told the textView the string had changed, but when it looked it hadn't changed initially, but finally when you did set a completely new string it registered the change and updated.

Look at this thread for both the reasoning and the solution. Davidson is one of the long time Apple engineers responsible for the text system (he answered the question).