在tableview中嵌入collectionview時,如果沒有正確設置高度,會產生collectionview不能全部展現,只能在內部滑動的現象,這與預期是不符的。
解決思路:在內嵌的collectionView進行佈局的時候,拿到contentSize的高度h,然後在更新tableView cell的高度等於h.
兩種方案:
- 不使用tableView自適應行高
- 使用AutoLayout配合tableView的自適應行高
1. 不使用tableView的自適應行高
思路
- 在tableView cellForRow階段對cell中的collectionView reloadData,然後調用layoutIfNeeded()獲取到的contentSize,並記錄下這個高度h
- cellForRowAt時,返回這個h就可以了.
需要注意
- 不要在cellForRowAt中通過tableView獲取cell,然後拿cell的高度,其實你拿也拿不到,因爲這個時候cell還沒有顯示出來,tableView.cellForRow(at: indexPath)拿到的是nil,另外collectionView reloadData後,需要layoutIfNeeded才能正確拿到contentSize,因爲reloadData不是同步的,沒辦法同步拿到佈局.
關鍵代碼
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
…
cell.collectionView.data = someData
cell.collectionView.reloadData()
cell.collectionView.layoutIfNeeded()
collectionCellHeight = cell.collectionvView.contentSize.height
…
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
…
//if indexPath.row == collectionview所在cell的row
return collectionCellHeight
…
}
2. 使用tableView的自適應行高
思路
- 首先啓用tableView的自動行高(ios 7還是8以後默認是啓用的)
- 放置一個container在tableView cell的contentView上,collectionView放在container上;contentView的高度動態隨container變化,container高度隨collectionView變化
- 當collection reload後立即layoutIfNeeded獲取contentSize,然後更新container的高度,由於是自動高度,因此tableView的cell也會調整到container的高度
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44 // 設置一個和你的collectionView接近的值吧,默認44
class CustomCell: UITableViewCell {
var dataArray: [String] = [] // 假定我們的數據結構就是一個String數組
var collectionView: UICollectionView!
var collectionContainer: UIView!
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
collectionContainer = UIView(frame: .zero)
contentView.addSubview(collectionContainer)
collectionContainer.mas_makeConstraints {
$0?.left.equalTo()(self.contentView)
// 這裏需要指定寬度,因爲collectionView佈局的時候,tableView的cell的frame還沒有正確設置還是estimate
// 所以不可以這樣!
// $0?.left.and()?.right()?.equalTo()(self.contentView)
$0?.width.equalTo()(UIScreen.main.bounds.width)
$0?.top.and()?.bottom()?.equalTo()(self.contentView) // container的上下邊和contentView一致,contentView會根據它的大小調整
$0?.height.equalTo()(44) // 默認先隨便一個值吧,最好接近真實值,後面會update到真實值
}
let layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = 10
layout.minimumInteritemSpacing = 10
layout.itemSize = CGSize(width: 50, height: 50)
layout.scrollDirection = .vertical
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionContainer.addSubview(collectionView)
collectionView.mas_makeConstraints {
$0?.edges.equalTo()(self.collectionContainer)
}
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "test")
collectionView.delegate = self // 這裏根據具體業務寫吧,demo中我就把代理直接設置成cell了..
collectionView.dataSource = self // 根據業務,demo直接先cell持有數據來控制collectionView的顯示了..
}
// 外部調用,用來設置collectionView的數據源
func setData(_ data: [String]) {
self.dataArray = data
collectionView.reloadData()
// 這裏需要 layoutIfNeeded 一下,否則我們不能同步拿到contentSize
// 注意,如果上面collectionContainer的佈局沒有指明寬度,這個時候cell的寬度是estimate狀態,所以下面就算layout也拿不到正確的contentSize
collectionView.layoutIfNeeded()
let height = collectionView.contentSize.height
collectionContainer.mas_updateConstraints { // 更新container的高度
$0?.height.equalTo()(height)
}
}
}
需要注意
- container需要指定寬度,否則拿到的是估算值,這個時候collectionView是他的子view,就算layoutIfNeeded也是以估算值來的,所以不不準確.
ps.
推薦一個自己寫的app:本人喜歡玩暴雪的遊戲,最近寫了一個暗黑破壞神3的遊戲工具,在裏面做了一些UI性能、流暢性的優化,感興趣的同學可以下載體驗下,順便給個評分~