UITableViewCell 的字幕不会更新

Posted

技术标签:

【中文标题】UITableViewCell 的字幕不会更新【英文标题】:Subtitles of UITableViewCell won't update 【发布时间】:2014-09-11 16:54:46 【问题描述】:

今天我开始为 ios8 准备我的应用程序。 我发现我的 UITableCells 的字幕不会在 viewWillAppear 内更新。 我把它归结为一个最小的例子:

我有一个带有 2 个单元格的静态单元格 TableViewController(样式 = 副标题) 一个字幕是空的,另一个是设置的。

我这样更新字幕:

- (void) viewWillAppear:(BOOL)animated

  [super viewWillAppear:animated];
  [[self.without detailTextLabel] setText:@"foobar"];
  [[self.with detailTextLabel] setText:@"barfoo"];

虽然在 iOS7(以及 6 和 5)下一切正常,但 iOS8 不会更新第一个单元格的标题。

但是,当我触摸单元格时,它会更新并显示文本。

这是模拟器问题吗?一个错误?我做错了吗?

【问题讨论】:

您是否尝试过将该代码放在 viewdidload 方法中? 虽然我需要它在viewWillAppear 内发生,但我试了一下。 viewDidLoad 显示完全相同(iOS8 无标题,viewDidAppear,iOS7和iOS6也没有字幕(这次是在真iPod上试的)。 你有没有像这样初始化你的无对象? without = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier]; 没有。它是界面生成器的一个出口。我猜它是在加载故事板时自动初始化的。今天我将一个 iPod 更新到 iOS8 以在真机上进行测试。我使用了商店中已有的应用程序(使用 iOS7 SDK 构建)。同样,iOS7 及以下版本一切正常。奇怪的是,字幕会在 iOS8 上显示,但会被截断(比如“foo...”而不是 foobar)。所以我认为这可能与更新字幕视图大小有关。 我在iOS8 prerelease release notes 中发现了一个提示,指出UILabel 的clipsToBounds 的默认值为YES。这与正常的 UIView 默认值 NO 不同。 但更改该标志不会改变任何内容。 【参考方案1】:

作为临时解决方法,我简单地在文本标签中引入了一个空格,并以编程方式将标签文本设置为 nil,我将其设置为空格

cell.detailTextLabel.text = @" ";

相应地调整你的逻辑,将一个单独的空格处理为 nil。

对我来说,即使在加载视图控制器之前我已经为它准备了一个字符串,详细文本标签也没有显示出来。每当视图控制器出现时,我的许多单元格之一都会在详细文本标签中默认显示当前日期,但这也不起作用。

我认为这与 iOS 8 最初设置为 nil 时无法更新文本标签的文本有关。

因此,请注意在界面构建器中的原型单元格中也引入一个空格。令人惊讶的是,我的自定义单元格标签中的文本工作正常。

【讨论】:

是的,这行得通。如果单元格中总是有非空文本,那么 Apple 的代码永远不会从视图层次结构中删除标签,并且布局总是按预期工作。这对于自定义单元格来说不是问题,因为在这种情况下,Apple 不会管理子视图;它们始终位于视图层次结构中(或者是您自己的代码删除它们)。对于使用 detailTextLabel 的单元格样式而言,这纯粹是一个问题,因为在设置非空文本后将其重新添加到视图层次结构时,时间是错误的。 实际上没有,看起来像递归调用的method_exchangeImplementations实际上是在调用原始实现。该方法的主体安装为真正的layoutSubviews,原实现切换为_detailfix_layoutSubviews方法。 @AceNeerav detailTextLabel 肯定会从视图层次结构中删除。在它不起作用的情况下,请尝试查看 cell.detailTextLabel.superview。它在 layoutSubviews 期间被重新添加到视图层次结构中,但最初它的大小保持为零(其大小为零文本),因为自动布局没有调整它的大小。在 next 布局过程中(比如选择一个单元格或旋转),然后它会正确调整大小并显示出来。我的雷达 (18344249) 有一个演示应用程序。我没有看到 textLabel 的问题,但总是有可能的。 @AceNeerav 感谢您的解决方法。虽然我不必在代码和 IB 中都设置空字符串。仅从 IB 设置它对我有用。 哦,终于……detailText 的行为真的很愚蠢 :)【参考方案2】:

