How to Create Custom Chat Attachments With SwiftUI

8 min read

Learn how to create custom attachments while using Stream Chat UI Components for SwiftUI.

Jeroen L.
Jeroen L.
Published June 7, 2023
Custom Chat Attachments With SwiftUI

When using Stream Chat, you most likely want tight integration between Stream Chat and the rest of your application. If something is a concept in your product, you want to be able to attach it to a chat message. By default, Stream Chat supports several attachment types already. Images are rendered in a gallery layout, files are displayed in a list, and links are shown with a rich preview of the content available at the link’s location.

With Stream Chat, you can render a custom attachment any way you want. You just need to facilitate two things:

  1. How to recognize the custom attachment
  2. How to render the custom attachment

What you want to render as custom attachments is up to your imagination and maybe your UX designers. The rendered custom attachments are SwiftUI views allowing for interaction and dynamic updating of contents.

Attachment Example in our SwiftUI Tutorial

In our SwiftUI tutorial, we have a basic attachment example. It detects an email address in a chat message and renders an envelop icon when found. This is the most basic form of a custom attachment. But we can go a lot further. This article will show you another attachment example that does just that. By the end of this tutorial, you can develop a use case for your environment and create a custom attachment according to your specifications.

Please review the SwiftUI tutorial before we begin. If you want to skip straight to what is in this article, look at the repository containing the finished tutorial example.

Attachment Recap

If you look closely at the attachment example in the SwiftUI tutorial, you will notice an attachment can be triggered on any property of a chat message. In the tutorial, the contents of the chat message are inspected for a string that looks like an email address.

What we will do in this article instead is demonstrate what is possible when using an actual custom attachment object. A piece of custom data that is a separate entity, a blob of data like an image or video. But you will need to tell the SDK how to handle this piece of data by extending the functionality of the attachment rendering.

Before we dive in, here are some examples of what you could do with custom attachments.

Defining the Data We Want to Render

As mentioned, we can use any data as an attachment. And here is one huge caveat to be aware of. When storing data in an attachment, and you are using Stream’s CDN, the data stored is “content-addressable.” This is a fancy way of saying if you know the URL of the stored information, you can get the stored information. There is no further access control in place. So you have to make sure the data in the attachment is ok to be public to some extent. With images and videos, this is usually not a big deal.

In the example in this article, we are sending a payment request as an attachment. In a real-life scenario, you would store the payment state in your service. To simplify this example, we hang on to the payment state in a message’s extraData field.

What we want to demo is a conversation between two people, which at some point requires a transfer of money. A common use case in banking apps.

To achieve this result, we need to do several things:

  • Declare a custom payment attachment type
  • Make sure the Stream Chat SDK can recognize, render and add these attachments
  • Define the behavior and look of a payment attachment

Everything else is provided out of the box by the Stream Chat SDK. The only limit is your imagination.

The entire example and all code in this article are available as an Xcode project on GitHub.

Defining the Payment Attachment Payload

First of all, we will define the attachment itself.

Building your own app? Get early access to our Livestream or Video Calling API and launch in days!
swift
import Foundation
import StreamChat

struct PaymentAttachmentPayload: AttachmentPayload, Identifiable {
    static let type: AttachmentType = .payment

    var id: String = UUID().uuidString
    var amount: Int
}

extension PaymentAttachmentPayload {

    static var preview: PaymentAttachmentPayload = PaymentAttachmentPayload(amount: 25)
}
swift
import StreamChat

extension AttachmentType {
    static let payment = Self(rawValue: "payment")
}

Next, we need to make sure the Stream Chat SDK can recognize the attachment type. To do that, we need to define a custom MessageResolver.

swift
import StreamChat
import StreamChatSwiftUI

class CustomMessageResolver: MessageTypeResolving {
    func hasCustomAttachment(message: ChatMessage) -> Bool {
        let paymentAttachments = message.attachments(payloadType: PaymentAttachmentPayload.self)
        return !paymentAttachments.isEmpty
    }
}

This detects the presence of our newly defined attachments.

We need to define how a payment looks when sent to another user.

swift
import SwiftUI
import StreamChat

struct PaymentAttachmentView: View {

    @ObservedObject var viewModel: MyChannelListViewModel

    var payload: PaymentAttachmentPayload
    var paymentState: PaymentState
    var paymentDate: String?
    var messageId: MessageId

    @State private var processing = false
    @State private var processingText = ""

But we also need UI to allow a user to send an attachment.

swift
import SwiftUI

struct PaymentAttachmentPickerView: View {

    @ObservedObject var viewModel: MyChannelListViewModel

    @State private var selectedAmount: Int? = nil

    var paymentAmounts: [Int] = [
        1, 5, 25, 50
    ]

    var body: some View {
        VStack(alignment: .leading, spacing: 20) {
            Text("Select amount")
swift
import SwiftUI
import StreamChat
import StreamChatSwiftUI

class MyChannelListViewModel: ChatChannelListViewModel {

    @Injected(\.chatClient) var chatClient

    @Published var selectedCustomAttachment: SelectedCustomAttachment = .payment

    var onPickerStateChange: ((AttachmentPickerState) -> Void)?
    var closeAttachments: (() -> Void)?

    func tryCallingPickerStateChange() {
        if let onPickerStateChange {

We have defined all the components we need, but we still need to tell the Stream Chat SDK how and when to display this.

To do that, we need to declare a custom view factory. This class overrides the defaults of our Chat SDK and allows for the injection of new or custom views based on conditions you define.

When implementing a custom view factory, you have access to a lot of the data and metadata related to all the chat messages being rendered. Based on this information, you can evaluate if and when you want to trigger the rendering of your custom attachment views.

swift
import SwiftUI
import StreamChat
import StreamChatSwiftUI

class MyViewFactory: ViewFactory {

    @Injected(\.chatClient) var chatClient: ChatClient

    @ObservedObject var viewModel: MyChannelListViewModel

    init(viewModel: MyChannelListViewModel) {
        self.viewModel = viewModel
    }

    func makeLeadingComposerView(

Especially the function makeCustomAttachmentViewType is of interest. When the CustomMessageResolver we defined earlier detects a custom attachment, the Stream SDK asks the view factory to create a custom attachment view. In our case a PaymentAttachmentView.

Similar things happen for the PaymentAttachmentPickerView and the PaymentAttachmentPreview. When a custom attachment is detected, a custom view is created. Exactly what we need. My recommendation is to run this code and see things happen in a debugger.

So to recap, there are three key moments to consider when dealing with a custom attachment.

  1. We need to enable the application to display a button in the message composer to initiate adding a custom attachment to a message. We do this by creating a custom LeadingComposerView, which adds a button to the message composer.

  2. We need logic and UI to allow the user to add the actual attachment to the message. This involves the CustomAttachmentView, which displays the PaymentAttachmentPickerView in case the attachment being added to the message is of type payment.

  3. On the message list side of things, we need a way to display a custom attachment when it has been sent by a user. This involves the PaymentAttachmentView and related models. On the PaymentAttachmentView you are free to do whatever you want in relation to your attachment. It could be about displaying static data like an image, but it can also be more dynamic, like a payment.

You will have noticed several details about getting a custom attachment going. But make no mistake, custom attachments are a powerful feature of our Chat SDK, allowing you to tightly integrate your domain with our Chat SDK. By creating a tight integration, your end users will enjoy the benefits of being able to share information relevant to your app with ease, and it will increase the engagement of your audience.

Conclusion

By now, you know how easy it is to add custom attachments to your app using Stream’s SwiftUI Chat SDK. With custom attachments, you can display custom media, location-based information, shopping details, and more content customized to suit your target audience’s needs.

To learn more about our in-app messaging SDK for SwiftUI, look at its GitHub repository. Make sure to give it a star while you are there. You should also take a look at our documentation to learn more. To get you started even quicker, we have a SwiftUI tutorial available as well. Our tutorial is the quickest way to get you started using our Chat SDK.

I hope you enjoyed this article. Look for us on Twitter and let us know what you think. If you have any questions, do reach out as well. We are always happy to help.

decorative lines
Integrating Video With Your App?
We've built an audio and video solution just for you. Launch in days with our new APIs & SDKs!
Check out the BETA!