【问题标题】:adding KVO to UITableViewCell将 KVO 添加到 UITableViewCell
【发布时间】:2011-12-01 03:54:15
【问题描述】:

我有一个自定义 UITableViewCell,它显示了 Person 对象的各种属性(由 Core Data 支持)......一些标签、图像等。我目前强制整个 tableview 在任何属性更改时重新加载,这显然效率不高.我知道使用 KVO,我应该能够向单元格中的标签添加一个侦听器,该标签可以侦听 Person 属性的更改。但我不确定如何实现它,也找不到任何示例。

这是我通常在 UITableView 的 cellForRowAtIndexPath 中执行的操作:

    - (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 何时更改,并仅更新名称标签。 谢谢

【问题讨论】:

    标签: objective-c core-data uitableview key-value-observing


    【解决方案1】:

    上述答案非常适合静态单元格。将 KVO 用于 UITableViewCells 仍然适用于单元重用。当单元格即将出现时添加你需要的观察者,当单元格不再显示时移除它们。唯一的技巧是 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>)

    【讨论】:

    • 这仅适用于 iOS 6.0+。有人有 iOS 5.1+ 的解决方案吗?
    • 非常彻底,完美!
    • 我认为KVO应该由单元格本身处理,而不是tableview。
    【解决方案2】:

    对于背景知识,如果您还没有阅读 Key-Value Observing 和 Key-Value Coding Guides,您可能需要阅读。然后查看 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。

    【讨论】:

    • 想我明白了,感谢您的帮助。在下面发布了代码,但会为您提供答案。
    • 观察任何未明确记录为符合 KVO 的关键路径是错误的。您无法观察标签的text
    • 如果我没记错的话,应该观察的不是cell,而是Person 对象!因为对象可能会改变,单元格应该做出响应!
    【解决方案3】:

    这行得通:

    在configureCell中:

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

    在自定义单元中:

    - (void)observeValueForKeyPath:(NSString *)keyPath
                          ofObject:(id)object
                            change:(NSDictionary *)change
                           context:(void *)context
    {
        Person *label = (Person *) object;
        self.namelabel.text = [label valueForKey:@"displayName"];
    }
    

    【讨论】:

    • 什么时候移除观察者,细胞再利用呢?
    • 在我的情况下,我不想删除观察者(因为我将观察者添加到托管对象,我猜它应该是每当它被删除或出错时?)。对于单元重用,您可能需要在添加观察者之前检查 if ([managedObject observationInfo] != nil)。实际上,我在发布此代码后重新编写了代码,因此根本没有使用此解决方案;所以我还没有弄清楚所有这些细节。
    【解决方案4】:

    在我的例子中,我在自定义单元格标签 forKeyPath "text" 中添加了一个观察者,带有选项 (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];
    
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-03-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-01-29
      • 1970-01-01
      相关资源
      最近更新 更多