UIMenuElement状态存UserDefaults的代码优化及APP退出前清数据方案
问题分析与优化方案
一、现有存储方式的问题
你的代码直接将UIMenuElement.State枚举实例存入UserDefaults,这种方式是错误的:
- UserDefaults无法直接存储自定义枚举类型(即使是系统枚举,也需要通过其
rawValue来存储,因为UIMenuElement.State遵循RawRepresentable协议,底层是Int类型)。 - 读取时的强制转换
as! UIMenuElement.State会在首次启动(无存储数据)或数据类型不匹配时直接崩溃。
二、代码优化点
- 统一UserDefaults键名:用常量替代硬编码的键名,避免拼写错误。
- 正确存储枚举值:存储
UIMenuElement.State的rawValue(Int类型),读取时通过rawValue初始化枚举并设置默认值。 - 修复菜单状态初始化逻辑:初始时根据存储的状态,正确设置两个
UIAction的选中状态,而非统一设为同一个值;移除手动设置action.state的冗余代码(因为菜单开启了.singleSelection选项,会自动管理选中状态)。 - 移除冗余代码:删除重复创建
subtitlesMenu的代码。 - 安全解包与默认值:读取UserDefaults时避免强制解包,使用可选绑定或默认值。
三、优化后的代码
// 定义UserDefaults键名常量 private extension UserDefaults { static let captionStateKey = "captionState" } private func addMenuControlForSubtitles() { // 读取存储的状态,默认值为.on(可根据需求调整) let savedRawValue = UserDefaults.standard.integer(forKey: UserDefaults.captionStateKey) let savedState = UIMenuElement.State(rawValue: savedRawValue) ?? .on let onAction = UIAction(title: Constants.Playback.subtitlesOnTitle, state: savedState == .on ? .on : .off) { [weak self] _ in self?.subtitlesLabel.isHidden = false // 存储选中状态的rawValue UserDefaults.standard.set(UIMenuElement.State.on.rawValue, forKey: UserDefaults.captionStateKey) } let offAction = UIAction(title: Constants.Playback.subtitlesOffTitle, state: savedState == .off ? .on : .off) { [weak self] _ in self?.subtitlesLabel.isHidden = true // 存储选中状态的rawValue UserDefaults.standard.set(UIMenuElement.State.off.rawValue, forKey: UserDefaults.captionStateKey) } // 创建子菜单,启用单选 let submenu = UIMenu(title: Constants.Playback.subtitlesMenuTitle, options: [.displayInline, .singleSelection], children: [onAction, offAction]) let captions = UIImage(systemName: "captions.bubble") subtitlesMenu = UIMenu(title: Constants.Playback.productCardMenuTitle, image: captions, children: [submenu]) // 初始化字幕显示状态(根据存储的状态) subtitlesLabel.isHidden = savedState == .off #if os(tvOS) if let subtitlesMenu = subtitlesMenu { playerViewController.transportBarCustomMenuItems.append(subtitlesMenu) } #endif }
四、应用退出前清除数据的实现
在AppDelegate中实现applicationWillTerminate方法,清除对应的UserDefaults数据:
import UIKit @main class AppDelegate: UIResponder, UIApplicationDelegate { func applicationWillTerminate(_ application: UIApplication) { // 清除字幕状态存储 UserDefaults.standard.removeObject(forKey: UserDefaults.captionStateKey) } }
如果你的项目使用SceneDelegate,则在sceneDidEnterBackground中处理(iOS 13+多场景应用中,applicationWillTerminate可能不会触发,退到后台时清除更稳妥):
class SceneDelegate: UIResponder, UIWindowSceneDelegate { func sceneDidEnterBackground(_ scene: UIScene) { UserDefaults.standard.removeObject(forKey: UserDefaults.captionStateKey) } }
内容的提问来源于stack exchange,提问作者user23492857
相关产品推荐
相关产品推荐

