Swiftpack.co - Package - EnesKaraosman/SwiftyChat

SwiftyChat

Content

About

Simple Chat Interface to quick start with built-in message cells.
Fully written in pure SwiftUI.

Features

  • ☑ HTML String support like <li>, <a> (not like h1 or font based tag)
  • ☑ Attributed string support that contains address, date, phoneNumber, url (text is automatically scanned)
  • ☑ Landscape orientation support (autoscales message cells with the given cellWidth property, if exists)
  • ☑ User Avatar (with different position options, optional usage)
  • ☑ Dismiss keyboard (on tapping outside).
  • ☐ Scroll To Bottom.

Quick Preview

| Contact, QuickReply, Text, Carousel | Map, Image | ContextMenu | :-------------------------:|:-------------------------:|:-------------------------: | |

Installation

SPM: https://github.com/EnesKaraosman/SwiftyChat.git

Message Kinds

public enum ChatMessageKind: CustomStringConvertible {
    
    /// A text message,
    /// supports emoji 👍🏻 (auto scales if text is all about emojis)
    case text(String)
    
    /// An image message, from local(UIImage) or remote(URL).
    case image(ImageLoadingKind)
    
    /// A location message, pins given location & presents on MapKit.
    case location(LocationItem)
    
    /// A contact message, generally for sharing purpose.
    case contact(ContactItem)
    
    /// Multiple options, disables itself after selection.
    case quickReply([QuickReplyItem])
    
    /// `CarouselItem`s that contains title, subtitle, image & button in a scrollable view
    case carousel([CarouselItem])
}

For displaying remote images (for the case image(.remote(URL)) Kingfisher library used as dependency.

Usage

  • ChatView
@State var messages: [MockMessages.ChatMessageItem] = [] // for quick test assign MockMessages.generatedMessages()

// ChatMessageItem & ChatUserItem is a sample objects/structs 
// that conforms `ChatMessage` & `ChatUser` protocols.
ChatView<MockMessages.ChatMessageItem, MockMessages.ChatUserItem> { (proxy) -> AnyView in
    // InputView here, continue reading..
}
// ▼ Optional
.onMessageCellTapped { message in
    // You may trigger .sheet here
    // To display images in a full screen for example.
    // Or open MapView for ChatMessageKind.location case
    switch message.messageKind {
        case ..
    }
}
// ▼ Optional, Implement to be notified when related attributed text typed
// like address, date, phoneNumber, url
.onAttributedTextTappedCallback {
    AttributedTextTappedCallback(
        didSelectDate: { print($0) },
        didSelectPhoneNumber: { print($0) },
        didSelectURL: { print($0) }
    )
}
// ▼ Optional
.messageCellContextMenu { message -> AnyView in
    switch message.messageKind {
    case .text(let text):
        return Button(action: {
            print("Forward Context Menu tapped!!")
            // Forward text
        }) {
            Text("Forward")
            Image(systemName: "arrowshape.turn.up.right")
        }.embedInAnyView()
    default:
        // If you don't want to implement contextMenu action 
        // for a specific case, simply return EmptyView like below;
        return EmptyView().embedInAnyView()
    }
}
// ▼ Implement in case ChatMessageKind.carousel
.onCarouselItemAction { (button: CarouselItemButton, message: ChatMessage) in
    // Here you can use the metadata of selected item in carousel
}
// ▼ Implement in case ChatMessageKind.quickReply
.onQuickReplyItemSelected { quickReply in
    // You may add new item to `messages`
    // Or send an information about it via network.
}
// ▼ Implement in case ChatMessageKind.contact
.contactItemButtons { (contact, message) -> [ContactCellButton] in
    return [
        .init(title: "Save", action: {
            print(contact.displayName)
        })
        ... or more
    ]
}
// ▼ Required
.environmentObject(
    // All parameters iniatilazed by default, 
    // change as you want.
    ChatMessageCellStyle()
)
...
...
  • InputView

You can investigate existing DefaultInputView in project.
You can use it if it suits your need, or create a new one.
It's quite easy to add custom InputView

DefaultInputView(
    // Pass ChatView's proxy (GeometryReader in ChatView)
    proxy: GeometryProxy, 
    sendAction: (ChatMessageKind) -> Void
)

// Pass in ChatView
ChatView(messages: $messages) { (proxy) -> AnyView in
    DefaultInputView(proxy: proxy) { (messageKind) in
        let newMessage = ChatMessage(
            user: .., 
            messageKind: messageKind, 
            isSender: true
        )
        self.messages.append(newMessage)
    }
    .edgesIgnoringSafeArea(.all)
    // ▼ An extension that wraps view inside AnyView
    .embedInAnyView() 
}
...
...

For custom InputView you can cheat using Default one.

Style and Customization

public class ChatMessageCellStyle: ObservableObject {
    
    /// Incoming Text Style
    let incomingTextStyle: TextCellStyle
    
    /// Outgoing Text Style
    let outgoingTextStyle: TextCellStyle
    
    /// Cell container inset for incoming messages
    let incomingCellEdgeInsets: EdgeInsets?
    
    /// Cell container inset for outgoing messages
    let outgoingCellEdgeInsets: EdgeInsets?
    
    /// Contact Cell Style
    let contactCellStyle: ContactCellStyle
    
    /// Image Cell Style
    let imageCellStyle: ImageCellStyle
    
    /// Quick Reply Cell Style
    let quickReplyCellStyle: QuickReplyCellStyle
    
    /// Carousel Cell Style
    let carouselCellStyle: CarouselCellStyle
    
    /// Location Cell Style
    let locationCellStyle: LocationCellStyle
    
    /// Incoming Avatar Style
    let incomingAvatarStyle: AvatarStyle
    
    /// Outgoing Avatar Style
    let outgoingAvatarStyle: AvatarStyle
    
}

You must initiate this class to build a proper style & inject it as environmentObject,
All styles has default initializer;

For detail documentation, visit Styles.md


Please feel free to contribute.

Inspiration

A UIKit Chat library MessageKit.

Github

link
Stars: 15

Used By

Total: 0