[重复造轮子] 输入面板布局

加号面板布局方式:
水平方向滑动,水平方向排列,垂直方向换行,水平方向翻页。

目标效果是像微信这样:
在这里插入图片描述

Demo + 实现链接

效果:

三个按钮:
在这里插入图片描述

很多按钮 第一页:
在这里插入图片描述

很多按钮 最后一页:
在这里插入图片描述

很多按钮 横屏之后,页数发生变化:

在这里插入图片描述

思路:主要是实现一种布局,重复劳动:

import UIKit
import SnapKit
  
/// 加号面板布局:水平方向滑动,item 折行排列
///
/// 内部会自动处理 safeAreaInsets、contentInsets
/// 暂不支持多个 section、补充视图等
class MyLayout: UICollectionViewLayout {
    /// will be accounted in contentSize
    var layoutInset: UIEdgeInsets = .zero
 
    var minimumInteritemSpacing: CGFloat = 25
    var maximumInteritemSpacing: CGFloat = 38
    var itemSize = CGSize(width: 60, height: 80)
    var lineSpacing: CGFloat = 25
    /// 这个属性的作用:如果外部想加一个 UIPageControl,然而屏幕旋转时每行展示的个数发生变化,会导致页数变化,
    /// 通过这个回调去改变 numberOfPages
    var layoutPrepareCompletion: ((_ pageCount: Int, _ currentPage: Int) -> Void)?
      
    private var arr = [UICollectionViewLayoutAttributes]()
    override func prepare() {
        arr.removeAll()
        guard let cv = collectionView else { return }
        let total = cv.numberOfItems(inSection: 0)
        // inset 修正:content inset 导致翻页不准确
        var inset = add(layoutInset, cv.contentInset)
        inset = add(inset, cv.safeAreaInsets)
        cv.contentInset = .zero
        cv.contentInsetAdjustmentBehavior = .never
        cv.scrollIndicatorInsets = UIEdgeInsets(top: 0, left: 0, bottom: cv.safeAreaInsets.bottom, right: 0)
        if #available(iOS 13.0, *) {
            cv.automaticallyAdjustsScrollIndicatorInsets = false
        }
        let w = cv.bounds.width - inset.left - inset.right
        let h = cv.bounds.height - inset.top - inset.bottom
        let itemW = itemSize.width, itemH = itemSize.height
        guard w >= itemW, h >= itemH, cv.numberOfSections == 1, total > 0 else {
            return
        }
        // will >= 1, e.g. Int(3.8) -> 3
        let maxCountPerLine = Int((minimumInteritemSpacing + w)
                                / (minimumInteritemSpacing + itemW))
        let minCountPerLine = Int((maximumInteritemSpacing + w)
                                    / (maximumInteritemSpacing + itemW))
        var countPerLine = (maxCountPerLine + minCountPerLine) / 2
        if countPerLine < maxCountPerLine { // e.g. 3.7 vs 4.1, use 4
            countPerLine += 1
        }
        var fixedX: CGFloat = -1
        var interItemSpacing: CGFloat = 0
        // 1 item per line, use fixed X
        if countPerLine == 1 {
            fixedX = (w - itemW) / 2
        } else { // use same interItemSpacing
            interItemSpacing = (w - CGFloat(countPerLine) * itemW)
                / CGFloat(countPerLine - 1)
        }
        let linesPerPage = Int((h + lineSpacing) / (itemH + lineSpacing))
        let expectedLines = ceil(CGFloat(total) / CGFloat(countPerLine))
        var pageCount: CGFloat = 1
        // only one line, make compact
        if total <= countPerLine {
            interItemSpacing = min(interItemSpacing, maximumInteritemSpacing)
        } else {
            pageCount = ceil(expectedLines / CGFloat(linesPerPage))
        }
         
        let useItemSpacing = fixedX < 0
        var currentLine = 0
        var pageStart: CGFloat = inset.left
        var lineStart: CGFloat = inset.top
        for i in 0 ..< total {
            let attr = UICollectionViewLayoutAttributes(forCellWith: IndexPath(item: i, section: 0))
            if useItemSpacing {
                attr.frame = CGRect(x: CGFloat(i % countPerLine) * (itemW + interItemSpacing) + pageStart,
                                    y: lineStart, width: itemW, height: itemH)
            } else {
                attr.frame = CGRect(x: fixedX + pageStart, y: lineStart,
                                    width: itemW, height: itemH)
            }
            arr.append(attr)
            
            if (i + 1) % countPerLine == 0 {
                currentLine += 1
                lineStart += itemH + lineSpacing
                if currentLine % linesPerPage == 0 {
                    currentLine = 0
                    lineStart = inset.top
                    pageStart += cv.bounds.width
                }
            }
        }
        
        var page = 0
        // 转屏后 修正 offset
        if let cell = cv.visibleCells.first, let item = cv.indexPath(for: cell) {
            page = item.item / (countPerLine * linesPerPage)
            let offset = CGPoint(x: page * Int(cv.bounds.width), y: 0)
           
            DispatchQueue.main.async {
                cv.setContentOffset(offset, animated: true)
            }
        }
     
        _contentSize = CGSize(width: cv.bounds.width * pageCount,
                              height: cv.bounds.height)
        DispatchQueue.main.async {
            self.layoutPrepareCompletion?(Int(pageCount), page)
        }
    }
     
    private var _contentSize = CGSize.zero
    override var collectionViewContentSize: CGSize {
        _contentSize
    }
    
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        arr.filter { rect.intersects($0.frame) }
    }
    
    private func add(_ inset1: UIEdgeInsets, _ inset2: UIEdgeInsets) -> UIEdgeInsets {
        UIEdgeInsets(top: inset1.top + inset2.top,
                     left: inset1.left + inset2.left,
                     bottom: inset1.bottom + inset2.bottom,
                     right: inset1.right + inset2.right)
    }
    
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值