Notice
Recent Posts
Recent Comments
Link
ยซ   2024/07   ยป
์ผ ์›” ํ™” ์ˆ˜ ๋ชฉ ๊ธˆ ํ† 
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
Archives
Today
Total
๊ด€๋ฆฌ ๋ฉ”๋‰ด

lgvv98

ch12 ์• ํ”Œ๋ฎค์งst ์Œ์•…์•ฑ ์ฝ”๋“œ๋ฆฌ๋ทฐ ๋ณธ๋ฌธ

โš ๏ธ deprecated โš ๏ธ/ํŒจ์บ (์˜ฌ์ธ์›)

ch12 ์• ํ”Œ๋ฎค์งst ์Œ์•…์•ฑ ์ฝ”๋“œ๋ฆฌ๋ทฐ

๐Ÿฅ• ์บ๋Ÿฟ๋งจ 2021. 6. 24. 15:47

โœ… ์ด๋ฒˆ ์‹œ๊ฐ„์—๋Š” ์ฃผ๋œ ๋‚ด์šฉ์€ AVFoundation์„ ํ™œ์šฉํ•œ ๋ฏธ๋””์–ด ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ž‘์—…์ด์•ผ.

ํ™•์‹คํžˆ ์ฝ”๋“œ๋ฆฌ๋ทฐ๋ฅผ ํ•˜๊ณ  ์ง€๋‚˜๊ฐ€์•ผ ์˜จ์ „ํžˆ ๋‚ด๊ฒƒ์œผ๋กœ ๋งŒ๋“œ๋Š” ๋Š๋‚Œ์ด ์žˆ์–ด์„œ ์ง„๋„๊ฐ€ ์กฐ๊ธˆ ๋Š๋ ค์ง€๋”๋ผ๋„ ๊ผญ ํ•˜๊ณ  ์ง€๋‚˜๊ฐ€๋Š”๊ฑธ๋กœ..!

 

AVFoundation์— ๋Œ€ํ•ด ์‚ฌ์šฉํ•ด๋ณธ ๊ฒฝํ—˜์ด ์ ์–ด์„œ ์ด๋ฒˆ์—๋Š” ์งš๊ณ  ๋„˜์–ด๊ฐ€์•ผํ•  ์ฝ”๋“œ๊ฐ€ ๋งŽ์€ ๊ฒƒ ๊ฐ™๋‹ค..!

์ต์ˆ™ํ•˜์ง€ ์•Š์„๋•Œ๋Š” ์—ญ์‹œ๋‚˜ ์• ํ”Œ ๊ฐœ๋ฐœ์ž ๋ฌธ์„œ๋ฅผ ๋ณด๋ฉด์„œ ์ง€๋‚˜๊ฐ€๋ณด์ž!

 

์• ํ”Œ ๊ณต์‹๋ฌธ์„œ์˜ AVFoundation์— ๋Œ€ํ•œ ํ”„๋ ˆ์ž„์›Œํฌ

์• ํ”Œ ๊ฐœ๋ฐœ์ž ๋ฌธ์„œ

 

https://developer.apple.com/documentation/avfoundation/

 

Apple Developer Documentation

 

developer.apple.com

 

๊ทธ๋Ÿผ ์ฝ”๋“œ ๋ฆฌ๋ทฐ ์‹œ์ž‘ํ•ด๋ณด์ž!!

 

(๋ชฉ์ฐจ)

1. ์ปฌ๋ ‰์…˜ ๋ทฐ ํ—ค๋”

2. AVFoundation ๋ฉ”ํƒ€ ๋ฐ์ดํ„ฐ ์ถ”์ถœ

3. TrackManager์— ๋Œ€ํ•œ ์„ค๋ช…

4. SimplePlayer์™€ ์‹ฑ๊ธ€ํ†ค์— ๋Œ€ํ•œ ์„ค๋ช…

5. CMTime์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด์ž.

6. ๋ฒ„ํŠผ์˜ ์‹œ์Šคํ…œ์ด๋ฏธ์ง€ ์‚ฌ์šฉ

7. ๋‹คํฌ๋ชจ๋“œ ๋Œ€์‘ 

 

โœ… ์šฐ์„  ์ฒซ๋ฒˆ์งธ๋กœ ์ปฌ๋ ‰์…˜ ๋ทฐ ํ—ค๋”์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด๋„๋ก ํ•˜์ž

์ปฌ๋ ‰์…˜ ๋ทฐ ํ—ค๋”์™€ ๊ด€๋ จํ•œ ์ด๋ฏธ์ง€๋“ค

 

