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("Incorrect item index.")
|
! |
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("Incorrect item index.")
|
! |
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("Incorrect item index.")
|
! |
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("Incorrect item index.")
|
! |
180 |
return
|
! |
181 |
}
|
|
182 |
items.remove(at: index)
|
|
183 |
}
|
|
184 |
|
|
185 |
}
|
|