Apple 似乎添加了一项优化,当值为 nil 时从视图层次结构中删除 detailTextLabel,并根据需要重新添加。不幸的是,在我的测试中,它是在布局传递期间添加的,但只有在 之后,它才会被调整大小以适应该布局传递的新文本,因此大小仍然为零。下一个布局通道(比如旋转)然后会显示 OK。

一种解决方法可能是在所有情况下强制将标签添加回视图层次结构(如果大小为零,似乎应该没问题)。我很想知道将以下类别放入您的应用程序是否可以解决问题。

#import <objc/runtime.h>
/*
 * In iOS8 GM (and beta5, unsure about earlier), detail-type UITableViewCells (the
 * ones which have a detailTextLabel) will remove that label from the view hierarchy if the
 * text value is nil.  It will get re-added during the cell's layoutSubviews method, but...
 * this happens just too late for the label itself to get laid out, and its size remains
 * zero.  When a subsequent layout call happens (e.g. a rotate, or scrolling the cells offscreen
 * and back) then things get fixed back up.  However the initial display has completely blank
 * values.  To fix this, we forcibly re-add it as a subview in both awakeFromNib and prepareForReuse.
 * Both places are necessary; one if the xib/storyboard has a nil value to begin with, and
 * the other if code explicitly sets it to nil during one layout cycle then sets it back).
 * Bug filed with Apple; Radar 18344249 .
 *
 * This worked fine in iOS7.
 */
@implementation UITableViewCell (IOS8DetailCellFix)

+ (void)load

    if ([UIDevice currentDevice].systemVersion.intValue >= 8)
    
        /* Swizzle the prepareForReuse method */
        Method original = class_getInstanceMethod(self, @selector(prepareForReuse));
        Method replace  = class_getInstanceMethod(self, @selector(_detailfix_prepareForReuse));
        method_exchangeImplementations(original, replace);

        /*
         * Insert an awakeFromNib implementation which calls super.  If that fails, then
         * UITableViewCell already has an implementation, and we need to swizzle it instead.
         * In IOS8 GM UITableViewCell does not implement the method, but they could add one
         * in later releases so be defensive.
         */
        Method fixawake = class_getInstanceMethod(self, @selector(_detailfix_super_awakeFromNib));
        if (!class_addMethod(self, @selector(awakeFromNib), method_getImplementation(fixawake), method_getTypeEncoding(fixawake)))
        
            original = class_getInstanceMethod(self, @selector(_detailfix_awakeFromNib));
            replace = class_getInstanceMethod(self, @selector(awakeFromNib));
            method_exchangeImplementations(original, replace);
        
    


- (void)__detailfix_addDetailAsSubviewIfNeeded

    /*
     * UITableViewCell seems to return nil if the cell style does not have a detail.
     * If it returns non-nil, force add it as a contentView subview so that it gets
     * view layout processing at the right times.
     */
    UILabel *detailLabel = self.detailTextLabel;
    if (detailLabel != nil && detailLabel.superview == nil)
    
        [self.contentView addSubview:detailLabel];
    


- (void)_detailfix_super_awakeFromNib

    [super awakeFromNib];
    [self __detailfix_addDetailAsSubviewIfNeeded];


- (void)_detailfix_awakeFromNib

    [self _detailfix_awakeFromNib];
    [self __detailfix_addDetailAsSubviewIfNeeded];


- (void)_detailfix_prepareForReuse

    [self _detailfix_prepareForReuse];
    [self __detailfix_addDetailAsSubviewIfNeeded];


@end

可能还有其他方法 - 如果您可以在正确的时间调用 setNeedsLayout,它可能会强制执行额外的布局传递来纠正问题,但我无法找到合适的时间。

编辑:下面的评论表明重新显示的单元格可能是一个问题。因此,一个更简单的解决方法可能是在调用 Apple 的实现之前调整 layoutSubviews 并进行检查。这可以解决所有问题,因为问题发生在布局调用期间。所以,下面是那个版本的修复——我很想看看它是否有效。

#import <objc/runtime.h>

@implementation UITableViewCell (IOS8DetailCellFix)

+ (void)load

    if ([UIDevice currentDevice].systemVersion.intValue >= 8)
    
        Method original = class_getInstanceMethod(self, @selector(layoutSubviews));
        Method replace  = class_getInstanceMethod(self, @selector(_detailfix_layoutSubviews));
        method_exchangeImplementations(original, replace);
    


