首页 > 解决方案 > 为什么当观察对象更新时我的数组会被清除?

问题描述

我是 SwiftUI 和 MVVM 的新手,并且一直在开发播客应用程序,但我终其一生都无法弄清楚如何解决这个问题。

我有一个播客剧集列表,在 VStack 中为每个剧集分配了一个按钮,按下时会更新 userStore 并显示一个最小化的播放器。发生这种情况时,我的列表消失了,我得到了 ActivityIndi​​cator 并且列表永远不会重新出现。我猜只要状态更新,数组就会被清除。我不想要这种行为。我究竟做错了什么?

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


标签: iosarraysswiftmvvmswiftui

解决方案


我接受了 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)
    
}

}

推荐阅读