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

SwiftUI中切换按钮联动已观看电影总数不生效问题求助

标记电影为已观看后,AccountView的已观看总数不更新的问题

我正在开发一款App,实现标记电影为已观看的功能。MovieDetailView里有个可在“watch”和“watched”间切换的按钮,切换时要增减已观看总数;AccountView展示这个总数,但现在两者没关联生效。

UserDefaults相关代码

extension UserDefaults: ObservableObject {

enum UserDefaultsKeys: String, CaseIterable, Identifiable {
    var id: String { rawValue }
    
    case watchedState
    case watchedCount
}

//MARK: - Watched

func setWatchedState(value: Bool, forMovieId movieId: Int) {
    set(value, forKey: "\(UserDefaultsKeys.watchedState.rawValue)_\(movieId)")
}

func getWatchedState(forMovieId movieId: Int) -> Bool {
    return bool(forKey: "\(UserDefaultsKeys.watchedState.rawValue)_\(movieId)")
}

//MARK: - Movie Count

func setWatchedMovieCount(value: Bool, forMovieId movieId: Int) {
    let watchedStateKey = "\(UserDefaultsKeys.watchedState.rawValue)_\(movieId)"
    let currentWatchedState = bool(forKey: watchedStateKey)
    
    // Check if the watched state is changing
    if currentWatchedState != value {
        set(value, forKey: watchedStateKey)
        
        // Update the movie count based on the new watched state
        let watchedCountKey = UserDefaultsKeys.watchedCount.rawValue
        var count = integer(forKey: watchedCountKey)
        
        if value {
            count += 1
        } else {
            count -= 1
        }
        
        set(count, forKey: watchedCountKey)
    }
}

func getWatchedMovieCount() -> Int {
    return integer(forKey: UserDefaultsKeys.watchedCount.rawValue)
}
}

MovieDetailView相关代码

struct MovieDetailView: View {

struct CustomColor {
    static let locked = Color("locked")
}

let movie: Movie
@State private var selectedTrailer: MovieVideo?
let imageLoader = ImageLoader()

let username: String
@State var isLoggedIn: Bool

@State private var selectedScore: Int = 1
@State private var watchedOn = false
@State private var savedOn = false
@State private var isMovieSaved: Bool = false

var body: some View {
    (...)
                
                Button(action: {
                    // Watch button
                    if UserDefaults.standard.getLoggedIn() {
                        if watchedOn {
                            UserDefaults.standard.setWatchedMovieCount(value: false, forMovieId: movie.id)
                        } else {
                            UserDefaults.standard.setWatchedMovieCount(value: true, forMovieId: movie.id)
                        }
                        watchedOn.toggle()
                    }
                }) {
                    VStack {
                        if !UserDefaults.standard.getLoggedIn() {
                            Image("Watch (locked)")
                            Text("watch").foregroundColor(CustomColor.locked)
                        } else if watchedOn {
                            Image("Watched")
                            Text("watched")
                        } else {
                            Image("Watch")
                            Text("watch")
                        }
                    }
                }
                .onAppear {
                    watchedOn = UserDefaults.standard.getWatchedState(forMovieId: movie.id)
                }
                .disabled(!UserDefaults.standard.getLoggedIn())
                
                (...)
}

AccountView相关代码

struct AccountView: View {
let username: String
@State private var isLoggedIn = true

@State private var savedMovies: [Movie] = []
@State private var watchedMovies: [Movie] = []

@State private var isMovieSaved: Bool = false

let movie: Movie
var movieListState = MovieListState()

var body: some View {
            
            (...)
            Section {
                HStack {
                    Spacer()
                    VStack {
                        Text("\(UserDefaults.standard.getWatchedMovieCount())")
                            .font(.system(size: 42, weight: .semibold))
                        Text("Movies Seen")
                            .font(.system(size: 14))
                        Text("0 min")
                            .font(.system(size: 14))
                    }
                    Spacer()
                }
            }
            .padding(.vertical)
            
            (...)
}

问题根源分析

  1. UserDefaults的ObservableObject扩展无效
    你给UserDefaults加了ObservableObject协议,但没有任何@Published属性或发送objectWillChange通知的逻辑,SwiftUI根本不知道UserDefaults里的值变了,自然不会刷新视图。

  2. AccountView没有监听计数变化
    AccountView直接调用UserDefaults.standard.getWatchedMovieCount()显示数值,但这个值更新时,视图不会自动重绘——因为它没有绑定到可观察的状态变量。

  3. MovieDetailView的状态同步有隐患
    你先调用setWatchedMovieCount再手动togglewatchedOn,如果setWatchedMovieCount内部因为状态未变化(比如重复点击同一状态)而不执行更新,watchedOn就会和UserDefaults里的实际状态不一致。


修复方案

1. 用独立的ObservableObject管理用户数据

放弃直接扩展UserDefaults,创建一个专门的UserData类来管理状态,自带可观察特性:

class UserData: ObservableObject {
    static let shared = UserData()
    
    enum Keys: String {
        case watchedState
        case watchedCount
    }
    
    // 用@Published让视图监听计数变化
    @Published var watchedCount: Int {
        didSet {
            UserDefaults.standard.set(watchedCount, forKey: Keys.watchedCount.rawValue)
        }
    }
    
    private init() {
        // 初始化时从UserDefaults读取已有数据
        watchedCount = UserDefaults.standard.integer(forKey: Keys.watchedCount.rawValue)
    }
    
    // 合并状态设置与计数更新逻辑
    func toggleWatchedState(forMovieId movieId: Int) {
        let key = "\(Keys.watchedState.rawValue)_\(movieId)"
        let currentState = UserDefaults.standard.bool(forKey: key)
        let newState = !currentState
        
        UserDefaults.standard.set(newState, forKey: key)
        watchedCount += newState ? 1 : -1
    }
    
    func getWatchedState(forMovieId movieId: Int) -> Bool {
        let key = "\(Keys.watchedState.rawValue)_\(movieId)"
        return UserDefaults.standard.bool(forKey: key)
    }
}

2. 修改MovieDetailView使用UserData

替换原来的UserDefaults调用,简化按钮逻辑:

struct MovieDetailView: View {
    // ... 其他代码不变
    @State private var watchedOn = false
    @ObservedObject private var userData = UserData.shared
    
    var body: some View {
        // ... 其他代码
        Button(action: {
            if UserDefaults.standard.getLoggedIn() {
                userData.toggleWatchedState(forMovieId: movie.id)
                watchedOn = !watchedOn
            }
        }) {
            // ... 按钮UI不变
        }
        .onAppear {
            watchedOn = userData.getWatchedState(forMovieId: movie.id)
        }
        // ... 其他代码
    }
}

3. 修改AccountView监听计数变化

通过@ObservedObject绑定UserData,实现自动刷新:

struct AccountView: View {
    // ... 其他代码不变
    @ObservedObject private var userData = UserData.shared
    
    var body: some View {
        // ... 其他代码
        Section {
            HStack {
                Spacer()
                VStack {
                    Text("\(userData.watchedCount)")
                        .font(.system(size: 42, weight: .semibold))
                    Text("Movies Seen")
                        .font(.system(size: 14))
                    Text("0 min")
                        .font(.system(size: 14))
                }
                Spacer()
            }
        }
        .padding(.vertical)
        // ... 其他代码
    }
}

内容的提问来源于stack exchange,提问作者Joost van Grieken

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 00:33:16