์ €๋ฒˆ ์‹œ๊ฐ„์—๋„ ๋ณด์•˜๋“ฏ์ด ์ปฌ๋ ‰์…˜ ๋ทฐ์—๋Š” ํ…Œ์ด๋ธ” ๋ทฐ์™€ ๋น„์Šทํ•œ ๊ธฐ๋Šฅ์— ์ถ”๊ฐ€๋กœ ๋ ˆ์ด์•„์›ƒ์„ ์žก์•„์ฃผ๋Š” ๋ฉ”์†Œ๋“œ๊ฐ€ ์žˆ์—ˆ์–ด.

๊ทธ๋Ÿฌ๋ฉด ํ—ค๋”๋Š” ์–ด๋–ป๊ฒŒ ํ‘œํ˜„ํ• ๊นŒ?

์ปฌ๋ ‰์…˜ ๋ทฐ ํ—ค๋”๋ฅผ ์ถ”๊ฐ€ํ•˜๊ธฐ ์œ„ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

์ €๊ธฐ ํŒŒ๋ž€ ๋ถ€๋ถ„์œผ๋กœ ๋œ ๊ฒƒ์„ ์ปฌ๋ ‰์…˜ ๋ทฐ์— ์ถ”๊ฐ€ํ•ด์•ผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด.

Collection Reusable View ์„ค๋ช…์„ ๋ณด๋ฉด footer์—๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ๋‚˜์™€์žˆ์Œ!

 

๊ทธ๋Ÿผ ํ—ค๋” ๋ทฐ๋ฅผ ์ ์šฉํ•˜๊ธฐ ์œ„ํ•œ ๋ฉ”์†Œ๋“œ๋ฅผ ํ•œ๋ฒˆ ๋ณด๋„๋ก ํ• ๊นŒ?

    func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
        switch kind {
        case UICollectionView.elementKindSectionHeader:
            guard let item = trackManager.todaysTrack else {
                return UICollectionReusableView()
            }
            
            guard let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "TrackCollectionHeaderView", for: indexPath) as? TrackCollectionHeaderView else {
                return UICollectionReusableView()
            }
            
            header.update(with: item)
            header.tapHandler = { item in
                let playerStoryboard = UIStoryboard.init(name: "Player", bundle: nil) // ์Šคํ† ๋ฆฌ๋ณด๋“œ ํŒŒ์ผ ๊ฐ์ฒด ์ƒ์„ฑ
                guard let playerVC = playerStoryboard.instantiateViewController(identifier: "PlayerViewController") as? PlayerViewController else { return } // ํ”Œ๋ ˆ์ด์–ด์Šคํ† ๋ฆฌ๋ณด๋“œ์—์„œ ID์— ํ•ด๋‹นํ•˜๋Š” ViewController ์ฐพ๊ธฐ
                playerVC.simplePlayer.replaceCurrentItem(with: item) // ์‹ฑ๊ธ€ํ†ค ํŒจํ„ด์„ ์ด์šฉํ•ด์„œ ๊ฐˆ์•„๋ผ๊ธฐ
                self.present(playerVC, animated: true, completion: nil)
            }
            
            return header
        default:
            return UICollectionReusableView()
        }
    }

ํ—ค๋”๋ทฐ๋Š” ์กฐ๊ธˆ ๋‹ค๋ฅด์ง€? viewForSupplementaryElementOfKind ๋ฅผ ํฌํ•จํ•˜๊ณ  ์žˆ๋Š” ๋ฉ”์†Œ๋“œ๋กœ ๊ตฌ์„ฑํ•ด์•ผํ•ด.

๊ทธ๋ฆฌ๊ณ  ํ—ค๋”๋ทฐ๋Š” ํ—ค๋”๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ๋„ ์กฐ๊ธˆ ๋‹ค๋ฅธ๋ฐ dequeueReusableSupplementaryView ๋ฅผ ์‚ฌ์šฉํ•ด์•ผํ•ด.

โ—๏ธdequeueReusableCell ์„ ์‚ฌ์šฉํ•˜๋ฉด ์•ˆ๋ผ!

๊ทธ๋ฆฌ๊ณ  switch๋ฌธ์„ ๋ณด๋ฉด kind๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด๋‘์—ˆ๋Š”๋ฐ, ์—ฌ๊ธฐ์„œ ์„น์…˜ ํ—ค๋”๋‚˜ ํ‘ธํ„ฐ๋ฅผ ์„ ํƒํ•  ์ˆ˜ ์žˆ์–ด.

 

