自动布局:获取UIImageView高度以正确计算单元格高度

时间:2014-09-25 15:00:06

标签: uitableview swift ios8 autolayout

到目前为止,我一直只在单元格中使用自动调整大小的文本标签。我通常将约束放在所有边(内容视图或邻居)上,并将以下行添加到viewDidLoad()

// 190.0 is the actual height of my custom cell (see next image) in the storyboard containing a label and a randomly sized UIImageView
tableView.estimatedRowHeight = 190.0
tableView.rowHeight = UITableViewAutomaticDimension

现在,当试图包含图像时,我遇到了几个问题。在一些研究中,我发现了几种用Objective-C编写的方法,但是我不确定哪些方法真正解决了我的核心问题,我仍在努力解决复杂的Objective-C代码问题。所以要恢复:我的最终目标是获得一个具有正确大小(正确的高度)单元格的表格,每个单元格包含一个符合屏幕宽度的标题和图像。那些图像可以具有不同的纵横比和不同的尺寸。为了简化一些挑战,我准备了一个更简单的新项目:

  1. 首先,我创建了一个UITableViewController和一个CustomCell类,其中包含两个IBOutlets title: StringpostedImage: UIImage(故事板中随机大小的UIImage)来配置单元格。我为边缘和彼此定义了约束。
    1. 构建时,两个单元格配置为包含相同的图像,但它们是从具有不同大小和分辨率的两个单独文件中获取的(900 x 171与半尺寸450 x 86)。 UIImage视图模式设置为“Aspect Fit”,但由于我无法让它以我想要的方式运行到目前为止我不确定这是正确的设置。虽然我曾期望根据重新调整的UIImage计算单元格高度,包括我设置的约束,但它似乎是基于图像文件的初始高度(所以171和86)。它实际上并不适应实际的UIImage视图高度(大约70我猜)。
    2. Both cell heights are based on the initial image file height and not on the actual UIImage height

      在我的一个更复杂的项目中,我想要不同的图像自动适应屏幕的宽度,无论它们是纵向还是横向 - 如果它们的宽度小于屏幕的宽度,它们应该按比例放大。与上面的项目一样,每个单元格高度应符合故事板中约束所定义的各个内容。无论如何,它从上面讨论的问题开始:我怎样才能让这两个单元格具有相同的正确高度,拥抱故事板中定义的内容?

      万分感谢您的帮助!

5 个答案:

答案 0 :(得分:73)

所以我认为潜在的问题是一种鸡和蛋的问题。

为了适应方面的需要。系统UIImageView的图像需要视图的大小,但我们希望视图的高度在方面确定视图的高度之后确定视图的已知宽度

解决方法是自己计算图像的宽高比,并在设置图像时将其设置为UIImageView的约束。这样,自动布局系统就有足够的信息来调整视图大小(宽度和宽高比)。

所以我将您的自定义单元格类更改为:

class CustomCell: UITableViewCell {

    @IBOutlet weak var imageTitle: UILabel!

    @IBOutlet weak var postedImageView: UIImageView!

    internal var aspectConstraint : NSLayoutConstraint? {
        didSet {
            if oldValue != nil {
                postedImageView.removeConstraint(oldValue!)
            }
            if aspectConstraint != nil {
                postedImageView.addConstraint(aspectConstraint!)
            }
        }
    }

    override func prepareForReuse() {
        super.prepareForReuse()
        aspectConstraint = nil
    }

    func setPostedImage(image : UIImage) {

        let aspect = image.size.width / image.size.height

        aspectConstraint = NSLayoutConstraint(item: postedImageView, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: postedImageView, attribute: NSLayoutAttribute.Height, multiplier: aspect, constant: 0.0)

        postedImageView.image = image
    }

}

然后在委托中执行此操作而不是直接设置图像:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as CustomCell

        cell.imageTitle.text = titles[indexPath.row]

        let image = images[titles[indexPath.row]]!
        cell.setPostedImage(image)

        return cell
}

你会得到:

enter image description here

希望这有帮助!

答案 1 :(得分:7)

我将Pollard的解决方案翻译成了像这样的objc版本。
我也收到了“无法同时满足约束”等警告。我的解决方案是将垂直空间约束的优先级设置为999而不是1000.

@interface PostImageViewCell()

@property (nonatomic, strong) NSLayoutConstraint *aspectContraint;
@end

@implementation PostImageViewCell

- (void) setAspectContraint:(NSLayoutConstraint *)aspectContraint {

    if (_aspectContraint != nil) {
        [self.postImage removeConstraint:_aspectContraint];
    }
    if (aspectContraint != nil) {
        [self.postImage addConstraint:aspectContraint];
    }
}

- (void) prepareForReuse {
    [super prepareForReuse];
    self.aspectContraint = nil;
}

- (void)setPicture:(UIImage *)image {

    CGFloat aspect = image.size.width / image.size.height;
    self.aspectContraint = [NSLayoutConstraint constraintWithItem:self.postImage
                                                        attribute:NSLayoutAttributeWidth
                                                        relatedBy:NSLayoutRelationEqual
                                                           toItem:self.postImage
                                                        attribute:NSLayoutAttributeHeight
                                                       multiplier:aspect
                                                         constant:0.0];
    [self.postImage setImage:image];
}
@end

答案 2 :(得分:1)

使用 Swift 3 + iOS 8

使用AutoLayout和可变大小的UITableViewCell对象 -

对于imageview,将top,bottom,leading和trailing space constraint设置为0。

要添加到viewDidLoad()的代码

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 200

定义UITableViewDelegate的heightForRowAtIndexPath()

extension ViewController: UITableViewDelegate {

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        let image = UIImage(named: list_images[indexPath.row])
        let aspect = (image?.size.width)! / (image?.size.height)!
        let cellHeight = (tableView.frame.width / aspect) + <<Height of image title label>>
        return cellHeight
    }
}

答案 3 :(得分:0)

setPostedImage中更新方面约束更方便,而不是使用自定义updateConstraints方法。这样,您可以直接更改UIImageView的图像,而无需其他辅助方法:

static NSLayoutConstraint *constraintWithMultiplier(NSLayoutConstraint *constrain, CGFloat multiplier) {
    return [NSLayoutConstraint constraintWithItem:constrain.firstItem
                                        attribute:constrain.firstAttribute
                                        relatedBy:constrain.relation
                                           toItem:constrain.secondItem
                                        attribute:constrain.secondAttribute
                                       multiplier:multiplier
                                         constant:constrain.constant];
}

-(void)viewDidLoad
{
    UIView *contentView = self.contentView;

    _imageView = [[UIImageView alloc] init];
    _imageView.translatesAutoresizingMaskIntoConstraints = NO;
    _imageView.contentMode = UIViewContentModeScaleAspectFit;
    [contentView addSubview:_imageView];

    _imageAspectConstraint = [_imageView.heightAnchor constraintEqualToAnchor:_imageView.widthAnchor multiplier:1.0f];
    NSArray <NSLayoutConstraint *> *constraints = @[
            [_imageView.leadingAnchor constraintEqualToAnchor:contentView.leadingAnchor constant:0],
            [_imageView.trailingAnchor constraintEqualToAnchor:contentView.trailingAnchor constant:0],
            [_imageView.topAnchor constraintEqualToAnchor:contentView.topAnchor constant:0],
            [_imageView.bottomAnchor constraintEqualToAnchor:contentView.bottomAnchor constant:0],
    ];
    [NSLayoutConstraint activateConstraints:constraints];
}

-(void)updateConstraints
{
    const CGSize size = _imageView.image.size;
    const CGFloat multiplier = size.width / size.height;
    _imageAspectConstraint.active = NO;
    _imageAspectConstraint = constraintWithMultiplier(_imageAspectConstraint, multiplier);
    _imageAspectConstraint.active = YES;
    [super updateConstraints];
}

答案 4 :(得分:0)

快捷键4

使用UIImage extension计算图像高度。

您可以根据图像的AspectRatio使用extension中的UIImage之后的高度。

extension UIImage {
    var cropRatio: CGFloat {
        let widthRatio = CGFloat(self.size.width / self.size.height)
        return widthRatio
    }
}

因此,在您的override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat方法中。

override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
            let currentImage = dataSource[indexPath.row].image
            let imageCrop = currentImage.cropRatio
            var _height = tableView.frame.width / imageCrop
            return _height
}