ํ๋ก๊ทธ๋๋จธ์ค ๊ณผ์ ๊ด์ FLO ๋ฎค์ง ํ๋ ์ด์ด iOS ์ฑ ์ฐ์ต ๊ณผ์ ๊ฐ ์์ด์, ์ด๋ฅผ ๊ฐ๋ฐํ๋ฉด์ ์ ์ฉํ ๊ฒ์ด๋ ๋ฐ์ํ๋ ์ด์๋ค์ ๋ํด์ ์ ๋ฆฌํด๋ณด๋๋ก ํ ๊ฒ์! ํด๋น ์ฑ์ ๊ฐ๋ฐํ๊ธฐ๋ก ํ ์ด์ ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
1. ์ ํด์ง ์ฑ์ ๊ฐ๋ฐ ํ๊ฒฝ, ํ๋ฉด ๊ตฌ์ฑ ์์, ๊ธฐ๋ฅ ์๊ตฌ์ฌํญ์ ๋ง์ถฐ ๊ฐ๋ฐํ๊ธฐ ์ํด์
2. ๋น๊ต์ ์์ ๊ท๋ชจ์ ์ฑ์์ MVVM ํจํด์ ์ ์ฉํด๋ณด๊ธฐ ์ํด์
3. ๋ถ์คํธ์ฝ์ค 1๊ฐ์์ ํ์ตํ ์์ ํ๋ ์ด์ด์ ์ฐ์ฅ์ ์ผ๋ก ํ์ตํ๊ธฐ ์ํด์
๐ต Demo ์์ (iPhone 11, iOS14.5)
โ๏ธ FLO ์ฑ์ ์์ ์ฌ์ ํ๋ฉด์์ MVVM ํจํด ์ ์ฉํ๊ธฐ
๐ก MVVM ํจํด์ ๊ธฐ๋ณธ ๋ฃฐ
1. View (View Controller)๋ View Model ์ ๊ฐ์ง๊ณ , View Model ์ Model ์ ๊ฐ์ง๋ค.
2. View Model ์ ์ ์ถ๋ ฅ์ ์ฒ๋ฆฌํ๊ณ UI ๊ฐ ์๊ตฌํ๋ ๋ก์ง์ ์ฒ๋ฆฌํ๋ ์ญํ ๋ง ๊ฐ์ง๋ค.
3. View Model ์ UI๋ฅผ ์์ ํ ์ ์์ผ๋ฉฐ, UIKit ๋ฑ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋ ๋ฆฝ์ ์ด๋ค.
* View ์ View Model ์ฌ์ด์ ๋ฐ์ดํฐ ๋ณ๊ฒฝ์ ์๋ ค์ค ์ ์๋ ๋ฐฉ๋ฒ์ด Data binding ์ด๋ค.
* Data binding ์ MVVM ํจํด ๋ฟ ์๋๋ผ, ๋ค๋ฅธ ํจํด์์๋ ์ ์ฉ๋ ์ ์๋ค.
- MVVM ํจํด์ ๊ธฐ์กด MVC ํจํด์์ ๊ฑฐ๋ํด์ง ViewController ์ ์ญํ ์ ๋์ด์ค ์ ์๋ ํจํด์ด๋ค.
- ์์ ์ฌ์ ํ๋ฉด์ธ Player View ๋ Player View Model ์ ๊ฐ๊ณ , Player View Model ์ Music model ์ ๊ฐ์ง๋ค.
- Player View ์์ ์ด๊ธฐ UI ์ Player ๋ฅผ ์ค์ ํ๊ธฐ ์ํด Player View Model ์ ๋ฐ์ดํฐ ๋ฐ์ธ๋ฉ์ ์ ์ฉํ๋ค.
- ์ด ๋, listener ์ bind ํจ์๋ฅผ ๊ฐ๋ Observable ํด๋์ค๋ฅผ ๊ตฌํํด ๋ฐ์ดํฐ ๋ฐ์ธ๋ฉ์ ์ ์ฉํ๋ค.
- Player View ์์ ์์ ์ฌ์ ์๊ฐ์ด ๋ณ๊ฒฝ๋ ๋๋ง๋ค UI ๋ฅผ ์ ๋ฐ์ดํธํ๊ธฐ ์ํด Music Player ์ ๋ฐ์ดํฐ ๋ฐ์ธ๋ฉ์ ์ ์ฉํ๋ค.
- ์ด ๋, AVPlayer.addPeriodicTimeObserver(forInterval: queue: using:) ๋ฉ์๋๋ฅผ ์ด์ฉํด ๋ฐ์ดํฐ ๋ฐ์ธ๋ฉ์ ์ ์ฉํ๋ค.
๐ก Observable ํด๋์ค๋ฅผ ์ด์ฉํ ๋ฐ์ดํฐ ๋ฐ์ธ๋ฉ(Data Binding)
- ๋ฐ์ดํฐ ๋ฐ์ธ๋ฉ์ ์ฑ์ UI์ UI๊ฐ ํ์ํ๋ ๋ฐ์ดํฐ ์ฌ์ด๋ฅผ ์ฐ๊ฒฐํด ๋ฐ์ดํฐ์ ๋ณ๊ฒฝ์ ์๋ ค์ค ์ ์๋ ๋ฐฉ๋ฒ์ด๋ค.
- ๋ฐ์ดํฐ ๋ฐ์ธ๋ฉ์ ์ ์ฉํ๊ธฐ ์ํด listener ์ bind ํจ์๋ฅผ ๊ฐ๋ Observable ์ด๋ผ๋ ํด๋์ค๋ฅผ ๊ตฌํํ๋ค.
- ๋ฐ์ดํฐ ๋ฐ์ธ๋ฉ์ ์ ์ฉํ๊ณ ์ถ์ ํ๋กํผํฐ๋ฅผ Observable ์ธ์คํด์ค๋ก ๋ง๋ค๊ณ , bind ํจ์๋ฅผ ํตํด listener ๋ฅผ ์ ๋ฌํ๋ฉด ๋๋ค.
- ์ดํ Observable ์ธ์คํด์ค์ value ํ๋กํผํฐ๊ฐ ๋ณ๊ฒฝ๋ ๋ ๋ง๋ค listener ๊ฐ ํธ์ถ๋๋ค.
- ์ฌ๊ธฐ์ listener ๋ value ๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง๋ค ํธ์ถ๋๋ ํด๋ก์ ธ์ด๋ค.
class Observable<T> {
typealias Listener = (T) -> Void
var listener: Listener?
func bind(_ listener: Listener?) {
self.listener = listener
listener?(value)
}
var value: T {
didSet {
listener?(value)
}
}
init(_ value: T) {
self.value = value
}
}
โ๏ธ ์์ ์ฌ์ ํ๋ฉด์ View - View Model ๊ฐ Data binding ์ ์ฉํ๊ธฐ
- ์์ ์ฌ์ ํ๋ฉด์ ๋ทฐ์ธ Player View Controller ์ Player View Model ์ music ํ๋กํผํฐ์ ๋ฐ์ดํฐ ๋ฐ์ธ๋ฉ์ ์ ์ฉํ๋ค.
- viewModel ์ music ํ๋กํผํฐ๋ฅผ Observable ํด๋์ค์ ์ธ์คํด์ค๋ก ์์ฑํ๋ค.
- viewController ์์ bind ํจ์๋ฅผ ํตํด listener ๋ฅผ ์ ๋ฌํ๋ค. -- ์ด๊ธฐ UI ์ค์ , AVPlayer ์ค์ ์์ ์ ํ๋ ํด๋ก์ ธ
- viewModel.fetchMusic() ํจ์๋ฅผ ํตํด ๋คํธ์ํน ์์ ์๋ฃ ํ model.value ๋ฅผ ๋ณ๊ฒฝํ๋ค. -- APIManager ํด๋์ค๋ฅผ ์ด์ฉํจ.
- model.value ๊ฐ์ด ๋ณ๊ฒฝ๋๊ฒ ๋๋ฉด ์์ listener ํจ์๊ฐ ํธ์ถ๋๊ณ , ์ง์ ํ ์์ ๋ค์ด ์ํ๋๊ฒ ๋๋ค.
1) View Model ์์ Observable ์ธ์คํด์ค ์์ฑํ๊ณ , value ๊ฐ ๋ณ๊ฒฝ๋๋ ๊ณผ์
class PlayerViewModel {
// MARK: - Singleton
static let shared = PlayerViewModel()
// MARK: - Properties
var apiManager: APIManger
var music: Observable<Music>
var imageData: Data
// Other properties and methods ...
// MARK: - Methods
func fetchMusic() {
apiManager.getMusic { (music) in
print("fetch music ::", music.title)
self.apiManager.loadImageData(url: music.image) { (image) in
self.imageData = image
self.music.value = music
self.getLyrics()
}
}
}
}
2) View Controller ์์ bind ๋ฅผ ์ํํ๋ฉฐ listener ์ ๋ฌํ๋ ๊ณผ์
class PlayerViewController: UIViewController {
// MARK: - Properties
var player = MusicPlayer.shared
var viewModel = PlayerViewModel.shared
var timeObserver: Any?
// Other properties and methods ...
// MARK: - View Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
addGestureLyricsLabel()
bindViewModel()
}
// MARK: - Custom method
func bindViewModel() {
viewModel.music.bind({ (music) in
DispatchQueue.main.async {
self.initializeUI()
self.initializePlayer()
}
})
viewModel.fetchMusic()
}
}
โ๏ธ ์์ ์ฌ์ ํ๋ฉด์ View Controller ์ Music Player ๊ฐ ๋ฐ์ดํฐ ๋ฐ์ธ๋ฉ ์ ์ฉํ๊ธฐ
- ์์ ์ด ์ฌ์๋จ์ ๋ฐ๋ผ ํ์ฌ ์ฌ์ ์๊ฐ, slider ์ ๊ฐ ๋ณ๊ฒฝ, ๊ฐ์ฌ ๋ณ๊ฒฝ ๋ฑ์ UI ์ ๋ฐ์ดํธ๊ฐ ํ์ํ๋ค.
- Player View ์์ ์์ ์ฌ์ ์๊ฐ์ด ๋ณ๊ฒฝ๋ ๋๋ง๋ค UI ๋ฅผ ์ ๋ฐ์ดํธํ๊ธฐ ์ํด Music Player ์ ๋ฐ์ดํฐ ๋ฐ์ธ๋ฉ์ ์ ์ฉํ๋ค.
- ์ด ๋, AVPlayer.addPeriodicTimeObserver(forInterval: queue: using:) ๋ฉ์๋๋ฅผ ์ด์ฉํด ๋ฐ์ดํฐ ๋ฐ์ธ๋ฉ์ ์ ์ฉํ๋ค.
1) Music Player ํด๋์ค์์ Observer ๋ฅผ ๋ฑ๋กํ๊ณ ํด์ ํ ์ ์๋ ํจ์๋ฅผ ์ถ๊ฐํ๋ค.
class MusicPlayer {
// MARK: - Singleton
static let shared = MusicPlayer()
// MARK: - Properties
var player = AVPlayer()
var isPlaying: Bool = false
// Other properties and methods ...
func addPeriodicTimeObserver(forInterval: CMTime, queue: DispatchQueue?, using: @escaping(CMTime) -> Void) -> Any {
player.addPeriodicTimeObserver(forInterval: forInterval, queue: queue, using: using)
}
func removeTimeObserver(token: Any) {
player.removeTimeObserver(token)
}
}
2) Player View Controller ์์ ์์ ํจ์๋ฅผ ํธ์ถํด, ์๊ฐ์ด ๋ณํ ๋๋ง๋ค ์ํํ ํด๋ก์ ธ๋ฅผ ์ ๋ฌํ๋ค.
-- ์๊ฐ์ด 1์ด์ฉ ๋ณํ ๋๋ง๋ค ํ์ฌ ์๊ฐ, slider ๊ฐ ๋ณ๊ฒฝ, ๊ฐ์ฌ ๋ณ๊ฒฝ ๋ฑ UI ์
๋ฐ์ดํธ๊ฐ ์ํ๋๋ค.
class PlayerViewController: UIViewController {
// MARK: - Properties
var player = MusicPlayer.shared
var viewModel = PlayerViewModel.shared
var timeObserver: Any?
// Other properties and methods ...
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
playPauseButton.isSelected = player.isPlaying
addObserverToPlayer()
}
func addObserverToPlayer() {
timeObserver = player.addPeriodicTimeObserver(forInterval: CMTime(seconds: 1, preferredTimescale: 1), queue: DispatchQueue.main) { time in
self.updateTime(time: time)
}
}
}
์ฐธ๊ณ ๋งํฌ / ํ์ต ์๋ฃ
- MVVM ํจํด๊ณผ Observable ํด๋์ค ๊ตฌํ์ ๋ํ Medium ๊ธ / --> ์ด๋ฅผ ๋ฒ์ญํ ํฌ์คํ
- FLO ์ฑ ๊ณผ์ ๋ฅผ ์งํํ ๋ค๋ฅธ ๋ถ์ ๊ฐ๋ฐ์ผ์ง
- ๋ถ์คํธ์ฝ์ค 1์ฅ ์์ ํ๋ ์ด์ด ๋ง๋ค๊ธฐ
- ํจ์คํธ์บ ํผ์ค ์ ํ๋ฎค์ง st ์ฑ ๋ง๋ค๊ธฐ
๋ค์ ๊ฐ๋ฐ ์ผ์ง
'๐ iOS > UIKit' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[iOS] ๋ค์ด๋ฒ์ง๋ ์ฑ ์ฐ๋ - Place ID ๋ก ์ฑ ์ด๊ธฐ (2) | 2021.07.16 |
---|---|
[iOS] FLO ์ฑ ๊ฐ๋ฐ ์ผ์ง #2. TableView ๋ก ๊ฐ์ฌ ํ๋ฉด ๊ฐ๋ฐํ๊ธฐ (0) | 2021.06.30 |
[iOS] MVC ํจํด (Model - View - Controller) (0) | 2021.06.18 |
[iOS] ๋์์ธ ํจํด & Singleton ํจํด ์ ์ฉ๋ ์ฌ๋ก (0) | 2021.06.16 |
[iOS] View ์ฒด๊ณ - Window&View, Frame vs Bounds, ๋๋ฒ๊น ํด (0) | 2021.06.16 |