【发布时间】: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
}
当 delegate 在 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(_:) 中设置它,但它并没有改变任何东西。
抱歉,代码块很大,但这让我发疯了。
【问题讨论】:
-
是
Encapsulated-Height-Layout值 == 故事板中的行高吗? -
Encapsulated-Height-Layout等于230,即集合视图的默认高度(200)+垂直间距(30) -
当该警告出现时。它告诉你它必须打破你的限制之一才能修复它。可能,(高度约束)修复此警告只需将该约束的优先级设为 999(如果它是 1000)。
-
@Performat 你有没有找到一个用户友好的解决方法?
-
不抱歉!这个项目已经有一段时间了,我不必再处理这个问题了。希望你能找到一些东西!
标签: ios swift uitableview uicollectionview ios-autolayout