【问题标题】:Using Auto Layout in UITableViewCell containing a UICollectionView with asynchronous image load在包含具有异步图像加载的 UICollectionView 的 UITableViewCell 中使用自动布局
【发布时间】:2016-05-04 15:57:51
【问题描述】:

TL;DR:第一次显示包含集合视图的单元格时,自动布局引擎计算的高度总是错误的。在表格视图上调用reloadData() 可以解决问题,但也会使表格视图跳转并且无法使用。有什么想法吗?

说明: 我有一个包含许多不同高度的不同单元格的表格视图。

其中一个单元格是一个图片库,其中可以包含一张或多张异步加载的图片。

我的限制如下:

  • 如果图库仅包含一张图片,并且该图片为横向,则图库的高度应为图片的高度
  • 否则,画廊的高度是固定的。

我面临的问题如下:

  • 当表格视图第一次尝试显示画廊单元格时,我得到了超级烦人的封装高度布局问题。即使集合视图的高度约束已经更新,这个封装的高度总是错误的值。

  • 表格视图始终不会在第一次尝试时获得正确的单元格大小。

    • 即使在显示单元格时已检索到图像,但单元格显示效果不佳,我必须向上/向下滚动以隐藏它,然后再次显示以获取正确的大小...直到下一次单元格大小必须重新计算。见下文:
  • 我可以强制表格视图正确显示单元格的唯一方法是当我在第一次加载图像后在表格视图上调用 reloadData ...这使得表格视图跳转并且基本上无法使用。

我正在使用 Kingfisher 检索图像,代码如下:

UICollectionViewCell 数据源:

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    
    let cell = collectionView.dequeueReusableCellWithReuseIdentifier("CarouselCollectionViewCell", forIndexPath: indexPath) as! CarouselCollectionViewCell
    guard let collectionView = collectionView as? CarouselCollectionView else { return cell }
    
    if let imagesURLs = data.imagesURLs {
        let url = imagesURLs[indexPath.item]
        
        if let smallURL = url.small {
            KingfisherManager.sharedManager.retrieveImageWithURL(
                smallURL,
                optionsInfo: KingfisherOptionsInfo(),
                progressBlock: nil,
                completionHandler: { (image, error, cacheType, imageURL) -> () in
                    if let image = image {
                     self.delegate?.imageIsReadyForCellAtIndexPath(image, collectionView: collectionView, screenshotIndex: indexPath.row)
                     cell.imageView.image = image
                    }
            })
        }
    }
    return cell
}

delegateimageIsReadyForCellAtIndexPath(image: UIImage, collectionView: UICollectionView, screenshotIndex: Int) 中的 RooViewController 上被调用时会发生以下情况:

func imageIsReadyForCellAtIndexPath(image: UIImage, collectionView: UICollectionView, screenshotIndex: Int) {
    guard let collectionView = collectionView as? CarouselCollectionView else { return }
    guard let collectionViewIndexPath = collectionView.indexPath else { return }
    guard let screenshotsCount = feed?.articles?[collectionViewIndexPath.section].content?[collectionViewIndexPath.row].data?.imagesURLs?.count else { return }
    
    let key = self.cachedSizesIndexPath(collectionViewIndexPath: collectionViewIndexPath, cellIndexPath: NSIndexPath(forItem: screenshotIndex, inSection: 0))
    var sizeToCache: CGSize!
    
    if screenshotsCount == 1 {
        
        // Resize the collectionView to fit a landscape image:
        if image.isOrientedInLandscape {
            sizeToCache = image.scaleToMaxWidthAndMaxHeight(maxWidth: Constants.maxImageWidth, maxHeight: Constants.maxImageHeight)
        } else {
            sizeToCache = image.scaleToHeight(Constants.maxImageHeight)
        }
        
        if collectionViewCellsCachedSizesObject.dict[key] == nil {
            
            let flowLayout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
            let imageWidth = sizeToCache.width
            let sidesInset = (collectionView.frame.width - imageWidth) / 2
            print("sidesInset: ", sidesInset)
            flowLayout.sectionInset = UIEdgeInsets(top: 0, left: sidesInset, bottom: 0, right: sidesInset)
            
            collectionViewCellsCachedSizesObject.dict[key] = sizeToCache
            collectionView.heightConstraint.constant = sizeToCache.height
            collectionView.collectionViewLayout.invalidateLayout()
            collectionView.setNeedsUpdateConstraints()
            
            tableView.reloadData()
        }
    } else {
        
        let sizeToCache = image.scaleToHeight(Constants.maxImageHeight)
        
        if collectionViewCellsCachedSizesObject.dict[key] == nil { // && collectionViewCellsCachedSizesObject.dict[key] != sizeToCache {
            collectionViewCellsCachedSizesObject.dict[key] = sizeToCache
            collectionView.collectionViewLayout.invalidateLayout()
        }
    }
}

