[UIKit] Spotify Clone: Albums

Junyoung Park·2022년 9월 5일
0

UIKit

목록 보기
13/142
post-thumbnail

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

Spotify Clone: Albums

구현 목표

  • 앨범 이미지 클릭 → 앨범 디테일 뷰
  • 헤더 + 앨범 컬렉션 뷰

구현 태스크

  1. 디테일 앨범 정보 데이터 패치 → 뷰 모델 바인딩
  2. 뷰 모델을 통해 컬렉션 뷰 셀 UI 패치
  3. 컬렉션 뷰 헤더: (1). 커스텀 헤더 이미지 (2). 날짜 커스텀

핵심 코드

class AlbumViewController: UIViewController {
    private let album: Album
    private let collectionView = UICollectionView(
    // Custom Collection View
    )
    private var viewModels = [AlbumCollectionViewCellViewModel]()
    ...
    
    private func fetchData() {
        APICaller.shared.getAlbumDetails(for: album) { [weak self] result in
            guard let self = self else { return }
            switch result {
            case .success(let model):
                self.viewModels = model.tracks.items.compactMap { track in
                    return AlbumCollectionViewCellViewModel(name: track.name,
                                                         artistName: track.artists.first?.name ?? "-")
                }
                DispatchQueue.main.async {
                    self.collectionView.reloadData()
                }
                break
            case .failure(let error):
                print(error.localizedDescription)
                break
            }
        }
    }
}

extension AlbumViewController: UICollectionViewDelegate, UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return viewModels.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: AlbumTrackCollectionViewCell.identifier, for: indexPath) as? AlbumTrackCollectionViewCell else {
            return UICollectionViewCell()
        }
        let viewModel = viewModels[indexPath.row]
        cell.configure(with: viewModel)
        return cell
    }
    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        collectionView.deselectItem(at: indexPath, animated: true)
    }
    
    func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
        guard
            kind == UICollectionView.elementKindSectionHeader,
            let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: PlaylistHeaderCollectionReusableView.identifier, for: indexPath) as? PlaylistHeaderCollectionReusableView else {
            return UICollectionReusableView()
        }
        let headerViewModel = PlaylistHeaderViewViewModel(name: album.name,
                                                          ownerName: album.artists.first?.name,
                                                          description: "Release Date : \(String.formattedDate(string: album.release_date))",
                                                          artworkURL: URL(string: album.images.first?.url ?? ""))
        header.configure(with: headerViewModel)
        header.delegate = self
        return header
    }
}
...
  • 플레이리스트 디테일 뷰를 그리는 것과 완전히 동일한 설계로 구현된 앨범 디테일 뷰
  • 특정 앨범이 가지고 있는 트랙 개수만큼 뷰 모델 패치 → 디스패치 메인 큐로 UI 그리기
  • 헤더에 표시할 이미지, 유저 이름, 이름 등 정보 패치 + 날짜는 별도의 데이트 포매터 사용
extension DateFormatter {
    static let dateFormatter: DateFormatter = {
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "YYYY-MM-dd"
        return dateFormatter
    }()
    
    static let displayDateFormatter: DateFormatter = {
        let dateFormatter = DateFormatter()
        dateFormatter.dateStyle = .full
        return dateFormatter
    }()
}

extension String {
    static func formattedDate(string: String) -> String {
        guard let date = DateFormatter.dateFormatter.date(from: string) else {
            return string
        }
        return DateFormatter.displayDateFormatter.string(from: date)
    }
}
...
description: "Release Date : \(String.formattedDate(string: album.release_date))"
...
  • 데이트 포매터, 문자열 익스텐션으로 구현한 위 함수는 문자열을 입력받아 Date 형식으로 변환 가능한지 guard 캐스팅을 통해 검사하고, 변환 가능하다면 해당 날짜의 표현 방법을 .long으로 출력

구현 화면

특정 데이터의 상세 정보를 읽어온 뒤(즉 API 호출이 완료된 뒤) 해당 정보를 UI에 그릴 때 테이블/컬렉션 뷰 데이터 리로드 함수를 호출해야 한다!

profile
JUST DO IT

0개의 댓글