繁体   English   中英

SwiftUI:使用 NavigationLink 从一个视图更改为另一个视图时复选标记消失

[英]SwiftUI: Checkmarks disappear when changing from one view to another using NavigationLink

我正在尝试制作一个应用程序,该应用程序根据单击的 NavigationLink 显示带有选择/复选标记的列表。 我遇到的问题是,当我 go 返回主视图然后我再次在 NavigationLink 中 go 时,我的选择消失了。 我正在尝试在 UserDefaults 中保存切换值,但它没有按预期工作。 下面我将粘贴详细的主要内容视图。

第二种观点:

struct CheckView: View {
    
    @State var isChecked:Bool = false
    @EnvironmentObject var numofitems: NumOfItems
    
    var title:String
    var count: Int=0
    
    var body: some View {
        HStack{
            ScrollView {
                Toggle("\(title)", isOn: $isChecked)
                    .toggleStyle(CheckToggleStyle())
                    .tint(.mint)
                    .onChange(of: isChecked) { value in
                        
                        if isChecked {
                            numofitems.num += 1
                            print(value)
                        } else{
                            numofitems.num -= 1
                        }
                        UserDefaults.standard.set(self.isChecked, forKey: "locationToggle")
                    }.onTapGesture {
                        
                    }
                    .onAppear {
                        self.isChecked = UserDefaults.standard.bool(forKey: "locationToggle")
                    }
                Spacer()
            }.frame(maxWidth: .infinity,alignment: .topLeading)
        }
    }
}

主视图:

struct CheckListView: View {
    
    @State private var menu = Bundle.main.decode([ItemsSection].self, from: "items.json")
    
    
    var body: some View {
        NavigationView{
            List{
                ForEach(menu){
                    section in
                    NavigationLink(section.name) {
                        VStack{
                            ScrollView{
                                ForEach(section.items) { item in
                                    CheckView( title: item.name)
                                }
                            }
                        }
                    }
                }
            }
        }.navigationBarHidden(true)
            .navigationViewStyle(StackNavigationViewStyle())
            .listStyle(GroupedListStyle())
            .navigationViewStyle(StackNavigationViewStyle())
    }
}

物品栏目:

[
    {
        "id": "9DC6D7CB-B8E6-4654-BAFE-E89ED7B0AF94",
        "name": "Africa",
        "items": [
            {
                "id": "59B88932-EBDD-4CFE-AE8B-D47358856B93",
                "name": "Algeria"
            },
            {
                "id": "E124AA01-B66F-42D0-B09C-B248624AD228",
                "name": "Angola"
            }

Model:

struct ItemsSection: Codable, Identifiable, Hashable {
    var id: UUID = UUID()
    var name: String
    var items: [CountriesItem]
}

struct CountriesItem: Codable, Equatable, Identifiable,Hashable {
    var id: UUID = UUID()
    var name: String
}

正如评论中已经提到的,我看不到您从 UserDefaults 中读取的位置,因此无论存储在那里,您都不会阅读它。 但即便如此,每个 Toggle 都使用相同的键,因此您正在覆盖该值。

我不使用仅在本地使用的@State var isChecked ,而是创建另一个结构项,该结构项从 json 获取标题,并包含一个 boolean 初始化为 false。

据我了解,我认为解决方案可能类似于以下代码。 只是几件事:

  1. 我不确定您的 json 的外观如何,所以我没有从 json 加载,我添加了带有标题和随机数量项目(实际上只是标题)的 ItemSections 对象和 ZC1C425268E68385D1AB5074C14
  2. 我在 UI 上添加了一个文本 output ,而不是带有已检查切换数量的打印。 它在第一页向您显示所有已检查切换的数量。
  3. 我没有使用 UserDefaults,而是使用了 @AppStorage。 要完成这项工作,您必须使Array符合RawRepresentable ,您可以使用以下代码/扩展来实现这一点(只需在项目中的某个位置添加一次)
  4. 也许你应该考虑一个 ViewModel(例如 ItemSectionViewModel),从 json 加载数据并将其作为 @ObservableObject 提供给视图。

视图的代码:

//
//  CheckItem.swift
//  CheckItem
//
//  Created by Sebastian on 24.08.22.
//

import SwiftUI

struct ContentView: View {
    
    var body: some View {
        VStack() {
            CheckItemView()
        }
    }
}

struct CheckItemView: View {
    
    let testStringForTestData: String = "Check Item Title"
    
    @AppStorage("itemSections") var itemSections: [ItemSection] = []
    
    func addCheckItem(title: String, numberOfItems: Int) {
        var itemArray: [Item] = []
        for i in 0...numberOfItems {
            itemArray.append(Item(title: "item \(i)"))
        }
        itemSections.append(ItemSection(title: title, items: itemArray))
    }
    
    func getSelectedItemsCount() -> Int{
        var i: Int = 0
        for itemSection in itemSections {
            let filteredItems = itemSection.items.filter { item in
                return item.isOn
            }
            i = i + filteredItems.count
        }
        return i
    }
    
    var body: some View {
        NavigationView{
            VStack() {
                List(){
                    ForEach(itemSections.indices, id: \.self){ id in
                        NavigationLink(destination: ItemSectionDetailedView(items: $itemSections[id].items)) {
                            Text(itemSections[id].title)
                        }
                        .padding()
                    }
                }
                Text("Number of checked items:  \(self.getSelectedItemsCount())")
                    .padding()
                
                Button(action: {
                    self.addCheckItem(title: testStringForTestData, numberOfItems: Int.random(in: 0..<4))
                }) {
                    Text("Add Item")
                }
                .padding()
            }
        }
    }
}

struct ItemSectionDetailedView: View {
    
    @Binding var items: [Item]
    
    var body: some View {
        ScrollView() {
            ForEach(items.indices, id: \.self){ id in
                Toggle(items[id].title, isOn: $items[id].isOn)
                    .padding()
            }
        }
    }
}

struct ItemSection: Identifiable, Hashable, Codable  {
    var id: String = UUID().uuidString
    var title: String
    var items: [Item]
}

struct Item: Identifiable, Hashable, Codable  {
    var id: String = UUID().uuidString
    var title: String
    var isOn: Bool = false
}

这里是使用@AppStorage 的调整:

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
    }
}

正如评论中所述,您必须将isChecked属性与CountryItem本身相关联。 为了让它工作,我改变了 model 并添加了一个isChecked属性。 如果 JSON allread 存在,您需要手动将其添加到 JSON 中

struct CheckView: View {
    
    @EnvironmentObject var numofitems: NumOfItems
    //use a binding here as we are going to manipulate the data coming from  the parent
    //and pass the complete item not only the name
    @Binding var item: CountriesItem
        
    var body: some View {
        HStack{
            ScrollView {
                //use the name and the binding to the item itself
                Toggle("\(item.name)", isOn: $item.isChecked)
                    .toggleStyle(.button)
                    .tint(.mint)
                // you now need the observe the isChecked inside of the item
                    .onChange(of: item.isChecked) { value in
                        if value {
                            numofitems.num += 1
                            print(value)
                        } else{
                            numofitems.num -= 1
                        }
                    }.onTapGesture {
                        
                    }
                Spacer()
            }.frame(maxWidth: .infinity,alignment: .topLeading)
        }
    }
}

struct CheckListView: View {
    
    @State private var menu = Bundle.main.decode([ItemsSection].self, from: "items.json")
    
    var body: some View {
        NavigationView{
            List{
                ForEach($menu){ // from here on you have to pass a binding on to the decendent views
                    // mark the $ sign in front of the property name
                    $section in
                    NavigationLink(section.name) {
                        VStack{
                            ScrollView{
                                ForEach($section.items) { $item in
                                    //Pass the complete item to the CheckView not only the name
                                    CheckView(item: $item)
                                }
                            }
                        }
                    }
                }
            }
        }.navigationBarHidden(true)
            .navigationViewStyle(StackNavigationViewStyle())
            .listStyle(GroupedListStyle())
            .navigationViewStyle(StackNavigationViewStyle())
    }
}

示例 JSON:

[
    {
        "id": "9DC6D7CB-B8E6-4654-BAFE-E89ED7B0AF94",
        "name": "Africa",
        "items": [
            {
                "id": "59B88932-EBDD-4CFE-AE8B-D47358856B93",
                "name": "Algeria",
                "isChecked": false
            },
            {
                "id": "E124AA01-B66F-42D0-B09C-B248624AD228",
                "name": "Angola",
                "isChecked": false
                
            }
        ]
    }
]

评论:

使用 JSON 并将其存储在包中的方法将阻止您在应用程序启动之间保留isChecked属性。 因为您不能从您的应用程序中写入 Bundle。 只要应用程序处于活动状态,该选择就会一直存在,但一旦您重新安装或强制退出它就会恢复默认设置。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM