【问题标题】:Auto-Layout: Get UIImageView height to calculate cell height correctly自动布局:获取 UIImageView 高度以正确计算单元格高度
【发布时间】:2014-11-20 10:01:30
【问题描述】:

到目前为止,我只是在应该自动调整大小的单元格中使用文本标签。我通常对所有边缘(内容视图或邻居)设置约束,并将以下几行添加到我的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)。

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

感谢一百万的帮助!

【问题讨论】:

  • 想在 github 或其他地方发布测试项目吗?
  • @MikePollard 抱歉,不太明白你的意思……顺便说一句,我编辑并指定了我的问题。
  • 我只是建议上传您的代码,这样我们就可以玩一玩并帮助您...
  • @MikePollard 代码非常基础,但结合我放置约束/设置的情节提要可能会有所帮助,所以我还是上传了项目:wetransfer.com/downloads/…
  • 谢谢,我玩了一场,找到了解决方法。希望我的回答有帮助...

标签: uitableview swift ios8 autolayout


【解决方案1】:

所以我认为根本问题是一种先有鸡还是先有蛋的问题。

为了使图像“适合”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
}

你会得到:

希望这会有所帮助!

【讨论】:

  • 太好了,谢谢!只是一个可选问题:您是如何继续分析该问题并获得正确的解决方法的?还是只是纯粹的经验?;-) 有什么关键点可以帮助我下次靠自己走得更远吗?
  • 我首先添加了一些日志来打印出 imageView 的 intrinsicSize、image 的 size 和 imageView 的 frame,然后尝试考虑自动布局系统如何使用这些信息来到达你想要的答案。过了一会儿,我才恍然大悟,“方面拟合”部分与自动布局部分是分开的......这有助于我的头在“自动布局模式”中最近一直在努力解决各种问题让布局在 4、5、6 和 6plus 上运行良好。
  • 这似乎是一个绝妙的解决方案。虽然我不是那种 Swift 的人。因此,我在自定义单元格上添加了 NSLayoutConstraint 作为 IBOutlet 并将其绑定到高度约束。我不想每次都添加和删除高度约束,我只想设置高度。但不知何故,这会导致“不符合”布局约束 IBOutlet 的键值编码的崩溃。我错过了什么?
  • 感谢您的出色回答。不过,我在实施您的解决方案时遇到了困难。我在添加新约束之前删除了约束,但最终似乎所有单元格都共享相同的约束......并不是每个单元格都是自己的......
  • 这太棒了!我刚刚在 obj-c 中为 UIImageView 创建了一个类别来执行此操作。
【解决方案2】:

我将 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

【讨论】:

  • setter 中缺少“set”:_aspectContraint = aspectContraint;
【解决方案3】:

与自定义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】:

    使用 Swift 3 + iOS 8

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

    对于 imageview,将顶部、底部、前导和尾随空间约束设置为 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
        }
    }
    

    【讨论】:

    • 我一直在努力解决这个问题 - 目前仍在努力 - 根据我的经验,使用 tableView:heightForRowAtIndexPath: 会使 UITableView 非常慢;滚动性能受到严重影响。您不想使用此功能,除非用于测试/检查可能的解决方案。
    【解决方案5】:

    斯威夫特 4

    使用UIImageextension计算图像高度。

    您可以使用extensionUIImage 根据图像的纵横比得到高度。

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

    所以,在您的 override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -&gt; 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
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-03-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多