如何解决UserDefault中收藏电影的状态同步与数据残留问题?
问题描述
我开发的moviefinder项目中,设有将电影添加至收藏页的按钮,且支持通过.onDelete删除收藏电影,但存在两个问题:
- 从收藏页删除某电影后,再次进入该电影详情页,按钮仍显示红色的「移除收藏」状态;
- 退出APP后添加新电影,已删除的旧电影会重新出现在新添加电影的上方。
相关代码
TrackedMoviesViewModel.swift
// // TrackedMoviesViewModel.swift // moviefinder import Foundation class TrackedMoviesViewModel: ObservableObject { @Published var trackedMovies: [TrackedMovieModel] = [] init() { fetchTrackedMovies() } let moviesKey: String = "trackedmovie_list" func addTrackedMovie(_ trackedMovie: TrackedMovieModel) { trackedMovies.append(trackedMovie) trackMovie() } func isMovieTracked(_ movie: TrackedMovieModel) -> Bool { return trackedMovies.contains { $0.id == movie.id } } func removeTrackedMovie(_ trackedMovie: TrackedMovieModel) { if let index = trackedMovies.firstIndex(where: { $0.id == trackedMovie.id }) { trackedMovies.remove(at: index) trackMovie() } } func deleteMovie(_trackedMovie: TrackedMovieModel){ //trackedMovies.remove(atOffsets: ) } private func trackMovie(){ if let encodedData = try? JSONEncoder().encode(trackedMovies){ UserDefaults.standard.set(encodedData, forKey: moviesKey) } } func fetchTrackedMovies() { if let data = UserDefaults.standard.data(forKey: moviesKey) { if let trackedMovies = try? JSONDecoder().decode([TrackedMovieModel].self, from: data) { self.trackedMovies = trackedMovies } } } }
MediaDetailsViewModel.swift
// // MediaDetailsViewModel.swift // moviefinder import Foundation class MediaDetailsViewModel: ObservableObject { @Published var isTracked: Bool = false var mediaId: Int? // 用于存储媒体ID的属性 //var viewModel: TrackedMoviesViewModel init(initialIsTracked: Bool, mediaId: Int,trackedMoviesViewModel: TrackedMoviesViewModel) { self.isTracked = initialIsTracked self.mediaId = mediaId // 初始化时设置媒体ID // self.viewModel = trackedMoviesViewModel //updateTrackingStatus() } func toggleTracking() { isTracked.toggle() updateTrackingStatus(isTracked) } func updateTrackingStatus(_ isTracked: Bool) { let defaults = UserDefaults.standard if let mediaId = mediaId { defaults.set(isTracked, forKey: "isTracked_\(mediaId)") } } private func loadTrackingStatus() -> Bool { let defaults = UserDefaults.standard if let mediaId = mediaId { return defaults.bool(forKey: "isTracked_\(mediaId)") } return false } }
MovieDetailsView中的按钮代码
Button(action: { let trackedMovie = TrackedMovieModel(id: details.id, title: details.title, overview: details.overview, poster: details.poster) if trackedMoviesViewModel.isMovieTracked(trackedMovie) { // 取消收藏电影 trackedMoviesViewModel.removeTrackedMovie(trackedMovie) } else { // 添加到收藏 trackedMoviesViewModel.addTrackedMovie(trackedMovie) } // 切换收藏状态 mediaDetailsViewModel.toggleTracking() }) { Text(mediaDetailsViewModel.isTracked ? "移除收藏" : "添加到收藏") .frame(height: 55) .frame(maxWidth: .infinity) .background(mediaDetailsViewModel.isTracked ? .red : .blue) .foregroundColor(.white) .cornerRadius(10) .padding(.horizontal, 10) }
解决方案
问题1:收藏页删除后详情页按钮状态错误
原因
代码维护了两份独立的收藏状态:TrackedMoviesViewModel的trackedMovies数组,以及MediaDetailsViewModel存在UserDefaults里的isTracked_xxx键值对。从收藏页删除电影时,仅更新了数组,但未同步UserDefaults中的状态,导致详情页读取到旧数据。
修复步骤
- 移除冗余状态存储:以
TrackedMoviesViewModel的trackedMovies数组作为唯一可信源,删除UserDefaults中单独存储的isTracked状态。 - 重构MediaDetailsViewModel:
// // MediaDetailsViewModel.swift // moviefinder import Foundation import Combine class MediaDetailsViewModel: ObservableObject { @Published var isTracked: Bool = false let mediaId: Int private let trackedVM: TrackedMoviesViewModel private var cancellables = Set<AnyCancellable>() init(mediaId: Int, trackedMoviesViewModel: TrackedMoviesViewModel) { self.mediaId = mediaId self.trackedVM = trackedMoviesViewModel // 从trackedVM获取初始状态 self.isTracked = trackedVM.isMovieTracked(TrackedMovieModel(id: mediaId, title: "", overview: "", poster: "")) // 监听trackedMovies变化,自动同步状态 trackedVM.$trackedMovies .sink { [weak self] movies in guard let self = self else { return } self.isTracked = movies.contains { $0.id == self.mediaId } } .store(in: &cancellables) } func toggleTracking(movie: TrackedMovieModel) { if isTracked { trackedVM.removeTrackedMovie(movie) } else { trackedVM.addTrackedMovie(movie) } } }
- 修改详情页按钮逻辑:
Button(action: { let trackedMovie = TrackedMovieModel(id: details.id, title: details.title, overview: details.overview, poster: details.poster) mediaDetailsViewModel.toggleTracking(movie: trackedMovie) }) { Text(mediaDetailsViewModel.isTracked ? "移除收藏" : "添加到收藏") .frame(height: 55) .frame(maxWidth: .infinity) .background(mediaDetailsViewModel.isTracked ? .red : .blue) .foregroundColor(.white) .cornerRadius(10) .padding(.horizontal, 10) }
- 完善收藏页删除方法:
在TrackedMoviesViewModel中补全删除逻辑,确保删除后同步到UserDefaults:
func deleteMovie(at offsets: IndexSet) { trackedMovies.remove(atOffsets: offsets) trackMovie() }
收藏页列表绑定该方法:
List { ForEach(trackedMoviesViewModel.trackedMovies) { movie in // 电影列表项 } .onDelete(perform: trackedMoviesViewModel.deleteMovie(at:)) }
问题2:退出APP后已删除电影重现
原因
当前TrackedMoviesViewModel中的deleteMovie方法为空,收藏页的.onDelete未执行实际删除操作,导致trackedMovies数组未更新,UserDefaults中仍保留旧数据,重启APP后加载旧数据。
修复步骤
直接使用上述问题1中补全的deleteMovie(at offsets: IndexSet)方法,并确保收藏页的.onDelete绑定该方法,即可实现删除后同步更新数组与UserDefaults,重启APP后不会出现已删除电影。
内容的提问来源于stack exchange,提问作者Abdelkarim Achtaou
相关产品推荐
相关产品推荐

