[UIKit] Spotify Clone: Browse UI 2

Junyoung Park·2022년 9월 4일
0

UIKit

목록 보기
9/142
post-thumbnail
post-custom-banner

Building Spotify App in Swift 5 & UIKit - Part 9 (Xcode 12, 2021, Swift 5) - Build App

Spotify Clone: Browse UI 2

구현 목표

  • 스포티파이 API를 통해 패치한 데이터를 통해 컬렉션 뷰 그리기
  • 커스텀 컬렉션 뷰 셀 구현
  • 커스텀 컬렉션 뷰 셀의 데이터 바인딩을 담당하는 뷰 모델 구현

구현 태스크

  1. 컬렉션 뷰에 커스텀 컬렉션 뷰 셀 등록하기
  2. 섹션 별 컬렉션 뷰 셀이 사용할 뷰 모델 패치
  3. 뷰 모델을 통한 셀 데이터 바인딩
  4. 뷰 모델 데이터를 통한 UI 레이아웃 배치

핵심 코드

enum BrowseSectionType {
    case newReleases(viewModels: [NewRelesesCellViewModel])
    case featuredPlaylists(viewModels: [NewRelesesCellViewModel])
    case recommendedTracks(viewModels: [NewRelesesCellViewModel])
}
...
collectionView.register(NewReleaseCollectionViewCell.self, forCellWithReuseIdentifier: NewReleaseCollectionViewCell.identifier)
collectionView.register(FeaturedPlaylistCollectionViewCell.self, forCellWithReuseIdentifier: FeaturedPlaylistCollectionViewCell.identifier)
collectionView.register(RecommendedTrackCollectionViewCell.self, forCellWithReuseIdentifier: RecommendedTrackCollectionViewCell.identifier)
private func fetchData() {
	let group = DispatchGroup()
        group.enter()
        group.enter()
        group.enter()
        var newReleases: NewReleasesResponse?
        var featuredPlaylist: FeaturedPlaylistsResponse?
        var recommendations: RecommendationsResponse?

	group.notify(queue: .main) {
            guard
                let newAlbums = newReleases?.albums.items,
                let playlists = featuredPlaylist?.playlists.items,
                let tracks = recommendations?.tracks
            else {
                fatalError("Models are nil")
            }
            self.configureModels(newAlbums: newAlbums, playlists: playlists, tracks: tracks)
        }
}
  • 데이터 패치를 동시에 하기 위한 디스패치 큐 그룹
  • 서로 다른 데이터를 데이터 패치를 통해 바인딩, 초기의 널 값으로 할당된 데이터를 뷰 모델로 주기
    private func configureModels(newAlbums: [Album], playlists: [Playlist], tracks: [AudioTrack]) {
        sections.append(.newReleases(viewModels: newAlbums.compactMap({ album in
            return NewRelesesCellViewModel(name: album.name, artworkURL: URL(string: album.images.first?.url ?? ""), numberOfTrack: album.total_tracks, artistName: album.artists.first?.name ?? "-")
        })))
...
        collectionView.reloadData()
    }
  • configureModels에서는 뷰 모델을 구성하는 데이터를 파라미터로 입력받아 셀을 그리는 뷰 모델을 받음
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let type = sections[indexPath.section]
        switch type {
        case .newReleases(viewModels: let viewModels):
            guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: NewReleaseCollectionViewCell.identifier, for: indexPath) as? NewReleaseCollectionViewCell else {
                return UICollectionViewCell()
            }
            let viewModel = viewModels[indexPath.row]
            cell.configure(with: viewModel)
            cell.backgroundColor = .systemGreen
            return cell
        ...
    }
  • 데이터 타입에 따라 커스텀 셀 호출
  • 메모리 낭비를 방지하기 위한 디큐 가능한 재사용 셀 호출 → guard let 캐스팅 주의
  • 해당 셀이 가지고 있는 뷰 모델을 통해 셀의 UI를 그리는 configure 함수 주의
import UIKit
import SDWebImage

class NewReleaseCollectionViewCell: UICollectionViewCell {
    static let identifier = "NewReleaseCollectionViewCell"
    
    private let albumCoverImageView: UIImageView = {
        // ImageView
    }()
    
    private let albumNameLabel: UILabel = {
        // Label
    }()
    
    private let numberOfTracksLabel: UILabel = {
        // Label
    }()
    
    private let artistNameLabel: UILabel = {
        // Label
    }()
    
    ...
    
    override func layoutSubviews() {
        // UI layout 
    }
    
    override func prepareForReuse() {
        super.prepareForReuse()
        albumNameLabel.text = nil
        artistNameLabel.text = nil
        numberOfTracksLabel.text = nil
        albumCoverImageView.image = nil
        // Optional <-> Data Binding
    }
    
    func configure(with viewModel: NewRelesesCellViewModel) {
        albumNameLabel.text = viewModel.name
        artistNameLabel.text = viewModel.artistName
        numberOfTracksLabel.text = "Tracks: \(viewModel.numberOfTrack)"
        albumCoverImageView.sd_setImage(with: viewModel.artworkURL, completed: nil)
    }
}
  • 디큐 재사용 가능한 셀을 사용할 때 메모리 낭비를 막기 위해 configure되는 담당 데이터는 옵셔널 선언 → prepareForReuse에서 해당 값을 널 값 할당

구현 화면

  • 각 컬렉션 뷰 섹션을 구성하고 있는 셀마다 서로 다른 뷰 모델을 담당하고 있기 때문에 셀 UI는 "뷰 모델이 가지고 있는 데이터를 어떻게 그릴지"만 생각하면 된다.
  • 셀 내 레이아웃 배치를 해당 클론 코딩 내에서는 x, y 좌표를 통해 조정하고 있는데, 오토 레이아웃 관련 코드로 view?.translatesAutoresizingMaskIntoConstraints=false를 주고 anchor 값을 조정해 커스텀했던 방법과는 색달랐다. 프레임 값을 미리 알고 있을 때 유용하게 사용할 수 있을 것 같다.
profile
JUST DO IT
post-custom-banner

0개의 댓글