UICollectionView实现标签视图 swift
UICollectionView的基础使用。
https://www.jianshu.com/p/34ab4bba228b
一定要切记,下面三个代理。不要搞混了。
UICollectionViewDataSource
extension SearchHistoryView: UICollectionViewDataSource { func numberOfSections(in collectionView: UICollectionView) -> Int { return 1 } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return self.dataSource.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MyCollectionViewCell", for: indexPath) as! MyCollectionViewCell cell.textLabel.text = self.dataSource[indexPath.row] return cell } func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { if kind == UICollectionView.elementKindSectionHeader { let header = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "header", for: indexPath) return header } else if kind == UICollectionView.elementKindSectionFooter { let footer = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: "footer", for: indexPath) return footer } return UICollectionReusableView() } }
UICollectionViewDelegate
extension SearchHistoryView: UICollectionViewDelegate { func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { let section = indexPath.section } }
UICollectionViewDelegateFlowLayout 这个代理,一定要注意!我就是以为下面里面的方法,都是 UICollectionViewDelegate 中的,导致布局宽高位置,啥的,调试了好久好久。
extension SearchHistoryView: UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { let titleString = dataSource[indexPath.row] let size = titleString.boundingRect(with: CGSize(width: kScreenWidth, height: kScreenHeight), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font : UIFont.systemFont(ofSize: 14)], context: nil) return CGSize(width: size.width + 24.0, height: 30) } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) } //item上下行间距 func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { return 12 } //item左右间距 func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { return 12 } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { return CGSize.init(width: kScreenWidth, height: 0.001) } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize { return CGSize.init(width: kScreenWidth, height: 0.001) } }
开发完之后,系统默认的为居中展示,下面的效果
期望是,居左对齐,下面的效果
参照下面的帖子,完成了居左对齐的需求样式
https://www.jianshu.com/p/e1d8b51fc2b9
Swift 5 UICollectionView中cell的对齐方法(重写flowlayout)
最终其他代码:
初始化 UICollectionView
class SearchHistoryView: UIView { lazy var collectionView = self.createCollectionView() private var dataSource: [String] = [] convenience init() { self.init(frame: CGRectZero) self.addSubview(self.collectionView) self.collectionView.snp.makeConstraints { make in make.top.equalToSuperview().offset(16) make.left.equalToSuperview().offset(24) make.right.equalToSuperview().offset(-24) make.bottom.equalToSuperview().offset(-kTabbarSafeBottomMargin) } } func updateData() { self.dataSource = ["林俊ddd顶顶顶顶杰","张学友","的的","陶的点点滴滴等等喆","王力宏","王菲","Taylor swift","周杰伦","owl city","汪苏泷","许嵩","李代沫"] self.collectionView.reloadData() } func createCollectionView() -> UICollectionView { let layout = AlignFlowLayout() layout.scrollDirection = .vertical //设置方向 layout.direction = .start //行间距 // layout.minimumLineSpacing = 0 // //左右间距 // layout.minimumInteritemSpacing = 22 // //设置边界的填充距离 // flowLayout.sectionInset = UIEdgeInsets.init(top: 10, left: 10, bottom: 10, right: 10) layout.register(CustomDecorationView.self, forDecorationViewOfKind: kCustomDecorationViewKind) let view = UICollectionView(frame: .zero, collectionViewLayout: layout) view.backgroundColor = .white view.dataSource = self view.delegate = self // view.contentInsetAdjustmentBehavior = .never view.showsVerticalScrollIndicator = false view.register(MyCollectionViewCell.self, forCellWithReuseIdentifier: "MyCollectionViewCell") view.register(CustomHeader.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "header") view.register(CustomFooter.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: "footer") return view } }
标签样式cell
class MyCollectionViewCell: UICollectionViewCell { let textLabel = UILabel() override init(frame: CGRect) { super.init(frame: frame) self.backgroundColor = UIColor(hexString: "#F5F7FA") self.layer.cornerRadius = 4 self.layer.masksToBounds = true // 配置 textLabel self.addSubview(textLabel) textLabel.textColor = UIColor(hexString: "#323233") textLabel.font = kFont(13) textLabel.textAlignment = .center self.textLabel.snp.makeConstraints { make in make.top.bottom.equalToSuperview() make.left.equalToSuperview().offset(12) make.right.equalToSuperview().offset(-12) } } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } }
为解决cell左对齐的代码
class CustomDecorationView: UICollectionReusableView { override init(frame: CGRect) { super.init(frame: frame) self.backgroundColor = UIColor.white } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } } class CustomHeader: UICollectionReusableView { override init(frame: CGRect) { super.init(frame: frame) self.backgroundColor = UIColor.orange } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } } class CustomFooter: UICollectionReusableView { override init(frame: CGRect) { super.init(frame: frame) self.backgroundColor = UIColor.green } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } }
AlignFlowLayout.swift里的代码,就是左对齐问题,参考
https://www.jianshu.com/p/e1d8b51fc2b9 里的源代码。可以去以上链接中找到此类,复制过去就行
// // AlignFlowLayout.swift // CollectionViewFlowLayout // // Created by user on 2021/2/5. // import UIKit let kCustomDecorationViewKind = "CustomDecorationView" enum AlignDirection { case start, end, dataEnd, center, auto } protocol AlignDelegateFlowLayout: UICollectionViewDelegateFlowLayout { //代理方法, 返回collectionView内容的尺寸 func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, collectionViewContentSize contentSize: CGSize) } class AlignFlowLayout: UICollectionViewFlowLayout { /// 默认自动对齐 var direction: AlignDirection = .auto /// 是否靠边对齐 var isFollow: Bool = false /// 所有cell的布局属性 private var layoutAttributes: [UICollectionViewLayoutAttributes] = [] /// 每一行cell的布局属性 private var layoutLine: [UICollectionViewLayoutAttributes] = [] /// 滚动范围 private var contentSize: CGSize = CGSize.zero override var collectionViewContentSize: CGSize { return contentSize } override func prepare() { super.prepare() // 清空之前的布局属性 layoutAttributes.removeAll() layoutLine.removeAll() if let collection = collectionView { // 获取分组个数 let sections = collection.numberOfSections // 遍历分组 for i in 0..<sections { // 获取组内元素个数 let rows = collection.numberOfItems(inSection: i) // 获取Header的布局属性,‘UICollectionView.elementKindSectionHeader’ 是注册头部视图的字符串,可以替换成自己注册的 if let layoutAttr = layoutAttributesForSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, at: IndexPath.init(row: 0, section: i)) { layoutAttributes.append(layoutAttr) } // 遍历获取组内每个元素的布局属性 for j in 0..<rows { if let layoutAttr = layoutAttributesForItem(at: IndexPath(row: j, section: i)) { layoutAttributes.append(layoutAttr) } } // 获取Footer的布局属性,‘UICollectionView.elementKindSectionFooter’ 是注册脚部视图的字符串,可以替换成自己注册的 if let layoutAttr = layoutAttributesForSupplementaryView(ofKind: UICollectionView.elementKindSectionFooter, at: IndexPath(row: 0, section: i)) { layoutAttributes.append(layoutAttr) } // 添加装饰视图支持,内容需要自定义 // MARK: DecorationView Example if let layoutAttr = layoutAttributesForDecorationView(ofKind: kCustomDecorationViewKind, at: IndexPath(row: 0, section: i)) { layoutAttributes.append(layoutAttr) } } } if let collection = self.collectionView { var contentWidth: CGFloat = 0, contentHeight: CGFloat = 0 // 逆向遍历 for item in layoutAttributes.reversed() { // 判断最后一个元素是Footer还是Item, 忽略DecorationView if item.representedElementCategory == .cell { contentWidth = item.frame.maxX + sectionInset.right contentHeight = item.frame.maxY + sectionInset.bottom break } else if item.representedElementCategory == .supplementaryView { contentWidth = item.frame.maxX contentHeight = item.frame.maxY break } } // 设置内容尺寸 switch scrollDirection { case .horizontal: contentSize = CGSize(width: contentWidth, height: collection.bounds.height) case .vertical: contentSize = CGSize(width: collection.bounds.width, height: contentHeight) default: contentSize = collection.bounds.size } if let alignDelegate = collection.delegate as? AlignDelegateFlowLayout { alignDelegate.collectionView(collection, layout: self, collectionViewContentSize: contentSize) } } } override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { if let attribute = super.layoutAttributesForItem(at: indexPath) { // 判断滚动方向 switch scrollDirection { case .vertical: // 竖直滚动,判断对齐方向 switch direction { case .start, .end, .center: // 左对齐、右对齐、中间对齐有部分相同的计算步骤 if let last = layoutAttributes.last, let collection = self.collectionView { if last.representedElementCategory == .cell { if last.frame.maxX + minimumInteritemSpacing + attribute.frame.width + sectionInset.right < collection.bounds.width { // 同一行显示 attribute.ex_x = last.frame.maxX + minimumInteritemSpacing } else { // 下一行显示 attribute.ex_x = sectionInset.left } // 判断是否处于跟随状态 if isFollow { // 获取同组的布局属性 let filter = layoutAttributes.filter { (item) -> Bool in return item.indexPath.section == attribute.indexPath.section && item.representedElementCategory == .cell } if filter.isEmpty { // 如果没有同组布局属性,则现有布局属性为该组的第一个布局属性 attribute.ex_y = last.frame.maxY + self.sectionInset.top } else { // 如果有同组布局属性,遍历同组cell,获取cell的最小maxY,记录该cell的minX值,获取同组cell的maxX值 var minY: CGFloat = -1, minX: CGFloat = 0, maxX: CGFloat = 0 _ = filter.map({ (item) in if item.frame.maxY < minY || minY == -1 { let sameX = filter.filter { (sameItem) -> Bool in return sameItem != item && sameItem.frame.minX == item.frame.minX && sameItem.frame.maxY > item.frame.maxY } if sameX.isEmpty { minY = item.frame.maxY minX = item.frame.minX } } if item.frame.maxX > maxX { maxX = item.frame.maxX } }) // 判断直接添加此cell到collectionView的下方是否越界 if maxX + minimumInteritemSpacing + attribute.frame.width + sectionInset.right > collection.bounds.width { // 越界,采用跟随坐标 attribute.ex_x = minX attribute.ex_y = minY + minimumLineSpacing } else { // 不越界,右方直接添加 attribute.ex_x = last.frame.maxX + minimumInteritemSpacing attribute.ex_y = last.frame.minY } } } else { // 不处于跟随状态下,切换行时会调整上一列的布局属性 if attribute.frame.minX < last.frame.minX { reloadlayoutAttributes() } } } else if last.representedElementCategory == .supplementaryView { attribute.ex_x = self.sectionInset.left // 如果上一个布局属性是属于头部或者尾部的,当前布局属性需要增加组内填充距离 if isFollow { attribute.ex_y = last.frame.maxY + self.sectionInset.top } } } else if isFollow { // 没有上一个布局属性,当前是第一个cell,直接设置x坐标 attribute.ex_y = self.sectionInset.top } // 添加进当前行 layoutLine.append(attribute) // 判断当前元素是当前组的最后一个元素,重置当前行的布局属性 if let items = collectionView?.numberOfItems(inSection: indexPath.section), indexPath.row == items - 1 { reloadlayoutAttributes() } case .dataEnd: // 右起显示需要获取collectionView显示区域的宽度 if let collection = collectionView { if isFollow { if let last = layoutAttributes.last { if last.representedElementCategory == .cell { var minY: CGFloat = -1, minX: CGFloat = 0, leftX: CGFloat = -1 _ = layoutAttributes.map { (item) in if item.frame.maxY < minY || minY == -1 { let sameX = layoutAttributes.filter { (sameItem) -> Bool in return sameItem != item && sameItem.frame.minX == item.frame.minX && sameItem.frame.maxY > item.frame.maxY } if sameX.isEmpty { minX = item.frame.minX minY = item.frame.maxY } } if item.frame.minX < leftX || leftX == -1 { leftX = item.frame.minX } } if leftX - minimumInteritemSpacing - attribute.frame.width - sectionInset.left < 0 { // 越界,切换行显示 attribute.ex_x = minX attribute.ex_y = minY + minimumInteritemSpacing } else { // 不越界,左边方直接添加 attribute.ex_x = last.frame.minX - minimumInteritemSpacing - attribute.frame.width attribute.ex_y = last.frame.minY } } else if last.representedElementCategory == .supplementaryView { attribute.ex_x = collection.bounds.width - sectionInset.right - attribute.frame.width attribute.ex_y = last.frame.maxY + sectionInset.top } } else { attribute.ex_x = collection.bounds.width - sectionInset.right - attribute.frame.width attribute.ex_y = sectionInset.top } } else { if let last = layoutAttributes.last, attribute.frame.minY == last.frame.minY { // 同行显示,向左逐步显示 attribute.ex_x = last.frame.minX - minimumInteritemSpacing - attribute.frame.width } else { // 下一行显示 attribute.ex_x = collection.bounds.width - sectionInset.right - attribute.frame.width } } } default: break } case .horizontal: switch direction { case .start, .center, .end: // 获取上一个布局属性 if let last = layoutAttributes.last, let collection = self.collectionView { // 判断是不是cell if last.representedElementCategory == .cell { if last.frame.maxY + minimumInteritemSpacing + attribute.frame.height + sectionInset.bottom < collection.bounds.height { // 同一列显示 attribute.ex_y = last.frame.maxY + minimumInteritemSpacing } else { // 下一列显示 attribute.ex_y = sectionInset.top } // 判断是否处于跟随状态 if isFollow { // 获取同组的布局属性 let filter = layoutAttributes.filter { (item) -> Bool in return item.indexPath.section == attribute.indexPath.section && item.representedElementCategory == .cell } if filter.isEmpty { // 如果没有同组布局属性,则现有布局属性为该组的第一个布局属性 attribute.ex_x = last.frame.maxX + self.sectionInset.left } else { // 如果有同组布局属性,遍历同组cell,获取cell的最小maxX,记录该cell的minY值,获取同组cell的maxY值 var minX: CGFloat = -1, minY: CGFloat = 0, maxY: CGFloat = 0 _ = filter.map({ (item) in if item.frame.maxX < minX || minX == -1 { let sameY = filter.filter { (sameItem) -> Bool in return sameItem != item && sameItem.frame.minY == item.frame.minY && sameItem.frame.maxX > item.frame.maxX } if sameY.isEmpty { minX = item.frame.maxX minY = item.frame.minY } } if item.frame.maxY > maxY { maxY = item.frame.maxY } }) // 判断直接添加此cell到collectionView的下方是否越界 if maxY + minimumInteritemSpacing + attribute.frame.height + sectionInset.bottom > collection.bounds.height { // 越界,采用跟随坐标 attribute.ex_x = minX + minimumLineSpacing attribute.ex_y = minY } else { // 不越界,下方直接添加 attribute.ex_x = last.frame.minX attribute.ex_y = last.frame.maxY + minimumInteritemSpacing } } } else { // 不处于跟随状态下,切换列时会调整上一列的布局属性 if attribute.frame.minX <= last.frame.minX { reloadlayoutAttributes() } } } else if last.representedElementCategory == .supplementaryView { attribute.ex_y = self.sectionInset.top // 如果上一个布局属性是属于头部或者尾部的,当前布局属性需要增加组内填充距离 if isFollow { attribute.ex_x = last.frame.maxX + self.sectionInset.left } } } else if isFollow { // 没有上一个布局属性,当前是第一个cell,直接设置x坐标 attribute.ex_x = self.sectionInset.left } // 添加进当前列 layoutLine.append(attribute) // 判断当前元素是当前组的最后一个元素,重置当前列的布局属性 if let items = collectionView?.numberOfItems(inSection: indexPath.section), indexPath.row == items - 1 { reloadlayoutAttributes() } case .dataEnd: // 下起显示需要获取collectionView显示区域的高度 if let collection = collectionView { if isFollow { if let last = layoutAttributes.last { if last.representedElementCategory == .cell { var minX: CGFloat = -1, minY: CGFloat = 0, topY: CGFloat = -1 _ = layoutAttributes.map { (item) in if item.frame.maxX < minX || minX == -1 { let sameY = layoutAttributes.filter { (sameItem) -> Bool in return sameItem != item && sameItem.frame.minY == item.frame.minY && sameItem.frame.maxX > item.frame.maxX } if sameY.isEmpty { minX = item.frame.maxX minY = item.frame.minY } } if item.frame.minY < topY || topY == -1 { topY = item.frame.minY } } if topY - minimumInteritemSpacing - attribute.frame.height - sectionInset.top < 0 { // 越界,切换列显示 attribute.ex_x = minX + minimumLineSpacing attribute.ex_y = minY } else { // 不越界,上方直接添加 attribute.ex_x = last.frame.minX attribute.ex_y = last.frame.minY - minimumInteritemSpacing - attribute.frame.height } } else if last.representedElementCategory == .supplementaryView { attribute.ex_x = last.frame.maxX + sectionInset.left attribute.ex_y = collection.bounds.height - sectionInset.bottom - attribute.frame.height } } else { attribute.ex_x = sectionInset.left attribute.ex_y = collection.bounds.height - sectionInset.bottom - attribute.frame.height } } else { if let last = layoutAttributes.last, last.representedElementCategory == .cell, attribute.frame.minX < last.frame.maxX { // 同列显示,向上逐步显示 attribute.ex_y = last.frame.minY - minimumInteritemSpacing - attribute.frame.height } else { // 下一行显示 attribute.ex_y = collection.bounds.height - sectionInset.bottom - attribute.frame.height } } } default: break } default: break } // 返回新的布局属性 return attribute } // 默认布局,返回原始布局属性 return super.layoutAttributesForItem(at: indexPath) } override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { // 获取头部或尾部视图的布局属性 let attribute = super.layoutAttributesForSupplementaryView(ofKind: elementKind, at: indexPath) if elementKind == UICollectionView.elementKindSectionHeader { //TODO: 第一组的头部暂时用不到修改属性,有需要可以自己修改 if indexPath.section > 0 { var max: CGFloat = 0 // 获取上一组的布局属性 let filter = layoutAttributes.filter { (item) -> Bool in return item.indexPath.section == indexPath.section - 1 } // 判断上一组布局属性是否为空,如果为空暂时不需要修改 if !filter.isEmpty { // 如果上一组的布局属性不为空,获取新坐标 if scrollDirection == .horizontal { _ = filter.map({ (item) in if item.frame.maxX > max { max = item.frame.maxX } }) attribute?.ex_x = max } else if scrollDirection == .vertical { _ = filter.map({ (item) in if item.frame.maxY > max { max = item.frame.maxY } }) attribute?.ex_y = max } return attribute } } } else if elementKind == UICollectionView.elementKindSectionFooter { var max: CGFloat = 0 // 获取同一组cell的布局属性 let filter = layoutAttributes.filter { (item) -> Bool in return item.indexPath.section == indexPath.section && item.representedElementCategory == .cell } if !filter.isEmpty { // 根据同一组cell的边距来修改footer的位置 if scrollDirection == .horizontal { _ = filter.map({ (item) in if item.frame.maxX > max { max = item.frame.maxX } }) attribute?.ex_x = max + sectionInset.right } else if scrollDirection == .vertical { _ = filter.map({ (item) in if item.frame.maxY > max { max = item.frame.maxY } }) attribute?.ex_y = max + sectionInset.bottom } return attribute } } return attribute } override func layoutAttributesForDecorationView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { let attribute = UICollectionViewLayoutAttributes(forDecorationViewOfKind: elementKind, with: indexPath) let filter = layoutAttributes.filter { (item) -> Bool in return item.representedElementCategory == .cell && item.indexPath.section == indexPath.section } if !filter.isEmpty { var minX: CGFloat = -1, maxX: CGFloat = 0, minY: CGFloat = -1, maxY: CGFloat = 0 _ = filter.map({ (item) in if item.frame.minX < minX || minX < 0 { minX = item.frame.minX } if item.frame.maxX > maxX { maxX = item.frame.maxX } if item.frame.minY < minY || minY < 0 { minY = item.frame.minY } if item.frame.maxY > maxY { maxY = item.frame.maxY } }) attribute.frame = CGRect(x: minX - 5, y: minY - 5, width: maxX - minX + 10, height: maxY - minY + 10) attribute.zIndex = -1 } return attribute } //重新绘制布局 func reloadlayoutAttributes() { if layoutLine.count == 0 {return} //防止越界 if direction == .end || direction == .center, let collection = collectionView { //计算填充比例, rightFlow为1, center为0.5 let scale: CGFloat = direction == .end ? 1 : 0.5 if isFollow, let first = layoutLine.first { switch scrollDirection { case .vertical: let firstArr = layoutLine.filter { (item) -> Bool in return item.frame.minY == first.frame.minY } var width = CGFloat(firstArr.count - 1) * minimumInteritemSpacing for item in firstArr { width += item.frame.width } let space = (collection.bounds.width - width - sectionInset.left - sectionInset.right) * scale for item in firstArr { let sameArr = layoutLine.filter { (sameItem) -> Bool in return sameItem.frame.minX == item.frame.minX } for sameItem in sameArr { sameItem.ex_x += space } } case .horizontal: let firstArr = layoutLine.filter { (item) -> Bool in return item.frame.minX == first.frame.minX } var height = CGFloat(firstArr.count - 1) * minimumInteritemSpacing for item in firstArr { height += item.frame.height } let space = (collection.bounds.height - height - sectionInset.top - sectionInset.bottom) * scale for item in firstArr { let sameArr = layoutLine.filter { (sameItem) -> Bool in return sameItem.frame.minY == item.frame.minY } for sameItem in sameArr { sameItem.ex_y += space } } default: break } } else { if let last = layoutLine.last { //重新绘制布局有右对齐和居中对齐两种 switch scrollDirection { case .vertical: let space = (collection.bounds.width - last.frame.maxX - sectionInset.right) * scale for layout in layoutLine { layout.ex_x += space } case .horizontal: let space = (collection.bounds.height - last.frame.maxY - sectionInset.bottom) * scale for layout in layoutLine { layout.ex_y += space } default: break } } } } layoutLine.removeAll() } override func layoutAttributesForElements(in rect:CGRect) -> [UICollectionViewLayoutAttributes]{ return layoutAttributes } } extension UICollectionViewLayoutAttributes { var ex_x: CGFloat { set { var newFrame = frame newFrame.origin.x = newValue frame = newFrame } get { return frame.origin.x } } var ex_y: CGFloat { set { var newFrame = frame newFrame.origin.y = newValue frame = newFrame } get { return frame.origin.y } } }
在北京的灯中,有一盏是我家的。这个梦何时可以实现?哪怕微微亮。北京就像魔鬼训练营,有能力的留,没能力的走……
分类:
others
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库