본문 바로가기
이모저모/UIKit

UIHostingConfiguration을 이용한 컬렉션 뷰 채팅 셀

by ARpple 2024. 9. 3.
 

UIHostingConfiguration | Apple Developer Documentation

A content configuration suitable for hosting a hierarchy of SwiftUI views.

developer.apple.com

장점

  • 빠른 뷰 코드 작성 가능
  • UIKit에서 작성한 코드 스타일에 크게 위반하지 않고 ListCell만 SwiftUI의 View를 도입해서 빠른 구현 가능

구현 요소

  1. ChatAsset ⇒ SwiftUI의 View에서 사용할 수 있는 형태의 데이터 모델
  2. DataSource ⇒ UICollectionView에서 사용하는 DiffableDatasource 적용함
  3. ChatCell ⇒ SwiftUI의 View

UICollectionView에 ChatCell Registration 프로퍼티 제작

extension DMChatViewController: UICollectionViewDelegate, UICollectionViewDataSourcePrefetching{
    var chatCellRegistration: UICollectionView.CellRegistration<UICollectionViewListCell,ChatItem.ID>{
            UICollectionView.CellRegistration {[weak self] cell, indexPath, itemIdentifier in
                guard let self else{ fatalError("메모리 SELF 오류!!") }
                guard let item = dataSource.chatModel.fetchByID(itemIdentifier) else {return}
                cell.backgroundColor = .clear
                cell.selectedBackgroundView = .none
                if let itemAssets = dataSource.chatAssetModel.object(forKey: "\(item.chatID)" as NSString){
                    cell.contentConfiguration = UIHostingConfiguration(content: {
                        ChatCell(chatItem: item,images: itemAssets, profileAction: self.profileAction(userID:), imageAction: self.imageAction)
                    }).background(.white)
                }else{
                    let itemAsset = self.dataSource.appendChatAssetModel(item: item)
                    cell.contentConfiguration = UIHostingConfiguration(content: {
                        ChatCell(chatItem: item,images: itemAsset, profileAction: self.profileAction(userID:), imageAction: self.imageAction)
                    }).background(.white)
                }
            }
        }
}

셀 이벤트 처리

  • 뷰 컨트롤러의 메서드로 뷰의 이벤트 처리를 위한 클로저를 삽입한다.

✅ 셀 선언 부 부분 - UIViewController

...
cell.contentConfiguration = UIHostingConfiguration(content: {
    ChatCell(chatItem: item,images: itemAssets,
        profileAction: self.profileAction(userID:),
        imageAction: self.imageAction)
}).background(.white)

✅ 셀 뷰 init 및 저장 프로퍼티 부분 - SwiftUI View

struct ChatCell:View{
        ...
    @ObservedObject var chatItem: CHChatView.ChatItem
    @ObservedObject var images: CHChatView.ChatAssets
    @DefaultsState(\.userID) var userID
    @State private var date:String = "08:16 오전"
    @State private var dateWidth:CGFloat = 0
    let profileAction: ((Int)->Void) // 이벤트 클로져
    let imageAction:(([String])->Void) // 이벤트 클로져
    init(chatItem: CHChatView.ChatItem, images: CHChatView.ChatAssets,
         profileAction: @escaping (Int) -> Void,imageAction:@escaping ([String]) -> Void) {
        self.chatItem = chatItem
        self.images = images
        self.profileAction = profileAction
        self.imageAction = imageAction
    }
    ...
}

✅ 뷰 이벤트 처리 부분 - 채팅에 포함된 이미지를 출력하는 뷰 컴포넌트 이벤트

extension ChatCell{
    var otherContents: some View{
    ...
        ContainerImage(realImage: $images.images)
                .drawingGroup()
                .clipShape(RoundedRectangle(cornerRadius: 8))
                .onTapGesture { // 이벤트 발생시 클로져 호출
                       imageAction(chatItem.images.map{$0.docFileToWebFile()})
                   }
    }
}

셀 뷰에서 사용하는 데이터 모델 처리 (ChatAsset)

  1. 모델 데이터는 ObservableObject을 준수해 SwiftUI 하위 뷰에서 새로운 데이터 할당 시, 변화를 감지 할 필요가 있다. ⇒ 모델을 Class로 작성해야한다.
  2. StateObject가 아닌 ObservedObject 사용. 데이터 변경 시, 즉시 하위 뷰가 Rerendering될 필요가 있기 때문이다.
  3. UICollectionView의 셀에서 가져오는 데이터도 같이 수행하기 때문에, Hashable 프로토콜을 준수해야한다.

✅ 셀 뷰에서 사용하는 데이터

class MessageCellItem:ObservableObject,Hashable,Identifiable{
    static func == (lhs: MessageCellItem, rhs: MessageCellItem) -> Bool {
        lhs.id == rhs.id
    }
    func hash(into hasher: inout Hasher) { hasher.combine(id) }
    var id : Int{ messageID }
    @Published var messageID: Int = 0
    @Published var content:String? = nil
    @Published var images:[String] = []
    @Published var createdAt:String
    @Published var profileID:Int = 0
    @Published var profileName:String = ""
    @Published var profileImage:String? = nil
    init(id: Int, content: String? = nil, images: [String], createdAt: String, profileID: Int, profileName: String, profileImage: String? = nil) {
        self.messageID = id
        self.content = content
        self.images = images
        self.createdAt = createdAt
        self.profileID = profileID
        self.profileName = profileName
        self.profileImage = profileImage
    }
}

댓글