SwiftUI Наблюдать за изменениями - PullRequest
2 голосов
/ 16 апреля 2020

Обзор:

  • У меня есть класс с именем Player и класс с именем Song.
  • Player содержит композицию
  • В представлении отображается название песни

Цель:

Когда я изменяю player.song.title, необходимо обновить представление.

Проблема:

Когда песня атрибуты изменяются, это не будет обновлять представление автоматически. Изменения будут отражены только при назначении новой песни.

Мои попытки:

Я сделал 2 попытки (код ниже), обе работают как положено.

Вопросы :

  • Есть ли лучший способ сделать это? (Кажется, это общая проблема, с которой можно столкнуться.) Являются ли мои попытки разумными и лучше ли попытка 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)
    }
}

Попытка1 - Использование 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()
        }
    }
}

Попытка2: использует 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 ]

2 голосов
/ 16 апреля 2020

Просто упростите модель, как показано ниже, и обновление будет работать. Протестировано с 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
}
1 голос
/ 16 апреля 2020

Мне было интересно, если бы Song был классом, как бы я go об этом

Хорошо, если мы ограничимся классами, тогда решение будет в упрощенном дизайне, как показано ниже (да, я предпочитаю простые решения). Протестировано с 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)
        }
    }
}
...