SwiftUI Наблюдать за изменениями
Обзор:
- У меня есть класс Player и класс Song.
- Плеер содержит песню
- В представлении отображается название песни
Цель:
Когда я меняю player.song.title, вид нужно обновить.
Проблема:
Когда атрибуты песни изменяются, вид автоматически не обновляется. Только когда назначена новая песня, изменения будут отражены.
Мои попытки:
Я сделал 2 попытки (код ниже), обе работают должным образом.
Вопросы:
- Есть ли способ лучше? (Кажется, это обычная проблема, с которой можно столкнуться.) Разумны ли мои попытки и лучше ли вторая?
- Или в моем дизайне есть что-то принципиально несовершенное? (Я надеялся, что песня будет внутри плеера, потому что она представляет текущую песню).
Исходный код (представление не обновляется):
Модель
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
}
}
Посмотреть
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 - Использование CombineLatest
Проблема: он не очень масштабируемый, поскольку количество свойств в песне увеличивается.
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: использует 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()
}
}
}
}
2 ответа
Решение
Мне было интересно, если бы Сонг был классом, как бы я это сделал
Хорошо, если мы ограничимся классами, тогда решение будет в упрощенном дизайне, как показано ниже (да, я предпочитаю простые решения). Протестировано с Xcode 11.4 / iOS 13.4.
// << keep your model "as is" in section "Model", instead modify views
struct ContentView: View {
@ObservedObject var player : Player
var body: some View {
VStack {
Text(String(player.duration))
SongDetailsView(song: player.song)
}
}
}
struct SongDetailsView: View {
@ObservedObject var song : Song
var body: some View {
VStack {
Text(song.title)
Text(song.artist)
}
}
}
Просто упростите модель, как показано ниже, и обновление будет работать. Протестировано с Xcode 11.4 / iOS 13.4
class Player : ObservableObject {
@Published var duration = 0
@Published var song: Song // published as soon as song.title changed
init(song: Song) {
self.song = song
}
}
struct Song : Identifiable { // value type
var id : Int
var title : String
var artist : String
}