SwiftUI 观察变化

3

概述:

  • 我有一个名为Player的类和一个名为Song的类。
  • Player包含一首歌曲
  • 一个视图显示歌曲标题

目标:

当我更改player.song.title时,视图需要更新。

问题:

当歌曲属性更改时,它不会自动更新视图。只有在分配新歌曲时才会反映更改。

我的尝试:

我做了两次尝试(下面是代码),两者都按预期工作。

问题:

  • 有更好的方法吗?(这似乎是常见问题,一个人会遇到。) 我的尝试是否合理,第二次尝试是否更好?
  • 还是我的设计存在根本性缺陷?(我希望将歌曲放在播放器内,因为它代表了当前的歌曲。)

原始代码(视图不会更新):

模型

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()
        }
    }
}

第二种尝试: 使用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个回答

3

只需简化以下模型,更新就可以正常工作。已测试过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
}

完全有道理,因为结构体是值类型,当它们的属性之一发生变化时,它将被改变,这意味着会导致歌曲被更改。我在想如果Song是一个类,我该怎么做。 - user1046037
如果Song是一个类,你想要观察它的变化,那么你应该将它设置为ObservableObject,并使用Published标记变量。这样,SwiftUI会为你创建一个发布者,并在值发生变化时通知订阅者(例如UI)。 - Victor Pro
@VictorPro,这已经在原帖中完成了,请检查。我理解为什么它没有发生,只是在寻找一个好的解决方案。 - user1046037

1

我在想如果Song是一个类,我该如何处理它

好的,如果我们限制在类方面,那么解决方案将会是简化设计,如下所示(是的,我更喜欢简单的解决方案)。已在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)
        }
    }
}

你能看一下 https://stackoverflow.com/questions/61264371/xcode-simulator-revert-to-default 吗? - user1046037

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接