본문 바로가기

Project/MovieClip

📌 컬렉션 뷰에서 .video 아이템 클릭 시 YouTube 영상 재생하기

 

✅ 목표:

  • video 아이템을 클릭하면 YouTube 영상이 앱에서 열리도록 만들기
  • 이벤트가 MediaCollectionViewCellMediaTableViewCellDetailViewController 로 전달되도록 구현

 

✅ 한 줄 요약:

사용자가 video 썸네일을 클릭하면 → MediaTableViewCell이 이벤트를 받아서 → DetailViewController가 YouTube 링크를 열어준다!

 

 

1️⃣ MediaCollectionViewCell (컬렉션뷰 셀)

하는 일:

  • YouTube 썸네일을 보여줌
  • 사용자가 썸네일을 탭하면, 해당 videoKey를 상위로 전달

쉽게 이해하기:
👉 "이 영상 클릭했어!" 라고 알려줌
👉 "이 videoKey를 가지고 있어!"

protocol MediaCollectionViewCellDelegate: AnyObject {
    func didTapVideo(with videoKey: String)
}

class MediaCollectionViewCell: UICollectionViewCell {
    
    weak var delegate: MediaCollectionViewCellDelegate?  // ✅ 이벤트를 전달할 delegate 선언
    private var videoKey: String?  // ✅ 클릭한 YouTube 영상 키 저장
    
    private let thumbnailImageView: UIImageView = {
        let imageView = UIImageView()
        imageView.contentMode = .scaleAspectFill    
        imageView.clipsToBounds = true
        imageView.layer.cornerRadius = 20
        return imageView
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        configureConstraints()
        setupTapGesture() // ✅ 탭 제스처 추가
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func configure(with content: MediaInfo) {
        let baseUrl = "https://img.youtube.com/vi/"
        
        switch content {
        case .video(let video):
            if let key = video.key {
                let thumbnailUrl = URL(string: "\(baseUrl)\(key)/hqdefault.jpg")
                thumbnailImageView.sd_setImage(with: thumbnailUrl, completed: nil)
                
                videoKey = key // ✅ 클릭 시 사용할 YouTube 영상 키 저장
            }
        case .poster:
            break
        }
    }

    private func setupTapGesture() {
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapThumbnail))
        thumbnailImageView.isUserInteractionEnabled = true
        thumbnailImageView.addGestureRecognizer(tapGesture)
    }

    @objc private func didTapThumbnail() {
        guard let videoKey = videoKey else { return }
        delegate?.didTapVideo(with: videoKey) // ✅ 이벤트를 `MediaTableViewCell`에 전달
    }
}

 

 

2️⃣ MediaTableViewCell (컬렉션뷰를 가진 테이블뷰 셀)

하는 일:

  • MediaCollectionViewCell에서 전달받은 videoKey를 다시 DetailViewController로 전달

✅ 쉽게 이해하기:
👉 "이 영상 클릭했어!" 라는 말을 DetailViewController에게 전달

protocol MediaTableViewCellDelegate: AnyObject {
    func didTapVideo(with videoKey: String)
}

class MediaTableViewCell: UITableViewCell {
    
    weak var delegate: MediaTableViewCellDelegate?  // ✅ `DetailViewController`로 이벤트 전달할 delegate 선언

    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()
    }
    
    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)
    }
}

// MARK: - UICollectionViewDelegate
extension MediaTableViewCell: UICollectionViewDelegate, UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MediaCollectionViewCell.reuseIdentifier, for: indexPath) as? MediaCollectionViewCell else { return UICollectionViewCell() }
        
        let mediaItem = mediaItems[indexPath.item]
        cell.delegate = self  // ✅ `MediaCollectionViewCell`의 delegate 설정
        cell.configure(with: mediaItem)
        
        return cell
    }
}

// MARK: - MediaCollectionViewCellDelegate
extension MediaTableViewCell: MediaCollectionViewCellDelegate {
    func didTapVideo(with videoKey: String) {
        delegate?.didTapVideo(with: videoKey) // ✅ `DetailViewController`로 이벤트 전달
    }
}

 

 

