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) (...) }
问题根源分析
UserDefaults的ObservableObject扩展无效
你给UserDefaults加了ObservableObject协议,但没有任何@Published属性或发送objectWillChange通知的逻辑,SwiftUI根本不知道UserDefaults里的值变了,自然不会刷新视图。AccountView没有监听计数变化
AccountView直接调用UserDefaults.standard.getWatchedMovieCount()显示数值,但这个值更新时,视图不会自动重绘——因为它没有绑定到可观察的状态变量。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
相关产品推荐
相关产品推荐

