본문 바로가기

ios 앱개발

[SWIFT] IOS에서 채팅기능 구현하기(2) with AWS Amplify

반응형

네, 접니다.

지난 게시물에 이어서 이번에는 실질적 채팅 기능을 구현하는 방법에 대해 작성하겠습니다.

 

 

https://pinlib.tistory.com/entry/SWIFT-IOS에서-채팅기능-구현하기1-with-AWS-Amplify

 

[SWIFT] IOS에서 채팅기능 구현하기(1) with AWS Amplify

예. 오랫만이네요. 일이 좀 많아서, 이제야 글을 쓰게 되었습니다. 오늘 소개할 내용은 IOS에서 AWS Amplify를 이용해 채팅 기능을 구현하는 방법을 알아볼 예정입니다. 현재 계획을로는 1편의 경우

pinlib.tistory.com

 

실질적인 기능을 구현하는 코드를 작성해야 하는 만큼 오늘 글은 이전 게시물보다 양이 좀 많을 것 같네요.

 

 

1. Message SPM download

우선 우리는 기존에 존재하는 형식의 디자인을 이용할 것입니다.

이를 위해서는 SPM으로 message kit라는 package를 download 해야 합니다.

 

SPM 접근 방식은 File -> Add Package Dependencies 입니다. 

이후 여기서 message kit를 download 받습니다.

 

 

2. Message Algorithm

message기능을 구현하기에 앞서, 우리는 어떤 방식으로 message를 보낼 것인지 정해야 합니다.

 

1) message kit에 내장된 InputBarAccessoryViewDelegate함수를 이용해 text읽어오기

2) saveMessage함수를 이용해 읽어온 text를 amplify storage에 저장하기

3)readMessage함수를 이용해 amplify storage로부터 메시지 읽어오기

 

대략적으로 크게는 이렇게 알고리즘을 구성했습니다.

 

3. Message Struct

message 기능을 구현하는데 있어서 데이터를 다루는 소위 구조체를 만들어 줘야 합니다.

아래는 message struct의 코드입니다.

import Foundation
import MessageKit
import UIKit


struct Message: MessageType {
    var sender: SenderType
    var messageId: String
    var sentDate: Date
    var kind: MessageKind
    var channel: String
}

extension Message: Comparable {
  static func == (lhs: Message, rhs: Message) -> Bool {
    return lhs.messageId == rhs.messageId
  }

  static func < (lhs: Message, rhs: Message) -> Bool {
    return lhs.sentDate < rhs.sentDate
  }
}

 

4. Sender Struct

유저들간의 채팅 구현에 있어 메시지 자체를 누가 보내는지 역시 구현해야 합니다. 

아래는 이러한 유저들 데이터의 구조체 코드입니다.

import Foundation
import MessageKit

struct Sender: SenderType {
    var senderId: String //user id
    var displayName: String // user name
}

 

 

5. 중간 정리

4번과 5번에서는 message를 구현하는데 필요한 struct들을 만들었습니다.

이제는 message기능을 구현하는데 핵심이 되는 chattingViewController에서 각각의 struct들을 다루기 위해

message와 sender를 선언해줘야 합니다.

import Amplify
import Combine
import UIKit
import MessageKit
import InputBarAccessoryView
import Photos
import KakaoSDKAuth
import KakaoSDKCommon
import KakaoSDKUser


class ChattingViewController: MessagesViewController {
    var otherPerson: String?
    
    var currentUser = Sender(senderId: "self", displayName: "current user")
        
    var otherUser = Sender(senderId: "other", displayName: "other user")
        
    var messages = [MessageType]()

 

6. Message kit 

이제는 message kit에서 제공하는 함수들을 이용해 큰 틀을 구성해줘야 합니다.

 

우선 클래스를 해당 형식으로 선언합니다.

class ChattingViewController: MessagesViewController

 

이후에는 각각의 기본 제공 함수들을 작성해줍니다.

    private func configure() {
        title = "가은이"
        navigationController?.navigationBar.prefersLargeTitles = false
    }
    
    private func setupMessageInputBar() {
        messageInputBar.inputTextView.tintColor = .primary
        messageInputBar.sendButton.setTitleColor(.primary, for: .normal)
        messageInputBar.inputTextView.placeholder = "Aa"
    }
    
