繁体   English   中英

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

[英]Using Auto Layout in UITableViewCell containing a UICollectionView with asynchronous image load

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

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

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

我有的限制如下:

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

我面临的问题如下:

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

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

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

以下是我设置Collection View的方法

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

并持有它的Custom Cell

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:)设置了集合视图的高度约束tableView(_:willDisplayCell:forRowAtIndexPath:)

我试图在cellForRowAtIndexPath(_:)设置它,但它不会改变任何东西。

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

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

试试后请给我一个反馈。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM