文本更改时,Autolayout动态高度表视图单元格呈现错误

时间:2015-06-06 14:53:29

标签: ios objective-c uitableview autolayout

我有一个使用UITableViewCellAutolayout的自定义iOS8 Dynamic Height,其中包含两个不同大小的标签。上面的标签可以是 1到2行的任何地方,底部标签总是只有1行。

:cellForRowAtIndexPath:中,我将上部标签的文本设置为“Loading ..”并调用方法从服务器(或缓存)中检索实际文本(可以是任意长度)。第二个标签设置为它的最终文本。

...
cell.title.text = @"Loading...";
cell.subtitle.text = source.name;

__weak NewsTableViewCell *weakCell = cell;
[[NewsManager sharedManager] getNewsForSource:source withBlock:^(NewsObject *object, NSError *error) {
    NewsTableViewCell *strongCell = weakCell;

    strongCell.title.text = object.articleHeadline;
}];
...

当方法返回并将文本从“正在加载...”更改为实际文本时,会发生以下三种情况之一:

  1. 一切都正确呈现。
  2. 删除第二个标签,并将第一个标签固定到一行。
  3. 第一个标签被切成半宽(这通常发生在将手机侧向翻转并再次回到肖像之后)。
  4. 我在[self.tableView reloadData]中调用viewWillAppear:,因此如果错误地呈现某些内容,只需打开另一个视图并返回此处就可以解决问题。将手机转到另一侧,然后再回到原件。

    这是一张图片,解释了会发生什么(注意:两个单元格应该像第二个单元格一样):

    enter image description here

    以下是我使用UITableViewCellStoryBoard中设置AutoLayout的方式:

    enter image description here enter image description here

    我没有更改任何属性(例如拥抱和压缩优先级)。

    有没有人能指引我正确的方向/让我知道我做错了什么?

    编辑1:这是一张照片,显示了不同的UILabel Frames对于(1)正确的布局(2)错误的布局(3)布局时的布局单元格正在等待它的最终文本(导致错误的布局发生)

    enter image description here

    编辑2:从块中调用[strongCell setNeedsLayout][strongCell layoutIfNeeded]并不能解决我的问题,但会导致此AutoLayout错误出现在控制台中。你能帮我调试吗?

    Unable to simultaneously satisfy constraints.
    Probably at least one of the constraints in the following list is one you 
    don't want. Try this: (1) look at each constraint and try to figure out which 
    you don't expect; (2) find the code that added the unwanted constraint or 
    constraints and fix it. (Note: If you're seeing 
    NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the 
    documentation for the UIView property 
    translatesAutoresizingMaskIntoConstraints) 
    
    (
    "<NSLayoutConstraint:0x1700927a0 UILabel:0x127d39c20'Horrifying moment lioness...'.top == UITableViewCellContentView:0x127d2e510.topMargin>",
    "<NSLayoutConstraint:0x170092930 V:[UILabel:0x127d39c20'Horrifying moment lioness...']-(8)-[UILabel:0x127d39dd0'Daily Mail Online']>",
    "<NSLayoutConstraint:0x170092a70 UILabel:0x127d39dd0'Daily Mail Online'.bottom == UITableViewCellContentView:0x127d2e510.bottomMargin>",
    "<NSLayoutConstraint:0x170092c00 'UIView-Encapsulated-Layout-Height' V:[UITableViewCellContentView:0x127d2e510(0)]>"
    )
    
    Will attempt to recover by breaking constraint 
    <NSLayoutConstraint:0x170092930 V:[UILabel:0x127d39c20'Horrifying moment lioness...']-(8)-[UILabel:0x127d39dd0'Daily Mail Online']>
    
    Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
    The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.
    

2 个答案:

答案 0 :(得分:1)

为了再次布置单元格,您需要使用reload方法reload单元格。

你可以通过这种方式实现......

首先,创建一个可变字典属性来存储单元格的文本......

@property (nonatomic, strong) NSMutableDictionary *textDictionary;

并在某处初始化。

然后在行的单元格中......

更新了以下评论

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    ...

    cell.subtitle.text = source.name;

    // get the text from the dictionary
    NSString *titleText = self.textDictionary[source];

    if (titleText) {
        // second time through the text exists so just use it.
        cell.title.text = titleText;
    } else {
        // first time through the text is nil so set it to default and download it.
        cell.title.text = @"Loading...";

        // this just stops the download starting again before it has completed
        self.textDictionary[source] = @"Loading...";
    }

    // recheck the text and reload if it has changed
    [[NewsManager sharedManager] getNewsForSource:source withBlock:^(NewsObject *object, NSError *error) {
        NSString *oldText = self.textDictionary[source];
        NSString *newText = object.articleHeadline;

        // reload if the text has changed
        if (![oldText isEqualToString:newText]) {
            // store the text in the dictionary
            self.textDictionary[source] = newText;

            // reload the indexPath (will only trigger if the cell is visible)
            [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
        }
    }];

    ....
}

第一次通过此操作会触发字符串的异步提取。完成后,它将其存储在本地,然后重新加载该行。

第二次通过它将获得本地存储的字符串并使用它而无需再次下载。

答案 1 :(得分:0)

  

如果您使用多行UILabel,请不要忘记需要正确设置preferredMaxLayoutWidth。

尝试设置上标签的preferredMaxLayoutWidth

cell.title.preferredMaxLayoutWidth  = cell.title.bounds.size.width;

修改

__weak NewsTableViewCell *weakCell = cell;
[[NewsManager sharedManager] getNewsForSource:source withBlock:^(NewsObject *object, NSError *error) {
    NewsTableViewCell *strongCell = weakCell;

    strongCell.title.text = object.articleHeadline;

    [strongCell setNeedsLayout];
    [strongCell layoutIfNeeded];
}];

再次编辑:

我强烈建议您在viewDidLoad中检索数据。表视图重用单元格。当一个单元格在屏幕外滚动时,它会被添加到一个队列中,并将被重新用于下一个要在屏幕上滚动的单元格。

如果您在cellForRow中请求数据,那么您必须像这样检查单元格:

NSInteger row = indexPath.row;
[[NewsManager sharedManager] getNewsForSource:source withBlock:^(NewsObject *object, NSError *error) {

    // Update the datasource, e.g. caching the news in getNewsForSource:withBlock:

    UITableViewCell *dangerousCell = [tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:row inSection:0]];
    if (dangerousCell != nil) {
        NSString *articleHeadline = object.articleHeadline;
        if ([dangerousCell.title.text isEqualToString:articleHeadline]) {
            return;
        }
        textLabel.text = articleHeadline;
        [tableView reloadData];
    }
}];