Почему мои привязки Cocoa сломаны?

У меня есть окно с NSTextField (в Snow Leopard), которое я привязал к функции NSString в своем классе WindowController. Эта строка будет объединять информацию о выборе и количестве моего табличного представления, предоставленную моим контроллером массива. Он получает начальное значение "0 0", но никогда не обновляется при изменении выбора или количества. Привязка выглядит следующим образом (владелец файла — MyWindowController):

альтернативный текст

Я реализовал + (NSSet *)keyPathsForValuesAffecting<key> (ниже), но привязка никогда не обновляется, даже когда изменяется общее количество и выбор контроллера массива.

(Выполнено дополнительное устранение неполадок) Первоначально я использовал привязку Display Pattern Value для NSTextField, но мне требовалась более сложная логика, чем та привязка, которую предоставляла эта привязка. Затем я начал прослушивать события изменения/изменения выбора в TableView, которые отображают содержимое контроллера массива и динамически изменяют привязки значения шаблона отображения, но это было похоже на хак и слишком сложно.

Я уверен, что есть что-то, что мне не хватает, но я не могу сказать, что. У кого-нибудь есть идеи? Я прочитал документацию Apple по наблюдению за ключом и значением, и это, кажется, все, что нужно. Я проверил, и мой keyPathsForValuesAffectingMyString вызывается, но myString вызывается только один раз. Я привел свой код ниже (обновлен x3).

Обновление от 21 января

Я все еще затыкаюсь, пытаясь понять это. Когда я addObserver to self для путей ключей arrayController, уведомления срабатывают, как и ожидалось, поэтому мои пути ключей и механизм наблюдения за значениями ключей в порядке. Когда я вызываю [self didChangeValueForKey:@"myString"]; в своем методе observeValueForKeyPath для тех же ключей, привязка по-прежнему не обновляется, что заставляет меня думать, что это проблема привязок, а не проблема KVO. Я собираюсь больше читать о механизме привязок...

@interface MyWindowController : NSWindowController {
    IBOutlet NSArrayController *arrayController;
}

- (NSArrayController *)arrayController;
- (NSString *)myString;

@end

@implementation MyWindowController

+ (NSSet *)keyPathsForValuesAffectingMyString {
    return [NSSet setWithObjects:
            @"arrayController.arrangedObjects",
            @"arrayController.selection",
            nil];
}

- (NSArrayController *)arrayController {
    return arrayController;
}

- (NSString *)myString {
    // Just as an example; I have more complicated logic going on in my real code
    return [NSString stringWithFormat:@"%@, %@",
            [arrayController valueForKeyPath:@"arrangedObjects.@count"], 
            [arrayController valueForKeyPath:@"selection.@count"]];
}

@end

person Dov    schedule 15.01.2011    source источник
comment
Вы ориентируетесь на Mac OS 10.5?   -  person    schedule 17.01.2011
comment
10.6, Snow Leopard, и меня не беспокоит обратная совместимость.   -  person Dov    schedule 17.01.2011
comment
Установите точку останова на -arrayController и запустите приложение. Первые несколько раз это вызывается, каково значение arrayController?   -  person Mike Abdullah    schedule 17.01.2011
comment
Я поместил туда оператор журнала, и он не равен нулю, когда он вызывается (дважды при запуске), он выводит стандартное строковое представление, как и следовало ожидать.   -  person Dov    schedule 17.01.2011


Ответы (5)


Я проверил точно такую ​​же ошибку. Кто-то из Cocoabuilder предположил, почему возникает ошибка:

http://www.cocoabuilder.com/archive/cocoa/284396-why-doesn-nsarraycontroller-selection-et-al-fire-keypathsforvaluesaffectingkey.html#284400

Я не могу сказать, верно ли это объяснение, но я точно не могу заставить +keyPathsForValues… работать с NSArrayControllers.

person Wil Shipley    schedule 25.03.2011

