Building Spotify App in Swift 5 & UIKit - Part 25 - Save Albums (Xcode 12, 2021, Swift 5) - App
LibraryAlbumsViewController
: 유저 선택한 앨범을 스포티파이 API로 호출AlbumViewController
: 현재 앨범을 유저의 앨범에 추가LibraryAlbumsViewController
의 앨범 테이블 뷰를 새롭게 그리는 함수를 호출하도록 옵저버 추가import UIKit
class LibraryAlbumsViewController: UIViewController {
var albums = [Album]()
private let noAlbumsView = ActionLabelView()
private let tableView: UITableView = {
let tableView = UITableView()
tableView.register(SearchResultSubtitleTableViewCell.self, forCellReuseIdentifier: SearchResultSubtitleTableViewCell.identifier)
tableView.isHidden = true
return tableView
}()
private var observer: NSObjectProtocol?
override func viewDidLoad() {
super.viewDidLoad()
setLibraryAlbumsViewUI()
fetchData()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
noAlbumsView.frame = CGRect(x: (view.width - 150) / 2, y: (view.height - 150) / 2, width: 150, height: 150)
tableView.frame = view.bounds
}
private func setLibraryAlbumsViewUI() {
view.backgroundColor = .clear
view.addSubview(noAlbumsView)
noAlbumsView.configure(with: ActionLabelViewViewModel(text: "You have no saved albums", actionTitle: "Browse"))
noAlbumsView.delegate = self
view.addSubview(tableView)
tableView.delegate = self
tableView.dataSource = self
observer = NotificationCenter.default.addObserver(forName: .albumSavedNotification, object: nil, queue: .main, using: { [weak self] _ in
guard let self = self else { return }
self.fetchData()
})
}
private func fetchData() {
albums.removeAll()
APICaller.shared.getCurrentUserAlbums { [weak self] result in
guard let self = self else { return }
switch result {
case .success(let albums):
self.albums = albums
DispatchQueue.main.async {
self.updateUI()
}
case .failure(let error):
print(error.localizedDescription)
break
}
}
}
private func updateUI() {
if albums.isEmpty {
noAlbumsView.isHidden = false
tableView.isHidden = true
} else {
tableView.reloadData()
noAlbumsView.isHidden = true
tableView.isHidden = false
}
}
}
extension LibraryAlbumsViewController: ActionLabelViewDelegate {
func actionLabelViewDidTapButton(_ actionView: ActionLabelView) {
tabBarController?.selectedIndex = 0
}
}
extension LibraryAlbumsViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return albums.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: SearchResultSubtitleTableViewCell.identifier, for: indexPath) as? SearchResultSubtitleTableViewCell else {
return UITableViewCell()
}
let album = albums[indexPath.row]
cell.configure(with: SearchResultSubtitleTableViewCellViewModel(title: album.name, subtitle: album.artists.first?.name ?? "-", imageURL: URL(string: album.images.first?.url ?? "")))
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let album = albums[indexPath.row]
let vc = AlbumViewController(album: album)
vc.navigationItem.largeTitleDisplayMode = .never
navigationController?.pushViewController(vc, animated: true)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 60
}
}
noAlbumsView
와 tableView
모두를 뷰에 추가 → fetchData()
함수를 통해 리턴한 결과를 통해 보여줄 뷰를 선택noAlbumView
에서는 추가할 앨범을 고르기 위해 탭 인덱스를 곧바로 변경 가능tableView
에서는 API로 리턴받은 앨범 데이터를 각 셀로 바인딩 @objc private func didTapActions() {
let actionSheet = UIAlertController(title: album.name, message: "Actions", preferredStyle: .actionSheet)
actionSheet.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
actionSheet.addAction(UIAlertAction(title: "Save Album", style: .default, handler: { [weak self] _ in
guard let self = self else { return }
// Save album
APICaller.shared.saveAlbum(album: self.album) { success in
if success {
NotificationCenter.default.post(name: .albumSavedNotification, object: nil)
print("Saved: \(success)")
}
}
}))
present(actionSheet, animated: true, completion: nil)
}
AlbumViewController
내에서의 우측 네비게이션 바 아이템을 클릭한 경우 실행되는 함수extension Notification.Name {
static let albumSavedNotification = Notification.Name("albumSavedNotification")
}
LibraryAlbumViewController
의 observer
가 해당 노티피케이션을 관찰하고 있기 때문에 fetchData()
함수를 재호출 가능public func getCurrentUserAlbums(completionHandler: @escaping (Result<[Album], Error>)->()) {
createRequest(with: URL(string: Constants.baseAPIURL + "/me/albums"), type: .GET) { request in
URLSession.shared.dataTask(with: request) { data, response, error in
guard
let data = data,
error == nil else {
completionHandler(.failure(APIError.failedToGetData))
return
}
do {
let result = try JSONDecoder().decode(LibraryAlbumsResponse.self, from: data)
completionHandler(.success(result.items.map({$0.album})))
} catch {
completionHandler(.failure(error))
}
}
.resume()
}
}
public func saveAlbum(album: Album, completionHandler: @escaping (Bool) -> ()) {
createRequest(with: URL(string: Constants.baseAPIURL + "/me/albums?ids=\(album.id)"), type: .PUT) { request in
var request = request
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
URLSession.shared.dataTask(with: request) { data, response, error in
guard
let data = data,
let httpResponse = response as? HTTPURLResponse,
error == nil else {
completionHandler(false)
return
}
let code = httpResponse.statusCode
print(code)
completionHandler(true)
}
.resume()
}
}
getCurrentUserAlbums
, 특정 앨범을 유저의 저장 앨범 목록에 추가하는 saveAlbum
함수옵저버를 통해 특정 뷰에서의 활동이 완료되었을 때 특정 함수(뷰를 다시 패치)를 사용하는 방법을 사용해 보았다. 델리게이트 패턴만이 아니라 다양한 방법이 있다는 데 주목!