    private func removeOutgoingMessageAvatars() {
        guard let layout = messagesCollectionView.collectionViewLayout as? MessagesCollectionViewFlowLayout else { return }
        layout.textMessageSizeCalculator.outgoingAvatarSize = .zero
        layout.setMessageOutgoingAvatarSize(.zero)
        let outgoingLabelAlignment = LabelAlignment(textAlignment: .right, textInsets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 15))
        layout.setMessageOutgoingMessageTopLabelAlignment(outgoingLabelAlignment)
    }
    
    private func confirmDelegates() {
        messagesCollectionView.messagesDataSource = self
        messagesCollectionView.messagesLayoutDelegate = self
        messagesCollectionView.messagesDisplayDelegate = self
        messageInputBar.delegate = self
    }

 

extension ChattingViewController: MessagesDataSource {
    
    var currentSender: MessageKit.SenderType {
        //return "pass" as! SenderType
        return Sender(senderId: "self", displayName: "current sender") //sender model과 연결
    }
    
    
    func numberOfSections(in messagesCollectionView: MessagesCollectionView) -> Int {
        return messages.count // message struct의 수를 나타내는 것이니깐 이거를 message dict으로 전환하게 된다면?
    }
    
    func messageForItem(at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageType {
        return messages[indexPath.section] //위와 동일
    }
    
    func messageTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
        let name = message.sender.displayName
        return NSAttributedString(string: name, attributes: [.font: UIFont.preferredFont(forTextStyle: .caption1),
                                                             .foregroundColor: UIColor(white: 0.3, alpha: 1)])
    }
}

 

extension ChattingViewController: MessagesLayoutDelegate {
    // 아래 여백
    func footerViewSize(for section: Int, in messagesCollectionView: MessagesCollectionView) -> CGSize {
        return CGSize(width: 0, height: 8)
    }
    
    // 말풍선 위 이름 나오는 곳의 height
    func messageTopLabelHeight(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGFloat {
        return 20
    }
}

 

extension ChattingViewController: MessagesDisplayDelegate {
    // 말풍선의 배경 색상
    func backgroundColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor {
        return isFromCurrentSender(message: message) ? .primary : .incomingMessageBackground
    }
    
    func textColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor {
        return isFromCurrentSender(message: message) ? .black : .white
    }
    
    // 말풍선의 꼬리 모양 방향
    func messageStyle(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageStyle {
        let cornerDirection: MessageStyle.TailCorner = isFromCurrentSender(message: message) ? .bottomRight : .bottomLeft
        return .bubbleTail(cornerDirection, .curved)
    }
}

 

extension ChattingViewController: InputBarAccessoryViewDelegate {
    func inputBar(_ inputBar: InputBarAccessoryView, didPressSendButtonWith text: String) {
        // Create a new ChatMessage
        
        let email = userGetAuth()
    
        Task{
            if(otherPerson != nil){
                //comment로 들어옴
                let member_channels = try await Amplify.DataStore.query(ChatChannel.self, where: ChatChannel.keys.Member1.eq(email) && ChatChannel.keys.Member2.eq(otherPerson))
                for member_channel in member_channels {
                    unique_channel = member_channel.id
                }
                if(unique_channel != nil){
                    //채팅방 존재의 경우 -> 현재 channel값 이용
                    await saveMessage(unique_channel,email!,text, .normal)
                } else {
                    await addChatRoom(email!, otherPerson!, "dateString", .normal)
                    let member_channel2s = try await Amplify.DataStore.query(ChatChannel.self, where: ChatChannel.keys.Member1.eq(email) && ChatChannel.keys.Member2.eq(otherPerson))
                    
                    for member_channel2 in member_channel2s {
                        unique_channel = member_channel2.id
                    }
                    await saveMessage(unique_channel,email!,text, .normal)
                }
            } else {
                //list view로 들어옴
                unique_channel = detailData["channel"]
                await saveMessage(unique_channel,email!,text, .normal)
            }
        }
        
        inputBar.inputTextView.text.removeAll()
    }
}

 

 

막상 쓰다보니 분량이 생각보다 더 많은 것 같습니다.

원래는 2탄에 마무리할려구 했는데 3탄까지는 가야 할 것 같습니다.

3탄의 계획은 위에서 보여드린 InputBarAccessoryViewDelegate함수와 

amplify와 통신하기위한 saveMessage, readMessage등등의 함수에 대해서 진득하게 작성해보겠습니다.

수고염.

반응형