You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何解决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中的状态,导致详情页读取到旧数据。

修复步骤

  1. 移除冗余状态存储:以TrackedMoviesViewModel的trackedMovies数组作为唯一可信源,删除UserDefaults中单独存储的isTracked状态。
  2. 重构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)
        }
    }
}
  1. 修改详情页按钮逻辑:
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)
}
  1. 完善收藏页删除方法:
    在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 04:24:55