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: ContiguousArray<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,
|
! |
35 |
height: height)
|
! |
36 |
}
|
! |
37 |
|
! |
38 |
var height: CGFloat {
|
! |
39 |
if let footer = footer {
|
! |
40 |
return footer.frame.maxY
|
! |
41 |
} else {
|
|
42 |
guard let lastItem = items.last else {
|
|
43 |
return header?.frame.maxY ?? .zero
|
|
44 |
}
|
|
45 |
return lastItem.locationHeight
|
|
46 |
}
|
! |
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: ChatLayoutRepresentation) {
|
|
58 |
self.id = id
|
|
59 |
self.items = items
|
|
60 |
self.collectionLayout = collectionLayout
|
|
61 |
self.header = header
|
7x |
62 |
self.footer = footer
|
7x |
63 |
}
|
7x |
64 |
|
7x |
65 |
mutating func assembleLayout() {
|
7x |
66 |
var offsetY: CGFloat = 0
|
7x |
67 |
|
7x |
68 |
if header != nil {
|
|
69 |
header?.offsetY = 0
|
|
70 |
offsetY += header?.frame.height ?? 0
|
|
71 |
}
|
|
72 |
|
|
73 |
items.withUnsafeMutableBufferPointer { directlyMutableItems in
|
|
74 |
for rowIndex in 0..<directlyMutableItems.count {
|
! |
75 |
directlyMutableItems[rowIndex].offsetY = offsetY
|
|
76 |
offsetY += directlyMutableItems[rowIndex].height + collectionLayout.settings.interItemSpacing
|
|
77 |
}
|
|
78 |
}
|
|
79 |
|
|
80 |
if footer != nil {
|
|
81 |
footer?.offsetY = offsetY
|
|
82 |
}
|
|
83 |
}
|
|
84 |
|
|
85 |
// MARK: To use when its is important to make the correct insertion
|
|
86 |
|
|
87 |
mutating func setAndAssemble(header: ItemModel) {
|
|
88 |
guard let oldHeader = self.header else {
|
|
89 |
self.header = header
|
! |
90 |
offsetEverything(below: -1, by: header.height)
|
! |
91 |
return
|
! |
92 |
}
|
! |
93 |
#if DEBUG
|
! |
94 |
if header.id != oldHeader.id {
|
! |
95 |
assertionFailure("Internal inconsistency.")
|
! |
96 |
}
|
! |
97 |
#endif
|
! |
98 |
self.header = header
|
! |
99 |
let heightDiff = header.height - oldHeader.height
|
! |
100 |
offsetEverything(below: -1, by: heightDiff)
|
! |
101 |
}
|
! |
102 |
|
! |
103 |
mutating func setAndAssemble(item: ItemModel, at index: Int) {
|
! |
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,
|
! |
123 |
footer.id != oldFooter.id {
|
! |
124 |
assertionFailure("Internal inconsistency.")
|
! |
125 |
}
|
! |
126 |
#endif
|
! |
127 |
self.footer = footer
|
! |
128 |
}
|
! |
129 |
|
! |
130 |
// MARK: Just updaters
|
! |
131 |
|
|
132 |
mutating func set(header: ItemModel?) {
|
|
133 |
self.header = header
|
|
134 |
}
|
! |
135 |
|
! |
136 |
mutating func set(items: ContiguousArray<ItemModel>) {
|
! |
137 |
self.items = items
|
|
138 |
}
|
! |
139 |
|
! |
140 |
mutating func set(footer: ItemModel?) {
|
! |
141 |
guard let _ = self.footer, let _ = footer else {
|
|
142 |
self.footer = footer
|
! |
143 |
return
|
! |
144 |
}
|
! |
145 |
self.footer = footer
|
! |
146 |
}
|
! |
147 |
|
! |
148 |
private mutating func offsetEverything(below index: Int, by heightDiff: CGFloat) {
|
! |
149 |
guard heightDiff != 0 else {
|
|
150 |
return
|
|
151 |
}
|
|
152 |
if index < items.count &- 1 {
|
|
153 |
let nextIndex = index &+ 1
|
|
154 |
items.withUnsafeMutableBufferPointer { directlyMutableItems in
|
|
155 |
DispatchQueue.concurrentPerform(iterations: directlyMutableItems.count &- nextIndex) { internalIndex in
|
|
156 |
directlyMutableItems[internalIndex &+ nextIndex].offsetY += heightDiff
|
|
157 |
}
|
|
158 |
}
|
|
159 |
}
|
|
160 |
footer?.offsetY += heightDiff
|
|
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 |
|
10000x |
173 |
mutating func replace(_ item: ItemModel, at index: Int) {
|
10000x |
174 |
guard index <= items.count else {
|
! |
175 |
assertionFailure("Incorrect item index.")
|
! |
176 |
return
|
10000x |
177 |
}
|
10000x |
178 |
items[index] = item
|
10000x |
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 |
}
|
|