Post

Compositional Layout:將cell當積木組合排列

運用 UIKit 的 Compositional Layout 實作靈活排版與組合效果

Compositional Layout:將cell當積木組合排列

Compositional Layout:將cell當積木組合排列

運用 UIKit 的 Compositional Layout 實作靈活排版與組合效果

一般來說,在製作 CollectionView 時通常都是 Cell “一個個” 往右或往下堆疊,如果我要三個三個往右堆疊怎麼辦?又或者需求很奇葩,要一個大 Cell 後面接兩個小 Cell ?(巢狀結構)這樣怎麼辦

AppStore

AppStore

總不能直接坐三行 CollectionView吧?這時就可用 Compositional Layout

基本的 CollectionView 的 layout架構

基本的 CollectionView 的 layout架構

Compositional Layout 的架構

Compositional Layout 的架構

因為多了一個 group 物件,這個物件依照陣列方式塞入多個 Item,而 group 可以再依照排列方式:垂直 或 平行 的方式去排列,就可以達成很多排列效果

程式碼:Item 垂直相疊的橫向滾動

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
lazy var collectionViewLayout: UICollectionViewLayout = {
    
    // items
    let itemSize1 = NSCollectionLayoutSize(widthDimension: .absolute(110),heightDimension: .absolute(30))                                            
    let item1 = NSCollectionLayoutItem(layoutSize: itemSize1)
    
    let itemSize2 = NSCollectionLayoutSize(widthDimension: .absolute(110),heightDimension: .absolute(60))    
    let item2 = NSCollectionLayoutItem(layoutSize: itemSize2)
    
    let itemSize3 = NSCollectionLayoutSize(widthDimension: .absolute(110),heightDimension: .absolute(90))  
    let item3 = NSCollectionLayoutItem(layoutSize: itemSize3)   
    
    // group
    let groupSize = NSCollectionLayoutSize(widthDimension: .absolute(110),heightDimension: .absolute(190))
    let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitems: [item1, item2, item3])  
    group.interItemSpacing = .fixed(5)
    group.edgeSpacing = NSCollectionLayoutEdgeSpacing(leading: nil, top: nil, trailing: .fixed(5), bottom: nil)
    
    // section
    let section = NSCollectionLayoutSection(group: group)
    
    // fixed:指定固定的距離
    // flexible:指定一個可伸長的最小距離

    // header
    let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
                                        heightDimension: .absolute(40))
    let headerItem = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: headerSize,
                                                                 elementKind: UICollectionView.elementKindSectionHeader,
                                                                 alignment: .top,
                                                                 absoluteOffset: CGPoint(x: 0, y: -5))

    // footer
    let footerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
                                            heightDimension: .absolute(40))
    let footerItem = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: footerSize,
                                                                 elementKind: UICollectionView.elementKindSectionFooter,
                                                                 alignment: .bottom,
                                                                 absoluteOffset: CGPoint(x: 0, y: 5))

    // 將header指定給section
        section.boundarySupplementaryItems = [headerItem, footerItem]
    
    // 畫面滾動方式
    section.orthogonalScrollingBehavior = .continuous
    //        none:顧名思義,就是不會有垂直向的滾動(預設值)
    //        continuous:連續的滾動
    //        continuousGroupLeadingBoundary:連續的滾動,但會最後停在 group 的前緣
    //        paging:每次會滾動跟 CollectionView 一樣寬(或一樣高)的距離
    //        groupPaging:每次會滾動一個 group
    //        groupPagingCentered:每次會滾動一個 group,並且停在 group 置中的地方
    
    section.contentInsets = NSDirectionalEdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 5)
        
    // CompositionalLayout
    let layout = UICollectionViewCompositionalLayout(section: section)

    return layout
}

override func viewDidLoad() {
    super.viewDidLoad()

    myCV.collectionViewLayout = collectionViewLayout
    myCV.dataSource = self
    myCV.delegate = self
    myCV.register(myCollectionViewCell.self, forCellWithReuseIdentifier: myCollectionViewCell.identifier)
    myCV.register(UICollectionReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "HeaderView")
    myCV.register(UICollectionReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: "FooterView")
        
}

// MARK: - UICollectionViewDelegate

extension ViewController: UICollectionViewDelegate {
    
}

extension ViewController: UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 30
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "myCollectionViewCell", for: indexPath) as? myCollectionViewCell else {
            return UICollectionViewCell()
        }
        
        cell.backgroundColor = .systemBlue
        
        return cell
    }
    
    // 當forSupplementaryViewOfKind不同時產生對應的CollectionReusableView
    func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
        var reusableView = UICollectionReusableView()
        
        if kind == UICollectionView.elementKindSectionHeader {
            
            // Header
            reusableView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "HeaderView", for: indexPath)
            reusableView.backgroundColor = .gray
            
            let label = UILabel(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 40))
            label.textAlignment = .center
            label.text = "Header"
            label.textColor = .black
            reusableView.addSubview(label)
            
        }else if kind == UICollectionView.elementKindSectionFooter {
            
            // Footer
            reusableView = collectionView.dequeueReusableSupplementaryView(
                                            ofKind: UICollectionView.elementKindSectionFooter,
                                            withReuseIdentifier: "FooterView",
                                            for: indexPath)
            reusableView.backgroundColor = .cyan
            
            let label = UILabel(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 40))
            label.textAlignment = .center
            label.text = "Footer"
            label.textColor = UIColor.black
            reusableView.addSubview(label)   
        }
        return reusableView
    }
}
                                             
        
  • 4~11行是將每個 item 設置好大小,注意是用 NSCollectionLayoutSize
  • 15行是敘述將每個 item 放進的排列方式
  • 16行 裏面每個 item 互相的間隔, 記得這個要將這個間隔算近 group

效果:

Item 垂直相疊的橫向滾動(像AppStore)

Item 垂直相疊的橫向滾動(像AppStore)

程式碼:巢狀結構(就只列出不一樣的地方)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// items
let itemSize1 = NSCollectionLayoutSize(widthDimension: .absolute(110),heightDimension: .absolute(30))
let item1 = NSCollectionLayoutItem(layoutSize: itemSize1)

let itemSize2 = NSCollectionLayoutSize(widthDimension: .absolute(110),heightDimension: .absolute(60))
let item2 = NSCollectionLayoutItem(layoutSize: itemSize2)

let itemSize3 = NSCollectionLayoutSize(widthDimension: .absolute(110),heightDimension: .absolute(95))
let item3 = NSCollectionLayoutItem(layoutSize: itemSize3)

// subGroup
let subGroupSize = NSCollectionLayoutSize(widthDimension: .absolute(110),heightDimension: .absolute(95))

let subGroup = NSCollectionLayoutGroup.vertical(layoutSize: subGroupSize, subitems:  [item1, item2])

// item在group內的間距
subGroup.interItemSpacing = .fixed(5)

// group
let groupSize = NSCollectionLayoutSize(widthDimension: .absolute(225),heightDimension: .absolute(95))

let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item3, subGroup])

group.interItemSpacing = .fixed(5)

group.edgeSpacing = NSCollectionLayoutEdgeSpacing(leading: nil, top: nil, trailing: .fixed(5), bottom: nil)

效果:

巢狀Cell

巢狀Cell

這次學習紀錄參照:

關於 Header 跟 Footer 的設定是參照:

Post converted from Medium by ZMediumToMarkdown.

This post is licensed under CC BY 4.0 by the author.