Slather logo

Coverage for "SectionModel.swift" : 84.76%

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