异步加载图像的表格视图单元格的动态高度

Posted

技术标签:

【中文标题】异步加载图像的表格视图单元格的动态高度【英文标题】:Dynamic height for table view cells with images asynchronous loaded 【发布时间】:2015-09-28 10:08:42 【问题描述】:

我在 XIB 文件中有一个具有动态高度的自定义单元格,该单元格显示带有消息和图像的帖子。因为图像将从我的 API 异步加载,所以图像的纵横比将与资源一起发送。在updateConstraints 方法中,将设置具有适当乘数的纵横比约束。

XIB 文件:

细胞类:

//
//  PostTableViewCell.swift
//  Lome
//
//  Created by Tobias Feistmantl on 10/09/15.
//  Copyright (c) 2015 Tobias Feistmantl. All rights reserved.
//

import UIKit
import Alamofire

class PostTableViewCell: UITableViewCell 

    @IBOutlet weak var userProfileImageView: TFImageView!
    @IBOutlet weak var usersNameLabel: UILabel!
    @IBOutlet weak var usernameLabel: UILabel!
    @IBOutlet weak var userProfileButton: TFCellButton!
    @IBOutlet weak var distanceLabel: UILabel!
    @IBOutlet weak var messageLabel: UILabel!
    @IBOutlet weak var postImageView: UIImageView!
    @IBOutlet weak var likeCountLabel: UILabel!
    @IBOutlet weak var timestampLabel: UILabel!
    @IBOutlet weak var likeButton: UIButton!

    @IBOutlet weak var constraintBetweenMessageLabelAndPostImageView: NSLayoutConstraint!

    var post: Post! 
        didSet 
            post.author.profileImage(version: .Thumbnail)  image, _ in
                self.userProfileImageView.image = image
            

            if let name = post.author.fullName 
                usersNameLabel.text = name
                usernameLabel.text = post.author.username
             else 
                usersNameLabel.text = post.author.username
                usernameLabel.hidden = true
            

            if let attributedMessage = post.attributedMessage 
                messageLabel.attributedText = attributedMessage
                messageLabel.hidden = false
                constraintBetweenMessageLabelAndPostImageView.constant = 15
             else 
                messageLabel.hidden = true
                messageLabel.attributedText = nil
                constraintBetweenMessageLabelAndPostImageView.constant = 0
            

            post.image  image, _ in
                if let image = image 
                    self.postImageView.image = image
                    self.setNeedsUpdateConstraints()
                
            

            timestampLabel.text = "Posted \(post.createdAt.timeAgoSinceNow())"
            distanceLabel.text = post.distanceText
            likeCountLabel.text = "\(post.likesCount) Likes"
            likeButton.setImage(post.likeButtonImage, forState: .Normal)
        
    

    override func updateConstraints() 
        if let aspectRatio = post.imageAspectRatio 
            postImageAspectConstraint = NSLayoutConstraint(item: postImageView, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: postImageView, attribute: NSLayoutAttribute.Height, multiplier: CGFloat(aspectRatio), constant: 0)
        

        super.updateConstraints()
    

    func setupUserProfileButton(indexPath: NSIndexPath, viewController: UIViewController) 
        userProfileButton.indexPath = indexPath
        userProfileButton.addTarget(viewController, action: "userProfileButtonDidTouch:", forControlEvents: .TouchUpInside)
    

    var postImageAspectConstraint: NSLayoutConstraint? 
        didSet 
            if let oldValue = oldValue 
                postImageView.removeConstraint(oldValue)
            
            if let postImageAspectConstraint = postImageAspectConstraint 
                postImageView.addConstraint(postImageAspectConstraint)
            
        
    

    override func prepareForReuse() 
        super.prepareForReuse()

        postImageAspectConstraint = nil
    


cellForRowAtIndexPath 方法:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell 
    let cell = tableView.dequeueReusableCellWithIdentifier("postCell", forIndexPath: indexPath) as! PostTableViewCell

    cell.post = posts[indexPath.row]
    cell.setupUserProfileButton(indexPath, viewController: self)

    return cell

在应用程序启动时,一切都按预期运行,但如果我稍微滑动一下,约束就会开始打破,图像的外观不再正确,或者出现其他一些奇怪的事情,如下所示:

错误:

