이전 포스팅
자세한 코드는 여기로!
https://github.com/yexjin/iOS_Study/tree/main/AppleMusicApp
9/12
오늘은 Track 모델을 구현해봤다!
자세히 말하면 Track 구조체와 이 Track을 View와 연결해줄 Track Manager을 만들었다.
이 부분은 Track 폴더로 묶어줄건데, Track 폴더안의 파일은
1. Track.swift : Track, Album 구조체
2. TrackModel.swift : Track Manager (Model 정의 + ViewModel의 메소드들)
3. Extension+AVPlayerItem.swift : AVPlayerItem 타입을 Track 타입으로 Casting 해주는 메서드 포함
이렇게 구성되어 있다.
Track.swift : Track, Album 구조체
// Track.swift
import UIKit
struct Track {
let title: String
let artist: String
let albumName: String
let artwork: UIImage
init(title: String, artist: String, albumName: String, artwork: UIImage) {
self.title = title
self.artist = artist
self.albumName = albumName
self.artwork = artwork
}
}
struct Album {
let title: String
let tracks: [Track]
var thumbnail: UIImage? {
return tracks.first?.artwork
}
var artist: String? {
return tracks.first?.artist
}
init(title: String, tracks: [Track]) {
self.title = title
self.tracks = tracks
}
}
는 다음과 같고,
TrackManager.swift
// TrackManager.swift
import UIKit
import AVFoundation
class TrackManager {
// TODO: 프로퍼티 정의하기 - 트랙들, 앨범들, 오늘의 곡
var tracks: [AVPlayerItem] = []
var albums: [Album] = []
var todayMusic: AVPlayerItem?
// TODO: 생성자 정의하기
init() {
let tracks = loadTracks()
self.tracks = tracks
self.albums = loadAlbums(tracks: tracks)
self.todayMusic = self.tracks.randomElement()
}
// TODO: 트랙 로드하기
func loadTracks() -> [AVPlayerItem] {
}
// TODO: 인덱스에 맞는 트랙 로드하기
func track(at index: Int) -> Track? {
}
// TODO: 앨범 로딩메소드 구현
func loadAlbums(tracks: [AVPlayerItem]) -> [Album] {
}
// TODO: 오늘의 트랙이 랜덤으로 선택되게 하는 함수
// - 헤더로 사용할..
func loadOtherTodaysTrack(){
}
}
요 ViewModel 메서드들을 하나하나 꼼꼼히 살펴보자
1️⃣ loadTracks()
- 트랙로드하기
- 파일을 읽어서 AVPlayerItem을 만들어서 로드될 트랙 묶음으로 반환!
func loadTracks() -> [AVPlayerItem] {
let urls = Bundle.main.urls(forResourcesWithExtension: "mp3", subdirectory: nil) ?? []
let items = urls.map { url in
return AVPlayerItem(url: url)
}
return items
}
Bundle.main.urls = 해당 앱.메인.urls
Bundle : 앱 안의 Boundary, 트랙 파일 자체가 local에 있으니, Bundle을 사용
urls = URL의 배열 타입
forResourcesWithExtension : 확장자
subdirectory: 하위폴더
2️⃣ track()
- 지정된 인덱스에 맞는 트랙 로드하기
// TODO: 인덱스에 맞는 트랙 로드하기
func track(at index: Int) -> Track? {
let playerItem = tracks[index]
// 여기서 가져온 playerItem은 타입이 AVPlayerItem
let track = playerItem.convertToTrack()
// convertToTrack : Track 타입으로 변경시키기 위한 함수
// 이 'convertToTrack' 함수는 Extension+AVPlayerItem 파일에 정의
return track
}
로드된 트랙인 playerItem = track[index]은 타입이 AVPlayerItem이다.
convertToTrack() 메서드로 Track 타입으로 변경이 필요하다.
이 convertToTrack() 은 Extension+AVPlayerItem 파일에 정의되어 있다.
잠시, Extension+AVPlayerItem 파일에서 이 convertToTrack()을 살펴보자
// Extension+AVPlayerItem.swift
import UIKit
import AVFoundation
extension AVPlayerItem {
// type casting 기능 : Track 타입으로 Return!
func convertToTrack() -> Track? {
//AVPlayerItem -> asset -> metadata
let metadataList = asset.metadata
var trackTitle: String?
var trackArtist: String?
var trackAlbumName: String?
var trackArtwork: UIImage?
for metadata in metadataList {
if let title = metadata.title {
trackTitle = title
}
if let artist = metadata.artist {
trackArtist = artist
}
if let albumName = metadata.albumName {
trackAlbumName = albumName
}
if let artwork = metadata.artwork {
trackArtwork = artwork
}
}
guard let title = trackTitle,
let artist = trackArtist,
let albumName = trackAlbumName,
let artwork = trackArtwork else {
return nil
}
return Track(title: title, artist: artist, albumName: albumName, artwork: artwork)
}
}
// AVPlayerItem의 Metadatas
// AVMetadataItem : AVPlayerItem의 assets 안의 metadata에 존재
extension AVMetadataItem {
var title: String? {
guard let key = commonKey?.rawValue, key == "title" else {
return nil
}
return stringValue
}
var artist: String? {
guard let key = commonKey?.rawValue, key == "artist" else {
return nil
}
return stringValue
}
var albumName: String? {
guard let key = commonKey?.rawValue, key == "albumName" else {
return nil
}
return stringValue
}
var artwork: UIImage? {
guard let key = commonKey?.rawValue, key == "artwork", let data = dataValue, let image = UIImage(data: data) else {
return nil
}
return image
}
}
해당 AVPlayerItem의 Metadatas까지 하나하나 묶어 Track 타입으로 반환시킨다
Metadatas?
- AVPlayerItem.assets.metadata 에 존재함 → AVMetaItem으로 extension하여 빼내왔음!
자, 다시 TrackManager.swift 파일로 돌아와서
특정 인덱스에 해당하는 트랙을 로드하는 것까지 살펴봤다.
3️⃣ loadAlbums()
- 앨범 로딩 메소드
트랙 메소드를 그전에 다시 확인해보면
→ 각 트랙들을 albumName을 기준으로 딕셔너리를 만들어 그룹핑하면
이렇게 앨범들을 모아 띄울때 유용하게 쓰일 수 있음 !
그래서 앨범 로딩 메소드를 구현해보면 아래와 같다.
func loadAlbums(tracks: [AVPlayerItem]) -> [Album] {
let trackList: [Track] = tracks.compactMap { track in track.convertToTrack() }
let albumDics = Dictionary(grouping: trackList) { track in track.albumName }
var albums: [Album] = []
for (key, value) in albumDics {
let title = key
let tracks = value
let album = Album(title: title, tracks: tracks)
albums.append(album)
}
return albums
}
- trackList : 현재 tracks는 AVPlayItem의 array이며, 이 array 안에 존재하는 item들의 타입들을 Track으로 변경
- albumDics : albumName(=key)을 기준으로 만든 딕셔너리
- key : albumName
- value : trackList 안의 albumName이 같은 모든 tracks들
- albums : album array
4️⃣ loadOtherTodaysTrack()
- 오늘의 트랙이 random하게 선택될 함수
- 아래의 사진과 같이 헤더로 사용될 것
구현코드는
func loadOtherTodaysTrack(){
self.todayMusic = self.tracks.randomElement()
}
데이터 구조는 어느정도 완료.
이제, 다음은 이 Track 모델을 통해 데이터를 HomeViewd와 연결시켜 띄우는 것을 해볼 것이다!
'iOS > Toy project' 카테고리의 다른 글
[iOS : Toy Project] Apple Music App (4) : HeaderView (CollectionReusableView) (2) | 2022.09.13 |
---|---|
[iOS : Toy Project] Apple Music App (3) : UICollectionViewCell 업데이트 (0) | 2022.09.13 |
[iOS : Toy Project] Apple Music App (1) : 뷰 구성 (0) | 2022.09.06 |
[iOS : Toy Project] Todo List 만들기 (5) (0) | 2022.08.19 |
[iOS : Toy Project] Todo List 만들기 (4) - CollectionView의 Datasource, Delegate (0) | 2022.08.19 |