๊ทธ๋Ÿผ ์ด์ œ ํ—ค๋”์— ๋Œ€ํ•œ ์ฝ”๋“œ๋ฅผ ์ฒ˜๋ฆฌ๋ผ๋Š” ๋‚ด์šฉ์„ ๋ณผ๊นŒ?

class TrackCollectionHeaderView: UICollectionReusableView {
    @IBOutlet weak var thumbnailImageView: UIImageView!
    @IBOutlet weak var descriptionLabel: UILabel!
    
    var item: AVPlayerItem?
    var tapHandler: ((AVPlayerItem) -> Void)?
    
    override func awakeFromNib() {
        super.awakeFromNib()
        thumbnailImageView.layer.cornerRadius = 4
    }
    
    func update(with item: AVPlayerItem) {
        // TODO: ํ—ค๋”๋ทฐ ์—…๋ฐ์ดํŠธ ํ•˜๊ธฐ
        self.item = item
        guard let track = item.convertToTrack() else { return } // ๋ณ€ํ™˜์•ˆ๋˜๋ฉด ๊ทธ๋ƒฅ ๋ฆฌํ„ด
        
        self.thumbnailImageView.image = track.artwork
        self.descriptionLabel.text = "Today's pick is \(track.artist)'s album - \(track.albumName), Let listen."
    }
    
    @IBAction func cardTapped(_ sender: UIButton) {
        // TODO: ํƒญํ–ˆ์„๋•Œ ์ฒ˜๋ฆฌ
        guard let todaysItem = item else { return }
        tapHandler?(todaysItem)
        
        
    }
}

์šฐ์„  ํƒญ ํ•ธ๋“ค๋Ÿฌ ๋ณ€์ˆ˜๋Š” ํƒญ ํ–ˆ์„๋•Œ, ์•ก์…˜์„ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ๋ณ€์ˆ˜์•ผ.

awakeFromNib() ์ฝ”๋“œ๊ฐ€ ์žˆ์ง€? ์ด ์ฝ”๋“œ๋Š” ์Šคํ† ๋ฆฌ๋ณด๋“œ์— ์žˆ๋Š” ์•„์ดํ…œ์—์„œ ์‹ค์ œ๋กœ ์•ฑ ์•ˆ์˜ ์–ด๋–ค UICollectionView๋กœ ๋กœ๋“œ๋ ๋•Œ, ํ˜ธ์ถœ๋˜๋Š” ๋ฉ”์†Œ๋“œ์•ผ.

์•„๊นŒ ํ—ค๋”๋ทฐ์— ๋Œ€ํ•œ ์—…๋ฐ์ดํŠธ๊ฐ€ ์–ด๋–ป๊ฒŒ ์ง„ํ–‰๋˜๋Š”์ง€ ๊ทธ๋Ÿผ ๋ณด์ž.

convertToTrack() ์ฝ”๋“œ๋Š” AVFoundation์˜ ๋ฐ์ดํ„ฐ ํƒ€์ž…์„ ์šฐ๋ฆฌ๊ฐ€ ํ”ํžˆ ์‚ฌ์šฉํ•˜๋Š” ํƒ€์ž…์œผ๋กœ ๋ณ€ํ™˜ํ•ด์ฃผ๋Š” ํ•จ์ˆ˜๋ฅผ ๊ตฌํ˜„ํ•œ๊ฑด๋ฐ, ์ž ์‹œ๋’ค์— ๋”ฐ๋กœ ์„ค๋ช…ํ•˜๋„๋ก ํ• ๊ฒŒ.

 

 

โœ… ๊ทธ๋Ÿผ AVFoundation์—์„œ ๋ฉ”ํƒ€ ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”์ถœํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์•Œ์•„๋ณด์ž

