ios - 为什么当观察对象更新时我的数组会被清除?
问题描述
我是 SwiftUI 和 MVVM 的新手,并且一直在开发播客应用程序,但我终其一生都无法弄清楚如何解决这个问题。
我有一个播客剧集列表,在 VStack 中为每个剧集分配了一个按钮,按下时会更新 userStore 并显示一个最小化的播放器。发生这种情况时,我的列表消失了,我得到了 ActivityIndicator 并且列表永远不会重新出现。我猜只要状态更新,数组就会被清除。我不想要这种行为。我究竟做错了什么?
struct PodcastDetailView: View {
@EnvironmentObject var userStore: UserStore
@ObservedObject var minimizableViewHandler: MinimizableViewHandler
@ObservedObject var player: Player = Container.player
@ObservedObject var podcastViewModel: PodcastViewModel
init(podcast: Podcast, player: Player = Container.player, minimizableViewHandler: MinimizableViewHandler) {
self.podcastViewModel = PodcastViewModel(podcast: podcast)
self.player = player
self.minimizableViewHandler = minimizableViewHandler
}
var body: some View {
ZStack{
Color(hex: "1B1D26")
.edgesIgnoringSafeArea([.all])
VStack(alignment: .leading, spacing: 10) {
PodcastDetailHeader(podcast: podcastViewModel.podcast)
if podcastViewModel.episodes.isEmpty {
ActivityIndicator()
.frame(width: 120, height: 120)
.foregroundColor(Color(hex: "813F97"))
.opacity(0.8)
.animation(.easeOut)
} else {
ScrollView {
VStack(alignment: .center, spacing: 10)
{
ForEach(podcastViewModel.episodes, id: \.self) { episode in
Button(action: {
if (self.player.state == .empty) {
self.userStore.selectedEpisode = episode
var newEpisodeData = self.podcastViewModel.episodes
if let selectedEpisodeIndex = newEpisodeData.firstIndex(where: {$0.id == episode.id}) {
newEpisodeData.remove(at: selectedEpisodeIndex)
newEpisodeData.insert(episode, at: newEpisodeData.startIndex)
self.player.setup(for: newEpisodeData)
self.minimizableViewHandler.present()
} else {
// item could not be found
}
} else {
print("new episode is " + episode.title)
self.userStore.selectedEpisode = episode
var newEpisodeData = self.podcastViewModel.episodes
if let selectedEpisodeIndex = newEpisodeData.firstIndex(where: {$0.id == episode.id}) {
newEpisodeData.remove(at: selectedEpisodeIndex)
newEpisodeData.insert(episode, at: newEpisodeData.startIndex)
self.player.setup(for: newEpisodeData)
self.player.play()
}
}
}) {
PodcastRowView(episode: episode)
.fixedSize(horizontal: false, vertical: true)
.padding(.top, 8)
}.buttonStyle(PlainButtonStyle())
.padding(.leading, 20)
.padding(.trailing, 10)
}
}
}
}
Spacer()
}
}
.navigationBarBackButtonHidden(true)
.navigationBarTitle(Text(self.podcastViewModel.podcast.title), displayMode: .inline)
.onAppear {
print("appearing")
self.podcastViewModel.loadEpisodes()
}
}
import Combine
import SwiftUI
class PodcastViewModel: ObservableObject {
private let apiService: APIService
private var episodesCancelable: Cancellable?
@Published var podcast: Podcast
@Published var episodes: [Episode] = []
init(podcast: Podcast, apiService: APIService = APIService()) {
self.podcast = podcast
self.apiService = apiService
}
deinit {
episodesCancelable?.cancel()
}
func loadEpisodes() {
episodesCancelable = apiService.episodes(for: podcast)
.receive(on: RunLoop.main)
.replaceError(with: [])
.assign(to: \.episodes, on: self)
}
}
解决方案
我接受了 FarouK 的建议并使用了@StateObject。我不得不改变几件事,但让它工作。
struct PodcastDetailView: View {
@EnvironmentObject var userStore: UserStore
@ObservedObject var minimizableViewHandler: MinimizableViewHandler
@ObservedObject var player: Player = Container.player
@StateObject var podcastViewModel: PodcastViewModel
var podcast: Podcast
var body: some View {
ZStack{
Color(hex: "1B1D26")
.edgesIgnoringSafeArea([.all])
VStack(alignment: .leading, spacing: 10) {
PodcastDetailHeader(podcast: podcastViewModel.podcast)
if podcastViewModel.episodes.isEmpty {
ActivityIndicator()
.frame(width: 120, height: 120)
.foregroundColor(Color(hex: "813F97"))
.opacity(0.8)
.animation(.easeOut)
} else {
ScrollView {
VStack(alignment: .center, spacing: 10)
{
ForEach(podcastViewModel.episodes, id: \.self) { episode in
Button(action: {
if (self.player.state == .empty) {
self.userStore.selectedEpisode = episode
var newEpisodeData = self.podcastViewModel.episodes
if let selectedEpisodeIndex = newEpisodeData.firstIndex(where: {$0.id == episode.id}) {
newEpisodeData.remove(at: selectedEpisodeIndex)
newEpisodeData.insert(episode, at: newEpisodeData.startIndex)
self.player.setup(for: newEpisodeData)
self.minimizableViewHandler.present()
} else {
// item could not be found
}
} else {
print("new episode is " + episode.title)
self.userStore.selectedEpisode = episode
var newEpisodeData = self.podcastViewModel.episodes
if let selectedEpisodeIndex = newEpisodeData.firstIndex(where: {$0.id == episode.id}) {
newEpisodeData.remove(at: selectedEpisodeIndex)
newEpisodeData.insert(episode, at: newEpisodeData.startIndex)
self.player.setup(for: newEpisodeData)
self.player.play()
}
}
}) {
PodcastRowView(episode: episode)
.fixedSize(horizontal: false, vertical: true)
.padding(.top, 8)
}.buttonStyle(PlainButtonStyle())
.padding(.leading, 20)
.padding(.trailing, 10)
}
}
}
}
Spacer()
}
}
.navigationBarBackButtonHidden(true)
.navigationBarTitle(Text(self.podcastViewModel.podcast.title), displayMode: .inline)
.onAppear {
self.podcastViewModel.podcast = self.podcast
self.podcastViewModel.loadEpisodes()
}
}
import Combine
import SwiftUI
class PodcastViewModel: ObservableObject {
private let apiService: APIService
private var episodesCancelable: Cancellable?
@Published var podcast: Podcast = Podcast(id: "", title: "", image: URL(string: ""), thumbnail: URL(string: ""), totalEpisodes: 0, explicitContent: true, description: "", language: "", country: "", rss: URL(string: ""), latestPubDateMs: Date(), earliestPubDateMs: Date(), publisher: "")
@Published var episodes: [Episode] = []
init(apiService: APIService = APIService()) {
self.apiService = apiService
}
deinit {
episodesCancelable?.cancel()
}
func loadEpisodes() {
episodesCancelable = apiService.episodes(for: podcast)
.receive(on: RunLoop.main)
.replaceError(with: [])
.assign(to: \.episodes, on: self)
}
}
推荐阅读
- mysql - 在 Windows 中安装 Magento
- r - R Shiny-如何子集数据框,保存子集数据框,打印摘要信息?
- sql - 按一列过滤,然后计算 SQL 中另一列中的唯一值
- javascript - 如何在一个脚本中将 Gmail 与文件附件合并。我已经完成了我的邮件合并脚本,但我不知道如何添加文件附件
- firebase - Firebase 翻译文本扩展以翻译多个字段
- python - 将字符串(带有列表列表)转换为列表
- javascript - 如何使用 puppeteer 获取网页上的所有可点击元素?
- python - 我无法导入任何 python 模块,也无法使用 pip 安装任何模块
- c++ - 线段和平面之间最近的两个 3D 点由三个点定义和限制(3D 三角形多边形)(已解决)
- c++-winrt - 在 C++/WinRT 中遍历可视化树时遇到问题