这是我设置收藏视图的方法:

class CarouselElement: Element {

let collectionView: CarouselCollectionView

func cachedSizesIndexPath(collectionViewIndexPath aCollectionViewIndexPath: NSIndexPath, cellIndexPath aCellIndexPath: NSIndexPath) -> String {
    return "\(aCollectionViewIndexPath), \(aCellIndexPath)"
}

override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
    
    let layout = UICollectionViewFlowLayout()
    layout.sectionInset = UIEdgeInsets(top: 0, left: Constants.horizontalPadding, bottom: 0, right: Constants.horizontalPadding)
    layout.scrollDirection = .Horizontal
    
    collectionView = CarouselCollectionView(frame: CGRectZero, collectionViewLayout: layout)
    collectionView.translatesAutoresizingMaskIntoConstraints = false
    
    collectionView.registerClass(CarouselCollectionViewCell.self, forCellWithReuseIdentifier: "CarouselCollectionViewCell")
    collectionView.allowsMultipleSelection = false
    collectionView.allowsSelection = true
    collectionView.backgroundColor = Constants.backgroundColor
    collectionView.showsHorizontalScrollIndicator = false
    
    super.init(style: style, reuseIdentifier: reuseIdentifier)
    
    addSubview(collectionView)
    
    addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(
        "|[collectionView]|",
        options: NSLayoutFormatOptions(),
        metrics: nil,
        views: ["collectionView":collectionView]))
    
    addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(
        "V:|[collectionView]-verticalPadding-|",
        options: NSLayoutFormatOptions(),
        metrics: ["verticalPadding":Constants.verticalPadding],
        views: ["collectionView":collectionView]))
    
    collectionView.heightConstraint = NSLayoutConstraint(
        item: collectionView,
        attribute: .Height,
        relatedBy: .Equal,
        toItem: nil,
        attribute: .NotAnAttribute,
        multiplier: 1.0,
        constant: 200)
    addConstraint(collectionView.heightConstraint)
}

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

func setCarouselDataSourceDelegate(dataSourceDelegate: CarouselDataSourceDelegate?, indexPath: NSIndexPath, cachedHeight: CGFloat?) {
    collectionView.indexPath = indexPath
    if let height = cachedHeight {
        collectionView.heightConstraint.constant = height
    }
    collectionView.dataSource = dataSourceDelegate
    collectionView.delegate = dataSourceDelegate
    collectionView.reloadData()
}

override func prepareForReuse() {
    super.prepareForReuse()
    collectionView.contentOffset = CGPointZero
}}

以及持有它的自定义单元

