Swift - 自定义UICollectionViewFlowLayout实现横向滑动
最近要实现collection横向滑动功能,看了很多代码,但感觉功能不太齐全,基本只实现了基础设置,一旦要求其他分区控制item大小就不行了,现在补充下,希望能帮到大家。
要实现横向滑动功能,首先我们要了解FlowLayout的实现
如图:
在collectionView默认设置下row的显示是这样的(layout.scrollDirection = .vertical),即竖向显示,上下拉动。通常情况下能满足我们大部分的业务需求,当我们需要做类似微信表情左右滑动功能,就不能用这个了。尝试设置属性layout.scrollDirection = .horizontal会出变成这样。如图:
并且按照分页滑动的时候回出现上页的边角,无法满足我们的需求。这时候我们就需要自定义FlowLayout,创建类WorkbenchCommonlyLayOut继承于UICollectionViewFlowLayout。
1、重写override func prepare() {}方法,这个方法主要用于计算控件布局。
2、重写func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {},这个方法主要用于返回rect中所有元素的布局属性
3、重新计算override var collectionViewContentSize: CGSize{ return CGSize(width: maxWidth, height: maxHeight) }如果不重新计算宽高,会出现最后一页显示不全的情况
下面上代码:(吐槽下:代码拷贝进来会自动出现**号,大家自动省略就行 我实在是不想删了)
var cols : Int = 0 //列
// 重新计算上下间距
var rewriteminimumInteritemSpacing: CGFloat = 0
var rewriteminimumLineSpacing: CGFloat = 0
// 重新计算上下左右间距
var rewriteTop: CGFloat = 0
var rewriteBottom: CGFloat = 0
var rewriteLeft: CGFloat = 0
var rewriteRight: CGFloat = 0
fileprivate lazy var cellAttrs : [UICollectionViewLayoutAttributes] = [UICollectionViewLayoutAttributes]()
fileprivate lazy var maxWidth : CGFloat = 0
fileprivate lazy var maxHeight: CGFloat = 0
**var** benchdelegate:WorkbenchCommonlyLayOutDelegate?
// 重写布局
**override** **func** prepare() {
**super**.prepare()
// 直接设置layout的值
sectionInset.top = rewriteTop
sectionInset.left = rewriteLeft
sectionInset.right = rewriteRight
sectionInset.bottom = rewriteBottom
minimumInteritemSpacing = rewriteminimumInteritemSpacing
minimumLineSpacing = rewriteminimumInteritemSpacing
//0.计算item的宽度和高度
itemSize.width = ((collectionView?.bounds.width)! - sectionInset.left - sectionInset.right - minimumInteritemSpacing * CGFloat(cols - 1)) / CGFloat(cols)
itemSize.height = ((collectionView?.bounds.height)! - sectionInset.top - sectionInset.bottom - minimumLineSpacing * CGFloat(rows - 1)) / CGFloat(rows)
//1.获取一共多少个组
**let** sectionCount = collectionView!.numberOfSections
//2.获取每个组中有多少个item
**var** prePageCount : Int = 0 //页数
**for** i **in** 0..<sectionCount {
**let** itemCount = collectionView!.numberOfItems(inSection: i)
**for** j **in** 0..<itemCount {
//2.1获取cell对应的indexPath把item和section对调(可以用系统的API看看效果就明白了)
**let** indexpath = IndexPath(item: j, section: i)
//2.2根据indexPath创建UICollectionViewLayoutAttributes
**let** attr = UICollectionViewLayoutAttributes(forCellWith: indexpath)
// 2.3.计算j在该组中第几页
**let** page = j / (cols * rows)
**let** index = j % (cols * rows)
//2.4设置attrs的frame
**let** itemY = sectionInset.top + (itemSize.height + minimumLineSpacing) * CGFloat(index / cols)
**let** itemX = CGFloat(prePageCount + page) * collectionView!.bounds.width + sectionInset.**left** + (itemSize.width + minimumInteritemSpacing) * CGFloat(index % cols)
attr.frame = CGRect(x: itemX, y: itemY, width: itemSize.width, height: itemSize.height)
\
//2.5加入到数组中
cellAttrs.append(attr)
}
prePageCount += (itemCount - 1) / (cols * rows) + 1
}
//计算最大宽度 CGFloat(70*indexline+10*(indexline-1)+20)
maxWidth = CGFloat(prePageCount) * collectionView!.bounds.width
maxHeight = CGFloat(rows * Int(itemSize.height) + Int(minimumLineSpacing) * (rows - 1) + Int(sectionInset.top) + Int(sectionInset.bottom))
benchdelegate!.collectionView(CGSize(width: maxWidth, height: maxHeight))
}
//返回rect中的所有的元素的布局属性
**override** **func** layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
**return** cellAttrs
}
//确定collectionView的所有内容的尺寸
**override** **var** collectionViewContentSize: CGSize{
**return** CGSize(width: maxWidth, height: maxHeight)
}
这时候我们已经完成基本的功能需求了, 初始化并设置值
**let** layout = WorkbenchCommonlyLayOut.init()
layout.benchdelegate = **self**
layout.rows = 5
layout.cols = 3
layout.rewriteTop = 10
layout.rewriteLeft = 15
layout.rewriteRight = 15
layout.rewriteBottom = 10
layout.rewriteminimumInteritemSpacing = 10
layout.rewriteminimumLineSpacing = 7
**let** indexline = 3
**let** collectView = UICollectionView.init(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height), collectionViewLayout: layout)
collectView.backgroundColor = AuColor.augroundColor
collectView.delegate = **self**
collectView.dataSource = **self**
collectView.showsHorizontalScrollIndicator = **false**
collectView.isPagingEnabled = **true**
collectView.isScrollEnabled = **true**
collectView.register(UINib.init(nibName: AUWorkbenchCommonlyCell.reuseIndentifier, bundle: Bundle.main), forCellWithReuseIdentifier: AUWorkbenchCommonlyCell.reuseIndentifier)
**return** collectView
}()
记住要实现delegate跟dataSource(偷懒下就不上代码了),最终效果图如下:
这个是左右滑动的本来想上个录制好的小视频,结果无法上传。
当然这样就完了么,当然不是,我们可以看到UICollectionViewFlowLayout是有代理的通过代理我们可以控制每个section中item的大小,如果按照上面的方法,是没办法实现的。
所以我们要在自定义的flowlayout写类似系统的代理,下面上代码
**protocol** WorkbenchCommonlyLayOutDelegate: AnyObject {
**func** collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
**func** collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets
**func** collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat
**func** collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat
**func** collectionView(_ size: CGSize)
}
然后在func prepare()方法中调用代理(就是上面实现的代码,放在获取indexpath下面就行)
// 通过代理获取值
// 获取item值
itemSize = (benchdelegate?.collectionView(**self**.collectionView!, layout: **self**, sizeForItemAt: indexpath))!
// 获取上下左右的UIEdgeInsets值
sectionInset = (benchdelegate?.collectionView(**self**.collectionView!, layout: **self**, insetForSectionAt: indexpath.section))!
// 获取上下间距
minimumLineSpacing = (benchdelegate?.collectionView(**self**.collectionView!, layout: **self**, minimumLineSpacingForSectionAt: indexpath.section))!
// 获取左右间距
minimumInteritemSpacing = (benchdelegate?.collectionView(**self**.collectionView!, layout: **self**, minimumInteritemSpacingForSectionAt: indexpath.section))!
然后实现代理WorkbenchCommonlyLayOutDelegate
最后再运行一次:如图
上面设置了2个section
// 重写layoutdelegate
**func** collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
**if** indexPath.section == 1 {
**return** CGSize(width: 50, height: 70)
}
**return** CGSize(width: CGFloat((Int(GK_SCREEN_WIDTH)-15*2-(5-1)*Int(rewriteminimumInteritemSpacing))/5), height: 70)
}
**func** collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
**return** UIEdgeInsets(top: 10, left: 15, bottom: 10, right: 15)
}
**func** collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
**return** rewriteminimumLineSpacing;
}
// 左右间距
**func** collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
**return** rewriteminimumInteritemSpacing;
}
**func** collectionView(_ size: CGSize) {
collectView.frame.size.height = size.height
}
小小的偷懒下,没有给行列做代理,有需求的话自己在WorkbenchCommonlyLayOutDelegate实现代理传入行列这样也能实现控制不同分区的行列。现在基本完成系统本身拥有的功能。有其他特殊需求的可以自行修改。