2015-09-28 11:58:27.546 Lome[32812:528936] 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:0x7f8a008bf6f0 V:[Lome.TFImageView:0x7f8a008bf470(35)]>",
    "<NSLayoutConstraint:0x7f89f9fbc490 V:|-(15)-[Lome.TFCellButton:0x7f8a0087fde0]   (Names: '|':UITableViewCellContentView:0x7f8a008bf270 )>",
    "<NSLayoutConstraint:0x7f8a00831520 Lome.TFCellButton:0x7f8a0087fde0.top == Lome.TFImageView:0x7f8a008bf470.top>",
    "<NSLayoutConstraint:0x7f8a008315c0 Lome.TFCellButton:0x7f8a0087fde0.bottom == Lome.TFImageView:0x7f8a008bf470.bottom>",
    "<NSLayoutConstraint:0x7f8a00831750 V:[Lome.TFCellButton:0x7f8a0087fde0]-(18)-[UILabel:0x7f8a008806d0'Water']>",
    "<NSLayoutConstraint:0x7f8a00831840 H:|-(0)-[UIImageView:0x7f8a00830910]   (Names: '|':UITableViewCellContentView:0x7f8a008bf270 )>",
    "<NSLayoutConstraint:0x7f8a00831890 H:[UIImageView:0x7f8a00830910]-(0)-|   (Names: '|':UITableViewCellContentView:0x7f8a008bf270 )>",
    "<NSLayoutConstraint:0x7f8a008318e0 V:[UILabel:0x7f8a008806d0'Water']-(15)-[UIImageView:0x7f8a00830910]>",
    "<NSLayoutConstraint:0x7f8a00831930 V:[UIImageView:0x7f8a00830910]-(20)-[UILabel:0x7f8a00830b90'0 Likes']>",
    "<NSLayoutConstraint:0x7f8a00831980 V:[UILabel:0x7f8a00830b90'0 Likes']-(20)-|   (Names: '|':UITableViewCellContentView:0x7f8a008bf270 )>",
    "<NSLayoutConstraint:0x7f8a00860870 UIImageView:0x7f8a00830910.width == 0.6672*UIImageView:0x7f8a00830910.height>",
    "<NSLayoutConstraint:0x7f8a0084e990 'UIView-Encapsulated-Layout-Height' V:[UITableViewCellContentView:0x7f8a008bf270(611)]>",
    "<NSLayoutConstraint:0x7f8a0084f930 'UIView-Encapsulated-Layout-Width' H:[UITableViewCellContentView:0x7f8a008bf270(375)]>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x7f8a008bf6f0 V:[Lome.TFImageView:0x7f8a008bf470(35)]>

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.
2015-09-28 11:58:27.547 Lome[32812:528936] 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:0x7f89f9fbc490 V:|-(15)-[Lome.TFCellButton:0x7f8a0087fde0]   (Names: '|':UITableViewCellContentView:0x7f8a008bf270 )>",
    "<NSLayoutConstraint:0x7f8a00831520 Lome.TFCellButton:0x7f8a0087fde0.top == Lome.TFImageView:0x7f8a008bf470.top>",
    "<NSLayoutConstraint:0x7f8a008315c0 Lome.TFCellButton:0x7f8a0087fde0.bottom == Lome.TFImageView:0x7f8a008bf470.bottom>",
    "<NSLayoutConstraint:0x7f8a00831750 V:[Lome.TFCellButton:0x7f8a0087fde0]-(18)-[UILabel:0x7f8a008806d0'Water']>",
    "<NSLayoutConstraint:0x7f8a00831840 H:|-(0)-[UIImageView:0x7f8a00830910]   (Names: '|':UITableViewCellContentView:0x7f8a008bf270 )>",
    "<NSLayoutConstraint:0x7f8a00831890 H:[UIImageView:0x7f8a00830910]-(0)-|   (Names: '|':UITableViewCellContentView:0x7f8a008bf270 )>",
    "<NSLayoutConstraint:0x7f8a008318e0 V:[UILabel:0x7f8a008806d0'Water']-(15)-[UIImageView:0x7f8a00830910]>",
    "<NSLayoutConstraint:0x7f8a00831930 V:[UIImageView:0x7f8a00830910]-(20)-[UILabel:0x7f8a00830b90'0 Likes']>",
    "<NSLayoutConstraint:0x7f8a00831980 V:[UILabel:0x7f8a00830b90'0 Likes']-(20)-|   (Names: '|':UITableViewCellContentView:0x7f8a008bf270 )>",
    "<NSLayoutConstraint:0x7f8a00860870 UIImageView:0x7f8a00830910.width == 0.6672*UIImageView:0x7f8a00830910.height>",
    "<NSLayoutConstraint:0x7f8a0084e990 'UIView-Encapsulated-Layout-Height' V:[UITableViewCellContentView:0x7f8a008bf270(611)]>",
    "<NSLayoutConstraint:0x7f8a0084f930 'UIView-Encapsulated-Layout-Width' H:[UITableViewCellContentView:0x7f8a008bf270(375)]>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x7f8a00860870 UIImageView:0x7f8a00830910.width == 0.6672*UIImageView:0x7f8a00830910.height>

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.

