채팅 뷰컨트롤러를 구현했다면 메세지 목록 뷰컨트롤러가 필요하다. 보통 이 뷰컨트롤러에서는 내가 채팅한 사용자와의 대화 중 최신 메세지만을 보여준다. 파이어베이스와 swift 기반으로 만든 앱에 해당 기능을 추가하려고 한다.

  • 채팅 뷰컨트롤러 = ChatViewController
  • 메세지목록뷰컨트롤러 = RecentMessageViewController
  1. RecentMessage Model 디자인 및 생성
  2. RecentMessage에 Listener 달기
    • change.type == .added, .modified
  3. 수정되는 값 처리
    • recentMessagesDictionary = [String: RecentMessage]()
    • function resetItem
    • 배열 정렬

1. RecentMessage Model 디자인 및 생성

ChatViewController에서 구현했던 것은 match된 사람과 메세지를 주고받는 것이었다. 이를 위해 사용된 model은 다음과 같다.

struct Message {
    let text, fromId, toId: String
    let timestamp: Timestamp
    let isFromCurrentLoggedUser: Bool
}

Model - RecentMessage

RecentMessageViewController에 띄울 최신 메세지 목록들은 기존 Message model에서 가져오려면 다시 Message database를 sorting하여 최신 메세지를 가져와야하는 번거로움이 있다. 이런 번거로움을 덜기위하여 새롭게 RecentMessage Model을 만든다.

struct RecentMessage {
    let uid, text, username, profileImgUrl: String
    let timestamp: Timestamp
}

view에 띄워질 필요한 property를 생각하여 디자인하는 것이 좋다.

Firestore 저장 경로

RecentMessage를 저장할 경로를 만들어주자.

recent_messages 라는 collection을 새롭게 만들어주고 그 안에 RecentMessage 모델이 들어가도록 한다.

먼저 dummy data를 넣어주는 것도 좋다. 처음에는 저장되는 document 이름 역할을 하는 uid가 중요하지 않기 때문에 아무 값이나 넣어주고 filed에 dummy data만 잘 넣어주면 된다.

2. RecentMessage에 Listener 달기

guard let currentUserId = Auth.auth().currentUser?.uid else { return }

Firestore.firestore().collection("matches_messages").document(currentUserId).collection("recent_messages").addSnapshotListener { (querySnapshot, err) in
            // check err
            
querySnapshot?.documentChanges.forEach({ (change) in
	  if change.type == .added {
				let dictionary = change.document.data()
				items.append(RecentMessage(dictionary: dictionary))
		}
		collectionView.reloadData()
})

이렇게 코드를 작성하면 firestore, recent_messages에 새롭게 데이터가 저장될 때에만 나오게 된다. 즉각적이 수정이 일어날 때 마다 반응하도록 변경해야한다.

change.type == .modified

addSnapshotListenerquerySnapshot.documentChanges 의 요소 타입이 .modified 도 존재하기 때문에 조건에 추가해준다.

querySnapshot?.documentChanges.forEach({ (change) in
	  if change.type == .added || change.type == .modified {
				let dictionary = change.document.data()
				let recentMessage = RecentMessage(dictionary: dictionary)
				self.recentMessagesDictionary[recentMessage.uid] = recentMessage
		}
})

하지만 이렇게 코드를 작성하면, 수정이 되면 또 추가되버려서 중복되는 현상이 발생한다.

Firebase - FIRQueryDocumentSnapshot

/** An enumeration of document change types. */
typedef NS_ENUM(NSInteger, FIRDocumentChangeType) {
  /** Indicates a new document was added to the set of documents matching the query. */
  FIRDocumentChangeTypeAdded,
  /** Indicates a document within the query was modified. */
  FIRDocumentChangeTypeModified,
  /**
   * Indicates a document within the query was removed (either deleted or no longer matches
   * the query.
   */
  FIRDocumentChangeTypeRemoved
} NS_SWIFT_NAME(DocumentChangeType);

3. 수정되는 값 처리

중복을 없애기 위해서 Dictionary를 사용한다. 모든 사용자의 uid는 고유하기 때문에 uid를 키로 갖고 RecentMessage를 value로 갖는 Dictionary를 만든다.

이렇게 Dictionary에 RecentMessage를 저장하면, 새롭게 추가되거나 수정되는 RecentMessage Model들을 모두 처리할 수 있다.

var recentMessagesDictionary = [String: RecentMessage]()

...

querySnapshot?.documentChanges.forEach({ (change) in
	  if change.type == .added || change.type == .modified {
				let dictionary = change.document.data()
				let recentMessage = RecentMessage(dictionary: dictionary)
				self.recentMessagesDictionary[recentMessage.uid] = recentMessage
		}
})

forEach문을 다 돌았다면, 업데이트된 Dictionary를 정렬을 한번 시켜준 후에 통해 화면에 반영시킨다.

    fileprivate func resetItems() {
        let values = Array(recentMessagesDictionary.values)
        items = values.sorted(by: { (rm1, rm2) -> Bool in
            return rm1.timestamp.compare(rm2.timestamp) == .orderedDescending
        })
        collectionView.reloadData()
    }

values

recentMessagesDictionary.values에 사용된 values(Instance Property)

document

var values: Dictionary<Key, Value>.Values { get set }

values

A collection containing just the values of the dictionary.

dictionary의 value들을 가지고 있는 collection이다. 이를 Array로 한번 type casting을 한 뒤에, 고차함수 sorted를 이용한다. 공식 문서에 의하면 보통 forEach나 for문을 사용하여 collection에 요소들에 접근하나보다.

sorted

items = values.sorted(by: { (rm1, rm2) -> Bool in
            return rm1.timestamp.compare(rm2.timestamp) == .orderedDescending
        })

Document

Returns the elements of the sequence, sorted using the given predicate as the comparison between elements.

func sorted(by areInIncreasingOrder: (RecentMessage, RecentMessage) throws -> Bool) rethrows -> [RecentMessage]

$0, $1 에 대해서 closure안이 true인 조건으로 elements를 sort한 후에 array를 return한다.

compare

- (NSComparisonResult)compare:(FIRTimestamp *)other {
  if (self.seconds < other.seconds) {
    return NSOrderedAscending;
  } else if (self.seconds > other.seconds) {
    return NSOrderedDescending;
  }

  if (self.nanoseconds < other.nanoseconds) {
    return NSOrderedAscending;
  } else if (self.nanoseconds > other.nanoseconds) {
    return NSOrderedDescending;
  }
  return NSOrderedSame;
}

태그: ,

카테고리:

업데이트:

댓글남기기