У меня есть обходной путь, но я не доволен им, так как в этом нет необходимости, и я все же предпочел бы, чтобы привязки работали правильно. Я не приму этот ответ и удалю его, если кто-то опубликует фактическое исправление. </disclaimer>

@interface MyWindowController : NSWindowController {
    IBOutlet NSArrayController *arrayController;
    IBOutlet NSTextField *fieldThatShouldBeBinded;
}

- (NSString *)myString;

@end

@implementation MyWindowController

- (void)awakeFromNib {
    [arrayController addObserver:self
                      forKeyPath:@"selection"
                         options:0
                         context:NULL];
    [arrayController addObserver:self
                      forKeyPath:@"arrangedObjects"
                         options:0
                         context:NULL];
}

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context {
    if( object == arrayController )
        [fieldThatShouldBeBinded setStringValue:[self myString]];
}

- (NSString *)myString {
    return [NSString stringWithFormat:@"%@, %@",
            [arrayController valueForKeyPath:@"arrangedObjects.@count"], 
            [arrayController valueForKeyPath:@"selection.@count"]];
}

@end
person Dov    schedule 22.01.2011

Убедитесь, что выход arrayController подключен в Interface Builder. Я предполагаю, что это ноль.

person Tom Dalling    schedule 15.01.2011
comment
Это определенно связано, и я использую его повсюду. - person Dov; 15.01.2011

Не используйте ключевое слово @count. Привязки и KVO на контроллерах массива будут обновляться при изменении содержимого. Если это не поможет, значит проблема где-то в другом.

Другой вариант — использовать привязки шаблона отображения вместо составного свойства. Привяжите значение шаблона отображения1 к arrayController.arrangedObjects.@count и значение шаблона отображения2 к arrayController.selection.@count и установите шаблон на "%{value1}@, %{value2}@"

person ughoavgfhw    schedule 16.01.2011
comment
Я попробовал это без ключевого слова @count (и обновил свой код выше) без каких-либо изменений в результате. Первоначально я использовал привязки Display Pattern Value, но хочу отойти от него, чтобы иметь более точный контроль и более сложные выражения. - person Dov; 17.01.2011

Я столкнулся с той же проблемой и нашел другой способ (но это все еще обходной путь). Вы должны объявить свойство динамического обходного пути. В разделе реализации просто верните для него новый пустой объект. Теперь вы можете использовать KVO для этого обходного пути.

@property(nonatomic,retain) NSArray *workaround;
@dynamic workaround;
- (NSArray *)workaround { return [NSArray array]; } // new *every* time
- (void)setWorkaround:(NSArray *)unused { }

+ (NSSet *)keyPathsForValuesAffectingMyString { return [NSSet setWithObject:@"workaround"]; }

Чтобы получить эту работу, вам все равно нужно вручную связать self.workaround с arrayController.selectedObjects (или что-то еще):

- (void)awakeFromNib // or similar place
{
    [super awakeFromNib];
    [self bind:@"workaround" toObject:arrayController withKeyPath:@"selectedObjects" options:nil];
}

Привязка вручную работает, как и ожидалось, обходной путь обновляется тем, к чему вы его привязали. Но KVO проверяет, действительно ли изменилось значение свойства (и прекращает распространение, если оно остается прежним). Если вы каждый раз возвращаете новое значение self.workaround, оно работает.

Предупреждение: никогда не вызывайте -[setWorkaround:] самостоятельно — это эффективно очистит другую сторону привязки (в данном случае arrayController.selectedObjects).

У этого метода есть некоторые преимущества: вы избегаете централизованного наблюденияValueForKeyPath:... и ваша логика находится в правильном месте. И он хорошо масштабируется, просто добавьте workaround2, 3 и так далее для подобных случаев.

person Community    schedule 05.03.2013
comment
И если вы все еще используете метод addObserver:, не забудьте вызвать соответствующий метод removeObserver: в Dealloc. - person ; 05.03.2013