добавление KVO в UITableViewCell - PullRequest
       31

добавление KVO в UITableViewCell

12 голосов
/ 05 октября 2011

У меня есть пользовательский UITableViewCell, который отображает различные атрибуты объекта Person (при поддержке Core Data) ... некоторые метки, изображения и т. Д. В настоящее время я заставляю всю таблицу перезагружать всякий раз, когда изменяется любое свойство, и это, очевидно, неэффективно , Я знаю, что с помощью KVO я должен иметь возможность добавлять прослушиватель в метку в ячейке, которая может прослушивать изменения свойств Person. Но я не уверен, как это реализовать, и не могу найти никаких примеров.

Вот что я обычно делаю в cellForRowAtIndexPath моего UITableView:

    - (UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath
    {
        static NSString *simple = @"CustomCellId";

        CustomCell *cell = (CustomCell *) [tableView dequeueReusableCellWithIdentifier:simple];

        if (cell == nil)
        {
            NSArray *nib =  [[NSBundle mainBundle] loadNibNamed:@"CustomCell" owner:self options:nil];

            for (id findCell in nib )
            {
                if ( [findCell isKindOfClass: [CustomCell class]])
                {
                    cell = findCell;
                }    
            }
         }
         Person *managedObject = [self.someArray objectAtIndex: indexPath.row];
         cell.namelabel.text =  managedObject.displayName;
         return cell;
}

Ячейка подключена в IB. Я хотел бы определить, когда изменяется displayName, и обновить только метку имени. Спасибо

Ответы [ 5 ]

18 голосов
/ 21 августа 2013

Приведенный выше ответ отлично подходит для статических ячеек. Использование KVO для UITableViewCell s по-прежнему работает с повторным использованием ячеек. Добавьте наблюдателей, которые вам нужны, когда ячейка должна появиться, и удалите их, когда ячейка больше не отображается. Единственная хитрость заключается в том, что Apple, похоже, не согласна с отправкой didEndDisplayingCell:, поэтому наблюдателей нужно удалить в двух местах на iOS 6.1

@implementation MyTableViewCell

@property MyTableViewController * __weak parentTVC;

- (UITableViewCell *)tableView:(UITableView *)tableView 
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    ((MyTableViewCell *)cell).parentTVC = self;
    // Don't add observers, or the app may crash later when cells are recycled
}


- (void)tableView:(UITableView *)tableView 
  willDisplayCell:(HKTimelineCell *)cell 
forRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Add observers
}

- (void)tableView:(UITableView *)tableView 
didEndDisplayingCell:(UITableViewCell *)cell 
forRowAtIndexPath:(NSIndexPath *)indexPath
{
    [self removeMyKVOObservers];
}

- (void)viewWillDisappear:(BOOL)animated
{
    for (MyTableViewCell *cell in self.visibleCells) {
        // note! didEndDisplayingCell: isn't sent when the entire controller is going away! 
        [self removeMyKVOObservers];
    }
}

Может произойти следующее, если наблюдатели не убраны. Наблюдатель может попытаться уведомить любой объект, находящийся в этом месте памяти, который может даже не существовать.

<NSKeyValueObservationInfo 0x1d6e4860> ( <NSKeyValueObservance 0x1d4ea9f0: Observer: 0x1d6c9540, Key path: someKeyPath, Options: <New: YES, Old: NO, Prior: NO> Context: 0x0, Property: 0x1c5c7e60> <NSKeyValueObservance 0x1d1bff10: Observer: 0x1d6c9540, Key path: someOtherKeyPath, Options: <New: YES, Old: NO, Prior: NO> Context: 0x0, Property: 0x1c588290>)

5 голосов
/ 05 октября 2011

В качестве фона вы, вероятно, захотите прочитать Руководства по кодированию значений ключа и значений ключей, если вы еще этого не сделали. Затем просмотрите методы категории NSKeyValueObserving.

http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Protocols/NSKeyValueObserving_Protocol/Reference/Reference.html

В двух словах, вам необходимо тщательно управлять добавлением и удалением объекта наблюдения в список наблюдаемых объектов (извините за кажущуюся избыточность этого утверждения). Вы не хотите, чтобы объект уходил, а наблюдатели все еще зарегистрированы, или вы получаете жалобы и возможные другие вопросы.

Тем не менее, вы используете -addObserver:keyPath:options:context, чтобы добавить объект в качестве наблюдателя. Контекст должен быть статически объявленной строкой. Аргумент options определяет, какие данные вы возвращаете в своем методе наблюдения (см. Ниже). KeyPath - это путь имен свойств от наблюдаемого объекта к наблюдаемому свойству (он может проходить через несколько объектов и будет обновляться при изменении промежуточных объектов, а не только при изменении свойства листа).

