Merge pull request #36 from diegosanchezr/dev
Allows a datasource to return different chatItem instances for same message id
This commit is contained in:
commit
e7e58ae8a1
|
|
@ -14,11 +14,14 @@
|
|||
C321DDBE1BE964DC00DE88CC /* BaseChatItemPresenterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C321DDBC1BE964D500DE88CC /* BaseChatItemPresenterTests.swift */; };
|
||||
C32BB72B1BE0504D0069EC50 /* Chatto.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C32BB7201BE0504D0069EC50 /* Chatto.framework */; };
|
||||
C3383E281BFFA49F00244F5C /* BaseChatViewControllerTestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3383E271BFFA49F00244F5C /* BaseChatViewControllerTestHelpers.swift */; };
|
||||
C342D0BD1C638681008A4605 /* ChatItemCompanion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C342D0BC1C638681008A4605 /* ChatItemCompanion.swift */; };
|
||||
C342D0C11C638A2C008A4605 /* ReadOnlyOrderedDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = C342D0C01C638A2C008A4605 /* ReadOnlyOrderedDictionary.swift */; };
|
||||
C342D0C41C638FDF008A4605 /* ReadOnlyOrderedDictionaryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C342D0C21C638FDF008A4605 /* ReadOnlyOrderedDictionaryTests.swift */; };
|
||||
C342D0C51C638FDF008A4605 /* SerialTaskQueueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C342D0C31C638FDF008A4605 /* SerialTaskQueueTests.swift */; };
|
||||
C36281E51BF0F0F0004D6BCE /* BaseChatViewController+Changes.swift in Sources */ = {isa = PBXBuildFile; fileRef = C36281E41BF0F0F0004D6BCE /* BaseChatViewController+Changes.swift */; };
|
||||
C36281E71BF0F196004D6BCE /* BaseChatViewController+Scrolling.swift in Sources */ = {isa = PBXBuildFile; fileRef = C36281E61BF0F196004D6BCE /* BaseChatViewController+Scrolling.swift */; };
|
||||
C36281EB1BF0F62F004D6BCE /* DummyChatItemPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C36281EA1BF0F62F004D6BCE /* DummyChatItemPresenter.swift */; };
|
||||
C36281ED1BF10086004D6BCE /* SerialTaskQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = C36281EC1BF10086004D6BCE /* SerialTaskQueue.swift */; };
|
||||
C36281F01BF12727004D6BCE /* SerialTaskQueueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C36281EE1BF126F7004D6BCE /* SerialTaskQueueTests.swift */; };
|
||||
C36281F21BF12A4B004D6BCE /* ChatCollectionViewLayoutTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C36281F11BF12A4B004D6BCE /* ChatCollectionViewLayoutTests.swift */; };
|
||||
C38621DF1BE6C89900421718 /* CollectionChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38621DE1BE6C89900421718 /* CollectionChanges.swift */; };
|
||||
C3E904D91BE0509E00C662A2 /* ChatCollectionViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3E904AF1BE0509E00C662A2 /* ChatCollectionViewLayout.swift */; };
|
||||
|
|
@ -50,11 +53,14 @@
|
|||
C32BB7201BE0504D0069EC50 /* Chatto.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Chatto.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
C32BB72A1BE0504D0069EC50 /* ChattoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ChattoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
C3383E271BFFA49F00244F5C /* BaseChatViewControllerTestHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseChatViewControllerTestHelpers.swift; sourceTree = "<group>"; };
|
||||
C342D0BC1C638681008A4605 /* ChatItemCompanion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatItemCompanion.swift; sourceTree = "<group>"; };
|
||||
C342D0C01C638A2C008A4605 /* ReadOnlyOrderedDictionary.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadOnlyOrderedDictionary.swift; sourceTree = "<group>"; };
|
||||
C342D0C21C638FDF008A4605 /* ReadOnlyOrderedDictionaryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadOnlyOrderedDictionaryTests.swift; sourceTree = "<group>"; };
|
||||
C342D0C31C638FDF008A4605 /* SerialTaskQueueTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SerialTaskQueueTests.swift; sourceTree = "<group>"; };
|
||||
C36281E41BF0F0F0004D6BCE /* BaseChatViewController+Changes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BaseChatViewController+Changes.swift"; sourceTree = "<group>"; };
|
||||
C36281E61BF0F196004D6BCE /* BaseChatViewController+Scrolling.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BaseChatViewController+Scrolling.swift"; sourceTree = "<group>"; };
|
||||
C36281EA1BF0F62F004D6BCE /* DummyChatItemPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DummyChatItemPresenter.swift; sourceTree = "<group>"; };
|
||||
C36281EC1BF10086004D6BCE /* SerialTaskQueue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SerialTaskQueue.swift; sourceTree = "<group>"; };
|
||||
C36281EE1BF126F7004D6BCE /* SerialTaskQueueTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SerialTaskQueueTests.swift; sourceTree = "<group>"; };
|
||||
C36281F11BF12A4B004D6BCE /* ChatCollectionViewLayoutTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatCollectionViewLayoutTests.swift; sourceTree = "<group>"; };
|
||||
C38621DE1BE6C89900421718 /* CollectionChanges.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionChanges.swift; sourceTree = "<group>"; };
|
||||
C3E904AF1BE0509E00C662A2 /* ChatCollectionViewLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatCollectionViewLayout.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -90,6 +96,8 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
55E85D821BE390BE001885AD /* Info.plist */,
|
||||
C342D0C21C638FDF008A4605 /* ReadOnlyOrderedDictionaryTests.swift */,
|
||||
C342D0C31C638FDF008A4605 /* SerialTaskQueueTests.swift */,
|
||||
C321DDBB1BE964D500DE88CC /* Chat Items */,
|
||||
C321C3941BE78835009803D1 /* ChatController */,
|
||||
);
|
||||
|
|
@ -103,7 +111,6 @@
|
|||
C31E91991BFF4CA300339585 /* BaseChatViewControllerTests.swift */,
|
||||
C3383E271BFFA49F00244F5C /* BaseChatViewControllerTestHelpers.swift */,
|
||||
C321C3951BE78835009803D1 /* CollectionChangesTests.swift */,
|
||||
C36281EE1BF126F7004D6BCE /* SerialTaskQueueTests.swift */,
|
||||
);
|
||||
path = ChatController;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -112,6 +119,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
C321DD941BE9649F00DE88CC /* BaseChatItemPresenter.swift */,
|
||||
C342D0BC1C638681008A4605 /* ChatItemCompanion.swift */,
|
||||
C321DD9A1BE9649F00DE88CC /* ChatItemProtocolDefinitions.swift */,
|
||||
C36281EA1BF0F62F004D6BCE /* DummyChatItemPresenter.swift */,
|
||||
);
|
||||
|
|
@ -158,6 +166,7 @@
|
|||
children = (
|
||||
C3E905031BE0521700C662A2 /* Chatto.h */,
|
||||
C3E905041BE0521700C662A2 /* Info.plist */,
|
||||
C342D0C01C638A2C008A4605 /* ReadOnlyOrderedDictionary.swift */,
|
||||
C36281EC1BF10086004D6BCE /* SerialTaskQueue.swift */,
|
||||
C321DD931BE9649F00DE88CC /* Chat Items */,
|
||||
C3E904AE1BE0509E00C662A2 /* ChatController */,
|
||||
|
|
@ -315,8 +324,10 @@
|
|||
C321DDAE1BE9649F00DE88CC /* ChatItemProtocolDefinitions.swift in Sources */,
|
||||
C3E904DD1BE0509E00C662A2 /* KeyboardTracker.swift in Sources */,
|
||||
C36281E51BF0F0F0004D6BCE /* BaseChatViewController+Changes.swift in Sources */,
|
||||
C342D0BD1C638681008A4605 /* ChatItemCompanion.swift in Sources */,
|
||||
C36281E71BF0F196004D6BCE /* BaseChatViewController+Scrolling.swift in Sources */,
|
||||
C36281EB1BF0F62F004D6BCE /* DummyChatItemPresenter.swift in Sources */,
|
||||
C342D0C11C638A2C008A4605 /* ReadOnlyOrderedDictionary.swift in Sources */,
|
||||
C38621DF1BE6C89900421718 /* CollectionChanges.swift in Sources */,
|
||||
C3E904D91BE0509E00C662A2 /* ChatCollectionViewLayout.swift in Sources */,
|
||||
);
|
||||
|
|
@ -326,10 +337,11 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
C342D0C51C638FDF008A4605 /* SerialTaskQueueTests.swift in Sources */,
|
||||
C3383E281BFFA49F00244F5C /* BaseChatViewControllerTestHelpers.swift in Sources */,
|
||||
C36281F01BF12727004D6BCE /* SerialTaskQueueTests.swift in Sources */,
|
||||
C36281F21BF12A4B004D6BCE /* ChatCollectionViewLayoutTests.swift in Sources */,
|
||||
C321C3961BE78835009803D1 /* CollectionChangesTests.swift in Sources */,
|
||||
C342D0C41C638FDF008A4605 /* ReadOnlyOrderedDictionaryTests.swift in Sources */,
|
||||
C321DDBE1BE964DC00DE88CC /* BaseChatItemPresenterTests.swift in Sources */,
|
||||
C31E919A1BFF4CA300339585 /* BaseChatViewControllerTests.swift in Sources */,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-present Badoo Trading Limited.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
public protocol ChatItemsDecoratorProtocol {
|
||||
func decorateItems(chatItems: [ChatItemProtocol]) -> [DecoratedChatItem]
|
||||
}
|
||||
|
||||
public struct DecoratedChatItem {
|
||||
public let chatItem: ChatItemProtocol
|
||||
public let decorationAttributes: ChatItemDecorationAttributesProtocol?
|
||||
public init(chatItem: ChatItemProtocol, decorationAttributes: ChatItemDecorationAttributesProtocol?) {
|
||||
self.chatItem = chatItem
|
||||
self.decorationAttributes = decorationAttributes
|
||||
}
|
||||
}
|
||||
|
||||
struct ChatItemCompanion: UniqueIdentificable {
|
||||
let chatItem: ChatItemProtocol
|
||||
let presenter: ChatItemPresenterProtocol
|
||||
var decorationAttributes: ChatItemDecorationAttributesProtocol?
|
||||
var uid: String {
|
||||
return self.chatItem.uid
|
||||
}
|
||||
}
|
||||
|
|
@ -43,7 +43,7 @@ extension BaseChatViewController: ChatDataSourceDelegateProtocol {
|
|||
self.updateQueue.addTask({ [weak self] (completion) -> () in
|
||||
guard let sSelf = self else { return }
|
||||
|
||||
let oldItems = sSelf.decoratedChatItems.map { $0.chatItem }
|
||||
let oldItems = sSelf.chatItemCompanionCollection
|
||||
sSelf.updateModels(newItems: newItems, oldItems: oldItems, context: context, completion: {
|
||||
if sSelf.updateQueue.isEmpty {
|
||||
sSelf.enqueueMessageCountReductionIfNeeded()
|
||||
|
|
@ -63,7 +63,7 @@ extension BaseChatViewController: ChatDataSourceDelegateProtocol {
|
|||
return
|
||||
}
|
||||
let newItems = sSelf.chatDataSource?.chatItems ?? []
|
||||
let oldItems = sSelf.decoratedChatItems.map { $0.chatItem }
|
||||
let oldItems = sSelf.chatItemCompanionCollection
|
||||
sSelf.updateModels(newItems: newItems, oldItems: oldItems, context: .MessageCountReduction, completion: completion )
|
||||
})
|
||||
}
|
||||
|
|
@ -90,24 +90,28 @@ extension BaseChatViewController: ChatDataSourceDelegateProtocol {
|
|||
func updateVisibleCells(changes: CollectionChanges) {
|
||||
// Datasource should be already updated!
|
||||
|
||||
func updateCellIfVisible(atIndexPath cellIndexPath: NSIndexPath, newDataIndexPath: NSIndexPath) {
|
||||
if let cell = self.collectionView.cellForItemAtIndexPath(cellIndexPath) {
|
||||
let presenter = self.presenterForIndexPath(newDataIndexPath)
|
||||
presenter.configureCell(cell, decorationAttributes: self.decorationAttributesForIndexPath(newDataIndexPath))
|
||||
presenter.cellWillBeShown(cell) // `createModelUpdates` may have created a new presenter instance for existing visible cell so we need to tell it that its cell is visible
|
||||
}
|
||||
}
|
||||
|
||||
let visibleIndexPaths = Set(self.collectionView.indexPathsForVisibleItems().filter { (indexPath) -> Bool in
|
||||
return !changes.insertedIndexPaths.contains(indexPath) && !changes.deletedIndexPaths.contains(indexPath)
|
||||
})
|
||||
})
|
||||
|
||||
var updatedIndexPaths = Set<NSIndexPath>()
|
||||
for move in changes.movedIndexPaths {
|
||||
updatedIndexPaths.insert(move.indexPathOld)
|
||||
if let cell = self.collectionView.cellForItemAtIndexPath(move.indexPathOld) {
|
||||
self.presenterForIndexPath(move.indexPathNew).configureCell(cell, decorationAttributes: self.decorationAttributesForIndexPath(move.indexPathNew))
|
||||
}
|
||||
updateCellIfVisible(atIndexPath: move.indexPathOld, newDataIndexPath: move.indexPathNew)
|
||||
}
|
||||
|
||||
// Update remaining visible cells
|
||||
let remaining = visibleIndexPaths.subtract(updatedIndexPaths)
|
||||
for indexPath in remaining {
|
||||
if let cell = self.collectionView.cellForItemAtIndexPath(indexPath) {
|
||||
self.presenterForIndexPath(indexPath).configureCell(cell, decorationAttributes: self.decorationAttributesForIndexPath(indexPath))
|
||||
}
|
||||
updateCellIfVisible(atIndexPath: indexPath, newDataIndexPath: indexPath)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -161,7 +165,7 @@ extension BaseChatViewController: ChatDataSourceDelegateProtocol {
|
|||
}
|
||||
}
|
||||
|
||||
private func updateModels(newItems newItems: [ChatItemProtocol], oldItems: [ChatItemProtocol], var context: UpdateContext, completion: () -> Void) {
|
||||
private func updateModels(newItems newItems: [ChatItemProtocol], oldItems: ChatItemCompanionCollection, var context: UpdateContext, completion: () -> Void) {
|
||||
let collectionViewWidth = self.collectionView.bounds.width
|
||||
context = self.isFirstLayout ? .FirstLoad : context
|
||||
let performInBackground = context != .FirstLoad
|
||||
|
|
@ -198,20 +202,37 @@ extension BaseChatViewController: ChatDataSourceDelegateProtocol {
|
|||
}
|
||||
}
|
||||
|
||||
private func createModelUpdates(newItems newItems: [ChatItemProtocol], oldItems: [ChatItemProtocol], collectionViewWidth: CGFloat) -> (changes: CollectionChanges, updateModelClosure: () -> Void) {
|
||||
private func createModelUpdates(newItems newItems: [ChatItemProtocol], oldItems: ChatItemCompanionCollection, collectionViewWidth: CGFloat) -> (changes: CollectionChanges, updateModelClosure: () -> Void) {
|
||||
let newDecoratedItems = self.chatItemsDecorator?.decorateItems(newItems) ?? newItems.map { DecoratedChatItem(chatItem: $0, decorationAttributes: nil) }
|
||||
let changes = Chatto.generateChanges(
|
||||
oldCollection: oldItems.map { $0 },
|
||||
newCollection: newDecoratedItems.map { $0.chatItem })
|
||||
let layoutModel = self.createLayoutModel(newDecoratedItems, collectionViewWidth: collectionViewWidth)
|
||||
let changes = Chatto.generateChanges(oldCollection: oldItems.map { $0.chatItem }, newCollection: newDecoratedItems.map { $0.chatItem })
|
||||
let itemCompanionCollection = self.createCompanionCollection(fromChatItems: newDecoratedItems, previousCompanionCollection: oldItems)
|
||||
let layoutModel = self.createLayoutModel(itemCompanionCollection, collectionViewWidth: collectionViewWidth)
|
||||
let updateModelClosure : () -> Void = { [weak self] in
|
||||
self?.layoutModel = layoutModel
|
||||
self?.decoratedChatItems = newDecoratedItems
|
||||
self?.chatItemCompanionCollection = itemCompanionCollection
|
||||
}
|
||||
return (changes, updateModelClosure)
|
||||
}
|
||||
|
||||
private func createLayoutModel(decoratedItems: [DecoratedChatItem], collectionViewWidth: CGFloat) -> ChatCollectionViewLayoutModel {
|
||||
private func createCompanionCollection(fromChatItems newItems: [DecoratedChatItem], previousCompanionCollection oldItems: ChatItemCompanionCollection) -> ChatItemCompanionCollection {
|
||||
return ChatItemCompanionCollection(items: newItems.map { (decoratedChatItem) -> ChatItemCompanion in
|
||||
let chatItem = decoratedChatItem.chatItem
|
||||
var presenter: ChatItemPresenterProtocol!
|
||||
// We assume that a same messageId can't mutate from one cell class to a different one.
|
||||
// If we ever need to support that then generation of changes needs to suppport reloading items.
|
||||
// Oherwise updateVisibleCells may try to update existing cell with a new presenter which is working with a different type of cell
|
||||
|
||||
// Optimization: reuse presenter if it's the same instance.
|
||||
if let oldChatItemCompanion = oldItems[chatItem.uid] where oldChatItemCompanion.chatItem === chatItem {
|
||||
presenter = oldChatItemCompanion.presenter
|
||||
} else {
|
||||
presenter = self.createPresenterForChatItem(decoratedChatItem.chatItem)
|
||||
}
|
||||
return ChatItemCompanion(chatItem: decoratedChatItem.chatItem, presenter: presenter, decorationAttributes: decoratedChatItem.decorationAttributes)
|
||||
})
|
||||
}
|
||||
|
||||
private func createLayoutModel(items: ChatItemCompanionCollection, collectionViewWidth: CGFloat) -> ChatCollectionViewLayoutModel {
|
||||
typealias IntermediateItemLayoutData = (height: CGFloat?, bottomMargin: CGFloat)
|
||||
typealias ItemLayoutData = (height: CGFloat, bottomMargin: CGFloat)
|
||||
|
||||
|
|
@ -224,24 +245,23 @@ extension BaseChatViewController: ChatDataSourceDelegateProtocol {
|
|||
|
||||
let isInbackground = !NSThread.isMainThread()
|
||||
var intermediateLayoutData = [IntermediateItemLayoutData]()
|
||||
var itemsForMainThread = [(index: Int, item: DecoratedChatItem, presenter: ChatItemPresenterProtocol?)]()
|
||||
var itemsForMainThread = [(index: Int, itemCompanion: ChatItemCompanion)]()
|
||||
|
||||
for (index, decoratedItem) in decoratedItems.enumerate() {
|
||||
let presenter = self.presenterForIndex(index, decoratedChatItems: decoratedItems)
|
||||
for (index, itemCompanion) in items.enumerate() {
|
||||
var height: CGFloat?
|
||||
let bottomMargin: CGFloat = decoratedItem.decorationAttributes?.bottomMargin ?? 0
|
||||
if !isInbackground || presenter.canCalculateHeightInBackground ?? false {
|
||||
height = presenter.heightForCell(maximumWidth: collectionViewWidth, decorationAttributes: decoratedItem.decorationAttributes)
|
||||
let bottomMargin: CGFloat = itemCompanion.decorationAttributes?.bottomMargin ?? 0
|
||||
if !isInbackground || itemCompanion.presenter.canCalculateHeightInBackground {
|
||||
height = itemCompanion.presenter.heightForCell(maximumWidth: collectionViewWidth, decorationAttributes: itemCompanion.decorationAttributes)
|
||||
} else {
|
||||
itemsForMainThread.append((index: index, item: decoratedItem, presenter: presenter))
|
||||
itemsForMainThread.append((index: index, itemCompanion: itemCompanion))
|
||||
}
|
||||
intermediateLayoutData.append((height: height, bottomMargin: bottomMargin))
|
||||
}
|
||||
|
||||
if itemsForMainThread.count > 0 {
|
||||
dispatch_sync(dispatch_get_main_queue(), { () -> Void in
|
||||
for (index, decoratedItem, presenter) in itemsForMainThread {
|
||||
let height = presenter?.heightForCell(maximumWidth: collectionViewWidth, decorationAttributes: decoratedItem.decorationAttributes)
|
||||
for (index, itemCompanion) in itemsForMainThread {
|
||||
let height = itemCompanion.presenter.heightForCell(maximumWidth: collectionViewWidth, decorationAttributes: itemCompanion.decorationAttributes)
|
||||
intermediateLayoutData[index].height = height
|
||||
}
|
||||
})
|
||||
|
|
@ -251,7 +271,7 @@ extension BaseChatViewController: ChatDataSourceDelegateProtocol {
|
|||
|
||||
public func chatCollectionViewLayoutModel() -> ChatCollectionViewLayoutModel {
|
||||
if self.layoutModel.calculatedForWidth != self.collectionView.bounds.width {
|
||||
self.layoutModel = self.createLayoutModel(self.decoratedChatItems, collectionViewWidth: self.collectionView.bounds.width)
|
||||
self.layoutModel = self.createLayoutModel(self.chatItemCompanionCollection, collectionViewWidth: self.collectionView.bounds.width)
|
||||
|
||||
}
|
||||
return self.layoutModel
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import Foundation
|
|||
extension BaseChatViewController: ChatCollectionViewLayoutDelegate {
|
||||
|
||||
public func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
return self.decoratedChatItems.count
|
||||
return self.chatItemCompanionCollection.count
|
||||
}
|
||||
|
||||
public func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
|
||||
|
|
@ -66,23 +66,16 @@ extension BaseChatViewController: ChatCollectionViewLayoutDelegate {
|
|||
self.presenterForIndexPath(indexPath).performMenuControllerAction(action)
|
||||
}
|
||||
|
||||
public func presenterForIndexPath(indexPath: NSIndexPath) -> ChatItemPresenterProtocol {
|
||||
return self.presenterForIndex(indexPath.item, decoratedChatItems: self.decoratedChatItems)
|
||||
func presenterForIndexPath(indexPath: NSIndexPath) -> ChatItemPresenterProtocol {
|
||||
return self.presenterForIndex(indexPath.item, chatItemCompanionCollection: self.chatItemCompanionCollection)
|
||||
}
|
||||
|
||||
public func presenterForIndex(index: Int, decoratedChatItems: [DecoratedChatItem]) -> ChatItemPresenterProtocol {
|
||||
guard index < decoratedChatItems.count else {
|
||||
func presenterForIndex(index: Int, chatItemCompanionCollection items: ChatItemCompanionCollection) -> ChatItemPresenterProtocol {
|
||||
guard index < items.count else {
|
||||
// This can happen from didEndDisplayingCell if we reloaded with less messages
|
||||
return DummyChatItemPresenter()
|
||||
}
|
||||
|
||||
let chatItem = decoratedChatItems[index].chatItem
|
||||
if let presenter = self.presentersByChatItem.objectForKey(chatItem) as? ChatItemPresenterProtocol {
|
||||
return presenter
|
||||
}
|
||||
let presenter = self.createPresenterForChatItem(chatItem)
|
||||
self.presentersByChatItem.setObject(presenter, forKey: chatItem)
|
||||
return presenter
|
||||
return items[index].presenter
|
||||
}
|
||||
|
||||
public func createPresenterForChatItem(chatItem: ChatItemProtocol) -> ChatItemPresenterProtocol {
|
||||
|
|
@ -95,6 +88,6 @@ extension BaseChatViewController: ChatCollectionViewLayoutDelegate {
|
|||
}
|
||||
|
||||
public func decorationAttributesForIndexPath(indexPath: NSIndexPath) -> ChatItemDecorationAttributesProtocol? {
|
||||
return self.decoratedChatItems[indexPath.item].decorationAttributes
|
||||
return self.chatItemCompanionCollection[indexPath.item].decorationAttributes
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,21 +24,10 @@
|
|||
|
||||
import UIKit
|
||||
|
||||
public protocol ChatItemsDecoratorProtocol {
|
||||
func decorateItems(chatItems: [ChatItemProtocol]) -> [DecoratedChatItem]
|
||||
}
|
||||
|
||||
public struct DecoratedChatItem {
|
||||
public let chatItem: ChatItemProtocol
|
||||
public let decorationAttributes: ChatItemDecorationAttributesProtocol?
|
||||
public init(chatItem: ChatItemProtocol, decorationAttributes: ChatItemDecorationAttributesProtocol?) {
|
||||
self.chatItem = chatItem
|
||||
self.decorationAttributes = decorationAttributes
|
||||
}
|
||||
}
|
||||
|
||||
public class BaseChatViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
|
||||
|
||||
typealias ChatItemCompanionCollection = ReadOnlyOrderedDictionary<ChatItemCompanion>
|
||||
|
||||
public struct Constants {
|
||||
var updatesAnimationDuration: NSTimeInterval = 0.33
|
||||
var defaultContentInsets = UIEdgeInsets(top: 10, left: 0, bottom: 10, right: 0)
|
||||
|
|
@ -51,7 +40,7 @@ public class BaseChatViewController: UIViewController, UICollectionViewDataSourc
|
|||
public var constants = Constants()
|
||||
|
||||
public private(set) var collectionView: UICollectionView!
|
||||
var decoratedChatItems = [DecoratedChatItem]()
|
||||
var chatItemCompanionCollection: ChatItemCompanionCollection = ReadOnlyOrderedDictionary(items: [])
|
||||
public var chatDataSource: ChatDataSourceProtocol? {
|
||||
didSet {
|
||||
self.chatDataSource?.delegate = self
|
||||
|
|
@ -195,8 +184,6 @@ public class BaseChatViewController: UIViewController, UICollectionViewDataSourc
|
|||
var accessoryViewRevealer: AccessoryViewRevealer!
|
||||
var inputContainer: UIView!
|
||||
var presenterBuildersByType = [ChatItemType: [ChatItemPresenterBuilderProtocol]]()
|
||||
var presenters = [ChatItemPresenterProtocol]()
|
||||
let presentersByChatItem = NSMapTable(keyOptions: .WeakMemory, valueOptions: .StrongMemory)
|
||||
let presentersByCell = NSMapTable(keyOptions: .WeakMemory, valueOptions: .WeakMemory)
|
||||
var updateQueue: SerialTaskQueueProtocol = SerialTaskQueue()
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-present Badoo Trading Limited.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
import Foundation
|
||||
|
||||
struct ReadOnlyOrderedDictionary<T where T: UniqueIdentificable> : CollectionType {
|
||||
|
||||
private let items: [T]
|
||||
private let itemsById: [String: Int] // Maping to the position in the array instead the item itself for better performance
|
||||
|
||||
init(items: [T]) {
|
||||
var dictionary = [String: Int](minimumCapacity: items.count)
|
||||
for (index, item) in items.enumerate() {
|
||||
dictionary[item.uid] = index
|
||||
}
|
||||
self.items = items
|
||||
self.itemsById = dictionary
|
||||
}
|
||||
|
||||
subscript(index: Int) -> T {
|
||||
return self.items[index]
|
||||
}
|
||||
|
||||
subscript(uid: String) -> T? {
|
||||
if let index = self.itemsById[uid] {
|
||||
return self.items[index]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func generate() -> AnyGenerator<T> {
|
||||
var index = 0
|
||||
|
||||
return anyGenerator({
|
||||
guard index < self.items.count else {
|
||||
return nil
|
||||
}
|
||||
|
||||
defer { index += 1 }
|
||||
return self.items[index]
|
||||
})
|
||||
}
|
||||
|
||||
var startIndex: Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
var endIndex: Int {
|
||||
return self.items.count
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-present Badoo Trading Limited.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import XCTest
|
||||
@testable import Chatto
|
||||
|
||||
class ReadOnlyOrderedDictionaryTests: XCTestCase {
|
||||
|
||||
var orderedDictionary: ReadOnlyOrderedDictionary<FakeChatItem>!
|
||||
override func setUp() {
|
||||
let items = [
|
||||
FakeChatItem(uid: "3", type: "type3"),
|
||||
FakeChatItem(uid: "1", type: "type1"),
|
||||
FakeChatItem(uid: "2", type: "type2"),
|
||||
]
|
||||
self.orderedDictionary = ReadOnlyOrderedDictionary<FakeChatItem>(items: items)
|
||||
}
|
||||
|
||||
func testThat_MapsCorrectly() {
|
||||
XCTAssertEqual(self.orderedDictionary.map { $0.uid}, ["3", "1", "2"])
|
||||
}
|
||||
|
||||
func testThat_NumberOfItemsIsCorrect() {
|
||||
XCTAssertEqual(self.orderedDictionary.count, 3)
|
||||
}
|
||||
|
||||
func testThat_WhenSubscriptingByIndex_ThenReturnsCorrectValue() {
|
||||
XCTAssertEqual(self.orderedDictionary[1].uid, "1")
|
||||
}
|
||||
|
||||
func testThat_WhenSubscriptingByExistingKey_ThenReturnsCorrectValue() {
|
||||
XCTAssertEqual(self.orderedDictionary["3"]?.type, "type3")
|
||||
}
|
||||
|
||||
func testThat_WhenSubscriptingByNonExistingKey_ThenReturnsNil() {
|
||||
XCTAssertTrue(self.orderedDictionary["non-existing"] == nil)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -14,7 +14,7 @@
|
|||
buildForArchiving = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = 'primary'
|
||||
BlueprintIdentifier = '83B4FF471C1843062137E9FF'
|
||||
BlueprintIdentifier = 'E01191D9BA46E65E3010363E'
|
||||
BlueprintName = 'Chatto'
|
||||
ReferencedContainer = 'container:Pods.xcodeproj'
|
||||
BuildableName = 'Chatto.framework'>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
buildForArchiving = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = 'primary'
|
||||
BlueprintIdentifier = 'A70ECE396742958C4F6531A1'
|
||||
BlueprintIdentifier = '3511596EA6005DAD4009182F'
|
||||
BlueprintName = 'ChattoAdditions'
|
||||
ReferencedContainer = 'container:Pods.xcodeproj'
|
||||
BuildableName = 'ChattoAdditions.framework'>
|
||||
|
|
|
|||
Loading…
Reference in New Issue