extension AVPlayerItem {
    func convertToTrack() -> Track? {
        let metadatList = asset.metadata // AVPlayItem ๊ฐ์ฒด ์•ˆ์— asset์ด๋ผ๋Š” ํ”„๋กœํผํ‹ฐ๊ฐ€ ์žˆ๊ณ , ๊ฑฐ๊ธฐ์—๋Š” ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ผ๊ณ  ํ•ด๊ฐ–๊ณ , ์–ด๋–ค ๊ณก์ด๋ž€ ํŒŒ์ผ์ด ์žˆ์„๋•Œ, ๊ณก ์Œ์›๋„ ์žˆ๊ณ , ๊ณก ์Œ์›์˜ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ผ๊ณ  ํ•ด๊ฐ€์ง€๊ณ  ์•„ํ‹ฐ์ŠคํŠธ ์ •๋ณด, ํƒ€์ด๋ธ”, ๊ณก์˜ ์ธ๋„ค์ผ ๋“ฑ์˜ ์ •๋ณด๋„ ๋“ค์–ด์žˆ๋Š”๋ฐ, ์ด๋Ÿฐ ์ •๋ณด๋“ค์„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜๊ฐ€ ์žˆ๋‹ค.
        
        var trackTitle: String?
        var trackArtist: String?
        var trackAlbumName: String?
        var trackArtwork: UIImage?
        
        // ์Œ์•…ํŒŒ์ผ์—์„œ ๊ณก์˜ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์ถ”์ถœํ•ด๋‚ด์„œ ํŠธ๋ž™์„ ๋งŒ๋“ค์ˆ˜๊ฐ€ ์žˆ๋‹ค. - ์ง์ ‘ ๊ตฌํ˜„ํ•  ํ•„์š”๋ณด๋‹ค ๊ฒ€์ƒ‰ํ•ด์„œ ์“ฐ๋Š”๊ฑธ๋กœ ํ•ด์š”.
        for metadata in metadatList {
            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)
    }
}
 
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
    }
}

extension AVPlayer {
    var isPlaying: Bool {
        guard self.currentItem != nil else { return false }
        return self.rate != 0
    }
}

 

 

์šฐ์„  convertToTrack์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด์ž.

asset์„ ์ด์šฉํ•˜์—ฌ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ์— ์ ‘๊ทผํ•œ ๋’ค, ๋ฐ˜๋ณต๋ฌธ์„ ํ†ตํ•ด ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค.

๋ฉ”ํƒ€๋ฐ์ดํ„ฐ์—๋Š” ์ฝ”๋“œ์— ์ฃผ์„์œผ๋กœ ์ž‘์„ฑํ•ด๋‘” ๊ฒƒ๊ณผ ๊ฐ™์€ ์ •๋ณด๊ฐ€ ๋“ค์–ด์žˆ๋Š”๋ฐ, ๋งŒ๋“ค์–ด๋„ ์ข‹์œผ๋‚˜ ์ฐพ์•„์“ฐ๋Š” ๊ฒƒ๋„ ํ•˜๋‚˜์˜ ์ข‹์€ ๋ฐฉ๋ฒ•!

AVPlayerItem์˜ ํ•˜๋‚˜์˜ ViewModel์˜ ํ˜•์‹์„ ๋ˆ๋‹ค.

isPlaying ๋ณ€์ˆ˜๋Š” ํ˜„์žฌ ์•„์ดํ…œ์ด ์‹คํ–‰์ค‘์ธ์ง€ ์•„๋‹Œ์ง€๋ฅผ ํ™•์ธํ•˜๋Š” ์ฝ”๋“œ์ด๋‹ค. 

 

 

โœ… ๋‹ค์Œ์€ TrackManager์— ๋Œ€ํ•œ ์„ค๋ช…์ด๋‹ค.

class TrackManager {
    // TODO: ํ”„๋กœํผํ‹ฐ ์ •์˜ํ•˜๊ธฐ - ํŠธ๋ž™๋“ค, ์•จ๋ฒ”๋“ค, ์˜ค๋Š˜์˜ ๊ณก
    var tracks : [AVPlayerItem] = [] // AVPlayerItem์œผ๋กœ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ํ•œ๋‹ค.
    var albums : [Album] = []
    var todaysTrack : AVPlayerItem?
    
    
    // TODO: ์ƒ์„ฑ์ž ์ •์˜ํ•˜๊ธฐ
    init() {
        let tracks = loadTracks()
        self.tracks = tracks
        self.albums = loadAlbums(tracks: tracks)
        self.todaysTrack = self.tracks.randomElement()
    }

    // TODO: ํŠธ๋ž™ ๋กœ๋“œํ•˜๊ธฐ
    func loadTracks() -> [AVPlayerItem] {
        // 1. ํŒŒ์ผ๋“ค ์ฝ์–ด์„œ AVPlayerItem ํƒ€์ž…์˜ ํ˜•ํƒœ๋กœ ๋งŒ๋“ค๊ธฐ.
        let urls = Bundle.main.urls(forResourcesWithExtension: "mp3", subdirectory: nil) ?? [] // .mp3 ํŒŒ์ผ ๊ฐ€์ ธ์˜ค๋Š”๋ฐ ์—†์œผ๋ฉด ๊นกํ†ต๋“ค ์„ธํŒ…ํ•ด์ฃผ๊ธฐ - bundle์€ ์•ฑ ์•ˆ์„ ์ด์•ผ๊ธฐํ•จ.
        let items = urls.map { url in
            return AVPlayerItem(url: url)
        }
        return items
    }
    
