Home

Awesome

<a href="https://exyte.com/"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/exyte/media/master/common/header-dark.png"><img src="https://raw.githubusercontent.com/exyte/media/master/common/header-light.png"></picture></a>

<a href="https://exyte.com/"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/exyte/media/master/common/our-site-dark.png" width="80" height="16"><img src="https://raw.githubusercontent.com/exyte/media/master/common/our-site-light.png" width="80" height="16"></picture></a>     <a href="https://twitter.com/exyteHQ"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/exyte/media/master/common/twitter-dark.png" width="74" height="16"><img src="https://raw.githubusercontent.com/exyte/media/master/common/twitter-light.png" width="74" height="16"> </picture></a> <a href="https://exyte.com/contacts"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/exyte/media/master/common/get-in-touch-dark.png" width="128" height="24" align="right"><img src="https://raw.githubusercontent.com/exyte/media/master/common/get-in-touch-light.png" width="128" height="24" align="right"></picture></a>

<table> <thead> <tr> <th>Chat</th> <th>Media</th> <th>Audio Messages</th> <th>Extra</th> </tr> </thead> <tbody> <tr> <td> <img src="https://github.com/exyte/Chat/assets/1358172/baf0167f-b3e0-4df2-bd3b-b6b1c4ee385d" /> </td> <td> <img src="https://github.com/exyte/Chat/assets/1358172/d62876ef-4475-4f07-933a-9d9366b02e28" /> </td> <td> <img src="https://github.com/exyte/Chat/assets/1358172/ebd2040d-1cf0-4066-9391-592af1426571" /> </td> <td> <img src="https://github.com/exyte/Chat/assets/1358172/053bcd73-0db7-44da-abd6-0a57f0f88a4b" /> </td> </tr> </tbody> </table> <p><h1>Chat</h1></p> <p><h4>A SwiftUI Chat UI framework with fully customizable message cells and a built-in media picker</h4></p>

SPM Compatible Cocoapods Compatible Carthage Compatible License: MIT

Features

Usage

Create a chat view like this:

@State var messages: [Message] = []

var body: some View {
    ChatView(messages: messages) { draft in
        yourViewModel.send(draft: draft)
    }
}

where:
messages - list of messages to display
didSendMessage - a closure which is called when the user presses the send button

Message is a type that Chat uses for the internal implementation. In the code above it expects the user to provide a list of Message structs, and it returns a DraftMessage in the didSendMessage closure. You can map it both ways to your own Message model that your API expects or use as is.

Available chat types

Chat type - determines the order of messages and direction of new message animation. Available options:

Reply mode - determines how replying to message looks. Available options:

To specify any of these pass them through init:

ChatView(messages: viewModel.messages, chatType: .comments, replyMode: .answer) { draft in
    yourViewModel.send(draft: draft)
}

Custom UI

You may customize message cells like this:

ChatView(messages: viewModel.messages) { draft in
    viewModel.send(draft: draft)
} messageBuilder: { message, positionInUserGroup, positionInCommentsGroup, showContextMenuClosure, messageActionClosure, showAttachmentClosure in
    VStack {
        Text(message.text)
        if !message.attachments.isEmpty {
            ForEach(message.attachments, id: \.id) { at in
                AsyncImage(url: at.thumbnail)
            }
        }
    }
}

messageBuilder's parameters:

You may customize the input view (a text field with buttons at the bottom) like this:

ChatView(messages: viewModel.messages) { draft in
    viewModel.send(draft: draft)
} inputViewBuilder: { textBinding, attachments, inputViewState, inputViewStyle, inputViewActionClosure, dismissKeyboardClosure in
    Group {
        switch inputViewStyle {
        case .message: // input view on chat screen
            VStack {
                HStack {
                    Button("Send") { inputViewActionClosure(.send) }
                    Button("Attach") { inputViewActionClosure(.photo) }
                }
                TextField("Write your message", text: textBinding)
            }
        case .signature: // input view on photo selection screen
            VStack {
                HStack {
                    Button("Send") { inputViewActionClosure(.send) }
                }
                TextField("Compose a signature for photo", text: textBinding)
                    .background(Color.green)
            }
        }
    }
}

inputViewBuilder's parameters:

Custom message menu

Long tap on a message will display a menu for this message (can be turned off, see Modifiers). To define custom message menu actions declare an enum conforming to MessageMenuAction. Then the library will show your custom menu options on long tap on message instead of default ones, if you pass your enum's name to it (see code sample). Once the action is selected special callbcak will be called. Here is a simple example:

