Slather logo

Coverage for "LayoutModel.swift" : 0.00%

(0 of 148 relevant lines covered)

ChatLayout/Classes/Core/Model/LayoutModel.swift

1
//
2
// ChatLayout
3
// LayoutModel.swift
4
// https://github.com/ekazaev/ChatLayout
5
//
6
// Created by Eugene Kazaev in 2020-2022.
7
// Distributed under the MIT license.
8
//
9
10
import Foundation
11
import UIKit
12
13
struct LayoutModel {
14
15
    private struct ItemUUIDKey: Hashable {
16
17
        let kind: ItemKind
18
19
        let id: UUID
20
21
    }
22
23
    private(set) var sections: [SectionModel]
24
25
    private unowned var collectionLayout: ChatLayoutRepresentation
26
27
    private var sectionIndexByIdentifierCache: [UUID: Int]?
28
29
    private var itemPathByIdentifierCache: [ItemUUIDKey: ItemPath]?
30
31
    init(sections: [SectionModel], collectionLayout: ChatLayoutRepresentation) {
!
32
        self.sections = sections
!
33
        self.collectionLayout = collectionLayout
!
34
    }
!
35
36
    mutating func assembleLayout() {
!
37
        var offset: CGFloat = collectionLayout.settings.additionalInsets.top
!
38
!
39
        var sectionIndexByIdentifierCache = [UUID: Int](minimumCapacity: sections.count)
!
40
        var itemPathByIdentifierCache = [ItemUUIDKey: ItemPath]()
!
41
!
42
        for sectionIndex in 0..<sections.count {
!
43
            sectionIndexByIdentifierCache[sections[sectionIndex].id] = sectionIndex
!
44
            sections[sectionIndex].offsetY = offset
!
45
            offset += sections[sectionIndex].height + collectionLayout.settings.interSectionSpacing
!
46
            if let header = sections[sectionIndex].header {
!
47
                itemPathByIdentifierCache[ItemUUIDKey(kind: .header, id: header.id)] = ItemPath(item: 0, section: sectionIndex)
!
48
            }
!
49
            for itemIndex in 0..<sections[sectionIndex].items.count {
!
50
                itemPathByIdentifierCache[ItemUUIDKey(kind: .cell, id: sections[sectionIndex].items[itemIndex].id)] = ItemPath(item: itemIndex, section: sectionIndex)
!
51
            }
!
52
            if let footer = sections[sectionIndex].footer {
!
53
                itemPathByIdentifierCache[ItemUUIDKey(kind: .footer, id: footer.id)] = ItemPath(item: 0, section: sectionIndex)
!
54
            }
!
55
        }
!
56
        self.itemPathByIdentifierCache = itemPathByIdentifierCache
!
57
        self.sectionIndexByIdentifierCache = sectionIndexByIdentifierCache
!
58
    }
!
59
60
    // MARK: To use when its is important to make the correct insertion
61
62
    mutating func setAndAssemble(header: ItemModel, sectionIndex: Int) {
!
63
        guard sectionIndex < sections.count else {
!
64
            assertionFailure("Incorrect section index.")
!
65
            return
!
66
        }
!
67
!
68
        let oldSection = sections[sectionIndex]
!
69
        sections[sectionIndex].setAndAssemble(header: header)
!
70
        let heightDiff = sections[sectionIndex].height - oldSection.height
!
71
        offsetEverything(below: sectionIndex, by: heightDiff)
!
72
    }
!
73
74
    mutating func setAndAssemble(item: ItemModel, sectionIndex: Int, itemIndex: Int) {
!
75
        guard sectionIndex < sections.count else {
!
76
            assertionFailure("Incorrect section index.")
!
77
            return
!
78
        }
!
79
        let oldSection = sections[sectionIndex]
!
80
        sections[sectionIndex].setAndAssemble(item: item, at: itemIndex)
!
81
        let heightDiff = sections[sectionIndex].height - oldSection.height
!
82
        offsetEverything(below: sectionIndex, by: heightDiff)
!
83
    }
!
84
85
    mutating func setAndAssemble(footer: ItemModel, sectionIndex: Int) {
!
86
        guard sectionIndex < sections.count else {
!
87
            assertionFailure("Incorrect section index.")
!
88
            return
!
89
        }
!
90
        let oldSection = sections[sectionIndex]
!
91
        sections[sectionIndex].setAndAssemble(footer: footer)
!
92
        let heightDiff = sections[sectionIndex].height - oldSection.height
!
93
        offsetEverything(below: sectionIndex, by: heightDiff)
!
94
    }
!
95
96
    func sectionIndex(by sectionId: UUID) -> Int? {
!
97
        guard let sectionIndexByIdentifierCache = sectionIndexByIdentifierCache else {
!
98
            assertionFailure("Internal inconsistency. Cache is not prepared.")
!
99
            return sections.firstIndex(where: { $0.id == sectionId })
!
100
        }
!
101
        return sectionIndexByIdentifierCache[sectionId]
!
102
    }
!
103
104
    func itemPath(by itemId: UUID, kind: ItemKind) -> ItemPath? {
!
105
        guard let itemPathByIdentifierCache = itemPathByIdentifierCache else {
!
106
            assertionFailure("Internal inconsistency. Cache is not prepared.")
!
107
            for (sectionIndex, section) in sections.enumerated() {
!
108
                switch kind {
!
109
                case .header:
!
110
                    if itemId == section.header?.id {
!
111
                        return ItemPath(item: 0, section: sectionIndex)
!
112
                    }
!
113
                case .footer:
!
114
                    if itemId == section.footer?.id {
!
115
                        return ItemPath(item: 0, section: sectionIndex)
!
116
                    }
!
117
                case .cell:
!
118
                    if let itemIndex = section.items.firstIndex(where: { $0.id == itemId }) {
!
119
                        return ItemPath(item: itemIndex, section: sectionIndex)
!
120
                    }
!
121
                }
!
122
            }
!
123
            return nil
!
124
        }
!
125
        return itemPathByIdentifierCache[ItemUUIDKey(kind: kind, id: itemId)]
!
126
    }
!
127
128
    private mutating func offsetEverything(below index: Int, by heightDiff: CGFloat) {
!
129
        guard heightDiff != 0 else {
!
130
            return
!
131
        }
!
132
        if index < sections.count - 1 {
!
133
            for index in (index + 1)..<sections.count {
!
134
                sections[index].offsetY += heightDiff
!
135
            }
!
136
        }
!
137
    }
!
138
139
    // MARK: To use only withing process(updateItems:)
140
141
    mutating func insertSection(_ section: SectionModel, at sectionIndex: Int) {
!
142
        var sections = self.sections
!
143
        guard sectionIndex <= sections.count else {
!
144
            assertionFailure("Incorrect section index.")
!
145
            return
!
146
        }
!
147
!
148
        sections.insert(section, at: sectionIndex)
!
149
        self.sections = sections
!
150
        resetCache()
!
151
    }
!
152
153
    mutating func removeSection(by sectionIdentifier: UUID) {
!
154
        guard let sectionIndex = sections.firstIndex(where: { $0.id == sectionIdentifier }) else {
!
155
            assertionFailure("Incorrect section identifier.")
!
156
            return
!
157
        }
!
158
        sections.remove(at: sectionIndex)
!
159
        resetCache()
!
160
    }
!
161
162
    mutating func removeSection(for sectionIndex: Int) {
!
163
        sections.remove(at: sectionIndex)
!
164
        resetCache()
!
165
    }
!
166
167
    mutating func insertItem(_ item: ItemModel, at indexPath: IndexPath) {
!
168
        sections[indexPath.section].insert(item, at: indexPath.item)
!
169
        resetCache()
!
170
    }
!
171
172
    mutating func replaceItem(_ item: ItemModel, at indexPath: IndexPath) {
!
173
        sections[indexPath.section].replace(item, at: indexPath.item)
!
174
        resetCache()
!
175
    }
!
176
177
    mutating func removeItem(by itemId: UUID) {
!
178
        var itemPath: ItemPath?
!
179
        for (sectionIndex, section) in sections.enumerated() {
!
180
            if let itemIndex = section.items.firstIndex(where: { $0.id == itemId }) {
!
181
                itemPath = ItemPath(item: itemIndex, section: sectionIndex)
!
182
                break
!
183
            }
!
184
        }
!
185
        guard let path = itemPath else {
!
186
            assertionFailure("Incorrect item identifier.")
!
187
            return
!
188
        }
!
189
        sections[path.section].remove(at: path.item)
!
190
        resetCache()
!
191
    }
!
192
193
    private mutating func resetCache() {
!
194
        itemPathByIdentifierCache = nil
!
195
        sectionIndexByIdentifierCache = nil
!
196
    }
!
197
198
}