下面是iOS实现自定义布局瀑布流的完整攻略:
1. 瀑布流布局简介
瀑布流布局指根据元素高度不同,按照一定的规则排列在网页或app页面上的布局方式,最早出现在Pinterest网站上,可以更好地展示图片等不同高度的元素。在iOS中,可以采用UICollectionView来实现瀑布流布局。
2. 实现瀑布流布局
2.1 UICollectionView自定义布局
在UICollectionView中,通过自定义UICollectionViewFlowLayout类来实现瀑布流布局,具体步骤如下:
-
创建UICollectionViewFlowLayout的子类FlowLayout。
-
重写
prepare()
函数,该函数在页面加载的时候被调用,用于计算每个item的大小和位置。 -
重写
layoutAttributesForElements()
函数,该函数返回一个数组,表示当前可见范围内所有item的布局信息,包括位置、大小等。 -
其中,可以使用UICollectionViewLayoutAttributes类来设置每个item的布局属性。
下面给出一个示例代码:
class FlowLayout: UICollectionViewFlowLayout {
private var attributeArray: [UICollectionViewLayoutAttributes] = [] //存放布局属性的数组
private var maxHeight: CGFloat = 0 //记录每一列的高度
private var itemWidth: CGFloat = 0 //记录每一个item的宽度
override func prepare() {
super.prepare()
let itemCount = collectionView?.numberOfItems(inSection: 0)
guard let count = itemCount, count > 0 else {
return
}
maxHeight = 0
itemWidth = (collectionView!.bounds.width - sectionInset.left - sectionInset.right - CGFloat(columnCount - 1) * minimumInteritemSpacing) / CGFloat(columnCount)
//重置数组
attributeArray.removeAll()
for i in 0..<count {
let indexPath = IndexPath(item: i, section: 0)
let attrs = layoutAttributesForItem(at: indexPath)!
attributeArray.append(attrs)
}
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
return attributeArray.filter { attributes in
return attributes.frame.intersects(rect)
}
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
let attrs = UICollectionViewLayoutAttributes(forCellWith: indexPath)
//计算每一个item的frame
let itemHeight = [100, 120, 80, 90, 110, 80, 100, 90, 120, 100][indexPath.item%10]
var minHeight = maxHeight
var minIndex = 0
for i in 0..<columnCount {
if (maxHeight - columnHeight[i] < 0) {
minHeight = maxHeight - columnHeight[i]
minIndex = i
break
}
if (maxHeight - columnHeight[i] < minHeight && (maxHeight - columnHeight[i] >= 0)) {
minHeight = maxHeight - columnHeight[i]
minIndex = i
}
}
//设置该item的frame
let itemX = sectionInset.left + (minimumInteritemSpacing + itemWidth) * CGFloat(minIndex)
let itemY = minHeight + minimumLineSpacing
attrs.frame = CGRect(x: itemX, y: itemY, width: itemWidth, height: CGFloat(itemHeight))
//更新该列的高度
columnHeight[minIndex] = maxHeight - (attrs.frame.origin.y - minHeight) + CGFloat(itemHeight)
return attrs
}
override var collectionViewContentSize: CGSize {
var maxHeight = CGFloat(0)
for height in columnHeight {
if (height > maxHeight) {
maxHeight = height
}
}
return CGSize(width: (collectionView?.bounds.width)!, height: maxHeight + sectionInset.bottom)
}
private let columnCount = 3 //设置列数
private let sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) //设置边距
private let minimumLineSpacing: CGFloat = 10.0 //设置行间距
private let minimumInteritemSpacing: CGFloat = 10.0 //设置列间距
private var columnHeight: [CGFloat] = [0, 0, 0] //记录每一列的高度
}
在上述代码中,FlowLayout类继承自UICollectionViewFlowLayout,通过重写prepare()
函数、layoutAttributesForElements()
函数和layoutAttributesForItem(at:)
函数来计算元素的位置和大小。其中,prepare()
函数用于初始化布局信息,layoutAttributesForElements()
函数返回可见范围内所有元素的布局信息,layoutAttributesForItem(at:)
函数返回指定元素的布局信息。
2.2 UICollectionView自定义布局的使用
在使用自定义布局的UICollectionView时,需要将其在Storyboard中设置为自定义布局,或者在代码中进行设置,示例代码如下:
let flowLayout = FlowLayout()
collectionView.collectionViewLayout = flowLayout
3. 示例说明
下面展示两个使用自定义布局实现瀑布流布局的示例:
3.1. 图片瀑布流
在图片展示的场景中,瀑布流布局可以更好地展示不同大小的图片,使得用户体验更加丝滑。下面给出一个简单的图片瀑布流实现:
class ImageFlowLayout: UICollectionViewFlowLayout {
private var attributeArray: [UICollectionViewLayoutAttributes] = [] //存放布局属性的数组
private var maxColumnHeight: CGFloat = 0 //记录当前一行中最高的item的高度
private var itemWidth: CGFloat = 0 //记录每一个item的宽度
override func prepare() {
super.prepare()
guard let collectionView = self.collectionView else { return }
maxColumnHeight = 0
itemWidth = (collectionView.bounds.width - sectionInset.left - sectionInset.right - CGFloat(columnCount - 1) * minimumInteritemSpacing) / CGFloat(columnCount)
//重置数组
attributeArray.removeAll()
//计算每个item的frame
let itemCount = collectionView.numberOfItems(inSection: 0)
for i in 0..<itemCount {
let indexPath = IndexPath(item: i, section: 0)
let attrs = UICollectionViewLayoutAttributes(forCellWith: indexPath)
let itemHeight = [100, 200, 150, 250, 180, 120, 210, 170, 140, 190][i%10]
let minHeight = maxColumnHeight
let minIndex = 0
var itemX = sectionInset.left + (minimumInteritemSpacing + itemWidth) * CGFloat(minIndex)
var itemY = minHeight + minimumLineSpacing
if i >= columnCount {
// 获取当前列, X 坐标不变
let column = i % columnCount
// 获取临近的 cell 的布局属性及最大 Y 坐标, X 坐标不变
let lastIndexPath = IndexPath(item: i - columnCount, section: 0)
let lastAttrs = attributeArray[lastIndexPath.row]
let lastMaxY = lastAttrs.frame.maxY
itemY = lastMaxY + minimumLineSpacing
maxColumnHeight = max(maxColumnHeight, lastMaxY)
// 获取目标 cell 的布局属性
attrs.frame = CGRect(x: itemX, y: itemY, width: itemWidth, height: CGFloat(itemHeight))
} else {
// 设置该item的frame
attrs.frame = CGRect(x: itemX, y: itemY, width: itemWidth, height: CGFloat(itemHeight))
}
maxColumnHeight = attrs.frame.maxY
attributeArray.append(attrs)
}
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
return attributeArray.filter { attributes in
return attributes.frame.intersects(rect)
}
}
override func collectionViewContentSize() -> CGSize {
return CGSize(width: collectionView?.bounds.width ?? 0, height: maxColumnHeight + minimumLineSpacing)
}
private let columnCount = 3 //设置列数
private let sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) //设置边距
private let minimumLineSpacing: CGFloat = 10.0 //设置行间距
private let minimumInteritemSpacing: CGFloat = 10.0 //设置列间距
}
3.2. 信息卡片瀑布流
在信息类app中,可以使用瀑布流布局来展示信息卡片,更好地呈现不同大小的信息。下面给出一个简单的信息卡片瀑布流实现:
class InfoCardWaterflowLayout: UICollectionViewFlowLayout {
let columns: Int = 2 // 列数
let spacing: CGFloat = 10 // 间距
var columnHeights: [CGFloat] = [] // 每列高度
var itemWidth: CGFloat = 0 // 每个项目宽度
var allAttributes: [[UICollectionViewLayoutAttributes]] = [] // 所有 item 的属性
override func prepare() {
super.prepare()
guard let collectionView = self.collectionView else { return }
allAttributes.removeAll()
itemWidth = (collectionView.bounds.width - spacing * CGFloat(columns + 1)) / CGFloat(columns)
for _ in 0..<columns {
columnHeights.append(0)
}
//遍历所有item,计算布局信息
let count = collectionView.numberOfItems(inSection: 0)
for i in 0..<count {
let indexPath = IndexPath(item: i, section: 0)
let attrs = UICollectionViewLayoutAttributes(forCellWith: indexPath)
let cellHeight = [120, 130, 110, 150, 170, 190, 200, 180, 210, 140][i%10]
var columnIndex = 0
var columnHeight = columnHeights[0]
// 找到最短的列
for j in 1..<columns {
let targetHeight = columnHeights[j]
if targetHeight < columnHeight {
columnIndex = j
columnHeight = targetHeight
}
}
let xOffset = spacing + (itemWidth + spacing) * CGFloat(columnIndex)
let yOffset = spacing + columnHeight
attrs.frame = CGRect(x: xOffset, y: yOffset, width: itemWidth, height: CGFloat(cellHeight))
columnHeights[columnIndex] = columnHeight + spacing + CGFloat(cellHeight)
// 保存当前 item 的属性
var sectionAttributes = allAttributes.indices.contains(indexPath.section) ? allAttributes[indexPath.section] : []
if sectionAttributes.isEmpty {
allAttributes.append([])
}
sectionAttributes.append(attrs)
allAttributes[indexPath.section] = sectionAttributes
}
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var attributesArray: [UICollectionViewLayoutAttributes] = []
for sectionItemsAttrs in allAttributes {
for attrs in sectionItemsAttrs {
if attrs.frame.intersects(rect) {
attributesArray.append(attrs)
}
}
}
return attributesArray
}
override var collectionViewContentSize: CGSize {
var maxHeight = columnHeights[0]
for i in 1..<columnHeights.count {
let height = columnHeights[i]
if height > maxHeight {
maxHeight = height
}
}
return CGSize(width: collectionView?.bounds.width ?? 0, height: maxHeight)
}
}
上述代码中,我们新建了一个InfoCardWaterflowLayout的子类,使用 columnHeights 数组记录每一列的总高度,使用 allAttributes 数组保存所有item的布局属性,以便在 layoutAttributesForElements(in:)
函数中使用。
总结
以上就是iOS实现自定义布局瀑布流的完整攻略,通过UICollectionView自定义子类实现瀑布流布局,可以更好地展现不同高度、不同类型的元素,提升用户体验。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:IOS实现自定义布局瀑布流 - Python技术站