GetStream / stream-chat-swift

💬 iOS Chat SDK in Swift - Build your own app chat experience for iOS using the official Stream Chat API
https://getstream.io/chat/sdk/ios/
Other
856 stars 209 forks source link

Leading alignment of all messages #946

Closed cjmconie closed 3 years ago

cjmconie commented 3 years ago

What are you trying to achieve?

Left aligning all message bubbles. The outcome we are looking for is something similar to Stream's livestream demo. This is fairly common UI for live-streaming and comment threads.

If possible, how can you achieve this currently?

Subclassing ChatMessageContentView so that all messages (both incoming and outgoing) are left aligned.

This looks to be possible but requires overriding a lot of methods e.g. setupMessageBubbleView, setupMetadataView etc. In addition to the overrides, properties like incomingMessageConstraints and incomingMessageIsThreadConstraints etc. are internal which means super's implementation can't be leveraged in the override, nor can those properties be referenced.

Partial example of overriding ChatMessageContentView

class LeadingOnlyChatMessageContentView: ChatMessageContentView {

    override func updateMessagePositionIfNeeded() {
        constraintsToActivate.append(contentsOf: incomingMessageConstraints)
        constraintsToDeactivate.append(contentsOf: outgoingMessageConstraints)
    }

    override func updateAvatarViewIfNeeded() {
        guard let message = message else { return /* todo */ }

        setupAvatarView()
        let authorAvatarView = self.authorAvatarView!

        let placeholder = uiConfig.images.userAvatarPlaceholder1
        if let imageURL = message.author.imageURL {
            authorAvatarView.imageView.loadImage(from: imageURL, placeholder: placeholder)
        } else {
            authorAvatarView.imageView.image = placeholder
        }
    }

}

class CustomChatMessageCollectionViewCell: СhatMessageCollectionViewCell {

    /*
     Seems necessary to create a custom subclass of ChatMessageCollectionViewCell.  I thought something like
     myCustomConfig.messageList.messageContentView = CustomChatMessageContentView.self 
    would be sufficient.

     The `СhatMessageCollectionViewCell` must specify the `ChatMessageContentView` – type which circumvents the     purpose of UIConfig?
     */
    override class var messageContentViewClass: _ChatMessageContentView<ExtraData>.Type {
        return LeadingOnlyChatMessageContentView.self
    }

}

What would be the better way?

I think the use case is broad enough to warrant a change to ChatMessageContentView. One solution would be to add a property contentAlignment of type ContentAlignment which controls the alignment.

var contentAlignment: ContentAlignment = .auto

enum ContentAlignment {
        case leading
        case trailing
        case auto
    }

The default is auto where incoming messages are leading aligned and outgoing are trailing aligned. Explicitly specifying .leading or trailing overrides the auto behaviour for both incoming and outgoing messages.

GetStream Environment

GetStream Chat version: 3.2.0-beta.5 GetStream Chat frameworks: StreamChatClient, StreamChatCore, StreamChat iOS version: 13.0 Swift version: 5.2.3 Xcode version: 12.4 Device: Any

Additional context

b-onc commented 3 years ago

Hello @cjmconie , Thanks for all the info! (Pasting the same message from #1065 since it's a duplicate) In the current beta version, the message cells are not implemented to be easily aligned. The recommended way is to implement your own cell and use it. However, since we've seen this request many times, we've re-engineered the message list layout system so this usecase is easily supported. Part of the solution is already merged in main. We'll be releasing a new beta with this new system soon. When we do that, I'll update this ticket with the new suggested solution.

b-onc commented 3 years ago

Hello @cjmconie

This is possible using our own Message cells with 4.0-beta.1. You just need to do:

class LeftAlignedMessageLayoutOptionsResolver: ChatMessageLayoutOptionsResolver {
    override func optionsForMessage(at indexPath: IndexPath, in channel: _ChatChannel<NoExtraData>, with messages: AnyRandomAccessCollection<_ChatMessage<NoExtraData>>) -> ChatMessageLayoutOptions {
        var options = super.optionsForMessage(at: indexPath, in: channel, with: messages)
        options.remove(.flipped)
        options.insert(.avatar)
        return options
    }
}

And before you display your Chat screen...

Components.default.messageLayoutOptionsResolver = LeftAlignedMessageLayoutOptionsResolver()

and it looks like:

Screen Shot 2021-05-21 at 19 55 42