    // TODO: ์ธ๋ฑ์Šค์— ๋งž๋Š” ํŠธ๋ž™ ๋กœ๋“œํ•˜๊ธฐ
    func track(at index: Int) -> Track? {
        let playerItem = tracks[index] // AVPlayerItem ํƒ€์ž…์ด๋ผ์„œ Trackํƒ€์ž…์œผ๋กœ ๋ฐ”๊ฟ”์•ผ ํ•ด
        return playerItem.convertToTrack()
        
    }

    // TODO: ์•จ๋ฒ” ๋กœ๋”ฉ๋ฉ”์†Œ๋“œ ๊ตฌํ˜„
    func loadAlbums(tracks: [AVPlayerItem]) -> [Album] {
        let trackList : [Track] = tracks.compactMap { $0.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
    }

    // TODO: ์˜ค๋Š˜์˜ ํŠธ๋ž™ ๋žœ๋ค์œผ๋กœ ์„ ์ฑ…
    func loadOtherTodaysTrack() {
        self.todaysTrack = self.tracks.randomElement()
    }
}

๋‹ค์Œ์€ TrackManager์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณผ๊ฑด๋ฐ, MVVM์—์„œ ๋ณด์•˜๋“ฏ์ด tracks๋Š” AVPlayerItem์„ ๋ฐ›์•„์„œ ์‚ฌ์šฉํ•œ๋‹ค.

์—ฌ๊ธฐ์„œ ์กฐ๊ธˆ ์ฃผ๋ชฉํ• ๋งŒ ํ•œ ๊ฒƒ์€ ํŠธ๋ž™์„ ๋กœ๋“œํ•˜๊ธฐ ์œ„ํ•œ ๋ถ€๋ถ„์ด๋‹ค.

Bundle์€ ์šฐ์„  ๋‚ด ์•ฑ ์•ˆ์˜ ์žˆ๋Š” ํŒŒ์ผ์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ์ด๋‹ค.

swift map์— ๋Œ€ํ•œ ๊ฒƒ์€ ์ฒ˜์Œ ๋ณด์•˜๋Š”๋ฐ ๊ธ€์˜ ๋งจ ์•„๋ž˜์— ์ฐธ๊ณ ๋ฌธ์„œ๋ฅผ ๋„ฃ์–ด๋‘์—ˆ์œผ๋‹ˆ ๊ฐœ๋…์„ ๊ฐ€์„œ ๋”ฐ๋กœ ํ™•์ธํ•˜๊ธฐ๋กœ..!

url์„ ํ•ด๋‹นํ•˜๋Š” ์ธ๋ฑ์Šค์— ๋งž๊ฒŒ ์•„์ดํ…œ์— ๋„ฃ๋Š”๋‹ค!

์•จ๋ฒ” ๋กœ๋”ฉ ๋ฉ”์†Œ๋“œ ์ชฝ์—๋Š” compactMap์ด ์žˆ๋Š”๋ฐ, ์ด๊ฑด ์ฐธ๊ณ ์ชฝ์„ ๋ณด๋ฉด ๋œ๋‹ค.

๋˜ํ•œ ๋”•์…”๋„ˆ๋ฆฌ์—์„œ grouping๋„ ์žˆ๋Š”๋ฐ, ์ด ๋ถ€๋ถ„๋„ ์ฐธ๊ณ ์ชฝ์„ ๋ณด๋ฉด ๋œ๋‹ค. ํŠธ๋ž™๋ฆฌ์ŠคํŠธ๋ฅผ ์ธ๋ฑ์Šค๋กœ ๊ทธ๋ฃนํ•‘ํ•œ๋‹ค. 

 

โœ… SimplePlayer์™€ ์‹ฑ๊ธ€ํ†ค์— ๋Œ€ํ•œ ๊ฐ„๋žต์„ค๋ช…

class SimplePlayer {
    // TODO: ์‹ฑ๊ธ€ํ†ค ๋งŒ๋“ค๊ธฐ, ์™œ ๋งŒ๋“œ๋Š”๊ฐ€?
    static let shared = SimplePlayer() // ์‹ฑ๊ธ€ํ†ค์€ static ํ‚ค์›Œ๋“œ ๋ถ™์ž„
    
    private let player = AVPlayer()

    var currentTime: Double {
        // TODO: currentTime ๊ตฌํ•˜๊ธฐ
        return player.currentItem?.currentTime().seconds ?? 0.0
    }
    
