可以左右滑动的collectionView

import UIKit

class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
    
    var collectionView: UICollectionView!
    let colors: [UIColor] = [.red, .green, .gray, .yellow, .orange, .purple, .brown, .blue, .cyan]
    
    var columnWidths = [CGFloat]()
    var numberOfRows = 30
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 创建和配置自定义布局
        let layout = CustomFlowLayout()
        layout.columnWidths = [100, 150, 80, 120, 200] // 示例宽度,根据需要设置
        layout.numberOfRows = numberOfRows
        layout.itemHeight = 50
//        layout.scrollDirection = .horizontal
//        layout.sectionHeadersPinToVisibleBounds = true
        columnWidths = layout.columnWidths

        // 初始化UICollectionView
        collectionView = UICollectionView(frame: self.view.bounds, collectionViewLayout: layout)
        collectionView.dataSource = self
        collectionView.delegate = self
        collectionView.bounces = false
        collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
        collectionView.register(CustomHeader.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: CustomHeader.reuseIdentifier)

        collectionView.backgroundColor = .white
        
        self.view.addSubview(collectionView)
    }
    
    // UICollectionViewDataSource 必要方法实现
    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 1
    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return columnWidths.count * numberOfRows // 根据你的需要设置项数
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
        cell.backgroundColor = colors.randomElement() // 简单地交替颜色以便区分单元格
        return cell
    }
    
    func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
        guard kind == UICollectionView.elementKindSectionHeader else {
            return UICollectionReusableView()
        }
        
        let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: CustomHeader.reuseIdentifier, for: indexPath) as! CustomHeader
        header.configure(with: "Section \(indexPath.section)")
        return header
    }

    // UICollectionViewDelegate 方法,根据需要实现
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        // 单元格被点击时的操作
        print("Selected cell at \(indexPath)")
//        changeLayout()
    }
    
    func changeLayout() {
        // 创建一个新的布局实例
        let newLayout = CustomFlowLayout()
        newLayout.columnWidths = [100, 100, 200, 100, 100] // 设置新的列宽
        newLayout.numberOfRows = numberOfRows
        newLayout.itemHeight = 50
        columnWidths = newLayout.columnWidths

        // 无动画地更新布局
        collectionView.setCollectionViewLayout(newLayout, animated: false)
        
        // 如果你希望有动画效果,可以设置 animated 为 true
        // collectionView.setCollectionViewLayout(newLayout, animated: true)
    }
    
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        guard let layout = collectionView.collectionViewLayout as? CustomFlowLayout else { return }
        if scrollView.contentOffset.x != layout.stickyHeaderXOffset {
            layout.invalidateLayout()
        }
    }
}


class CustomFlowLayout: UICollectionViewLayout {
    private var cache = [UICollectionViewLayoutAttributes]()
    private var headersCache = [Int: UICollectionViewLayoutAttributes]()  // Header cache
    
    var stickyHeaderXOffset: CGFloat = 0  // 用于保存固定 header 的 X 偏移量

    var columnWidths: [CGFloat] = [] // 每列的宽度
    var numberOfRows = 20
    var itemHeight: CGFloat = 50
    var headerWidth: CGFloat = 50  // Header 的宽度
    
    var totalHeight: CGFloat {
        itemHeight * CGFloat(numberOfRows)
    }

    override var collectionViewContentSize: CGSize {
        let width = columnWidths.reduce(0, +) // 所有列宽度之和
        let height = totalHeight // 总高度不包括 header
        return CGSize(width: width + headerWidth, height: height)
    }

    override func prepare() {
        guard let collectionView = collectionView else { return }
//        cache.removeAll()
//        headersCache.removeAll()

        // 计算 header 的位置
        stickyHeaderXOffset = collectionView.contentOffset.x
        
        // 其他 cell 的布局计算
        // 确保不重复添加 header 的布局属性
        if headersCache.isEmpty {
            let headerAttributes = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, with: IndexPath(item: 0, section: 0))
            headerAttributes.frame = CGRect(x: stickyHeaderXOffset, y: 0, width: headerWidth, height: totalHeight)
            headerAttributes.zIndex = 1024
            headersCache[0] = headerAttributes
        } else {
            // 只更新 X 位置
            if let headerAttributes = headersCache[0] {
                headerAttributes.frame.origin.x = stickyHeaderXOffset
            }
        }

//        var xOffset: CGFloat = headerWidth + collectionView.contentOffset.x // 开始布局 cell 的 x 偏移量
        var yOffset: CGFloat = 0  // y 偏移量

        guard cache.isEmpty else { return }
        for item in 0..<collectionView.numberOfItems(inSection: 0) {
            let column = item % columnWidths.count
            let indexPath = IndexPath(item: item, section: 0)
            let xOffset = columnWidths[0..<column].reduce(headerWidth, +) + collectionView.contentOffset.x
            let frame = CGRect(x: xOffset, y: yOffset, width: columnWidths[column], height: itemHeight)
            let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
            attributes.frame = frame
            cache.append(attributes)
            
            yOffset += (item + 1) % columnWidths.count == 0 ? itemHeight : 0
        }
    }

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        var visibleLayoutAttributes = [UICollectionViewLayoutAttributes]()

        // Add header attributes if in the rect
        if let headerAttributes = headersCache[0], rect.intersects(headerAttributes.frame) {
            visibleLayoutAttributes.append(headerAttributes)
        }

        // Add cell attributes if they intersect with the rect
        visibleLayoutAttributes.append(contentsOf: cache.filter { attributes in
            rect.intersects(attributes.frame)
        })

        return visibleLayoutAttributes
    }

    override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        return headersCache[indexPath.section]
    }
}




class CustomHeader: UICollectionReusableView {
    static let reuseIdentifier = "CustomHeader"

    private let titleLabel = UILabel()

    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .black
        setupViews()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setupViews()
    }
    
    private func setupViews() {
        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        addSubview(titleLabel)
        titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 10).isActive = true
        titleLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -10).isActive = true
        titleLabel.topAnchor.constraint(equalTo: topAnchor, constant: 10).isActive = true
        titleLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -10).isActive = true
        
        backgroundColor = .lightGray // Set the background color for the header
    }
    
    func configure(with title: String) {
        titleLabel.text = title
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容