본문 바로가기
Project/NETFLIX

NETFLIX CLONE 7편 (오픈소스 SDWebImage, 컬렉션뷰셀 적용)

by 밤새는탐험가 2024. 5. 8.

 

✅ 오픈 소스 SDWebImage 적용

✅ 테이블 뷰에 들어가 있는 컬렉션뷰에 포스터 이미지 적용

  • TitleCollectionViewCell 이라는 컬렉션뷰의 셀에 들어갈 이미지를 적용한 파일을 생성 
  • configure 메서드에서 TMDB에서 얻어오는 이미지 주소를 posterImageView에 적용
  • CollectionViewTableViewCell 클래스 내에 configure 메서드를 통해 titles라는 배열에 데이터를 넣고 collectionView를 새로고침한다
  • CollectionViewTableViewCell 의 configure 메서드를 홈뷰컨트롤러에서 호출

 


✅ 오픈 소스 SDWebImage 적용

  • 오픈 소스 사이트를 통해 Xcode -> AddPackage 적용

https://github.com/SDWebImage/SDWebImage

 

GitHub - SDWebImage/SDWebImage: Asynchronous image downloader with cache support as a UIImageView category

Asynchronous image downloader with cache support as a UIImageView category - SDWebImage/SDWebImage

github.com

 

 

  • AddPackage가 완료되면 프로젝트 -> packageDependencies 를 확인 가능

 

 

  • TitleCollectionViewCell 파일 내에 SDWebImage를 불러오고, configureI() 메서드 생성
  • 이 메서드의 역할은 model 이라는 파라미터로 받아오는 값 (주소)을 통해 posterImageView에 적용
import UIKit
import SDWebImage

class TitleCollectionViewCell: UICollectionViewCell {
    
    static let identifier = "TitleCollectionViewCell"
    
    private let posterImageView: UIImageView = {
       
        let imageView = UIImageView()
        imageView.contentMode = .scaleAspectFill
        return imageView
        
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        contentView.addSubview(posterImageView)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        posterImageView.frame = contentView.bounds
    }
    
    public func configure(with model: String) {
        guard let url = URL(string: model) else { return }
        posterImageView.sd_setImage(with: url, completed: nil)
    }
    
}

 

 

  • 홈뷰컨트롤러에서 fetchData() 메서드 삭제
  • 이를 개선하여 단일 모델로 선언하고자 한다. 
  • 홈뷰컨트롤러에 Sections 라는 열거형을 통해 각 섹션을 case로 분류 
enum Sections: Int {
    case TrendingMovies = 0
    case TrendingTv = 1
    case Popular = 2
    case Upcoming = 3
    case TopRated = 4
}

 

 

  • CollectionViewTableViewCell 클래스에서 collectionView 변수에 register 함수의 전달인자로 TitleCollectionViewCell를 등록
import UIKit

class CollectionViewTableViewCell: UITableViewCell {
    
    static let identifier = "CollectionViewTableViewCell"
    
    private let collectionView: UICollectionView = {
       
        let layout = UICollectionViewFlowLayout()
        layout.scrollDirection = .horizontal
        layout.itemSize = CGSize(width: 144, height: 200)
        
        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
        collectionView.register(TitleCollectionViewCell.self, forCellWithReuseIdentifier: TitleCollectionViewCell.identifier)
        return collectionView
    }()

 

 

  • Extension CollectionViewTableViewCell 부분에서 indexPath 파라미터를 가진 collectionView 메서드 수정 
  • cell.configure에서는 이미지 주소를 받아온다. 
extension CollectionViewTableViewCell: UICollectionViewDelegate, UICollectionViewDataSource {
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 10
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TitleCollectionViewCell.identifier, for: indexPath) as? TitleCollectionViewCell else { return UICollectionViewCell() }
        
        cell.configure(with: "")
        
        return cell
    }
    
}

 

 

  • CollectionViewTableViewCell 파일 내에 빈 배열 선언 
import UIKit

class CollectionViewTableViewCell: UITableViewCell {
    
    static let identifier = "CollectionViewTableViewCell"
    
    
    private var titles: [Ttile] = [Title]()

 

 

  • titles 변수에 configure 를 통해 얻어오는 데이터를 할당
class CollectionViewTableViewCell: UITableViewCell {
    
    static let identifier = "CollectionViewTableViewCell"
    
    
    private var titles: [Title] = [Title]()

    
    private let collectionView: UICollectionView = {
       
        let layout = UICollectionViewFlowLayout()
        layout.scrollDirection = .horizontal
        layout.itemSize = CGSize(width: 144, height: 200)
        
        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
        collectionView.register(TitleCollectionViewCell.self, forCellWithReuseIdentifier: TitleCollectionViewCell.identifier)
        return collectionView
    }()
    
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        contentView.backgroundColor = .systemBlue
        
