Использование Auto Layout в UITableViewCell, содержащем UICollectionView с асинхронной загрузкой изображения

TL;DR: высота, вычисляемая механизмом автомакета при отображении ячейки, содержащей представление коллекции, всегда неверна в первый раз. Вызов reloadData() в табличном представлении устраняет проблему, но также делает табличное представление скачком и становится непригодным для использования. Есть идеи?

Пояснения. У меня есть табличное представление с множеством разных ячеек разной высоты.

Одна из этих ячеек представляет собой галерею изображений, которая может содержать одно или несколько изображений, загруженных асинхронно.

Ограничения у меня следующие:

  • если галерея содержит только одно изображение и если это изображение ориентировано в альбомной ориентации, высота галереи должна быть равна высоте изображения
  • в противном случае галерея имеет фиксированную высоту.

Проблемы, с которыми я сталкиваюсь, следующие:

  • Я получаю проблему Super Annoying Encapsulated-Height-Layout, когда табличное представление пытается отобразить ячейку галереи в первый раз. Эта инкапсулированная высота всегда имеет неправильное значение, даже несмотря на то, что ограничение высоты в представлении коллекции было обновлено.

  • Табличное представление никогда не получает правильный размер ячейки с первой попытки.

    • Even if the image is already retrieved when the cell is displayed, the cell displays poorly and I have to scroll up / down to hide it, then display it again to get the right size... until next time the cell size has to be computed again. See below: 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
}

Вот что происходит, когда делегат вызывается в RooViewController в imageIsReadyForCellAtIndexPath(image: UIImage, collectionView: UICollectionView, screenshotIndex: Int):

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(_:) , но это ничего не меняет.

Извините за большой кусок кода, но это сводит меня с ума.


person Performat    schedule 27.01.2016    source источник
comment
значение Encapsulated-Height-Layout == высота строки в раскадровке?   -  person Ismail    schedule 27.01.2016
comment
Encapsulated-Height-Layout равно 230, что является высотой представления коллекции по умолчанию (200) + интервал по вертикали (30).   -  person Performat    schedule 27.01.2016
comment
Когда появляется это предупреждение. он говорит вам, что ему пришлось сломать одно из ваших ограничений, чтобы исправить его. возможно, (ограничение по высоте), чтобы исправить это предупреждение, просто сделайте приоритет этого ограничения 999 (если он был 1000).   -  person Ismail    schedule 27.01.2016
comment
@Performat, вы нашли удобное решение для этого?   -  person Josh    schedule 02.01.2018
comment
Нет извините! Прошло много времени с момента этого проекта, и мне больше не приходилось сталкиваться с этой проблемой. Надеюсь, вы найдете что-нибудь!   -  person Performat    schedule 30.01.2018


Ответы (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()

Пожалуйста, дайте мне отзыв после того, как вы попробуете это.

person Ismail    schedule 27.01.2016
comment
Я установил макет ячейки, используя три набора ограничений. Одно горизонтальное, одно вертикальное (оба с использованием vfl) и последнее — это единственное ограничение, предназначенное для высоты представления коллекции. В предупреждении говорится, что система восстановилась, нарушив вертикальный набор ограничений: Will attempt to recover by breaking constraint <NSLayoutConstraint:0x170292480 V:[Catch_App.CarouselCollectionView:0x150820c00]-(30)-| (Names: '|':Catch_App.CarouselElement:0x14fd751a0'PictureCarouselIdentifier' )> - person Performat; 27.01.2016
comment
Пробовал звонить setNeedsLayout() и cell.layoutifNeeded() на сотовый, к сожалению, не помогло. Что касается ограничений, я понизил приоритет ограничения высоты представления коллекции, но это тоже не помогло. Как намекает сообщение отладки, я думаю, это потому, что это не ограничение, которое фактически генерирует предупреждение. Но не уверен, как мне понизить приоритет всех вертикальных ограничений... Сейчас попробую установить их по отдельности и понизить их приоритет. - person Performat; 27.01.2016
comment
не могли бы вы попробовать перезагрузить саму ячейку. Пожалуйста, смотрите мой обновленный ответ. и дать мне обратную связь? - person Ismail; 27.01.2016
comment
Я попытался перезагрузить саму ячейку, и это имеет тот же эффект, что и reloadData(). Я также пробовал tableView.beginUpdates(), tableView.endUpdates(). Это заставляет tableView прыгать и становиться непригодным для использования. Я также попытался понизить приоритет всех вертикальных ограничений до 999, а затем попытался понизить приоритет каждого из них до 999, оставив для остальных значение по умолчанию 1000, но предупреждение продолжает отображаться. - person Performat; 27.01.2016
comment
@Performat, не могли бы вы использовать teamviewer, чтобы я мог увидеть, что происходит, чтобы я мог предложить вам лучшее решение? - person Ismail; 27.01.2016
comment
Спасибо, что предложили вам помощь, я действительно ценю. К сожалению, я нахожусь в профессиональной среде, которая не позволяет мне это сделать. - person Performat; 27.01.2016
comment
Не беспокойтесь, я нашел это руководство может быть вам пригодится - person Ismail; 27.01.2016
comment
Хотя это очень странно, я получаю ожидаемый результат, который заставляет ячейку правильно отображаться с первой попытки. Однако вызов scrollRectToVisible(_ :, animated: ) приводит к остановке прокрутки. Поэтому всякий раз, когда изображение загружается, прокрутка в виде таблицы перестает ???? Так что мне придется продолжать поиск более удобного для пользователя подхода. - person Performat; 27.01.2016