- (void)_detailfix_layoutSubviews

    /*
     * UITableViewCell seems to return nil if the cell type does not have a detail.
     * If it returns non-nil, force add it as a contentView subview so that it gets
     * view layout processing at the right times.
     */
    UILabel *detailLabel = self.detailTextLabel;
    if (detailLabel != nil && detailLabel.superview == nil)
    
        [self.contentView addSubview:detailLabel];
    

    [self _detailfix_layoutSubviews];


@end

编辑:看来这个错误在 iOS9 中已修复。所以,条件可以改为:

if ([UIDevice currentDevice].systemVersion.intValue == 8)

如果应用只需要支持 iOS9 及以上版本,则可以去掉 swizzle fix 类别。

【讨论】:

太好了,我遇到了这个问题,这为我解决了这个问题。打算欺骗雷达。 谢谢。虽然这完美地解决了示例问题,但它只修复了原始问题的一半。我提出另一个视图控制器让用户选择值。当子视图被关闭时,iOS 在父视图上调用 viewWillAppear。此时,表格单元已经创建,并且不会调用 swizzle 代码 - 它仍然拒绝更新字幕。我会坚持从viewDidAppear 拨打setNeedsLayout 有趣。如果您在 viewWillAppear 上调用了 reloadData,那么可能会这样做。但是,也许最好在 UITableViewCell 上调整 layoutSubviews (并在调用原始视图之前进行处理)。我可能会看看这是否有效;可能是一个更简单的补丁。 第二个版本对我来说似乎很好用。我最初在重用单元格时注意到了这个问题,这可以解决我的测试用例。 谢谢。知道 Apple 是否正在采取措施解决这个问题吗?【参考方案3】:

几天过去了,我想我必须使用一种解决方法:

- (void) viewDidAppear:(BOOL)animated

  [super viewDidAppear:animated];
  [self.without setNeedsLayout];

这有明显的延迟,但这是我能找到的使文本在 iOS8 上显示和可读的唯一方法。

【讨论】:

对我不起作用,但将[self.tableView reloadData] 添加到viewDidAppear: 得到相同的结果;瞬间可见的延迟,但单元格最终看起来正确。 @Thompson 这也为我做了。 @lupz 在我的情况下,setNeedsLayout 不起作用,但 [self.without layoutSubviews] 起作用。 这被选为正确答案只是因为它似乎可以解决问题,但我向您保证它不会。它只是隐藏了部分时间的问题。这是由 iOS8 中的一个优化错误引起的,该错误从视图层次结构中删除了 detailTextLabel。请参阅下面@AceNeerav 的回答。 实际上,请查看@Carl Lindberg 的解决方案。在 detailTextLabel 中使用 @" " 会导致表格单元格的布局就像有一个详细标签一样,即使实际上不应该有。卡尔的解决方案是透明的。【参考方案4】:

已确认。如果初始化为 nil,iOS8 不会绘制 detailText。这条简单的线应该可以解决问题。

self.detailTextLabel.attributedText = [[NSAttributedString alloc] initWithString:@" "];

【讨论】:

愚蠢的错误......让我坚持了一个小时,直到我发现这个。设置属性文本属性对我有用。谢谢! 这对我有用,我将 detailTextLabel.text 设置为 @"",感谢您的帮助。【参考方案5】:

我有同样的问题,在cellForRowAtIndexPath 我在返回单元格之前调用cell.layoutSubviews()。它解决了问题。

【讨论】:

【参考方案6】:

我在 iOS8 上看到了导致此问题的两个原因。

    如果您将详细标签中的文本设置为 nil 或“”(最常见的做法是在 prepareForReuse 和/或从情节提要中删除默认文本) [如上述答案所示,iOS9 已解决此问题]

    如果您子类化一个 subtitle 类型的 UITableViewCell,然后以编程方式注册您在情节提要中创建的单元格。 [这是一个编程错误,您不需要注册在故事板中创建的原型单元]

【讨论】:

第 2 点救了我的命...我忘记了这一点,为 nil 问题玩了几个小时,谢谢。

以上是关于UITableViewCell 的字幕不会更新的主要内容,如果未能解决你的问题,请参考以下文章

UITableViewCell 中的字幕对齐

UITableViewCell 样式字幕多行不起作用

UILabel 字体大小“System 0.0”,如 UITableViewCell 字幕

如何正确地将代码中的字幕 UITableViewCell 出列?

iOS Swift - 带有左侧细节和右侧细节和字幕的 UITableViewCell?

从 cellForRowAt 更新时,UITableViewCell 中的活动约束不会更新