在UITableViewCell中使用自动布局,其中包含带异步图像加载的UICollectionView

时间:2016-01-27 15:43:36

标签: ios swift uitableview uicollectionview ios-autolayout

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

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

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

我的约束如下:

  • 如果图库仅包含一个图像,并且此图像以横向方向定向,则图库的高度应为图像的高度
  • 否则,画廊有一个固定的高度。

我面临的问题如下:

  • 当表格视图第一次尝试显示图库单元格时,我收到了Super Annoying Encapsulated-Height-Layout问题。即使已更新集合视图上的高度约束,此封装高度始终是错误的值。

  • 表格视图在第一次尝试时始终无法正确获取单元格的大小。

    • 即使在显示单元格时已经检索到图像,单元格也显示不佳,我必须向上/向下滚动以隐藏它,然后再次显示它以获得正确的大小...直到下一次单元格大小必须再次计算。见下文: enter image description here
  • 我可以强制表视图正确显示单元格的唯一方法是在第一次加载图像时在表视图上调用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
}

以下是在imageIsReadyForCellAtIndexPath(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(_:) 中设置它,但它不会改变任何内容。

对于巨大的代码块感到抱歉,但这让我感到疯狂。

1 个答案:

答案 0 :(得分:0)

出现该警告时。它告诉你,它必须打破你的一个约束才能修复它。可能,(高度约束)修复此警告只是使约束的优先级为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()

请在尝试后给我反馈。