✅ DetailViewController에서 video, poster 데이터를 받아 UICollectionView로 설정하는 로직 정리
1️⃣ 개요
- DetailViewController에서 fetchContentDetail()을 통해 API에서 데이터를 가져옴.
- video, poster 데이터를 enum을 활용해 타입을 구분하여 저장.
⭐️ DetailViewController에서 콘텐츠 유형(movie, tv, people)을 구분
⭐️ MediaTableViewCell에서 미디어 유형(poster, video)을 구분
- MediaTableViewCell에서 collectionView를 이용해 데이터를 표시.
- MediaCollectionViewCell에서 video와 poster에 따라 다른 UI 적용.
2️⃣ DetailViewController에서 데이터를 받아서 처리하는 흐름
✅ 1단계: API를 통해 video, poster 데이터를 가져옴.
✅ 2단계: 가져온 데이터를 mediaVideos, mediaPosters 배열에 저장.
✅ 3단계: UITableView의 cellForRowAt에서 MediaTableViewCell에 데이터 전달.
✅ 4단계: MediaTableViewCell에서 collectionView를 통해 MediaCollectionViewCell에 개별 데이터 전달.
✅ 5단계: MediaCollectionViewCell에서 poster와 video에 맞는 UI 설정 적용.
3️⃣ 단계별 상세 구현
📌 1단계: DetailViewController에서 API 데이터 가져오기
private func fetchContentDetail() {
Task {
do {
var fetchedDetail: ContentDetail?
var fetchedGenres: [String] = []
var castingList: TopBilledCastInfoWelcome?
var videoInfo: VideoInfoWelcome?
var posterInfo: PosterInfoWelcome?
switch contentType {
case .movie:
let movieDetail = try await NetworkManager.shared.getMovieDetailInfo(movieID: contentID)
fetchedDetail = .movie(movieDetail)
fetchedGenres = getGenresFromHomeSection(for: contentID)
castingList = try await NetworkManager.shared.getMovieCastInfo(contentID: contentID)
videoInfo = try await NetworkManager.shared.getMovieVideoInfo(contentID: contentID)
posterInfo = try await NetworkManager.shared.getMoviePosterInfo(contentID: contentID)
case .tv:
let tvDetail = try await NetworkManager.shared.getTVDetailInfo(tvID: contentID)
fetchedDetail = .tv(tvDetail)
fetchedGenres = getGenresFromHomeSection(for: contentID)
castingList = try await NetworkManager.shared.getTVCastInfo(contentID: contentID)
videoInfo = try await NetworkManager.shared.getTvVideoInfo(contentID: contentID)
posterInfo = try await NetworkManager.shared.getTvPosterInfo(contentID: contentID)
case .people:
let peopleDetail = try await NetworkManager.shared.getPeopleDetailInfo(peopleID: contentID)
fetchedDetail = .people(peopleDetail)
fetchedGenres = getGenresFromHomeSection(for: contentID)
}
DispatchQueue.main.async {
self.detailTableHeaderView()
guard let contentDetail = fetchedDetail else { return }
self.contentDetail = contentDetail
// ✅ 데이터 저장
switch contentDetail {
case .movie, .tv:
self.mediaVideos = videoInfo?.results ?? []
self.mediaPosters = posterInfo?.posters ?? []
case .people:
break
}
self.detailTableView.reloadData()
}
} catch {
print("❌ 데이터 로드 실패: \(error)")
}
}
}
🔥 주요 포인트
- movie, tv일 경우 mediaVideos와 mediaPosters에 데이터 저장.
- people은 미디어 정보가 필요 없으므로 데이터 저장 로직 제외.
- DispatchQueue.main.async에서 UI 업데이트.
✔ MediaType (비디오 & 포스터 구분)
✅ MediaTableViewCell에서 비디오와 포스터를 다르게 렌더링할 때 사용.
enum MediaType {
case poster
case video
}
✔ MediaInfo (비디오 & 포스터 데이터 저장)
✅ MediaCollectionViewCell에서 개별 데이터를 처리할 때 사용.
enum MediaInfo {
case poster(PosterInfoBackdrop)
case video(VideoInfoResult)
}
📌 2단계: cellForRowAt에서 MediaTableViewCell에 데이터 전달
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let sectionType = DetailSection.allCases[indexPath.section]
switch sectionType {
case .video:
guard let cell = tableView.dequeueReusableCell(withIdentifier: MediaTableViewCell.reuseIdentifier, for: indexPath) as? MediaTableViewCell else { return UITableViewCell() }
switch contentType {
case .movie, .tv:
cell.configure(with: mediaVideos.map { MediaInfo.video($0) }, type: .video) // ✅ 비디오 데이터 전달
case .people:
break
}
return cell
case .poster:
guard let cell = tableView.dequeueReusableCell(withIdentifier: MediaTableViewCell.reuseIdentifier, for: indexPath) as? MediaTableViewCell else { return UITableViewCell() }
switch contentType {
case .movie, .tv:
cell.configure(with: mediaPosters.map { MediaInfo.poster($0) }, type: .poster) // ✅ 포스터 데이터 전달
case .people:
break
}
return cell
default:
return UITableViewCell()
}
}
🔥 주요 포인트
- movie, tv의 경우에만 video, poster 데이터를 넘김.
- map { MediaInfo.video($0) } → VideoInfoResult 배열을 MediaInfo.video 타입으로 변환.
- map { MediaInfo.poster($0) } → PosterInfoBackdrop 배열을 MediaInfo.poster 타입으로 변환.
- .people에는 미디어 정보가 필요 없으므로 break.
📌 3단계: MediaTableViewCell에서 UICollectionView로 데이터 전달
class MediaTableViewCell: UITableViewCell {
static let reuseIdentifier: String = "MediaTableViewCell"
private var mediaItems: [MediaInfo] = []
private var mediaType: MediaType = .video
private let mediaCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 10
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.showsHorizontalScrollIndicator = true
collectionView.backgroundColor = .clear
return collectionView
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupCollectionView()
configureConstraints()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupCollectionView() {
mediaCollectionView.delegate = self
mediaCollectionView.dataSource = self
mediaCollectionView.register(MediaCollectionViewCell.self, forCellWithReuseIdentifier: MediaCollectionViewCell.reuseIdentifier)
}
func configure(with items: [MediaInfo], type: MediaType) {
self.mediaItems = items
self.mediaType = type
if let layout = mediaCollectionView.collectionViewLayout as? UICollectionViewFlowLayout {
layout.itemSize = type == .poster ? CGSize(width: 150, height: 250) : CGSize(width: 220, height: 130)
}
mediaCollectionView.reloadData()
}
}
📌 4단계: MediaCollectionViewCell에서 UI 설정
func configure(with content: MediaInfo) {
switch content {
case .video(let video):
if let key = video.key {
let thumbnailUrl = URL(string: "https://img.youtube.com/vi/\(key)/hqdefault.jpg")
thumbnailImageView.sd_setImage(with: thumbnailUrl, completed: nil)
thumbnailImageView.layer.cornerRadius = 10
}
case .poster(let poster):
if let posterPath = poster.filePath {
let posterUrl = URL(string: "https://image.tmdb.org/t/p/w500/\(posterPath)")
thumbnailImageView.sd_setImage(with: posterUrl, completed: nil)
}
}
}
📌 5단계: MediaCollectionViewCell 데이터 전달
extension MediaTableViewCell: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
switch mediaItems {
case .poster(let posters):
return posters.count
case .video(let videos):
return videos.count
case .none:
return 0
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MediaCollectionViewCell.reuseIdentifier, for: indexPath) as? MediaCollectionViewCell else { return UICollectionViewCell() }
switch mediaItems {
case .poster(let posters):
let posterItem = posters[indexPath.item]
cell.configure(with: .poster(posterItem))
case .video(let videos):
let videoItem = videos[indexPath.item]
cell.configure(with: .video(videoItem))
case .none:
break
}
return cell
}
}
'Project > MovieClip' 카테고리의 다른 글
📌 컬렉션 뷰에서 .video 아이템 클릭 시 YouTube 영상 재생하기 (0) | 2025.02.12 |
---|---|
상세 페이지 내에서 영화, 티비 관련한 유사한 내용은 컬렉션뷰로 보여주기 (0) | 2025.02.11 |
헷갈리기 쉬운 주요 출연진 정보를 받아오는 로직 (0) | 2025.02.10 |
테이블의 rowheight을 동적으로 할 때 주의할 점 (0) | 2025.02.10 |
TMDB에서 특정 조건의 video 정보 받아오기 (0) | 2025.02.10 |