        contentView.addSubview(collectionView)
        
        collectionView.delegate = self
        collectionView.dataSource = self
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    
    // 제약조건 설정 
    override func layoutSubviews() {
        super.layoutSubviews()
        collectionView.frame = contentView.bounds
    }
    
    
    public func configure(with titles: [Title]) {
        self.titles = titles
    }
}

 

  • 홈뷰컨트롤러의 extension 부분의 indexPath 파라미터를 가진 tableView 함수에 configure 메서드 호출
 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        guard let cell = tableView.dequeueReusableCell(withIdentifier: CollectionViewTableViewCell.identifier, for: indexPath) as? CollectionViewTableViewCell else { return UITableViewCell() }
        
        // 열거형으로 구분한 sections 별 함수 실행
        switch indexPath.section {
        case Sections.TrendingMovies.rawValue:
            
            APICaller.shared.getTrendingMovies { result in
                switch result {
                case .success(let titles):
                    cell.configure(with: titles)
                case .failure(let error):
                    print(error.localizedDescription)
                }
            }
            
        case Sections.TrendingTv.rawValue:
            
            APICaller.shared.getTrendingTvs { result in
                switch result {
                case .success(let titles):
                    cell.configure(with: titles)
                case .failure(let error):
                    print(error.localizedDescription)
                }
            }
            
        case Sections.Popular.rawValue:
            
            APICaller.shared.getPopular { result in
                switch result {
                case .success(let titles):
                    cell.configure(with: titles)
                case .failure(let error):
                    print(error.localizedDescription)
                }
            }
            
        case Sections.Upcoming.rawValue:
            
            APICaller.shared.getUpcomingMovies { result in
                switch result {
                case .success(let titles):
                    cell.configure(with: titles)
                case .failure(let error):
                    print(error.localizedDescription)
                }
            }
            
        case Sections.TopRated.rawValue:
            
            APICaller.shared.getTopRated { result in
                switch result {
                case .success(let titles):
                    cell.configure(with: titles)
                case .failure(let error):
                    print(error.localizedDescription)
                }
            }
            
        default:
            return UITableViewCell()
        }
        
        return cell
    }

 

  • CollectionViewTableViewCell 파일에서 extension 부분을 아래 코드로 변경
  • model 이라는 변수에는 titles라는 배열에 indexPath.row 순서에 따른 poster_path 할당 
extension CollectionViewTableViewCell: UICollectionViewDelegate, UICollectionViewDataSource {
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return titles.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TitleCollectionViewCell.identifier, for: indexPath) as? TitleCollectionViewCell else { return UICollectionViewCell() }
        
        
        guard let model = titles[indexPath.row].poster_path else { return UICollectionViewCell() }
        cell.configure(with: model)
        
        return cell
    }
    
}

 

  • CollectionViewTableViewCell 클래스 내의 collectionView의 reloadData() 시점을 확인하고 실행
  • titles에는 대량의 데이터를 받아오고, 이를 새로고침 해준다. 
    // 배열에 값을 가져오는 함수
    public func configure(with titles: [Title]) {
        self.titles = titles
        DispatchQueue.main.async { [weak self] in
            self?.collectionView.reloadData()
        }
    }

 

  • TitleCollectionViewCell 클래스 내의 configure 메서드를 아래와 같이 수정
  • model 이라는 파라미터의 값이 나오게 설정
    public func configure(with model: String) {
        
        print(model)
//        guard let url = URL(string: model) else { return }
//        posterImageView.sd_setImage(with: url, completed: nil)
    }

 

 

  • 앱을 실행시켜보면 콘솔창이 다음과 같이 나온다는 것을 확인

 

  • TMDB에서 이미지는 url 주소를 띄고 있다.이를 불러오는 방법에 대한 설명은 아래 사이트를 참고

https://developer.themoviedb.org/docs/image-basics

 

Basics

How to build an image URL.

developer.themoviedb.org

 

  • TitleCollectionViewCell 파일 내에 configure 메서드를 수정 
import UIKit
import SDWebImage

class TitleCollectionViewCell: UICollectionViewCell {
    
    static let identifier = "TitleCollectionViewCell"
    
    private let posterImageView: UIImageView = {
       
        let imageView = UIImageView()
        imageView.contentMode = .scaleAspectFill
        return imageView
        
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        contentView.addSubview(posterImageView)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        posterImageView.frame = contentView.bounds
    }
    
    public func configure(with model: String) {
        
        guard let url = URL(string: "https://image.tmdb.org/t/p/w500/\(model)") else { return }
        
        posterImageView.sd_setImage(with: url, completed: nil)
    }
    
}

 

 

  • 앱을 실행하면 아래와 같이 나온다

 

https://youtu.be/SOSX_ENJtFg?si=aMRJMwyF6D4nvQox