struct CustomMessageTextView: View {
@Injected(\.colors) var colors
@Injected(\.fonts) var fonts
var message: ChatMessage
var isFirst: Bool
public var body: some View {
Text(message.text)
.padding()
.messageBubble(for: message, isFirst: isFirst)
.foregroundColor(Color.blue)
.font(fonts.bodyBold)
.overlay(
BottomRightView {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(.green)
.offset(x: 1, y: -1)
}
)
}
}
Message Attachments
Message Attachments Overview
The SwiftUI SDK supports the following types of attachments:
- Images
- Giphy files
- Videos
- Links
- Files
They all come with default views displaying the attachments. In addition, they are all swappable, so you can easily change some of them if you want a more customized look.
Swapping Attachment Views
Let’s see a simple example of how you can swap the existing attachments with your custom view. We will create a custom message view that displays the texts, but with bold font and different text color, as well as small checkmark in the bottom right corner, hinting that the message was sent.
Next, we need to implement makeMessageTextView
in the ViewFactory
protocol, in our own CustomFactory
.
class CustomFactory: ViewFactory {
@Injected(\.chatClient) public var chatClient
private init() {}
public static let shared = CustomFactory()
func makeMessageTextView(
for message: ChatMessage,
isFirst: Bool,
availableWidth: CGFloat,
scrolledId: Binding<String?>
) -> some View {
CustomMessageTextView(
message: message,
isFirst: isFirst,
scrolledId: scrolledId
)
}
}
Finally, we need to inject the CustomFactory
in our view hierarchy.
var body: some Scene {
WindowGroup {
ChatChannelListView(viewFactory: CustomFactory.shared)
}
}
These are all the steps needed to change the default SDK view with your custom one.
Image Attachment View
Similarly, you can change the other types of attachments view in the SDK. To update the view that presents images, you need to implement the following method:
func makeImageAttachmentView(
for message: ChatMessage,
isFirst: Bool,
availableWidth: CGFloat,
scrolledId: Binding<String?>
) -> some View {
CustomImageAttachment(
factory: self,
message: message,
width: availableWidth,
isFirst: isFirst,
scrolledId: scrolledId
)
}
Giphy Attachment View
To update the view that presents gifs, you should implement the following method:
func makeGiphyAttachmentView(
for message: ChatMessage,
isFirst: Bool,
availableWidth: CGFloat,
scrolledId: Binding<String?>
) -> some View {
GiphyAttachmentView(
factory: self,
message: message,
width: availableWidth,
isFirst: isFirst,
scrolledId: scrolledId
)
}
Link Attachment View
You can also change the way links are displayed in the message list, by implementing the following method:
func makeLinkAttachmentView(
for message: ChatMessage,
isFirst: Bool,
availableWidth: CGFloat,
scrolledId: Binding<String?>
) -> some View {
CustomLinkAttachmentView(
factory: self,
message: message,
width: availableWidth,
isFirst: isFirst,
scrolledId: scrolledId
)
}
File Attachment View
File attachments can be customized by implementing the method below:
func makeFileAttachmentView(
for message: ChatMessage,
isFirst: Bool,
availableWidth: CGFloat,
scrolledId: Binding<String?>
) -> some View {
CustomFileAttachmentsView(
factory: self,
message: message,
width: availableWidth,
isFirst: isFirst,
scrolledId: scrolledId
)
}
Video Attachments
To replace the way video attachments are presented, you need to provide your own implementation of this method:
func makeVideoAttachmentView(
for message: ChatMessage,
isFirst: Bool,
availableWidth: CGFloat,
scrolledId: Binding<String?>
) -> some View {
CustomVideoAttachmentsView(
factory: self,
message: message,
width: availableWidth,
scrolledId: scrolledId
)
}
Handling Custom Attachments
You can go a step further and introduce your own custom attachments with their corresponding custom views. Use-cases can be workout attachments, food delivery, money sending and anything else that might be supported within your apps.
Supporting custom attachment views is straightforward and can be implemented in a few simple steps. As an example, let’s create a custom view that will detect if there’s an email address in our message, and if there is - it will create a custom view with an additional email icon.
First, you need to create a custom view.
struct CustomAttachmentView: View {
let message: ChatMessage
let width: CGFloat
let isFirst: Bool
var body: some View {
HStack {
Image(systemName: "envelope")
Text(message.text)
}
.padding()
.frame(maxWidth: width)
.messageBubble(for: message, isFirst: isFirst)
}
}
Next, you need to define a custom rule in our MessageTypeResolving
protocol, which has a default implementation in our SDK. What we are interested in is the hasCustomAttachment
method. Therefore, we will provide our own implementation of it to check if there are any emails in the text message.
class CustomMessageResolver: MessageTypeResolving {
func hasCustomAttachment(message: ChatMessage) -> Bool {
let messageComponents = message.text.components(separatedBy: CharacterSet.whitespacesAndNewlines)
return messageComponents.filter { component in
isValidEmail(component)
}
.count > 0
}
private func isValidEmail(_ email: String) -> Bool {
let emailRegEx = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
let emailPred = NSPredicate(format:"SELF MATCHES %@", emailRegEx)
return emailPred.evaluate(with: email)
}
}
Next, we need to replace the default MessageTypeResolving
implementation, with the one we have just created in our Utils
class of the StreamChat
object.
let messageTypeResolver = CustomMessageResolver()
let utils = Utils(messageTypeResolver: messageTypeResolver)
let streamChat = StreamChat(chatClient: chatClient, utils: utils)
Next, in our CustomFactory
, we need to return the new view we have created above, in the method makeCustomAttachmentViewType
.
func makeCustomAttachmentViewType(
for message: ChatMessage,
isFirst: Bool,
availableWidth: CGFloat,
scrolledId: Binding<String?>
) -> some View {
CustomAttachmentView(
message: message,
width: availableWidth,
isFirst: isFirst
)
}
Finally, you need to inject the CustomFactory
into our view hierarchy if you haven’t done it already.
Note, if you want to support several different custom views, you will need to do if-else or switch logic in both the message type resolver and the custom view, which will act as container for several views.