    var totalDurationTime: Double {
        // TODO: totalDurationTime ๊ตฌํ•˜๊ธฐ
        return player.currentItem?.duration.seconds ?? 0.0
    }
    
    var isPlaying: Bool {
        // TODO: isPlaying ๊ตฌํ•˜๊ธฐ
        return player.isPlaying
    }
    
    var currentItem: AVPlayerItem? {
        // TODO: currentItem ๊ตฌํ•˜๊ธฐ
        return player.currentItem
    }
    
    init() { }
    
    func pause() {
        // TODO: pause๊ตฌํ˜„
        player.pause()
    }
    
    func play() {
        // TODO: play๊ตฌํ˜„
        player.play()
        
    }
    
    func seek(to time:CMTime) {
        // TODO: seek๊ตฌํ˜„
        player.seek(to: time)
    }
    
    func replaceCurrentItem(with item: AVPlayerItem?) {
        // TODO: replace current item ๊ตฌํ˜„
        player.replaceCurrentItem(with: item)
    }
    
    func addPeriodicTimeObserver(forInterval: CMTime, queue: DispatchQueue?, using: @escaping (CMTime) -> Void) {
        player.addPeriodicTimeObserver(forInterval: forInterval, queue: queue, using: using)
    }
}

singleํ†ค ํŒจํ„ด์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด์ž. ๊ฐ„๋žตํ•˜๊ฒŒ ๋งํ•˜์ž๋ฉด ์“ฐ๋˜ ๊ฐ์ฒด๋ฅผ ์—ฌ๋Ÿฌ๋ฒˆ ๋Œ๋ ค์“ด๋‹ค๋Š” ์˜๋ฏธ์ด๋‹ค.

์‹ฑ๊ธ€ํ†ค์€ ๊ฐ์ฒด๋Š” static์„ ๋ถ™์ธ๋‹ค๊ณ  ํ•œ๋‹ค.

currentTime์€ ํ˜„์žฌ์‹œ๊ฐ„ duration์€ ํ† ํƒˆ์‹œ๊ฐ„์„ ์˜๋ฏธํ•œ๋Œ€ seek์€ ์šฐ๋ฆฌ๊ฐ€ ์Šฌ๋ผ์ด๋” ๋ฐ”๋ฅผ ์›€์ง์ผ ๋•Œ ์กฐ์ •ํ•˜๋Š” ๋ฉ”์†Œ๋“œ

 

โœ… CMTime์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด์ž.

@IBAction func seek(_ sender: UISlider) {
        // TODO: ์‹œํ‚น ๊ตฌํ˜„
        guard let currentItem = simplePlayer.currentItem else { return }
        
        let position = Double(sender.value) // 0~1 ์‚ฌ์ด์˜ ๊ฐ’
        let seconds = position * currentItem.duration.seconds // ์ „์ฒด์‹œ๊ฐ„์„ ๊ณฑํ•ด์ฃผ๋ฉด ์–ด๋Š์ •๋„ ์œ„์น˜์— ์žˆ์–ด์•ผํ•˜๋Š”์ง€ ์•Œ ์ˆ˜ ์žˆ๋‹ค.
        let time = CMTime(seconds: seconds, preferredTimescale: 100) // double ํƒ€์ž…์ด๋ผ ๊ธธ์–ด์งˆ ์ˆ˜ ์žˆ๋Š”๋ฐ, ์šฐ๋ฆฌ๋Š” ์†Œ์ˆ˜์  2์ž๋ฆฌ๊นŒ์ง€๋งŒ ์“ฐ๊ฒ ๋‹ค.
        simplePlayer.seek(to: time)
}

CMTime์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด๋ฉด

position์€ ์Šฌ๋ผ์ด๋” ์œ„์น˜์ธ๋ฐ ์Šฌ๋ผ์ด๋”์˜ ์œ„์น˜๋Š” sender๋ฅผ ํ†ตํ•ด ๋ฐ›์•„์˜ค๊ณ  0~1์‚ฌ์ด์˜ ๊ฐ’์œผ๋กœ ๋‚˜์˜จ๋‹ค.

seconds๋Š” ์ „์ฒด ์‹œ๊ฐ„์— ์Šฌ๋ผ์ด๋”์˜ ๊ฐ’์„ ๊ณฑํ•ด์ฃผ๋ฉด ํ˜„์žฌ ์‹œ๊ฐ„์„ ์ฐพ์„ ์ˆ˜ ์žˆ๋‹ค.

๋‹ค์Œ time ๋ถ€๋ถ„์—๋Š” 0.xxx * (์ „์ฒด์‹œ๊ฐ„) ์„ ๊ณฑํ–ˆ์„๋•Œ, ๊ฒฐ๊ณผ๊ฐ€ ์†Œ์ˆ˜์  0.xxxxx... ์œผ๋กœ ๊ธธ๊ฒŒ ๋‚˜์˜ฌ ์ˆ˜๊ฐ€ ์žˆ๋Š”๋ฐ, CMTime์„ ์‚ฌ์šฉํ•˜๋ฉด ์‹œ๊ฐ„์„ 100์กฐ๊ฐ์œผ๋กœ ๋‚˜๋ˆ„๊ฒ ๋‹ค๋Š” ์˜๋ฏธ๋กœ ์ฆ‰, ์†Œ์ˆ˜์  2๋ฒˆ์งธ ์ž๋ฆฌ๊นŒ์ง€๋งŒ ์‚ฌ์šฉํ•˜๊ฒ ๋‹ค๋Š” ๋ง์ด๋‹ค.

CMTime์˜ seconds์—๋Š” ์‹œ๊ฐ„, preferredTimescale์—๋Š” ๋ช‡ ์กฐ๊ฐ์œผ๋กœ ๋‚˜๋ˆŒ๊ฑด์ง€์— ๋Œ€ํ•ด์„œ ์ž‘์„ฑํ•˜๋ฉด ๋œ๋‹ค.

0.1์ดˆ์˜ ๊ฒฝ์šฐ ๊ฐ๊ฐ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ 1๊ณผ 10์„ ๋„ฃ์–ด์ฃผ๋ฉด ๋œ๋‹ค.

 

 

โœ… ๋ฒ„ํŠผ์˜ ์‹œ์Šคํ…œ ์ด๋ฏธ์ง€์‚ฌ์šฉ

๋ฒ„ํŠผ์˜ ์‹œ์Šคํ…œ์ด๋ฏธ์ง€ ์‚ฌ์šฉ

์‹œ์Šคํ…œ ์ด๋ฏธ์ง€๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ๋‹ค. play.fill๋กœ ์„ค์ •๋˜์–ด ์žˆ๋Š”๋ฐ, ์•„์ด์ฝ˜์˜ ํฌ๊ธฐ๊ฐ€ ์ž‘๊ฒŒ ๋‚˜์˜ฌ ์ˆ˜ ์žˆ๋‹ค.

์‹œ์Šคํ…œ ์ด๋ฏธ์ง€์˜ ํฌ๊ธฐ๋ฅผ ๋ฐ”๊ฟ”์ฃผ๊ธฐ ์œ„ํ•ด์„œ๋Š” Default Symnbol Configuration - Configuration ์„ Pont Size๋กœ ๋ฐ”๊พผ ํ›„ Pont-Size์˜ ํฌ๊ธฐ๋ฅผ ์กฐ์ •ํ•ด์ค˜์•ผ ํ•œ๋‹ค.

๋˜ํ•œ ์ฝ”๋“œ๋ฅผ ํ†ตํ•ด์„œ๋„ ์ด๋ฏธ์ง€๋ฅผ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ๋Š”๋ฐ,

