使用异步加载图片的动态高度表格视图单元格

4

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

XIB文件:

My XIB file

单元格类:

//
//  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
}

在应用程序启动时,一切都按预期运行,但如果我滚动一点,约束条件就开始破裂,图像的外观不再正确或出现其他奇怪的问题,如下所示:

Broken 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间距,然后又试图根据高度更改宽度。 - Gandalf
这是一个宽高比约束,而不是宽度约束。UIImageView的高度比宽度大0.6672倍。但是,如果我在IB中设置固定高度并且不添加此宽高比约束,则可以工作,但我始终具有相同的高度和错误的宽高比,因为每个图像都不同。 - Tobias
1
我知道你已经应用了宽高比约束。我想你误解了我的意思。在你的宽高比约束中,“第一项”是“宽度”。这意味着约束将尝试对该视图的“宽度”进行更改,而不是“高度”。因此,如果你从中颠倒第一和第二项,并在乘数参数中反转宽高比,你将得到相同的约束,但根据图像宽高比会有可变的高度。 - Gandalf
谢谢!它解决了我的问题!请将此评论作为答案编写,我会将其标记为正确答案。 - Tobias
谢谢提供覆盖updateLayoutConstraints的提示!这救了我的一命!!!代码不错哦 :-) - blackjacx
你还需要在TableView上调用beingupdates吗?你是如何确保TableView相应地更新的? - perrohunter
3个回答

1
这个问题是由于应用于 ImageView 的两个冲突的约束条件造成的。如果查看调试器输出,您会发现。
Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x7f8a00860870 UIImageView:0x7f8a00830910.width == 0.6672*UIImageView:0x7f8a00830910.height>

您已经将水平约束设置为与superview间隔为0的ImageView
现在,当更新宽高比约束时,您正尝试将ImageViewwidth(通过保持宽度为第一项)设置为其height的比例,这将与其前导/后续约束冲突。 您需要通过交换第一和第二项并反转纵横比系数来设置此ImageView的高度。
postImageAspectConstraint = NSLayoutConstraint(item: postImageView, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: postImageView, attribute: NSLayoutAttribute.Width, multiplier: 1/CGFloat(aspectRatio), constant: 0)

0

一旦获取了图像的高度,您应该更新数组,例如rowHeightArray,该数组跟踪所有行高,例如

[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。通过自动布局约束,行高将自动定义。我的问题是,在上下滚动后,纵横比约束被打破了。 - Tobias
你尝试给UIImageView添加约束了吗?图片的宽度和高度相同或至少比例相同吗?如果不是,你应该删除先前的约束并添加新的约束,或者检查ScaleToFit或ScaleToFill...我不太清楚你的具体操作方式。 - Leonardo

0

要删除约束,请检查:

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

或者

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接