Swift - 让CollectionView里的Section分别设置不同的背景色

时间:2024-04-01 12:39:53

http://www.hangge.com/blog/cache/detail_1844.html

我们知道想要给 UICollectionView 设置背景色只需要通过 backgroundColor 属性即可。但如果想让不同的 Section (分区)能显示不同的背景颜色,UICollectionView 本身就没有提供相关的属性或方法了。

要实现这个效果,需要我们通过自定义布局来实现,下面通过样例进行演示。
 

1,实现原理

(1)想通过自定义布局实现 Section 背景色,需要创建如下三个类的子类:

  • 继承 UICollectionReusableView 来自定义一个装饰视图(Decoration 视图),用来作为各个分组的背景视图(section's background view)。
  • 继承 UICollectionViewLayoutAttributes 来自定义一个新的布局属性,里面添加一个backgroundColor 属性,用来表示 Section 的背景颜色。
  • 继承 UICollectionViewFlowLayout(默认的 Flow 布局)来自定义一个新布局,在这里我们会计算及返回各个分组背景视图的布局属性(位置、尺寸、颜色)

(2)为方便使用我们还要新增一个协议方法,使得 section 背景色可以在外面通过数据源来设置(就像设置 Cell 视图那样)

 

2,效果图

通过自定义布局,Collection View 里每个 section 都显示不同颜色的背景。

Swift - 让CollectionView里的Section分别设置不同的背景色

 

3,样例代码

(1)自定义布局(SectionBgCollectionViewLayout.swift)

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

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

import UIKit

 

//表示我们自定义的分区背景(装饰视图)

private let SectionBg "SectionBgCollectionReusableView"

 

//增加自己的协议方法,使其可以像cell那样根据数据源来设置section背景色

protocol SectionBgCollectionViewDelegateUICollectionViewDelegateFlowLayout {

    func collectionView(_ collectionView: UICollectionView,

                        layout collectionViewLayout: UICollectionViewLayout,

                        backgroundColorForSectionAt section: Int) -> UIColor

}

 

//定义一个UICollectionViewLayoutAttributes子类作为section背景的布局属性,

//(在这里定义一个backgroundColor属性表示Section背景色)

private class SectionBgCollectionViewLayoutAttributesUICollectionViewLayoutAttributes {

     

    //背景色

    var backgroundColor = UIColor.white

     

    //所定义属性的类型需要遵从 NSCopying 协议

    override func copy(with zone: NSZone? = nil) -> Any {

        let copy super.copy(with: zone) asSectionBgCollectionViewLayoutAttributes

        copy.backgroundColor = self.backgroundColor

        return copy

    }

     

    //所定义属性的类型还要实现相等判断方法(isEqual)

    override func isEqual(_ object: Any?) -> Bool {

        guard let rhs = object asSectionBgCollectionViewLayoutAttributes else {

            return false

        }

         

        if !self.backgroundColor.isEqual(rhs.backgroundColor) {

            return false

        }

        return super.isEqual(object)

    }

}

 

//继承UICollectionReusableView来自定义一个装饰视图(Decoration 视图),用来作为Section背景

private class SectionBgCollectionReusableViewUICollectionReusableView {

     

    //通过apply方法让自定义属性生效

    override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {

        super.apply(layoutAttributes)

         

        guard let attr = layoutAttributes asSectionBgCollectionViewLayoutAttributes else

        {

            return

        }

         

        self.backgroundColor = attr.backgroundColor

    }

}

 

//自定义布局(继承系统内置的 Flow 布局)

class SectionBgCollectionViewLayoutUICollectionViewFlowLayout {

     

    //保存所有自定义的section背景的布局属性

    private var decorationViewAttrs: [UICollectionViewLayoutAttributes] = []

     

    override init() {

        super.init()

         

        setup()

    }

     

    required init?(coder aDecoder: NSCoder) {

        super.init(coder: aDecoder)

    }

     

    override func awakeFromNib() {

        super.awakeFromNib()

         

        setup()

    }

     

    //初始化时进行一些注册操作

    func setup() {

        //注册我们自定义用来作为Section背景的 Decoration 视图

        self.register(SectionBgCollectionReusableView.classForCoder(),

                      forDecorationViewOfKind: SectionBg)

    }

     

    //对一些布局的准备操作放在这里

    override func prepare() {

        super.prepare()

         

        //如果collectionView当前没有分区,或者未实现相关的代理则直接退出

        guard let numberOfSections = self.collectionView?.numberOfSections,

            let delegate = self.collectionView?.delegate

                asSectionBgCollectionViewDelegate

            else {

                return

        }

         

        //先删除原来的section背景的布局属性

        self.decorationViewAttrs.removeAll()

         

        //分别计算每个section背景的布局属性

        for section in 0..<numberOfSections {

            //获取该section下第一个,以及最后一个item的布局属性

            guard let numberOfItems = self.collectionView?.numberOfItems(inSection:

                section),

                numberOfItems > 0,

                let firstItem = self.layoutAttributesForItem(at:

                    IndexPath(item: 0, section: section)),

                let lastItem = self.layoutAttributesForItem(at:

                    IndexPath(item: numberOfItems - 1, section: section))

                else {

                    continue

            }

             

            //获取该section的内边距

            var sectionInset = self.sectionInset

            if let inset = delegate.collectionView?(self.collectionView!,

                                    layout: self, insetForSectionAt: section) {

                sectionInset = inset

            }

             

            //计算得到该section实际的位置

            var sectionFrame = firstItem.frame.union(lastItem.frame)

            sectionFrame.origin.x = 0

            sectionFrame.origin.y -= sectionInset.top

             

            //计算得到该section实际的尺寸

            if self.scrollDirection == .horizontal {

                sectionFrame.size.width += sectionInset.left + sectionInset.right

                sectionFrame.size.height = self.collectionView!.frame.height

            else {

                sectionFrame.size.width = self.collectionView!.frame.width

                sectionFrame.size.height += sectionInset.top + sectionInset.bottom

            }

             

            //更具上面的结果计算section背景的布局属性

            let attr = SectionBgCollectionViewLayoutAttributes(

                forDecorationViewOfKind: SectionBg,

                with: IndexPath(item: 0, section: section))

            attr.frame = sectionFrame

            attr.zIndex = -1

            //通过代理方法获取该section背景使用的颜色

            attr.backgroundColor = delegate.collectionView(self.collectionView!,

                           layout: self, backgroundColorForSectionAt: section)

             

            //将该section背景的布局属性保存起来

            self.decorationViewAttrs.append(attr)

        }

    }

     

    //返回rect范围下所有元素的布局属性(这里我们将自定义的section背景视图的布局属性也一起返回)

    override func layoutAttributesForElements(in rect: CGRect)

        -> [UICollectionViewLayoutAttributes]? {

        var attrs = super.layoutAttributesForElements(in: rect)

        attrs?.append(contentsOf: self.decorationViewAttrs.filter {

            return rect.intersects($0.frame)

        })

        return attrs

    }

     

    //返回对应于indexPath的位置的Decoration视图的布局属性

    override func layoutAttributesForDecorationView(ofKind elementKind: String,

                at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {

        //如果是我们自定义的Decoration视图(section背景),则返回它的布局属性

        if elementKind == SectionBg {

            return self.decorationViewAttrs[indexPath.section]

        }

        return super.layoutAttributesForDecorationView(ofKind: elementKind,

                                                       at: indexPath)

    }

}


(2)接着打开 StoryBoard,将 Collection View 的 Layout 设置成我们自定义的布局。

   Swift - 让CollectionView里的Section分别设置不同的背景色    Swift - 让CollectionView里的Section分别设置不同的背景色

 

(3)最后让 Collection View Controller 实现我们定义的 SectionBgCollectionViewDelegate 协议,返回每个 Section 的背景色即可。

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

import UIKit

 

//重用单元格identifier

private let cellIdentifier = "Cell"

 

class CollectionViewControllerUICollectionViewControllerSectionBgCollectionViewDelegate

{

    override func viewDidLoad() {

        super.viewDidLoad()

 

        //注册可重用的单元格

        self.collectionView!.register(UICollectionViewCell.self,

                                      forCellWithReuseIdentifier: cellIdentifier)

         

        //将collectionView背景色设为白色

        self.collectionView?.backgroundColor = UIColor.white

    }

 

    override func didReceiveMemoryWarning() {

        super.didReceiveMemoryWarning()

    }

 

    //返回分区数

    override func numberOfSections(in collectionView: UICollectionView) -> Int {

        return 4

    }

 

    //返回每个分区下单元格个数

    override func collectionView(_ collectionView: UICollectionView,

                                 numberOfItemsInSection section: Int) -> Int {

        //奇数section里有8个单元格,偶数section里有4个单元格

        return (section % 2 == 1) ? 4 : 8

    }

 

    //返回每个单元格视图

    override func collectionView(_ collectionView: UICollectionView,

                             cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier,

                                                      for: indexPath)

        cell.backgroundColor = UIColor.white

        return cell

    }

     

    //返回每个分区的内边距

    func collectionView(_ collectionView: UICollectionView,

                        layout collectionViewLayout: UICollectionViewLayout,

                        insetForSectionAt section: Int) -> UIEdgeInsets {

        let numberOfItems = collectionView.numberOfItems(inSection: section)

        return numberOfItems > 0 ? UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) :

            UIEdgeInsets.zero

    }

     

    //返回每个分区的背景色

    func collectionView(_ collectionView: UICollectionView,

                        layout collectionViewLayout: UICollectionViewLayout,

                        backgroundColorForSectionAt section: Int) -> UIColor {

        if section == 0 {

            return UIColor.green

        else if section == 1 {

            return UIColor.cyan

        else if section == 2 {

            return UIColor.blue

        }

        return UIColor.purple

    }

}


原文出自:www.hangge.com  转载请保留原文链接:http://www.hangge.com/blog/cache/detail_1844.html