  func updatePlayButton() {
        // TODO: ํ”Œ๋ ˆ์ด๋ฒ„ํŠผ ์—…๋ฐ์ดํŠธ UI์ž‘์—… > ์žฌ์ƒ/๋ฉˆ์ถค
        if simplePlayer.isPlaying {
            let configuration = UIImage.SymbolConfiguration(pointSize: 40) // ์•„์ด์ฝ˜ ์ด๋ฏธ์ง€
            let image = UIImage(systemName: "pause.fill", withConfiguration: configuration)
            playControlButton.setImage(image, for: .normal)
        } else {
            let configuration = UIImage.SymbolConfiguration(pointSize: 40) // ์•„์ด์ฝ˜ ์ด๋ฏธ์ง€
            let image = UIImage(systemName: "play.fill", withConfiguration: configuration)
            playControlButton.setImage(image, for: .normal)
            
        }
    }

์ด ๋ฐฉ๋ฒ•์„ ํ™œ์šฉํ•˜๋ฉด ์ฝ”๋“œ๋ฅผ ํ†ตํ•ด์„œ๋„ ์ด๋ฏธ์ง€๋ฅผ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ๋‹ค. ์ด๋•Œ๋Š” ๊ผญ, ํฐํŠธ ์‚ฌ์ด์ฆˆ๋ฅผ ๋”ฐ๋กœ ์ฃผ์–ด์•ผํ•จ์„ ์žŠ์ง€ ๋ง์ž.

 

โœ… ๋‹คํฌ๋ชจ๋“œ ๋Œ€์‘ 

 

class DefaultStyle {
    public enum Colors {
        public static let tint : UIColor = {
            if #available(iOS 13.0, *) {
                return UIColor { traitCollction in
                    if traitCollction.userInterfaceStyle == .dark {
                        return .white
                    } else {
                        return .black
                    }
                }
            } else {
                return .black
            }
        }()
    }
}

