포스트

Final (22)

내용이 많아 이어서 작성…

커뮤니티 유저 프로필 구현

ChatManager 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import FoundationAdd
import FirebaseStorage
import FirebaseDatabase
import FirebaseFirestore


class ChatManager {
    
    func getSenders(channelName: String, completion: @escaping ([String]) -> Void) {
        channelCollection.whereField(db_channelName, isEqualTo: channelName).getDocuments { (snapshot, error) in
            guard let snapshot = snapshot, error == nil else {
                print("Error fetching channel: \(String(describing: error))")
                completion([])
                return
            }
            
            var senderIds = Set<String>()
            for document in snapshot.documents {
                let threadCollection = channelCollection.document(document.documentID).collection("thread")
                threadCollection.getDocuments { (threadSnapshot, error) in
                    guard let threadSnapshot = threadSnapshot, error == nil else {
                        print("Error fetching thread: \(String(describing: error))")
                        return
                    }
                    
                    for threadDocument in threadSnapshot.documents {
                        if let senderId = threadDocument.data()["senderId"] as? String {
                            senderIds.insert(senderId)
                        }
                    }
                    completion(Array(senderIds))
                }
            }
        }
    }
    
}

보내는 사람이 누군지를 알아내는 함수이다.


ChatVC - 보낸 사람 프로필 이미지 표시 기능 정리

개요

채팅 화면에서 각 발신자의 프로필 이미지를 메시지 셀 옆에 표시하기 위해 다음을 구현했다:

  • 본인 및 채널 내 발신자의 프로필 이미지 URL을 불러오기
  • 이미지 캐싱을 통한 중복 요청 방지
  • MessagesKit 델리게이트를 활용한 셀 아바타 표시

1. 주요 프로퍼티

역할 요약:

  • chatFirestoreStream: 채팅 메시지를 수신하는 스트림
  • chatManager: 채널에 포함된 사용자 ID 리스트를 불러오기 위한 매니저
  • profileImageUrls: senderId → 이미지 URL 매핑
  • imageCache: senderId → UIImage 매핑 (중복 요청 방지 목적)

정의:

1
2
3
4
5
let chatFirestoreStream = ChatFirestoreStream()
let chatManager = ChatManager()

private var profileImageUrls = [String: String]()
private var imageCache = [String: UIImage]()

2. viewDidLoad() 내부 동작

무엇을 하나?

  • 본인 프로필 이미지 먼저 불러와 저장
  • 이후 채널 내 모든 sender의 이미지를 요청
  • 메시지 콜렉션뷰 초기화 및 스크롤 위치 조정
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
override func viewDidLoad() {
    super.viewDidLoad()
    configureColor()

    fetchDisplayNameAndProfileImage { [weak self] displayName, imageUrl in
        self?.currentDisplayName = displayName ?? "Unknown"
        if let profileImageUrl = imageUrl, let userId = self?.user?.uid {
            self?.profileImageUrls[userId] = profileImageUrl
        }
        self?.messagesCollectionView.reloadData()
        DispatchQueue.main.async {
            self?.messagesCollectionView.scrollToLastItem()
        }
    }

    getSenderImage() // 🔽 아래에서 설명
    confirmDelegates()
    removeOutgoingMessageAvatars()
    addCameraBarButtonToMessageInputBar()
    listenToMessages()

    let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap))
    messagesCollectionView.addGestureRecognizer(tapGesture)
}

3. 사용자 이미지 수집 흐름

getSenderImage()

설명: 채널 내 모든 발신자 UID를 가져온 뒤 각자의 이미지 URL을 가져온다.

1
2
3
4
5
func getSenderImage() {
    chatManager.fetchAllSenderIds(channelId: channelId) { [weak self] senderIds in
        self?.fetchProfileImages(for: senderIds)
    }
}

fetchProfileImages(for:)

설명: senderId 리스트를 받아 각각 반복적으로 이미지 요청

1
2
3
4
5
func fetchProfileImages(for senderIds: [String]) {
    for senderId in senderIds {
        fetchUserDataAndProfileImage(for: senderId)
    }
}

fetchUserDataAndProfileImage(for:)

설명: 해당 UID의 Firebase 데이터베이스에서 닉네임과 프로필 이미지 URL을 가져옴. 가져온 URL은 profileImageUrls에 저장.

