✅ 현재TMDB에서 API를 통해 받아오는 데이터를 관리하는 구조
- TvTMDBData & TvTMDBResult 구조체: API를 통해 받아오는 데이터를 직접 관리
- TvCombineData 구조체: TvTMDBData를 배열로 통합해서 관리하는 구조체
- TvResultItem 구조체: TvTMDBData타입의 데이터와 별도의 섹션을 구분하는 데이터를 관리하는 구조체
영화를 받아오는 정보 외에 다른 부분을 컬렉션뷰에 동일하게 넣되, 레이아웃은 유지하고 싶다면?
굳이 데이터 모델을 하나로 통합할 필요가 있나? 즉 섹션마다 다른 데이터 모델을 사용하려면?
🌟 해결 방법
각 섹션마다 다른 데이터 모델을 사용하려면 다양한 데이터 모델을 포함할 수 있는 enum 타입을 만들어서 활용하면 됩니다.
✅ 데이터 모델 설계
enum SectionType {
case fruit
case family
}
struct Fruit {
let name: String
}
struct Family {
let dad: String
}
각 섹션에 맞는 아이템을 저장할 수 있도록 열거형을 활용한 Item 모델을 만듭니다.
enum Item {
case fruit(Fruit)
case family(Family)
}
✅ Diffable Data Source 적용
Diffable Data Source에서 섹션과 아이템을 다르게 관리할 수 있도록 설정합니다.
typealias DataSource = UICollectionViewDiffableDataSource<SectionType, Item>
이제 Item이 UICollectionViewDiffableDataSource의 아이템 역할을 하므로, 섹션마다 다른 타입을 넣을 수 있습니다.
✅ 데이터 설정 (스냅샷)
이제 스냅샷을 만들어 섹션별로 다른 데이터를 추가하면 됩니다.
var snapshot = NSDiffableDataSourceSnapshot<SectionType, Item>()
let fruits = [Fruit(name: "Apple"), Fruit(name: "Banana")].map { Item.fruit($0) }
let family = [Family(dad: "John")].map { Item.family($0) }
snapshot.appendSections([.fruit, .family])
snapshot.appendItems(fruits, toSection: .fruit)
snapshot.appendItems(family, toSection: .family)
dataSource.apply(snapshot, animatingDifferences: true)
✅ 셀 구성 (cellProvider)
셀을 설정할 때, switch 문을 사용하여 각 Item에 맞는 셀을 반환하면 됩니다.
dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { collectionView, indexPath, item in
switch item {
case .fruit(let fruit):
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FruitCell", for: indexPath) as! FruitCell
cell.configure(with: fruit)
return cell
case .family(let family):
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FamilyCell", for: indexPath) as! FamilyCell
cell.configure(with: family)
return cell
}
}
✅ 결론
✔️ enum Item을 사용하면 여러 가지 데이터 모델을 하나의 컬렉션 뷰에서 관리 가능
✔️ UICollectionViewDiffableDataSource를 활용하여 섹션별로 다른 데이터를 적용
✔️ UICollectionViewCompositionalLayout을 활용하면 섹션별 레이아웃도 다르게 구성 가능
🌟 데이터 모델이 동일하다면, 통합할 필요 없이 그대로 사용할 수 있지 않나?
✅ 섹션별로 개별 데이터 관리하는 방식
1️⃣ 섹션 정의 (영화 타입)
enum MovieSection: CaseIterable {
case popular // 인기 영화
case nowPlaying // 상영 중인 영화
case upcoming // 개봉 예정작
case topRated // 순위 높은 영화
}
2️⃣ 아이템 모델
모든 API의 데이터 모델이 동일하므로 Movie 모델을 그대로 사용
struct Movie: Hashable {
let id: Int
let title: String
let releaseDate: String
let posterURL: String
}
🔷 Movie 모델이 Hashable을 따르도록 해야 UICollectionViewDiffableDataSource에서 사용 가능
✅ Diffable Data Source 적용
typealias DataSource = UICollectionViewDiffableDataSource<MovieSection, Movie>
var dataSource: DataSource!
✅ 데이터 로드 및 스냅샷 업데이트
각 API에서 받아온 데이터를 각 섹션에 맞게 적용
var snapshot = NSDiffableDataSourceSnapshot<MovieSection, Movie>()
let popularMovies: [Movie] = fetchPopularMovies()
let nowPlayingMovies: [Movie] = fetchNowPlayingMovies()
let upcomingMovies: [Movie] = fetchUpcomingMovies()
let topRatedMovies: [Movie] = fetchTopRatedMovies()
snapshot.appendSections(MovieSection.allCases)
snapshot.appendItems(popularMovies, toSection: .popular)
snapshot.appendItems(nowPlayingMovies, toSection: .nowPlaying)
snapshot.appendItems(upcomingMovies, toSection: .upcoming)
snapshot.appendItems(topRatedMovies, toSection: .topRated)
dataSource.apply(snapshot, animatingDifferences: true)
✅ 셀 구성
dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { collectionView, indexPath, movie in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MovieCell", for: indexPath) as! MovieCell
cell.configure(with: movie)
return cell
}
🎯 결론
✅ 하나의 모델(Movie)로 통합할 필요 없이 API 별로 관리 가능
✅ 각 API의 데이터를 개별 섹션으로 분리하여, 더 명확한 구조 유지
✅ UICollectionViewDiffableDataSource + UICollectionViewCompositionalLayout을 활용하면 깔끔하게 적용 가능
✨ 리팩토링된 데이터 모델
✅ TvCombineData 제거 → TvTMDBData 자체를 배열로 관리
✅ TvTMDBData에서 type 제거 → TvResultItem에서 섹션 타입을 관리하도록 변경
✅ 가독성 향상
✅ 데이터 흐름이 더 직관적으로 변함
// MARK: - TV Section Type
enum TvSectionType: String, Codable, Hashable {
case airingToday = "Air Today"
case onTheAir = "On The Air"
case popular = "Popular"
case topRated = "Top Rated"
}
// MARK: - TV Data from TMDB
struct TvTMDBData: Codable, Hashable {
let page: Int
let results: [TvTMDBResult]
let totalPages: Int
let totalResults: Int
enum CodingKeys: String, CodingKey {
case page, results
case totalPages = "total_pages"
case totalResults = "total_results"
}
}
// MARK: - TV Show Details
struct TvTMDBResult: Codable, Hashable {
let id: Int
let name: String
let originalName: String
let overview: String
let popularity: Double
let voteAverage: Double
let voteCount: Int
let firstAirDate: String
let originalLanguage: String
let originCountry: [String]
let posterPath: String?
let backdropPath: String?
let genreIDs: [Int]
var genreNames: [String]? // 클라이언트에서 변환하여 사용
enum CodingKeys: String, CodingKey {
case id, name, overview, popularity
case originalName = "original_name"
case voteAverage = "vote_average"
case voteCount = "vote_count"
case firstAirDate = "first_air_date"
case originalLanguage = "original_language"
case originCountry = "origin_country"
case posterPath = "poster_path"
case backdropPath = "backdrop_path"
case genreIDs = "genre_ids"
}
}
// MARK: - TV Result Item for Diffable Data Source
struct TvResultItem: Hashable {
let tvResult: TvTMDBResult
let sectionType: TvSectionType
func hash(into hasher: inout Hasher) {
hasher.combine(tvResult.id)
hasher.combine(sectionType)
}
static func == (lhs: TvResultItem, rhs: TvResultItem) -> Bool {
return lhs.tvResult.id == rhs.tvResult.id &&
lhs.sectionType == rhs.sectionType
}
}
'Project > MovieClip' 카테고리의 다른 글
📍 함수의 역할 -> createDataSource() { } (0) | 2025.02.21 |
---|---|
📍함수의 역할 -> configure<T: SelfConfiguringTVCell> () { } (0) | 2025.02.21 |
✅ hash(into:) 메서드와 == 연산자의 역할 (0) | 2025.02.20 |
❌ 문제 발생... UICollectionViewDiffableDataSource.. 데이터 중복으로 인한 데이터 누락 (0) | 2025.02.20 |
⭐️ 버튼의 애니메이션 효과 주기 (0) | 2025.02.18 |