Closed MrShiY closed 6 years ago
+1
I accomplished this by adding a new DecoratedChatItem for every message (see ChatItemsDecoratorProtocol)
Thanks so much Daxito! Works like a charm :)
What you said was to register ChatItemPresenter Cell, like timeSeparator and messageSendingStatus ok?
Hey MrShiY, as I know it takes a while to even get started, I will do my best to give you an easy example to get you in the right direction.
Basically, in my case I subclassed the BaseChatViewController in ChatAdditions where the decorator handler via property decoratorItems is specified.
class MySubclassChatViewController: BaseChatViewController {
override func viewDidLoad() {
super.viewDidLoad()
// The line below specifies your custom decorator
self.chatItemsDecorator = MyChatItemsDecorator(messagesSelector: self.messagesSelector)
}
override func createPresenterBuilders() -> [ChatItemType: [ChatItemPresenterBuilderProtocol]] {
let textMessagePresenter = TextMessagePresenterBuilder(
viewModelBuilder: TextMessageViewModelBuilder(),
interactionHandler: TextMessageHandler(baseHandler: self.baseMessageHandler)
)
textMessagePresenter.baseMessageStyle = BaseMessageCollectionViewCellAvatarStyle()
let photoMessagePresenter = PhotoMessagePresenterBuilder(
viewModelBuilder: PhotoMessageViewModelBuilder(),
interactionHandler: PhotoMessageHandler(baseHandler: self.baseMessageHandler)
)
photoMessagePresenter.baseCellStyle = BaseMessageCollectionViewCellAvatarStyle()
return [
TextMessageModel.chatItemType: [textMessagePresenter],
PhotoMessageModel.chatItemType: [photoMessagePresenter],
//NOTE: This allows your new decorator to be considered
UsernameModel.chatItemType: [UsernamePresenterBuilder()],
SendingStatusModel.chatItemType: [SendingStatusPresenterBuilder()],
TimeSeparatorModel.chatItemType: [TimeSeparatorPresenterBuilder()]
]
}
}
It should be noted that I literally copied my decorator from the Chatto Demo application, as I wasn't sure what needed to be there. Below its called MyChatItemsDecorator.
class MyChatItemsDecorator: ChatItemsDecoratorProtocol {
func decorateItems(_ chatItems: [ChatItemProtocol]) -> [DecoratedChatItem] {
func decorateItems(_ chatItems: [ChatItemProtocol]) -> [DecoratedChatItem] {
var decoratedChatItems = [DecoratedChatItem]()
let calendar = Calendar.current
for (index, chatItem) in chatItems.enumerated() {
let next: ChatItemProtocol? = (index + 1 < chatItems.count) ? chatItems[index + 1] : nil
let prev: ChatItemProtocol? = (index > 0) ? chatItems[index - 1] : nil
var bottomMargin = self.separationAfterItem(chatItem, next: next)
var showsTail = false
var additionalItems = [DecoratedChatItem]()
var addTimeSeparator = false
var isSelected = false
var isShowingSelectionIndicator = false
if let currentMessage = chatItem as? MessageModelProtocol {
if let nextMessage = next as? MessageModelProtocol {
showsTail = currentMessage.senderId != nextMessage.senderId
} else {
showsTail = true
}
if let previousMessage = prev as? MessageModelProtocol {
addTimeSeparator = !calendar.isDate(currentMessage.date, inSameDayAs: previousMessage.date)
} else {
addTimeSeparator = true
}
if let myMessage = chatItem as? MyTextMessageModel,
let username = myMessage.username as? String, showsTail == true {
bottomMargin = 0.0. //HERE: I ensure there is no space after the Avatar/message if its the last message , aka showTail
//NOTE: Add username here , determines if the username is on left or right with the .isOwner property in my custom model.
additionalItems.append(
DecoratedChatItem(
chatItem: UsernameModel(uid: "\(currentMessage.uid)-decoration-username",
username: username, owner: myMessage.isOwner),
decorationAttributes: nil))
}
if self.showsStatusForMessage(currentMessage) {
additionalItems.append(
DecoratedChatItem(
chatItem: SendingStatusModel(uid: "\(currentMessage.uid)-decoration-status", status: currentMessage.status),
decorationAttributes: nil)
)
}
if addTimeSeparator {
let dateTimeStamp = DecoratedChatItem(chatItem: TimeSeparatorModel(uid: "\(currentMessage.uid)-time-separator", date: currentMessage.date.toWeekDayAndDateString()), decorationAttributes: nil)
decoratedChatItems.append(dateTimeStamp)
}
isSelected = self.messagesSelector.isMessageSelected(currentMessage)
isShowingSelectionIndicator = self.messagesSelector.isActive && self.messagesSelector.canSelectMessage(currentMessage)
}
decoratedChatItems.append(
DecoratedChatItem(
chatItem: chatItem,
decorationAttributes: ChatItemDecorationAttributes(bottomMargin: bottomMargin,
canShowTail: showsTail, canShowAvatar: showsTail, canShowFailedIcon: true)
)
)
decoratedChatItems.append(contentsOf: additionalItems)
}
return decoratedChatItems
}
}
}
Basically, what is happening is each message is iterated, and the decorator reviews each row providing the ability to add additional items as if they were added to the dataSource specifically. In this case, by virtue of addition of a Status message, or userName to the decoratedChatItems that is returned, they are respectively turned into new CollectionViewCell's you specify below each row, etc.
As a result, you will need to create a new cell for your username, though since Status message cell only displays text which is true for your username, you could use it instead. HOwever, because I wanted my username to be of a different font and float left/right respectively depending on who made the comment, I literally copied the SendingStatusPresenter and replaced the cells/logic used with my own.
class UsernameCollectionViewCell: UICollectionViewCell {
@IBOutlet weak var username: UILabel!
}
class UsernamePresenter: ChatItemPresenterProtocol {
let usernameModel: UsernameModel
init(model: UsernameModel) {
self.usernameModel = model
}
static func registerCells(_ collectionView: UICollectionView) { //NOTE: USE MY OWN CELL
collectionView.register(UINib(nibName: "UsernameCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "UsernameCollectionViewCell")
}
func dequeueCell(collectionView: UICollectionView, indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "UsernameCollectionViewCell", for: indexPath)
return cell
}
func configureCell(_ cell: UICollectionViewCell, decorationAttributes: ChatItemDecorationAttributesProtocol?) {
guard let statusCell = cell as? UsernameCollectionViewCell else {
assert(false, "expecting status cell")
return
}
let attrs = [
NSFontAttributeName: Stylesheet.boldFont(size: 12),
NSForegroundColorAttributeName: UIColor.blue
]
if let usernameCell = cell as? UsernameCollectionViewCell {
//NOTE: Notice I float my text left/right by considered the new property I added to my username model (e.g. isOwner)'
usernameCell.username.textAlignment = self.usernameModel.isOwner ? .left : .right
usernameCell.username.attributedText = NSAttributedString(
string: "@\(self.usernameModel.username)",
attributes: attrs)
}
}
var canCalculateHeightInBackground: Bool {
return true
}
func heightForCell(maximumWidth width: CGFloat, decorationAttributes: ChatItemDecorationAttributesProtocol?) -> CGFloat {
return 19
}
}
public class UsernamePresenterBuilder: ChatItemPresenterBuilderProtocol {
public func canHandleChatItem(_ chatItem: ChatItemProtocol) -> Bool {
return chatItem is UsernameModel ? true : false
}
public func createPresenterWithChatItem(_ chatItem: ChatItemProtocol) -> ChatItemPresenterProtocol {
assert(self.canHandleChatItem(chatItem))
return UsernamePresenter(model: chatItem as! UsernameModel)
}
public var presenterType: ChatItemPresenterProtocol.Type {
return UsernamePresenter.self
}
}
While its a big pill to swallow, copying the implementation of each decorated item in the Chatto app could be of help as I hope this minor example might be. Additional applicable links are below: Thanks!
Decorator explanation: https://github.com/badoo/Chatto/wiki/Decorator
How to create a new cell (in your case, username) https://github.com/badoo/Chatto/wiki/Tutorial_Add_Custom_Cell
Very cool, thank neilabdev and Daxito for your help
Please tell me,Thank you very much