我认为这与单元格的重用有关,但正如您所见,约束将在 prepareForReuse 方法中删除。

我目前非常沮丧,因为几天以来我一直在尝试解决这个问题,但没有找到解决方案。

希望有人能解决。

提前致谢。

【问题讨论】:

您可以尝试设置图像视图的高度而不是updateConstraints: 中的宽度。乍一看,这似乎是一个模棱两可的情况,因为您已经将 ImageView 固定为具有 0 间距的水平布局,然后尝试根据高度更改宽度。 这是一个纵横比约束而不是宽度约束。 UIImageView 的高度是宽度的 0.6672 倍。但是,如果我在 IB 中设置固定高度并且不添加此纵横比约束,它可以工作,但我始终具有相同的高度和错误的纵横比,因为每个图像都是不同的。 我知道您已应用纵横比限制。我想你误解了我的意思。在您的纵横比约束中,first itemwidth。这意味着约束将尝试在 width 中进行更改,而不是对该视图的 height 进行更改。因此,如果您从中反转第一项和第二项并反转乘数参数中的纵横比,您将具有相同的约束,但根据图像纵横比具有不同的高度。 谢谢!!它解决了我的问题!将此评论写为答案,我会将其标记为正确答案。 感谢您提供覆盖 updateLayoutConstraints 的提示!这救了我的命!!!不错的代码顺便说一句:-) 【参考方案1】:

此问题是由应用于 ImageView 的 2 个冲突约束引起的。如果您查看调试器输出

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x7f8a00860870 UIImageView:0x7f8a00830910.width == 0.6672*UIImageView:0x7f8a00830910.height>

您已经对以 0 间距固定到 superviewImageView 具有水平约束。

现在在更新纵横比约束时,您尝试将ImageViewwidth(保持宽度作为第一项)设置为其height 的比率,这将导致与它的前导/尾随冲突约束。 您需要通过交换第一项和第二项并反转纵横比因子来设置此 ImageView 的高度。

postImageAspectConstraint = NSLayoutConstraint(item: postImageView, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: postImageView, attribute: NSLayoutAttribute.Width, multiplier: 1/CGFloat(aspectRatio), constant: 0)

【讨论】:

【参考方案2】:

一旦你得到图像高度,你应该 更新您的数组,例如rowHeightArray,它跟踪所有的rowHeights,例如

[rowHeightArray replaceObjectAtIndex:indexPath.row withObject:[NSNumber numberWithFloat:imageHeight]];

然后调用

[aTableView reloadRowsAtIndexPaths:indexPath withRowAnimation:YES];

它会调用

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath

    return [[rowHeightArray objectAtIndex:indexPath.row] floatValue];   

在 indexPath 处返回具有行高的对象。

【讨论】:

单元格的行高设置为UITableViewAutomaticDimension。通过自动布局约束,将自动定义行高。我遇到的问题是纵横比约束在上下滚动后被破坏。 您是否尝试向 UIImageView 添加约束?图像是否具有相同的宽度和高度或至少相同的比例?如果没有,你应该删除以前的约束并添加一个新的约束或检查 ScaleToFit 或 ScaleToFill...我真的不知道你这样做的方式。【参考方案3】:

要删除约束,请检查:

[aView setTranslatesAutoresizingMaskIntoConstraints:NO];
[aView removeConstraints:view.constraints];

for(NSLayoutConstraint *constraint in aView.constraints)
    [aView.view removeConstraint:constraint];

【讨论】:

以上是关于异步加载图像的表格视图单元格的动态高度的主要内容,如果未能解决你的问题,请参考以下文章

具有异步图像加载的自调整 UICollectionView 单元格

异步图像下载和调整图像高度后如何更新表格视图单元格

从自定义表格视图单元格类加载时如何调整单元格的高度?

将图像异步加载到自定义 UITableViewCell 部分工作

UITableViewCell 内存问题中的异步图像加载

设置 tableviewcell 高度以显示具有动态高度单元格的完整内部/嵌套 uitableview