这里是“iOS瀑布流的简单实现(Swift)”的完整攻略。
一、前言
瀑布流是一种非常常见的UI布局方式,在iOS开发中也有很多应用。本文将介绍如何在Swift中实现一个简单的瀑布流布局。
二、实现思路
我们可以采用UICollectionView实现这个瀑布流布局,具体思路如下:
-
继承UICollectionViewFlowLayout,重写prepareLayout方法,设置每个cell的frame;
-
在prepareLayout方法中计算出每个cell的x、y、width、height值;
-
通过UICollectionViewDelegateFlowLayout协议设置每个cell的大小;
-
在UICollectionViewDataSource协议中返回每个cell的相关信息。
三、实现步骤
1.创建CollectionViewFlowLayout子类
首先创建一个CollectionViewFlowLayout子类,并重写prepareLayout方法和UICollectionViewDelegateFlowLayout协议的相关方法,代码如下:
class WaterfallFlowLayout: UICollectionViewFlowLayout, UICollectionViewDelegateFlowLayout {
private var layoutAttributes: [UICollectionViewLayoutAttributes] = []
private var columnTopY: [CGFloat] = []
private let kDefaultColumnCount = 3 // 默认列数
private let kDefaultColumnSpacing: CGFloat = 10.0 // 默认列间距
private let kDefaultRowSpacing: CGFloat = 10.0 // 默认行间距
private let kDefaultEdgeInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) // 默认section insets
// 重写prepareLayout方法
override func prepare() {
super.prepare()
self.delegate = self
self.columnTopY = [CGFloat](repeating: kDefaultEdgeInsets.top, count: kDefaultColumnCount)
self.layoutAttributes.removeAll()
let itemCount = self.collectionView?.numberOfItems(inSection: 0) ?? 0
for item in 0..<itemCount {
let indexPath = IndexPath(item: item, section: 0)
let layoutAttr = self.layoutAttributesForItem(at: indexPath)!
self.layoutAttributes.append(layoutAttr)
}
}
// 重写layoutAttributesForElementsInRect方法,
// 返回所有itemAttributes的集合
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
return self.layoutAttributes
}
// 通过行列数、行间距计算每个cell的frame
private func calculateCellFrame(columnCount: Int, row: Int, column: Int, columnSpacing: CGFloat, rowSpacing: CGFloat, edgeInsets: UIEdgeInsets, cellWidth: CGFloat, cellHeight: CGFloat) -> CGRect {
let x = edgeInsets.left + CGFloat(column) * (cellWidth + columnSpacing)
var y = edgeInsets.top
if row > 0 {
y = self.columnTopY[column] + rowSpacing
}
self.columnTopY[column] = y + cellHeight
let cellFrame = CGRect(x: x, y: y, width: cellWidth, height: cellHeight)
return cellFrame
}
// 通过UICollectionViewDelegateFlowLayout协议设置每个cell的大小
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// 确定当前cell的宽度
let rowWidth = collectionView.bounds.width - kDefaultEdgeInsets.left - kDefaultEdgeInsets.right
let columnSpacing = (kDefaultColumnCount - 1) * kDefaultColumnSpacing
let cellWidth = (rowWidth - columnSpacing) / CGFloat(kDefaultColumnCount)
// 通过图片比例计算当前cell的高度
let imageRatio: CGFloat = 1.5 // 模拟图片比例
let cellHeight = imageRatio * cellWidth
return CGSize(width: cellWidth, height: cellHeight)
}
// 通过UICollectionViewDelegateFlowLayout协议设置每个section的insets
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return kDefaultEdgeInsets
}
// 通过UICollectionViewDelegateFlowLayout协议设置每个cell的行间距和列间距
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return kDefaultRowSpacing
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return kDefaultColumnSpacing
}
// 计算每个cell的frame
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
let cellSize = self.collectionView(self.collectionView!, layout: self, sizeForItemAt: indexPath)
let columnCount = kDefaultColumnCount
let columnIndex = self.findShortestColumnIndex()
let cellFrame = calculateCellFrame(columnCount: columnCount, row: columnIndex.row, column: columnIndex.column, columnSpacing: kDefaultColumnSpacing, rowSpacing: kDefaultRowSpacing, edgeInsets: kDefaultEdgeInsets, cellWidth: cellSize.width, cellHeight: cellSize.height)
let layoutAttr = UICollectionViewLayoutAttributes(forCellWith: indexPath)
layoutAttr.frame = cellFrame
return layoutAttr
}
// 找到当前高度最短的列
private func findShortestColumnIndex() -> (row: Int, column: Int) {
var shortestColumn = 0
var shortestHeight = self.columnTopY[0]
for columnIndex in 1..<self.columnTopY.count {
let top = self.columnTopY[columnIndex]
if top < shortestHeight {
shortestHeight = top
shortestColumn = columnIndex
}
}
return (row: shortestColumn, column: shortestColumn)
}
// 返回collectionView的ContentSize
override var collectionViewContentSize: CGSize {
var contentHeight = self.columnTopY[0]
for columnIndex in 1..<self.columnTopY.count {
let top = self.columnTopY[columnIndex]
if top > contentHeight {
contentHeight = top
}
}
return CGSize(width: self.collectionView!.bounds.width, height: contentHeight + kDefaultEdgeInsets.bottom)
}
}
2.设置CollectionViewFlowLayout
在需要使用瀑布流的地方创建UICollectionView,并设置它的layout为我们创建的子类WaterfallFlowLayout:
let layout = WaterfallFlowLayout()
let collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
3.实现UICollectionViewDataSource协议
最后,我们需要实现UICollectionViewDataSource协议中的方法,为每个cell提供数据信息。这里以在线图片为例子:
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 100 // 假设有100张图片
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellID", for: indexPath)
// 此处应该设置cell的图片,这里省略
return cell
}
}
四、示例说明
这里提供两个简单的示例:
1.瀑布流展示图片
class ImageViewController: UIViewController {
lazy var collectionView: UICollectionView = {
let layout = WaterfallFlowLayout()
let collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
collectionView.delegate = self // 这里需要设置delegate,以实现点击图片查看大图的功能
collectionView.dataSource = self
collectionView.register(ImageCell.self, forCellWithReuseIdentifier: "cellID")
collectionView.backgroundColor = .white
return collectionView
}()
let images = ["1.jpg", "2.jpg", "3.jpg", "4.jpg", "5.jpg"] // 图片数组
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(collectionView)
}
}
extension ImageViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return images.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellID", for: indexPath) as! ImageCell
cell.imageView.image = UIImage(named: images[indexPath.item]) // 设置图片
return cell
}
}
extension ImageViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let imageVC = ImageDetailViewController()
imageVC.imageUrl = images[indexPath.row] // 传递图片地址
navigationController?.pushViewController(imageVC, animated: true)
}
}
2.瀑布流展示热门话题
class TopicViewController: UIViewController {
lazy var collectionView: UICollectionView = {
let layout = WaterfallFlowLayout()
let collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
collectionView.dataSource = self
collectionView.register(TopicCell.self, forCellWithReuseIdentifier: "cellID")
collectionView.backgroundColor = .white
return collectionView
}()
let topics = ["Apple WWDC 2022", "iOS 16新特性快报", "全球5G发展成果展览会", "Swift5入门必备", "WWF地球一小时环保公益活动"] // 话题数组
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(collectionView)
}
}
extension TopicViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return topics.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellID", for: indexPath) as! TopicCell
cell.topicLabel.text = topics[indexPath.item] // 设置话题名称
return cell
}
}
总结
通过继承UICollectionViewFlowLayout类以及实现UICollectionViewDelegateFlowLayout协议,我们可以快速实现一个瀑布流布局。同时,我们需要确定每个cell的大小和内容信息,以便在UICollectionViewDataSource协议中正确为每个cell提供数据信息。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:iOS瀑布流的简单实现(Swift) - Python技术站