Merge pull request #36 from diegosanchezr/dev

Allows a datasource to return different chatItem instances for same message id
This commit is contained in:
Diego Sánchez 2016-02-08 18:26:17 +00:00
commit e7e58ae8a1
11 changed files with 1467 additions and 3607 deletions

View File

@ -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 */,
);

View File

@ -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
}
}

View File

@ -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

View File

@ -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
}
}

View File

@ -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()

View File

@ -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
}
}

View File

@ -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

View File

@ -14,7 +14,7 @@
buildForArchiving = "YES">
<BuildableReference
BuildableIdentifier = 'primary'
BlueprintIdentifier = '83B4FF471C1843062137E9FF'
BlueprintIdentifier = 'E01191D9BA46E65E3010363E'
BlueprintName = 'Chatto'
ReferencedContainer = 'container:Pods.xcodeproj'
BuildableName = 'Chatto.framework'>

View File

@ -14,7 +14,7 @@
buildForArchiving = "YES">
<BuildableReference
BuildableIdentifier = 'primary'
BlueprintIdentifier = 'A70ECE396742958C4F6531A1'
BlueprintIdentifier = '3511596EA6005DAD4009182F'
BlueprintName = 'ChattoAdditions'
ReferencedContainer = 'container:Pods.xcodeproj'
BuildableName = 'ChattoAdditions.framework'>