圖片類的應用我們常常會看到所謂的「瀑布流排版」,各種不同大小的圖片拼接擺放在畫面上,而也有人直接稱這種排版為Pinterest排版,
可能是因為Pinterest是早期經典的RWD設計網站之一。而正式一點的說法應該是Masonry Layout,Dynamic Grid Layout。
比如這張Pinterest官網的圖:
動手做「瀑布流排版」
上面是我們最終的實現效果,感覺不錯的話可以繼續往下看:D
排版的邏輯
- 第一排橘色的部分,直接從左至右放下圖片。
- 接下來不斷的將新的圖片,安插在最短的column上,從而實現瀑布流的排版方式,可以參考上面放置圖片的數字順序。
自定Layout
我們打算通過UICollectionViewFlowLayout來實現這個佈局,
prepare()是它的入口,在這裡可以做一些初始化的設定,比如基本的邊界、cell之間的距離等等。
因為demo中是提供了切換佈局的功能,而我們希望佈局在計算過後不用再重新計算,所以會先判斷是否已經算過。
如果沒有計算過則執行我們的computeAndStoreAttributesWithItemWidth方法來計算佈局信息。
override func prepare() {
super.prepare()
minimumInteritemSpacing = 10
minimumLineSpacing = 10
sectionInset.top = 10
sectionInset.left = 10
sectionInset.right = 10
// 如果之前沒有計算過Layout則計算並存入cache中
if layoutAttributes[layoutType.keyName] == nil && collectionView != nil{
// 根據想要的column數量來計算一個cell的寬度
let contentWidth:CGFloat = collectionView!.bounds.size.width - sectionInset.left - sectionInset.right
let itemWidth = (contentWidth - minimumInteritemSpacing * (CGFloat(layoutType.column)-1)) / CGFloat(layoutType.column)
// 計算cell的佈局
computeAndStoreAttributes(layoutType ,CGFloat(itemWidth))
}
}
「排版的本質是去計算每一個cell在scrollView中的位置」。
下面是計算每一個Cell的Attribute方法,其中包含了計算後存起來的動作,計算後會將結果存起來。
// 計算cell的frame以及設定item size來提供系統計算UICollectionView的contentSize
fileprivate func computeAndStoreAttributes(_ layoutType:LayoutType ,_ itemWidth:CGFloat) {
// 以sectionInset.top作為最初始的高度,紀錄每一個column的高度
var columnHeights = [CGFloat](repeating: sectionInset.top, count: layoutType.column)
// 記錄每一個column的item個數
var columnItemCount = [Int](repeating: 0, count: layoutType.column)
// 紀錄每一個cell的attributes
var attributes = [UICollectionViewLayoutAttributes]()
var row = 0
for item in items {
// 建立一個attribute
let indexPath = IndexPath.init(row: row, section: 0)
let attribute = UICollectionViewLayoutAttributes(forCellWith: indexPath)
// 找出最短的Column
let minHeight = columnHeights.sorted().first!
let minHeightColumn = columnHeights.index(of: minHeight)!
// 新的照片放到最短Column上
columnItemCount[minHeightColumn] += 1
let itemX = (itemWidth + minimumInteritemSpacing) * CGFloat(minHeightColumn) + sectionInset.left
let itemY = minHeight
// 計算高度,按照原圖片大小等比例縮放
let itemHeight = item.size.height * itemWidth / item.size.width
// 設定Frame,加入到attributes中
attribute.frame = CGRect(x: itemX, y: CGFloat(itemY), width: itemWidth, height: CGFloat(itemHeight))
attributes.append(attribute)
// 計算最短的column當前的高度
columnHeights[minHeightColumn] += itemHeight + minimumLineSpacing
row += 1
}
// 找出最高的Column
let maxHeight = columnHeights.sorted().last!
let column = columnHeights.index(of: maxHeight)
// 用於系統計算collectionView的contentSize - 根據最高的Column來設置itemSize,使用總高度的平均值
let itemHeight = (maxHeight - minimumLineSpacing * CGFloat(columnItemCount[column!])) / CGFloat(columnItemCount[column!])
itemSize = CGSize(width: itemWidth, height: itemHeight)
// 將計算後的結果存起來
layoutAttributes[layoutType.keyName] = attributes
layoutItemSize[layoutType.keyName] = itemSize
}
下面是系統讀取attributes的方法,因為layoutAttributes中只有每一個cell的frame資訊,
所以要記得同時修改itemSize,否則因為itemSize的錯誤而導致UICollectionView的contentSize是錯的。
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
// 佈局變化時記得跟著改變itemSize
if let size = layoutItemSize[layoutType.keyName] {
itemSize = size
}
return layoutAttributes[layoutType.keyName]
}
Demo中其他的效果
UICollectionView切換的動畫的方法,要記得先執行collectionViewLayout.invalidateLayout,然後再換成新的佈局。
// 更換layout動畫
UIView.animate(withDuration: 1, animations: {
self.aCollectionView.collectionViewLayout.invalidateLayout()
self.aCollectionView.performBatchUpdates({
self.aCollectionView.setCollectionViewLayout(self.flowLayout, animated: true)
}, completion: nil)
})
推薦和參考
- 歡迎分享本文「Swift實現瀑布流排版」,https://ios.devdon.com/archives/593
- 本篇文章的例子已上傳Github,請參考「Swift實現瀑布流排版」。
<img class="alignnone wp-image-595 size-large" src="https://ios.devdon.com/wp-content/uploads/2017/03/pinterest-857x1024.jpg" alt="" width="640" height="765" srcset="https://ios.devdon.com/wp-content/uploads/2017/03/pinterest-857x1024.jpg 857w, https://ios.devdon.com/wp-content/uploads/2017/03/pinterest-251x300.jpg 251w, https://ios.devdon.com/wp-content/uploads/2017/03/pinterest-768x918.jpg 768w, https://ios.devdon.com/wp-content/uploads/2017/03/pinterest.jpg 1024w" sizes="(max-width: 640px) 100vw, 640px" />
<img class="alignnone wp-image-604 size-full" src="https://ios.devdon.com/wp-content/uploads/2017/03/massonry-layout.png" alt="" width="644" height="699" srcset="https://ios.devdon.com/wp-content/uploads/2017/03/massonry-layout.png 644w, https://ios.devdon.com/wp-content/uploads/2017/03/massonry-layout-276x300.png 276w" sizes="(max-width: 644px) 100vw, 644px" />