Overview:
- I have a class called
and a class calledPlayer
.Song
contains aPlayer
Song
- A view is showing the song title
Aim:
When I change the
player.song.title
, the view needs to be updated.
Problem:
When the song's attributes changes, it wouldn't update the view automatically. Only when a new song is assigned will the changes be reflected.
My Attempts:
I have made 2 attempts (code below), both work as expected.
Questions:
- Is there a better way to do it ? (It seems like a common problem, one would encounter.)
- Are my attempts reasonable and is attempt 2 better?
- Or is there something fundamentally flawed with my design? (I was hoping to have the song inside the player because it represented the current song).
Original Code (view wouldn't be updated):
Model
import Foundation
import Combine
class Player : ObservableObject {
@Published var duration = 0
@Published var song : Song
init(song: Song) {
self.song = song
}
}
class Song : ObservableObject {
@Published var id : Int
@Published var title : String
@Published var artist : String
init(id: Int,
title: String,
artist: String) {
self.id = id
self.title = title
self.artist = artist
}
}
View:
import SwiftUI
struct ContentView: View {
@ObservedObject var player : Player
var body: some View {
VStack {
Text(String(player.duration))
Text(player.song.title)
Text(player.song.artist)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
let song = Song(id: 1, title: "title1", artist: "artist1")
let player = Player(song: song)
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
player.song.title = "title2"
}
return ContentView(player: player)
}
}
Attempt1 - Using
CombineLatest
Problem: It is not very scalable as the number of properties in a song increases.
class Player : ObservableObject {
@Published var duration = 0
@Published var song : Song
private var songChangeCanceller : AnyCancellable?
init(song: Song) {
self.song = song
songChangeCanceller = song.$title.combineLatest(song.$artist, song.$id).sink { _, _, _ in
self.objectWillChange.send()
}
}
}
Attemp2: Uses
objectWillChange.sink
class Player : ObservableObject {
@Published var duration = 0
@Published var song : Song
private var songChangeCanceller : AnyCancellable?
private var songAttributesChangeCanceller : AnyCancellable?
init(song: Song) {
self.song = song
songChangeCanceller = $song.sink { newSong in
self.songAttributesChangeCanceller = newSong.objectWillChange.sink { _ in
self.objectWillChange.send()
}
}
}
}