SwiftUI 观察变化
SwiftUI Observe Changes
概览:
- 我有一个名为 Player 的 class 和一个名为 Song 的 class。
- 播放器包含一首歌曲
- 一个视图正在显示歌曲名称
目标:
当我更改 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()
}
}
}
}
只需按如下方式简化模型,更新即可。使用 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
}
I was wondering if Song was a class how I would go about it
好的,如果我们限制为类,那么解决方案将采用简化设计,如下所示(是的,我更喜欢简单的解决方案)。使用 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)
}
}
}
概览:
- 我有一个名为 Player 的 class 和一个名为 Song 的 class。
- 播放器包含一首歌曲
- 一个视图正在显示歌曲名称
目标:
当我更改 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()
}
}
}
}
只需按如下方式简化模型,更新即可。使用 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
}
I was wondering if Song was a class how I would go about it
好的,如果我们限制为类,那么解决方案将采用简化设计,如下所示(是的,我更喜欢简单的解决方案)。使用 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)
}
}
}