enum Action: MessageMenuAction {
    case reply, edit

    func title() -> String {
        switch self {
        case .reply:
            "Reply"
        case .edit:
            "Edit"
        }
    }
    
    func icon() -> Image {
        switch self {
        case .reply:
            Image(systemName: "arrowshape.turn.up.left")
        case .edit:
            Image(systemName: "square.and.pencil")
        }
    }
}

ChatView(messages: viewModel.messages) { draft in
    viewModel.send(draft: draft)
} messageMenuAction: { (action: Action, defaultActionClosure, message) in // <-- here: specify the name of your `MessageMenuAction` enum
    switch action {
    case .reply:
        defaultActionClosure(message, .reply)
    case .edit:
        defaultActionClosure(message, .edit { editedText in
            // update this message's text on your BE
            print(editedText)
        })
    }
}

messageMenuAction's parameters:

When implementing your own MessageMenuActionClosure, write a switch statement passing through all the cases of your MessageMenuAction, inside each case write your own action handler, or call the default one. NOTE: not all default actions work out of the box - e.g. for .edit you'll still need to provide a closure to save the edited text on your BE. Please see CommentsExampleView in ChatExample project for MessageMenuActionClosure usage example.

Small view builders:

These use AnyView, so please try to keep them easy enough

Modifiers

isListAboveInputView - messages table above the input field view or not
showDateHeaders - show section headers with dates between days, default is true
isScrollEnabled - forbid scrolling for messages' UITabelView
showMessageMenuOnLongPress - turn menu on long tap on/off
showNetworkConnectionProblem - display network error on/off
assetsPickerLimit - set a limit for MediaPicker built into the library
setMediaPickerSelectionParameters - a struct holding MediaPicker selection parameters (assetsPickerLimit and others like mediaType, selectionStyle, etc.).
orientationHandler - handle screen rotation

enableLoadMore(offset: Int, handler: @escaping ChatPaginationClosure) - when user scrolls to offset-th message from the end, call the handler function, so the user can load more messages. NOTE: New messages won't appear in the chat unless it's scrolled up to the very top - it's an optimization.

Customize default UI

You can use chatTheme to customize colors and images of default UI. You can pass all/some colors and images:

.chatTheme(
    ChatTheme(
        colors: .init(
            mainBackground: .red,
            buttonBackground: .yellow,
            addButtonBackground: .purple
        ),
        images: .init(
            camera: Image(systemName: "camera")
        )
    )
)

Please use mediaPickerTheme in a similar fashion to customize the built-in photo picker.

makes sense only for built-in message view

avatarSize - the default avatar is a circle, you can specify its diameter here tapAvatarClosure - closure to call on avatar tap
messageUseMarkdown - use markdown (e.g. ** to make something bold) or not showMessageTimeView - show timestamp in a corner of the message
setMessageFont - pass custom font to use for messages

makes sense only for built-in input view

setAvailableInput - hide some buttons in default InputView. Available options are: - .full - media + text + audio
- .textAndMedia
- .textAndAudio
- .textOnly

<img src="https://raw.githubusercontent.com/exyte/media/master/Chat/pic2.png" width="300">

Examples

There are 2 example projects:

Create your firestore app https://console.firebase.google.com/ Create firesote database (for light weight text data) https://firebase.google.com/docs/firestore/manage-data/add-data Create cloud firestore database (for images and voice recordings) https://firebase.google.com/docs/storage/web/start

Example

To try out the Chat examples:

Installation

Swift Package Manager

dependencies: [
    .package(url: "https://github.com/exyte/Chat.git")
]

CocoaPods

pod 'ExyteChat'

Carthage

github "Exyte/Chat"

Requirements

Our other open source SwiftUI libraries

PopupView - Toasts and popups library
Grid - The most powerful Grid container
ScalingHeaderScrollView - A scroll view with a sticky header which shrinks as you scroll
AnimatedTabBar - A tabbar with a number of preset animations
MediaPicker - Customizable media picker
OpenAI Wrapper lib for OpenAI REST API
AnimatedGradient - Animated linear gradient
ConcentricOnboarding - Animated onboarding flow
FloatingButton - Floating button menu
ActivityIndicatorView - A number of animated loading indicators
ProgressIndicatorView - A number of animated progress indicators
FlagAndCountryCode - Phone codes and flags for every country
SVGView - SVG parser
LiquidSwipe - Liquid navigation animation