Slather logo

Coverage for "SectionModel.swift" : 85.45%

(94 of 110 relevant lines covered)

ChatLayout/Classes/Core/Model/SectionModel.swift

1
//
2
// ChatLayout
3
// SectionModel.swift
4
// https://github.com/ekazaev/ChatLayout
5
//
6
// Created by Eugene Kazaev in 2020-2022.
7
// Distributed under the MIT license.
8
//
9
// Become a sponsor:
10
// https://github.com/sponsors/ekazaev
11
//
12
13
import Foundation
14
import UIKit
15
16
struct SectionModel<Layout: ChatLayoutRepresentation> {
17
18
    let id: UUID
19
20
    private(set) var header: ItemModel?
21
22
    private(set) var footer: ItemModel?
23
24
    private(set) var items: ContiguousArray<ItemModel>
25
26
    var offsetY: CGFloat = 0
27
28
    private unowned var collectionLayout: Layout
29
30
    var frame: CGRect {
10x
31
        let additionalInsets = collectionLayout.settings.additionalInsets
10x
32
        return CGRect(x: 0,
10x
33
                      y: offsetY,
10x
34
                      width: collectionLayout.visibleBounds.width - additionalInsets.left - additionalInsets.right,
10x
35
                      height: height)
10x
36
    }
10x
37
38
    var height: CGFloat {
39
        if let footer = footer {
40
            return footer.frame.maxY
41
        } else {
42
            guard let lastItem = items.last else {
2x
43
                return header?.frame.maxY ?? .zero
!
44
            }
2x
45
            return lastItem.frame.maxY
2x
46
        }
2x
47
    }
!
48
49
    var locationHeight: CGFloat {
50
        offsetY + height
51
    }
52
53
    init(id: UUID = UUID(),
54
         header: ItemModel?,
55
         footer: ItemModel?,
56
         items: ContiguousArray<ItemModel> = [],
57
         collectionLayout: Layout) {
60x
58
        self.id = id
60x
59
        self.items = items
60x
60
        self.collectionLayout = collectionLayout
60x
61
        self.header = header
60x
62
        self.footer = footer
60x
63
    }
60x
64
65
    mutating func assembleLayout() {
118x
66
        var offsetY: CGFloat = 0
118x
67
118x
68
        if header != nil {
118x
69
            header?.offsetY = 0
115x
70
            offsetY += header?.frame.height ?? 0
115x
71
        }
118x
72
118x
73
        items.withUnsafeMutableBufferPointer { directlyMutableItems in
118x
74
            for rowIndex in 0..<directlyMutableItems.count {
75
                directlyMutableItems[rowIndex].offsetY = offsetY
76
                offsetY += directlyMutableItems[rowIndex].size.height + collectionLayout.settings.interItemSpacing
77
            }
78
        }
118x
79
118x
80
        if footer != nil {
118x
81
            footer?.offsetY = offsetY
117x
82
        }
118x
83
    }
118x
84
85
    // MARK: To use when its is important to make the correct insertion
86
87
    mutating func setAndAssemble(header: ItemModel) {
3x
88
        guard let oldHeader = self.header else {
3x
89
            self.header = header
!
90
            offsetEverything(below: -1, by: header.size.height)
!
91
            return
!
92
        }
3x
93
        #if DEBUG
3x
94
        if header.id != oldHeader.id {
3x
95
            assertionFailure("Internal inconsistency.")
!
96
        }
3x
97
        #endif
3x
98
        self.header = header
3x
99
        let heightDiff = header.size.height - oldHeader.size.height
3x
100
        offsetEverything(below: -1, by: heightDiff)
3x
101
    }
3x
102
103
    mutating func setAndAssemble(item: ItemModel, at index: Int) {
10000x
104
        guard index < items.count else {
10000x
105
            assertionFailure("Incorrect item index.")
!
106
            return
!
107
        }
10000x
108
        let oldItem = items[index]
10000x
109
        #if DEBUG
10000x
110
        if item.id != oldItem.id {
10000x
111
            assertionFailure("Internal inconsistency.")
!
112
        }
10000x
113
        #endif
10000x
114
        items[index] = item
10000x
115
10000x
116
        let heightDiff = item.size.height - oldItem.size.height
10000x
117
        offsetEverything(below: index, by: heightDiff)
10000x
118
    }
10000x
119
120
    mutating func setAndAssemble(footer: ItemModel) {
3x
121
        #if DEBUG
3x
122
        if let oldFooter = self.footer,
3x
123
           footer.id != oldFooter.id {
3x
124
            assertionFailure("Internal inconsistency.")
!
125
        }
3x
126
        #endif
3x
127
        self.footer = footer
3x
128
    }
3x
129
130
    // MARK: Just updaters
131
132
    mutating func set(header: ItemModel?) {
5x
133
        self.header = header
5x
134
    }
5x
135
136
    mutating func set(items: ContiguousArray<ItemModel>) {
5x
137
        self.items = items
5x
138
    }
5x
139
140
    mutating func set(footer: ItemModel?) {
5x
141
        guard let _ = self.footer, let _ = footer else {
5x
142
            self.footer = footer
1x
143
            return
1x
144
        }
4x
145
        self.footer = footer
4x
146
    }
4x
147
148
    private mutating func offsetEverything(below index: Int, by heightDiff: CGFloat) {
10000x
149
        guard heightDiff != 0 else {
10000x
150
            return
9000x
151
        }
9000x
152
        if index < items.count &- 1 {
1000x
153
            let nextIndex = index &+ 1
1000x
154
            items.withUnsafeMutableBufferPointer { directlyMutableItems in
1000x
155
                DispatchQueue.concurrentPerform(iterations: directlyMutableItems.count &- nextIndex) { internalIndex in
156
                    directlyMutableItems[internalIndex &+ nextIndex].offsetY += heightDiff
157
                }
158
            }
1000x
159
        }
1000x
160
        footer?.offsetY += heightDiff
1000x
161
    }
1000x
162
163
    // MARK: To use only withing process(updateItems:)
164
165
    mutating func insert(_ item: ItemModel, at index: Int) {
166
        guard index <= items.count else {
167
            assertionFailure("Incorrect item index.")
!
168
            return
!
169
        }
170
        items.insert(item, at: index)
171
    }
172
173
    mutating func replace(_ item: ItemModel, at index: Int) {
10300x
174
        guard index <= items.count else {
10300x
175
            assertionFailure("Incorrect item index.")
!
176
            return
!
177
        }
10300x
178
        items[index] = item
10300x
179
    }
10300x
180
181
    mutating func remove(at index: Int) {
182
        guard index < items.count else {
183
            assertionFailure("Incorrect item index.")
!
184
            return
!
185
        }
186
        items.remove(at: index)
187
    }
188
189
}