์ผ | ์ | ํ | ์ | ๋ชฉ | ๊ธ | ํ |
---|---|---|---|---|---|---|
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 |
- ios
- raywenderlich
- SwiftUI
- designpattern
- BFS
- node.js
- rxcocoa
- SnapKit
- Kuring
- combine
- Flutter
- UIKit
- ํ๋ก๊ทธ๋๋จธ์ค
- reactorkit
- realm
- RxSwift
- Lv2
- BOJ
- visionOS
- CollectionView
- Xcode
- TCA
- tableView
- MVVM
- ํจ์คํธ์บ ํผ์ค
- ๋ฐฑ์ค
- XCTest
- Swfit
- swift
- arkit
- Today
- Total
lgvv98
ch12 ์ ํ๋ฎค์งst ์์ ์ฑ ์ฝ๋๋ฆฌ๋ทฐ ๋ณธ๋ฌธ
ch12 ์ ํ๋ฎค์งst ์์ ์ฑ ์ฝ๋๋ฆฌ๋ทฐ
๐ฅ ์บ๋ฟ๋งจ 2021. 6. 24. 15:47โ ์ด๋ฒ ์๊ฐ์๋ ์ฃผ๋ ๋ด์ฉ์ 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
[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
'โ ๏ธ deprecated โ ๏ธ > ํจ์บ (์ฌ์ธ์)' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
๐ ch13 swift Equatable?! (0) | 2021.06.25 |
---|---|
๐ฆง ch13 Static Cell (TableView) (0) | 2021.06.25 |
๐ ch11 ํ์๊ธ ๋ญํน์ฑ ์ฝ๋๋ฆฌ๋ทฐ(Animation)๐ (0) | 2021.06.22 |
ch11 ํ์๊ธ ๋ญํน์ฑ ์ฝ๋๋ฆฌ๋ทฐ(CollectionView) (0) | 2021.06.22 |
๐ ch11 Animation - ๊ฐ๋ ํธ๐ (0) | 2021.06.22 |