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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 15:33:25