SwiftUI:在ViewModel中结合JSON文件使用UserDefaults
解决方案
核心实现逻辑:
- 数据加载优先级:优先从
UserDefaults读取已保存的选中状态,无本地数据时加载本地JSON文件 - 自动同步:监听
itemSections的变化,实时将数据保存到UserDefaults - 清除选中项:遍历所有选项,将
isOn状态统一重置为false并同步保存
修改后的 ViewModel
import Foundation class ItemSelectionViewModel: ObservableObject { @Published var itemSections: [CountriesSection] = [] { didSet { // 数据变更时自动保存到UserDefaults saveData() } } private let userDefaultsKey = "savedCountriesSections" init(){ loadData() } func loadData() { // 先尝试从UserDefaults读取本地保存的数据 if let savedSections = UserDefaults.standard.object(forKey: userDefaultsKey) as? String, let sections = [CountriesSection](rawValue: savedSections) { self.itemSections = sections return } // 无本地保存时加载JSON文件 guard let url = Bundle.main.url(forResource: "countries", withExtension: "json") else { print("Json文件未找到") return } do { let data = try Data(contentsOf: url) let sections = try JSONDecoder().decode([CountriesSection].self, from: data) self.itemSections = sections } catch { print("加载或解析失败,错误信息: ", error) } } private func saveData() { let encodedSections = itemSections.rawValue UserDefaults.standard.set(encodedSections, forKey: userDefaultsKey) } func getSelectedItemsCount() -> Int{ // 简化选中项计数逻辑 itemSections.flatMap { $0.items }.filter { $0.isOn }.count } // 清除所有选中项的方法 func clearSelections() { itemSections = itemSections.map { section in var updatedSection = section updatedSection.items = updatedSection.items.map { country in var updatedCountry = country updatedCountry.isOn = false return updatedCountry } return updatedSection } // 借助didSet自动触发saveData,无需额外调用 } } extension Array: RawRepresentable where Element: Codable { public init?(rawValue: String) { guard let data = rawValue.data(using: .utf8), let result = try? JSONDecoder().decode([Element].self, from: data) else { return nil } self = result } public var rawValue: String { guard let data = try? JSONEncoder().encode(self), let result = String(data: data, encoding: .utf8) else { return "[]" } return result } }
修改后的 ContentView
import SwiftUI struct ContentView: View { @EnvironmentObject var viewModel: ItemSelectionViewModel var body: some View { VStack() { CheckItemView() Total().multilineTextAlignment(.center) Spacer() Button("清除选中项"){ viewModel.clearSelections() } .padding() .background(.mint) .foregroundColor(.white) .cornerRadius(8) } } }
修改后的 Total 视图
import SwiftUI struct Total: View { @EnvironmentObject var viewModel: ItemSelectionViewModel var body: some View { VStack(alignment: .center){ Text("已选中项目数量: \(viewModel.getSelectedItemsCount())") .padding() } } } struct Total_Previews: PreviewProvider { static var previews: some View { Total() .environmentObject(ItemSelectionViewModel()) } }
其他无需修改的视图代码
Model
import Foundation struct CountriesSection: Codable, Identifiable, Hashable { var id: UUID = UUID() var name: String var items: [Country] } struct Country: Codable, Equatable, Identifiable, Hashable { var id: UUID = UUID() var name: String var isOn: Bool = false }
CheckItemView
import SwiftUI struct CheckItemView: View { @EnvironmentObject var viewModel: ItemSelectionViewModel var body: some View { NavigationView{ VStack() { List(){ ForEach(viewModel.itemSections.indices, id: \.self){ id in NavigationLink(destination: ItemSectionDetailedView( items: $viewModel.itemSections[id].items)) { Text(viewModel.itemSections[id].name) } .padding() } } } .listStyle(.insetGrouped) .navigationViewStyle(StackNavigationViewStyle()) } } }
ItemSectionDetailedView
import SwiftUI struct ItemSectionDetailedView: View { @Binding var items: [Country] var body: some View { VStack{ ScrollView() { ForEach(items.indices, id: \.self){ id in HStack{ Toggle(items[id].name, isOn: $items[id].isOn) .toggleStyle(CheckToggleStyle()) .tint(.mint) .padding() Spacer() } } } .frame(maxWidth: .infinity, alignment: .topLeading) } } } struct CheckToggleStyle: ToggleStyle { func makeBody(configuration: Configuration) -> some View { Button { configuration.isOn.toggle() } label: { Label { configuration.label } icon: { Image(systemName: configuration.isOn ? "checkmark.square.fill" : "square") .foregroundColor(configuration.isOn ? .accentColor : .secondary) .accessibility(label: Text(configuration.isOn ? "已选中" : "未选中")) .imageScale(.large) } } .buttonStyle(PlainButtonStyle()) } }
Main View
import SwiftUI @main struct MyApp: App { @StateObject var varModel = ItemSelectionViewModel() var body: some Scene { WindowGroup { ContentView() .environmentObject(varModel) } } }
界面效果

内容的提问来源于stack exchange,提问作者paulatraveler
相关产品推荐
相关产品推荐