1
2
3
4
5
6
7
8
9
func fetchUserDataAndProfileImage(for uid: String? = nil) {
    guard let uid = uid ?? user?.uid else { return }

    UserManager().fetchUserData(uid: uid) { [weak self] user in
        guard let user = user else { return }
        self?.profileImageUrls[uid] = user.profileImageUrl
        self?.messagesCollectionView.reloadData()
    }
}

fetchDisplayNameAndProfileImage()

설명: 로그인한 본인 계정의 닉네임과 이미지 URL을 불러오는 래퍼 메서드

1
2
3
4
5
func fetchDisplayNameAndProfileImage(completion: @escaping (String?, String?) -> Void) {
    fetchUserDataAndProfileImage { user in
        completion(user?.nickName, user?.profileImageUrl)
    }
}

4. 이미지 사전 로딩 처리

preloadProfileImages(for:)

설명: 메시지를 받아왔을 때, 발신자 목록을 기준으로 이미지가 아직 캐시되지 않은 경우 미리 받아두는 역할

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func preloadProfileImages(for messages: [Message]) {
    let senderIds = messages.map { $0.sender.senderId }
    let uniqueSenderIds = Set(senderIds)

    for senderId in uniqueSenderIds {
        if imageCache[senderId] == nil {
            if let urlString = profileImageUrls[senderId],
               let url = URL(string: urlString) {
                KingfisherManager.shared.retrieveImage(with: url) { result in
                    if case .success(let value) = result {
                        self.imageCache[senderId] = value.image
                        self.messagesCollectionView.reloadData()
                    }
                }
            }
        }
    }
}

5. 메시지 셀 구성 (UICollectionViewDataSource)

cellForItemAt

설명: MessagesKit이 메시지 셀을 생성할 때 avatarView를 구성하기 위해 호출됨

1
2
3
4
5
6
7
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = super.collectionView(collectionView, cellForItemAt: indexPath) as! MessageContentCell
    let message = messages[indexPath.section]
    configureAvatarView(cell.avatarView, for: message, at: indexPath, in: collectionView as! MessagesCollectionView)
    return cell
}


6. MessagesKit Avatar 관련 델리게이트

avatarFor(message:)

설명: 아바타에 표시할 이미지를 제공함. 캐시에 있으면 이미지, 없으면 이니셜만 보여줌.

1
2
3
4
5
6
7
8
9
func avatarFor(message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> Avatar {
    let senderId = message.sender.senderId

    if let image = imageCache[senderId] {
        return Avatar(image: image)
    } else {
        return Avatar(initials: String(senderId.prefix(1)))
    }
}

avatarSize(for:)

설명: 아바타 뷰의 크기 설정

1
2
3
4
func avatarSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize {
    return CGSize(width: 30, height: 30)
}

configureAvatarView(_:for:at:in:)

설명: 아바타 뷰에 이미지를 적용하는 메서드

  • profileImageUrls에서 URL 가져오기
  • Kingfisher로 다운로드
  • imageCache에 저장 후 뷰 업데이트
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func configureAvatarView(_ avatarView: AvatarView, for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) {
    let senderId = message.sender.senderId

    if let cachedImage = imageCache[senderId] {
        avatarView.image = cachedImage
        return
    }

    if let urlString = profileImageUrls[senderId],
       let url = URL(string: urlString) {
        avatarView.kf.setImage(with: url) { result in
            if case .success(let value) = result {
                self.imageCache[senderId] = value.image
            }
        }
    }
}

정리

파트기능설명
profileImageUrlssenderId → 이미지 URL사용자별 이미지 주소 저장
imageCachesenderId → UIImage다운로드된 이미지 메모리 캐시
fetchUserDataAndProfileImage사용자 데이터 가져오기Firebase에서 nickname + 이미지 URL 획득
getSenderImage모든 발신자 이미지 요청 시작채널 내 전체 sender들 대상
preloadProfileImages캐시 없는 경우 이미지 사전 로딩메시지 수신 시 빠른 표시 대비
configureAvatarView셀에 이미지 적용Kingfisher 비동기 다운로드 및 캐시 저장

이 구현은 메시지 화면에서 사용자 아바타를 시각적으로 빠르고 정확하게 보여주기 위한 전체 처리 흐름이다.
성능 개선을 위한 캐시 전략, UX를 고려한 비동기 처리, MessagesKit의 델리게이트 활용이 핵심 포인트다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.