可以左右滑動的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()
        
        // 創(chuàng)建和配置自定義布局
        let layout = CustomFlowLayout()
        layout.columnWidths = [100, 150, 80, 120, 200] // 示例寬度,根據(jù)需要設(shè)置
        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 必要方法實現(xiàn)
    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 1
    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return columnWidths.count * numberOfRows // 根據(jù)你的需要設(shè)置項數(shù)
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
        cell.backgroundColor = colors.randomElement() // 簡單地交替顏色以便區(qū)分單元格
        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 方法蚜枢,根據(jù)需要實現(xiàn)
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        // 單元格被點擊時的操作
        print("Selected cell at \(indexPath)")
//        changeLayout()
    }
    
    func changeLayout() {
        // 創(chuàng)建一個新的布局實例
        let newLayout = CustomFlowLayout()
        newLayout.columnWidths = [100, 100, 200, 100, 100] // 設(shè)置新的列寬
        newLayout.numberOfRows = numberOfRows
        newLayout.itemHeight = 50
        columnWidths = newLayout.columnWidths

        // 無動畫地更新布局
        collectionView.setCollectionViewLayout(newLayout, animated: false)
        
        // 如果你希望有動畫效果,可以設(shè)置 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 的布局計算
        // 確保不重復(fù)添加 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
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末颠黎,一起剝皮案震驚了整個濱河市脆霎,隨后出現(xiàn)的幾起案子总处,更是在濱河造成了極大的恐慌,老刑警劉巖睛蛛,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鹦马,死亡現(xiàn)場離奇詭異,居然都是意外死亡忆肾,警方通過查閱死者的電腦和手機荸频,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來客冈,“玉大人旭从,你說我怎么就攤上這事〕≈伲” “怎么了和悦?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長渠缕。 經(jīng)常有香客問我鸽素,道長,這世上最難降的妖魔是什么亦鳞? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任馍忽,我火速辦了婚禮,結(jié)果婚禮上燕差,老公的妹妹穿的比我還像新娘遭笋。我一直安慰自己,他們只是感情好谁不,可當(dāng)我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布坐梯。 她就那樣靜靜地躺著,像睡著了一般刹帕。 火紅的嫁衣襯著肌膚如雪吵血。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天偷溺,我揣著相機與錄音蹋辅,去河邊找鬼。 笑死挫掏,一個胖子當(dāng)著我的面吹牛侦另,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼褒傅,長吁一口氣:“原來是場噩夢啊……” “哼弃锐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起殿托,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤霹菊,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后支竹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體旋廷,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年礼搁,在試婚紗的時候發(fā)現(xiàn)自己被綠了饶碘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡馒吴,死狀恐怖扎运,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情募书,我是刑警寧澤绪囱,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布测蹲,位于F島的核電站莹捡,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏扣甲。R本人自食惡果不足惜篮赢,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望琉挖。 院中可真熱鬧启泣,春花似錦、人聲如沸示辈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽矾麻。三九已至纱耻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間险耀,已是汗流浹背弄喘。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留甩牺,地道東北人蘑志。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親急但。 傳聞我的和親對象是個殘疾皇子澎媒,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容