class CarouselCollectionViewCell: UICollectionViewCell {

let imageView: UIImageView

override init(frame: CGRect) {
    
    imageView = UIImageView.autolayoutView() as! UIImageView
    imageView.image = Constants.placeholderImage
    imageView.contentMode = .ScaleAspectFit
    
    super.init(frame: frame)
    
    translatesAutoresizingMaskIntoConstraints = false
    
    addSubview(imageView)
    
    addConstraints(
        NSLayoutConstraint.constraintsWithVisualFormat(
            "|[imageView]|",
            options: NSLayoutFormatOptions(),
            metrics: nil,
            views: ["imageView":imageView]))
    addConstraints(
        NSLayoutConstraint.constraintsWithVisualFormat(
            "V:|[imageView]|",
            options: NSLayoutFormatOptions(),
            metrics: nil,
            views: ["imageView":imageView]))
}

override func prepareForReuse() {
    imageView.image = Constants.placeholderImage
}

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}   }

最后但同样重要的是,我在tableView(_:willDisplayCell:forRowAtIndexPath:) 中设置了集合视图的高度约束

我也尝试在cellForRowAtIndexPath(_:) 中设置它,但它并没有改变任何东西。

抱歉,代码块很大,但这让我发疯了。

【问题讨论】:

  • Encapsulated-Height-Layout 值 == 故事板中的行高吗?
  • Encapsulated-Height-Layout等于230,即集合视图的默认高度(200)+垂直间距(30)
  • 当该警告出现时。它告诉你它必须打破你的限制之一才能修复它。可能,(高度约束)修复此警告只需将该约束的优先级设为 999(如果它是 1000)。
  • @Performat 你有没有找到一个用户友好的解决方法?
  • 不抱歉!这个项目已经有一段时间了,我不必再处理这个问题了。希望你能找到一些东西!

标签: ios swift uitableview uicollectionview ios-autolayout


【解决方案1】:

当该警告出现时。它告诉你它必须打破你的限制之一才能修复它。可能,(高度约束)修复此警告只需将该约束的优先级设为 999(如果它是 1000)。


用于在设置图像后更新单元格。你会试试这个:

dispatch_async(dispatch_get_main_queue()) { () -> Void in
                            self.delegate?.imageIsReadyForCellAtIndexPath(image, collectionView: collectionView, screenshotIndex: indexPath.row)
                            cell.imageView.image = image
                            // get the content offset.
                             let offset = self.tableView.contentOffset
                             let visibleRect = CGRect(origin: offset, size: CGSize(width: 1, height: 1)
                            // reload the cell
                            tableView.beginUpdates()
                            tableView.endUpdates()
                            // to prevent the jumping you may scroll to the same visible rect back without animation.
                            self.tableView.scrollRectToVisible(visibleRect, animated: false)
                        }

并删除tableView.reloadData()

尝试后请给我反馈。

【讨论】:

  • 我使用三组约束设置单元格的布局。一个水平,一个垂直(都使用 vfl),最后一个是专用于集合视图高度的单个约束。警告说系统通过打破垂直约束集恢复:Will attempt to recover by breaking constraint <NSLayoutConstraint:0x170292480 V:[Catch_App.CarouselCollectionView:0x150820c00]-(30)-| (Names: '|':Catch_App.CarouselElement:0x14fd751a0'PictureCarouselIdentifier' )>
  • 我尝试在单元格上调用setNeedsLayout()cell.layoutifNeeded(),但不幸的是它没有帮助。关于约束,我确实降低了集合视图的高度约束的优先级,但它也没有帮助。正如调试消息所暗示的那样,我认为这是因为它不是实际生成警告的约束。但不知道我应该如何降低所有垂直约束的优先级......我现在将尝试单独设置它们并降低它们的优先级。
  • 您是否尝试重新加载单元格本身。请参阅我更新的答案。并给我反馈?
  • 我确实尝试过重新加载单元格本身,它的效果与reloadData() 相同。我也试过tableView.beginUpdates()tableView.endUpdates()。它使 tableView 跳转并且无法使用。我还尝试将所有垂直约束的优先级降低到 999,然后尝试将它们中的每一个的优先级降低到 999,将其他的保留为默认值 1000,但警告仍然显示。
  • 别担心,我发现这个tutorial 可能对你有用
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-04-07
  • 1970-01-01
  • 2019-07-29
相关资源
最近更新 更多