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 无标题,作为临时解决方法,我简单地在文本标签中引入了一个空格,并以编程方式将标签文本设置为 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 的字幕不会更新的主要内容,如果未能解决你的问题,请参考以下文章
UILabel 字体大小“System 0.0”,如 UITableViewCell 字幕
如何正确地将代码中的字幕 UITableViewCell 出列?