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
}
Другие вопросы по тегам