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 {
|
|
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: [ItemModel]
|
|
25 |
|
|
26 |
var offsetY: CGFloat = 0
|
|
27 |
|
|
28 |
private unowned var collectionLayout: ChatLayoutRepresentation
|
|
29 |
|
|
30 |
var frame: CGRect {
|
|
31 |
let additionalInsets = collectionLayout.settings.additionalInsets
|
|
32 |
return CGRect(x: 0,
|
|
33 |
y: offsetY,
|
|
34 |
width: collectionLayout.visibleBounds.width - additionalInsets.left - additionalInsets.right,
|
10x |
35 |
height: height)
|
10x |
36 |
}
|
10x |
37 |
|
10x |
38 |
var height: CGFloat {
|
10x |
39 |
if let footer = footer {
|
10x |
40 |
return footer.frame.maxY
|
10x |
41 |
} else {
|
|
42 |
guard let lastItem = items.last else {
|
|
43 |
return header?.frame.maxY ?? .zero
|
|
44 |
}
|
|
45 |
return lastItem.locationHeight
|
|
46 |
}
|
2x |
47 |
}
|
! |
48 |
|
2x |
49 |
var locationHeight: CGFloat {
|
2x |
50 |
offsetY + height
|
2x |
51 |
}
|
! |
52 |
|
|
53 |
init(id: UUID = UUID(),
|
|
54 |
header: ItemModel?,
|
|
55 |
footer: ItemModel?,
|
|
56 |
items: [ItemModel] = [],
|
|
57 |
collectionLayout: ChatLayoutRepresentation) {
|
|
58 |
self.id = id
|
|
59 |
self.items = items
|
|
60 |
self.collectionLayout = collectionLayout
|
|
61 |
self.header = header
|
57x |
62 |
self.footer = footer
|
57x |
63 |
}
|
57x |
64 |
|
57x |
65 |
mutating func assembleLayout() {
|
57x |
66 |
var offsetY: CGFloat = 0
|
57x |
67 |
|
57x |
68 |
if header != nil {
|
|
69 |
header?.offsetY = 0
|
115x |
70 |
offsetY += header?.frame.height ?? 0
|
115x |
71 |
}
|
115x |
72 |
|
115x |
73 |
items.withUnsafeMutableBufferPointer { directlyMutableItems in
|
112x |
74 |
for rowIndex in 0..<directlyMutableItems.count {
|
112x |
75 |
directlyMutableItems[rowIndex].offsetY = offsetY
|
115x |
76 |
offsetY += directlyMutableItems[rowIndex].height + collectionLayout.settings.interItemSpacing
|
115x |
77 |
}
|
|
78 |
}
|
|
79 |
|
|
80 |
if footer != nil {
|
|
81 |
footer?.offsetY = offsetY
|
115x |
82 |
}
|
115x |
83 |
}
|
114x |
84 |
|
115x |
85 |
// MARK: To use when its is important to make the correct insertion
|
115x |
86 |
|
|
87 |
mutating func setAndAssemble(header: ItemModel) {
|
|
88 |
guard let oldHeader = self.header else {
|
|
89 |
self.header = header
|
3x |
90 |
offsetEverything(below: -1, by: header.height)
|
3x |
91 |
return
|
! |
92 |
}
|
! |
93 |
#if DEBUG
|
! |
94 |
if header.id != oldHeader.id {
|
3x |
95 |
assertionFailure("Internal inconsistency.")
|
3x |
96 |
}
|
3x |
97 |
#endif
|
! |
98 |
self.header = header
|
3x |
99 |
let heightDiff = header.height - oldHeader.height
|
3x |
100 |
offsetEverything(below: -1, by: heightDiff)
|
3x |
101 |
}
|
3x |
102 |
|
3x |
103 |
mutating func setAndAssemble(item: ItemModel, at index: Int) {
|
3x |
104 |
guard index < items.count else {
|
|
105 |
assertionFailure("Incorrect item index.")
|
10000x |
106 |
return
|
10000x |
107 |
}
|
! |
108 |
let oldItem = items[index]
|
! |
109 |
#if DEBUG
|
10000x |
110 |
if item.id != oldItem.id {
|
10000x |
111 |
assertionFailure("Internal inconsistency.")
|
10000x |
112 |
}
|
10000x |
113 |
#endif
|
! |
114 |
items[index] = item
|
10000x |
115 |
|
10000x |
116 |
let heightDiff = item.height - oldItem.height
|
10000x |
117 |
offsetEverything(below: index, by: heightDiff)
|
10000x |
118 |
}
|
10000x |
119 |
|
10000x |
120 |
mutating func setAndAssemble(footer: ItemModel) {
|
10000x |
121 |
#if DEBUG
|
|
122 |
if let oldFooter = self.footer,
|
3x |
123 |
footer.id != oldFooter.id {
|
3x |
124 |
assertionFailure("Internal inconsistency.")
|
3x |
125 |
}
|
3x |
126 |
#endif
|
! |
127 |
self.footer = footer
|
3x |
128 |
}
|
3x |
129 |
|
3x |
130 |
// MARK: Just updaters
|
3x |
131 |
|
|
132 |
mutating func set(header: ItemModel?) {
|
|
133 |
self.header = header
|
|
134 |
}
|
5x |
135 |
|
5x |
136 |
mutating func set(items: [ItemModel]) {
|
5x |
137 |
self.items = items
|
|
138 |
}
|
5x |
139 |
|
5x |
140 |
mutating func set(footer: ItemModel?) {
|
5x |
141 |
guard let _ = self.footer, let _ = footer else {
|
|
142 |
self.footer = footer
|
5x |
143 |
return
|
5x |
144 |
}
|
1x |
145 |
self.footer = footer
|
1x |
146 |
}
|
4x |
147 |
|
4x |
148 |
private mutating func offsetEverything(below index: Int, by heightDiff: CGFloat) {
|
4x |
149 |
guard heightDiff != 0 else {
|
|
150 |
return
|
10000x |
151 |
}
|
10000x |
152 |
if index < items.count &- 1 {
|
9000x |
153 |
let nextIndex = index &+ 1
|
9000x |
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 |
}
|
|
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 |
|
10300x |
173 |
mutating func replace(_ item: ItemModel, at index: Int) {
|
10300x |
174 |
guard index <= items.count else {
|
! |
175 |
assertionFailure("Incorrect item index.")
|
! |
176 |
return
|
10300x |
177 |
}
|
10300x |
178 |
items[index] = item
|
10300x |
179 |
}
|
|
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 |
}
|
|