В вашем случае вы могли наблюдать метку и использовать text keyPath или ячейку, а также путь к ключу nameLabel.text. Если класс табличного представления был разработан по-другому, вы могли бы наблюдать весь массив ячеек, но такого свойства в UITableView нет. Проблема с наблюдением за ячейкой состоит в том, что табличное представление может удалить ее в любое время (если ваш дизайн использует несколько ячеек, которые служат одной и той же цели в списке переменной длины). Если вы знаете, что ваши клетки статичны, вы, вероятно, можете наблюдать их без беспокойства.

Как только вы зарегистрируете наблюдателя, этот наблюдатель должен -observeValueForKeyPath:ofObject:change:context:, подтвердите, что контекст совпадает (просто сравните значение указателя с адресом вашей статической строки; в противном случае вызовите реализацию super), затем посмотрите в словаре изменений нужные данные (или просто запросите объект напрямую) и используйте его для обновления модели по своему усмотрению.

Существует множество примеров KVO в примерах кода, в том числе на сайте разработчиков Apple, и в качестве части примеров привязок на сайте Malcolm Crawford (mmalc), но большинство из них для Mac OS X, а не iOS.

3 голосов
/ 05 октября 2011

Это работает:

В configureCell:

[managedObject addObserver: cell forKeyPath: @"displayName" options:NSKeyValueObservingOptionNew context: @"Context"];

В CustomCell:

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
    Person *label = (Person *) object;
    self.namelabel.text = [label valueForKey:@"displayName"];
}
1 голос
/ 19 августа 2014

В моем случае я добавил наблюдателя в пользовательскую метку ячейки для ключа «текст» с параметрами (NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld).

Наблюдая значение для keyPath , я проверяю, чтобы убедиться, что keyPath - тот, который я хочу, просто в качестве дополнительной меры, а затем я вызываю свой метод для любой операции, которую я хочу выполнить с этим этикетка

например, в моем случае

-(id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];

    if (self) {
        // Helpers
        CGSize cellSize = self.contentView.frame.size;

        CGRect sizerFrame = CGRectZero;
        sizerFrame.origin.x = kDefaultUITableViewCellContentLeftInset;
        sizerFrame.origin.y = kDefaultUITableViewCellContentTopInset;

        // The Profile Image
        CGRect imageFrame = CGRectMake(sizerFrame.origin.x, sizerFrame.origin.y, kDefaultProfilePictureSizeBWidth, kDefaultProfilePictureSizeBHeight);
        self.userProfilePictureUIImageView = [[UIImageView alloc] initWithFrame:imageFrame];
        [self.userProfilePictureUIImageView setImage:[UIImage imageNamed:@"placeholderImage"]];
        [ApplicationUtilities formatViewLayer:self.userProfilePictureUIImageView withBorderRadius:4.0];

        // adjust the image content mode based on the lenght of it's sides
        CGSize avatarSize = self.userProfilePictureUIImageView.image.size;

        if (avatarSize.width < avatarSize.height) {
            [self.userProfilePictureUIImageView setContentMode:UIViewContentModeScaleAspectFill];
        } else {
            [self.userProfilePictureUIImageView setContentMode:UIViewContentModeScaleAspectFit];
        }

        CGFloat readStateSize = 10.0;
        CGRect readStateFrame = CGRectMake((imageFrame.origin.x + imageFrame.size.width) - readStateSize, CGRectGetMaxY(imageFrame) + 4, readStateSize, readStateSize);

        // Read State
        self.readStateUIImageView = [[UIImageView alloc] initWithFrame:readStateFrame];
        self.readStateUIImageView.backgroundColor = RGBA2UIColor(0.0, 157.0, 255.0, 1.0);
        [ApplicationUtilities formatViewLayer:self.readStateUIImageView withBorderRadius:readStateSize/2];


        sizerFrame.origin.x = CGRectGetMaxX(imageFrame) + kDefaultViewContentHorizontalSpacing;
        // read just the width of the senders label based on the width of the message label
        CGRect messageLabelFrame = sizerFrame;
        messageLabelFrame.size.width = cellSize.width - (CGRectGetMinX(messageLabelFrame) + kDefaultViewContentHorizontalSpacing);
        messageLabelFrame.size.height = kDefaultInitialUILabelHeight;

        // Store the original frame for resizing
        initialLabelFrame = messageLabelFrame;

        self.messageLabel = [[UILabel alloc]initWithFrame:messageLabelFrame];
        [self.messageLabel setBackgroundColor:[UIColor clearColor]];
        [self.messageLabel setFont:[UIFont systemFontOfSize:14.0]];
        [self.messageLabel setTextColor:[UIColor blackColor]];
        [self.messageLabel setNumberOfLines:2];
        [self.messageLabel setText:@""];

        // Modify Sizer Frame for Message Date Label
        sizerFrame = initialLabelFrame;
        // Modify the y offset
        sizerFrame.origin.y = CGRectGetMaxY(sizerFrame) + kDefaultViewContentVerticalSpacing;

        // Message Date
        self.messageDateLabel = [[UILabel alloc] initWithFrame:CGRectZero];
        [self.messageDateLabel setBackgroundColor:[UIColor clearColor]];
        [self.messageDateLabel setFont:[UIFont systemFontOfSize:12.0]];
        [self.messageDateLabel setTextColor:RGBA2UIColor(200.0, 200.0, 200.0, 1.0)];
        [self.messageDateLabel setHighlightedTextColor:[UIColor whiteColor]];
        [self.messageDateLabel setTextAlignment:NSTextAlignmentRight];
        [self.messageDateLabel setNumberOfLines:1];
        [self.messageDateLabel setText:@"Message Date"];
        [self.messageDateLabel sizeToFit];

        [self.contentView addSubview:self.userProfilePictureUIImageView];
        [self.contentView addSubview:self.readStateUIImageView];
        [self.contentView addSubview:self.messageDateLabel];
        [self.contentView addSubview:self.messageLabel];

        // Add KVO for all text labels
        [self.messageDateLabel addObserver:self forKeyPath:@"text" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:NULL];
        [self.messageLabel addObserver:self forKeyPath:@"text" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:NULL];

    }
    return self;
}

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if ([keyPath isEqual:@"text"]) {

        [self resizeCellObjects];
    }
}