3️⃣ DetailViewController (YouTube 영상 실행)

하는 일:

  • MediaTableViewCell에서 전달받은 videoKey를 사용해 YouTube 영상 열기

쉽게 이해하기:
👉 "이 영상 클릭했어!" 를 받으면 YouTube 실행!

class DetailViewController: UIViewController {
    
    private let detailTableView: UITableView = {
        let tableView = UITableView(frame: .zero, style: .grouped)
        tableView.separatorStyle = .none
        tableView.backgroundColor = .clear
        return tableView
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupTableViewDelegate()
    }
    
    private func setupTableViewDelegate() {
        detailTableView.delegate = self
        detailTableView.dataSource = self
        detailTableView.register(MediaTableViewCell.self, forCellReuseIdentifier: MediaTableViewCell.reuseIdentifier)
    }
}

// MARK: - UITableViewDelegate
extension DetailViewController: UITableViewDelegate, UITableViewDataSource {
    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() }
            cell.delegate = self  // ✅ Delegate 설정
            cell.configure(with: .video(mediaVideos), type: .video)
            return cell
        default:
            return UITableViewCell()
        }
    }
}

// MARK: - MediaTableViewCellDelegate
extension DetailViewController: MediaTableViewCellDelegate {
    func didTapVideo(with videoKey: String) {
        // ✅ YouTube 영상 실행
        if let url = URL(string: "https://www.youtube.com/watch?v=\(videoKey)") {
            UIApplication.shared.open(url)
        }
    }
}

 

최종 데이터 흐름 정리

🎬 사용자가 video 클릭 시 흐름

1️⃣ 사용자가 MediaCollectionViewCell에서 video를 탭

  • "이 영상 클릭했어!" 라고 MediaTableViewCell에게 알림

2️⃣ MediaTableViewCell이 DetailViewController에게 전달

  • "이 영상 클릭했어! YouTube 열어줘!"

3️⃣ DetailViewController가 YouTube 링크 실행

  • "YouTube 실행 완료!"

 

✅ 왜 didTapVideo 이름을 동일하게 했을까?

1️⃣ 이벤트를 자연스럽게 전달하기 위해

 

📌 기본 개념:
video를 클릭하면, 이벤트가 위쪽으로 전달
이 과정에서 이름을 같이 쓰면 코드가 깔끔해지고, 가독성이 좋음

 

📌 이벤트 흐름:
MediaCollectionViewCell → MediaTableViewCell → DetailViewController

protocol MediaCollectionViewCellDelegate: AnyObject {
    func didTapVideo(with videoKey: String)  // 🎯 1️⃣ 동일한 함수명
}

protocol MediaTableViewCellDelegate: AnyObject {
    func didTapVideo(with videoKey: String)  // 🎯 2️⃣ 동일한 함수명
}

 

2️⃣ Delegate 패턴에서는 함수명이 같을수록 좋음

Delegate는 특정 이벤트를 전달하는 역할
✅ 역할이 동일한 경우, 같은 이름의 함수를 쓰면 코드의 가독성이 좋음

❌ 만약, 다르게 지으면 헷갈림 😵‍💫
예를 들어, 이렇게 다르게 하면?

protocol MediaCollectionViewCellDelegate: AnyObject {
    func cellDidTapVideo(with videoKey: String)
}

protocol MediaTableViewCellDelegate: AnyObject {
    func tableViewCellDidTapVideo(with videoKey: String)
}

 

👉 이렇게 되면 연결되는 느낌이 약해지고 코드가 복잡해질 수 있어.
👉 반면, 같은 didTapVideo를 사용하면 이벤트가 깔끔하게 전달됨!

 

3️⃣ didTapVideo는 역할을 잘 설명하는 좋은 이름

✔ didTapVideo → "사용자가 video를 탭했다!" 라는 직관적인 의미
✔ 굳이 추가적인 설명 없이도 코드를 쉽게 이해할 수 있음

💡 즉, Delegate를 거쳐 이벤트가 전달될 때, 같은 함수명을 쓰면

  • 📌 연결이 명확해지고
  • 📌 코드가 더 직관적이고 이해하기 쉬워짐! 🎯