iOS 13 ์ดํ›„์—๋Š” ๋‹คํฌ๋ชจ๋“œ ๋Œ€์‘์ด ์ƒ๋‹นํžˆ ์ค‘์š”ํ•ด์กŒ๋‹ค.

    func updateTintColor() {
        playControlButton.tintColor = DefaultStyle.Colors.tint
        timeSlider.tintColor = DefaultStyle.Colors.tint
    }

๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ฝ”๋“œ๋กœ๋„ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค..!

๋‹คํฌ๋ชจ๋“œ๋Š” ์‹œ์Šคํ…œ ์ปฌ๋Ÿฌ๋ฅผ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ ์• ํ”Œ์—์„œ ๋‹คํฌ๋ชจ๋“œ ๋Œ€์‘์„ ์•Œ์•„์„œ ํ•ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์— ์ด ๊ธฐ๋Šฅ์„ ์ž˜ ํ™œ์šฉํ•œ๋‹ค๋ฉด ๋”์šฑ ์ข‹์ง€ ์•Š์„๊นŒ ์‹ถ๋‹ค.

 

 

๊ทธ๋Ÿผ ์ด๋งŒ...!

 

 

์ฐธ๊ณ 

- http://minsone.github.io/mac/ios/swift-map-filter-reduce-and-inference 

 

[Swift]Map, Filter, Reduce ๊ทธ๋ฆฌ๊ณ  ์ถ”๋ก 

์šฐ์„  Swift์˜ Map, Filter, Reduce์— ์„ค๋ช…ํ•˜๊ธฐ ์•ž์„œ Closure์—์„œ ์‚ฌ์šฉ๋  ์ถ”๋ก ์— ๋Œ€ํ•ด ๋จผ์ € ์„ค๋ช…ํ•˜๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค. ์ถ”๋ก (Inference) ์• ํ”Œ ๋ฌธ์„œ์—๋„ ๋‚˜์™€์žˆ์ง€๋งŒ Swift์—์„œ ์ถ”๋ก ์€ ์•„์ฃผ ๊ฐ•๋ ฅํ•˜๋ฉฐ, ์ฝ”๋“œ์˜ ์–‘์„ ์ค„์—ฌ

minsone.github.io

https://jinshine.github.io/2018/12/14/Swift/22.%EA%B3%A0%EC%B0%A8%ED%95%A8%EC%88%98(2)%20-%20map,%20flatMap,%20compactMap/ 

 

[Swift] ๊ณ ์ฐจํ•จ์ˆ˜(2) - map, flatMap, compactMap - jinShine

map map์€ ๋ฐฐ์—ด ๋‚ด๋ถ€์˜ ๊ฐ’์„ ํ•˜๋‚˜์”ฉ mappingํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•˜๋ฉด ์‰ฝ๊ฒŒ ๋‹ค๊ฐ€์˜ฌ๊ป๋‹ˆ๋‹ค. ๊ฐ ์š”์†Œ์— ๋Œ€ํ•œ ๊ฐ’์„ ๋ณ€๊ฒฝํ•˜๊ณ ์ž ํ• ๋•Œ ์‚ฌ์šฉํ•˜๊ณ , ๊ทธ ๊ฒฐ๊ณผ๋“ค์„ ๋ฐฐ์—ด์˜ ์ƒํƒœ๋กœ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. Declaration 1func map (_ transform:

jinshine.github.io

https://poisonf2.tistory.com/m/75

 

[Swift] Dictionary - init, grouping, by

Dictionary์˜ init์ธ grouping by์„ ์จ๋ณด์ž! ๋จผ์ € ๋‹จ์ˆœํ•œ Int ๋ฐฐ์—ด๋“ค๋กœ grouping์„ ๋ณด์—ฌ ๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค. ์ถœ๋ ฅ Dictionary์˜ init์œผ๋กœ value ๊ฐ’์œผ๋กœ grouping์˜ sequnce๊ฐ€ ๋“ค์–ด๊ฐ€๊ณ  ํ‚ค ๊ฐ’์œผ๋กœ๋Š” byํŒŒ๋ผ๋ฏธํ„ฐ์˜ ๊ฐ’์ด..

poisonf2.tistory.com

 

Comments