-(void)resizeCellObjects
{
    // Resize and reposition the message label
    CGRect messageLabelFrame = initialLabelFrame;

    self.messageLabel.frame = messageLabelFrame;
    [self.messageLabel setNumberOfLines:2];
    [self.messageLabel sizeToFit];

    // Resize the messageDate label
    CGRect messageDateFrame = initialLabelFrame;
    messageDateFrame.origin.y = CGRectGetMaxY(self.messageLabel.frame) + kDefaultViewContentVerticalSpacing;
    self.messageDateLabel.frame = messageDateFrame;

    [self.messageDateLabel sizeToFit];

}
0 голосов
/ 20 мая 2016

Я предпочитаю решение, в котором UITableViewCell выполняет все КВО самостоятельно. Моя установка выглядит следующим образом:

В моем подклассе ячеек у меня есть свойство, которое содержит строгую ссылку на мой класс модели, из которого я извлекаю свои данные, и метод, который я вызываю, когда хочу присоединить новый объект к свойству:

@interface MyTableViewCell : UITableViewCell

@property (atomic) id object;
- (void)populateFromObject:(id)object;

Реализация:

- (void)awakeFromNib {
[super awakeFromNib];
self.contentView.hidden = YES;// avoid displaying an unpopulated cell
}

- (void)populateFromObject:(id)object {
    dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0),^{// handle KVO on a bg thread
        if (object && (self.object != object)) {// if new object differs from property...
            [self unregisterFromKVO];// ...unregister from old object and...
            self.object = object;
            for (NSString *keyToObserve in [[object class] displayKeys]) {// ...register to new object
                [object addObserver:self forKeyPath:keyToObserve options:0 context:nil];
            }
        }
        dispatch_async(dispatch_get_main_queue(), ^{// UI updates on main thread only
            // update your outlets here
            self.contentView.hidden     = NO;// finally display the cell now that it is properly populated
        });
    });
}



// ===========
#pragma mark - KVO
// ===========

// KVO notification
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
    [self populateFromObject:object];
}

- (void)unregisterFromKVO {
        for (NSString *keyToObserve in [[self.object class] displayKeys]) {
            [self.object removeObserver:self forKeyPath:keyToObserve];
        }
}

- (void)dealloc {
    [self unregisterFromKVO];
}

Обратите внимание, что фактическое KVO обрабатывается в фоновом потоке, чтобы избежать блокировки основного потока во время прокрутки. Также обратите внимание, что -populateFromObject: возвращается немедленно и поэтому будет отображать незаполненную ячейку. Чтобы избежать этого, мы скрываем представление контента, пока ячейка не будет заполнена полностью. Теперь единственное, что осталось реализовать, - это метод класса в YourModelObject, который возвращает массив ключей, которые вы хотите использовать для KVO:

+ (NSArray<NSString *> *)displayKeys {
    return @[@"name",@"Street", @"ZipCode"];
}

.. и в UITableViewController:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyTableViewCell *cell       = [tableView dequeueReusableCellWithIdentifier:@"reuseid" forIndexPath:indexPath];
YourModelObject *obj    = [myModelArray objectAtIndex:indexPath.row];
[cell populateFromObject:obj];

return cell;
}

Сильная ссылка от ячейки на объект модели гарантирует, что объект не будет освобожден, пока ячейка все еще наблюдает одно из своих свойств, то есть является видимым. Как только ячейка освобождается, KVO не регистрируется, и только тогда объект модели будет освобожден. Для удобства у меня также есть слабая ссылка от объекта модели обратно на ячейку, которая может пригодиться при реализации UITableView методов делегата.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...