简体   繁体   English

保存 NavigationLink 选择

[英]Save NavigationLink selection

I'm building a desktop application that has a sidebar.我正在构建一个带有侧边栏的桌面应用程序。 It should behave like this:它的行为应该是这样的:

  1. The user is able to add and remove elements to a list that is displayed in the sidebar.用户可以在边栏中显示的列表中添加和删除元素。
  2. A selected list element needs to remain selected when restarting the application.重新启动应用程序时,选定的列表元素需要保持选中状态。
  3. Upon adding a new element, the new element gets selected.添加新元素后,新元素将被选中。

I'm having issues with achieving the 2. and 3. point.我在实现 2. 和 3. 点时遇到问题。

This is my view:这是我的看法:

import SwiftUI
import CoreData

struct Sidebar: View {
    @Environment(\.managedObjectContext) private var viewContext

    @FetchRequest(entity: Element.entity(),  sortDescriptors: [NSSortDescriptor(keyPath: \Element.title, ascending: false)])
    private var elements: FetchedResults<Element>
    
    @State var selectedElementId: NSManagedObjectID?

    var body: some View {
        VStack (alignment: .leading) {
            List(self.elements) { element in
                NavigationLink(destination: ContentView(), tag: element.objectID, selection: self.$selectedElementId) {
                    Text(element.title!)
                }
            }
            .listStyle(SidebarListStyle())
            
            HStack {
                Button(action: addElement) {
                    Image(systemName: "plus")
                }
                
                Button(action: removeElement) {
                    Image(systemName: "minus")
                }
            }
        }
    }
    
    private func addElement() {
        let element = Element(context: viewContext)
        
        element.title = "My title"
      
        do {
            try viewContext.save()
        } catch {
            print("Unresolved error \(error)")
        }
    }
    
    private func removeElement() {
        if self.$selectedElementId.wrappedValue == nil {
            return
        }
        
        let element = viewContext.object(with: self.$selectedElementId.wrappedValue!)
        
        viewContext.delete(element)
        
        do {
            try viewContext.save()
        } catch {
            print("Unresolved error \(error)")
        }
    }
}

To achieve the 2. and 3. point, I tried this:为了实现 2. 和 3. 点,我尝试了这个:

I'm using core data as my storage.我使用核心数据作为我的存储。 My idea is to have an attribute Element.selection which would tell me what list item was last selected, but I don't know how to update the $selectedElementId .我的想法是有一个属性Element.selection可以告诉我最后选择了哪个列表项,但我不知道如何更新$selectedElementId I tried this in addElement but it doesn't work as expected:我在addElement中尝试了这个,但它没有按预期工作:

private func addElement() {
    let element = Element(context: viewContext)
            
    element.title = "My title"

    if self.$selectedElementId.wrappedValue != nil {
        let selectedElement = viewContext.object(with: self.$selectedElementId.wrappedValue!)
            
        selectedElement.entity.selected = false
    }
       
    self.$selectedElementId.wrappedValue = element.objectID

    do {
        try viewContext.save()
    } catch {
        print("Unresolved error \(error)")
    }
}

This has two issues:这有两个问题:

  1. I want to set the new element as the selected element, therefore I need to update self.$selectedElementId to element.objectId .我想将新元素设置为选定元素,因此我需要将self.$selectedElementId更新为element.objectId How can I do that?我怎样才能做到这一点? wrappedValue seems to be read-only. wrappedValue似乎是只读的。
  2. I need to set Element.selection to false for the currently selected attribute.对于当前选定的属性,我需要将Element.selection设置为false I've fetched the element in selectedElement , but I don't know how to update any values as it's of type NSManagedObject , not Element .我已经在selectedElement中获取了元素,但我不知道如何更新任何值,因为它的类型NSManagedObject ,而不是Element

I'm new to Swift.我是 Swift 的新手。 Please let me know if there are also other elements of my approach that are wrong or could be done better.请让我知道我的方法中是否还有其他错误或可以做得更好的元素。

You'll need to fix a few things to get this working.您需要解决一些问题才能使其正常工作。

First, your addElement() method isn't working because you need to set your state var directly to trigger an update, ie:首先,您的addElement()方法不起作用,因为您需要直接设置 state var 以触发更新,即:

// Do this
self.selectedElementId = element.objectID

// Not this
self.$selectedElementId.wrappedValue = element.objectID

Second, you're trying to store application state — the user's selection — in your database.其次,您尝试将应用程序 state (用户的选择)存储在您的数据库中。 In general, databases are for user data and app state should be stored elsewhere.通常,数据库用于用户数据,应用程序 state 应存储在其他位置。 On Mac and iOS that place is usually UserDefaults (aka @AppStorage in SwiftUI), but for what you want SwiftUI has a specific solution: @SceneStorage .在 Mac 和 iOS 上,该位置通常是UserDefaults (在@AppStorage中也称为 @AppStorage),但对于您想要的 SwiftUI 有一个特定的解决方案: @SceneStorage Anything you put in SceneStorage is saved per- scene , which is perfect for your case because users can open multiple windows and have different selection in each window.您放入SceneStorage的任何内容都会按场景保存,这非常适合您的情况,因为用户可以打开多个 windows 并在每个 window 中进行不同的选择。

And better yet, SceneStorage is super simple, just add the property @SceneStorage with a unique key to whatever property should be saved:更好的是,SceneStorage 非常简单,只需将属性@SceneStorage与唯一键添加到应保存的任何属性中:

@SceneStorage("com.appname.selected_element") var selectedElementId: NSManagedObjectID?

… oh except that won't compile because SceneStorage only works for a few basic types like Int and String. ......哦,除了不会编译,因为SceneStorage只适用于一些基本类型,如 Int 和 String。 If you add a string property identifier to Element , then you can use it everywhere instead of NSManagedObjectID:如果将字符串属性identifier添加到Element ,则可以在任何地方使用它而不是 NSManagedObjectID:

struct Sidebar: View {
    @Environment(\.managedObjectContext) private var viewContext

    @FetchRequest(entity: Element.entity(),  sortDescriptors: [NSSortDescriptor(keyPath: \Element.title, ascending: false)])
    private var elements: FetchedResults<Element>
    
    @SceneStorage("com.appname.selected_element") var selectedElementId: String?

    var body: some View {
        VStack (alignment: .leading) {
            List(self.elements) { element in
                NavigationLink(destination: ContentView(),
                               tag: element.identifier!,
                               selection: self.$selectedElementId) {
                    Text(element.title!)
                }
            }
            .listStyle(SidebarListStyle())
            
            HStack {
                Button(action: addElement) {
                    Image(systemName: "plus")
                }
                
                Button(action: removeElement) {
                    Image(systemName: "minus")
                }
            }
        }
    }
    
    private func addElement() {
        let element = Element(context: viewContext)
        element.title = "My title"

        // set identifier to a random UUID
        element.identifier = UUID().uuidString
        do {
            try viewContext.save()
        } catch {
            print("Unresolved error \(error)")
        }

        // select our newly-added element
        self.selectedElementId = element.identifier
    }
    
    private func removeElement() {
        // TODO: fetch selected object with identifier == $selectedElementId
    }
}

One more thing that may help is setting the selection in your List , but this doesn't seem to be required:可能有帮助的另一件事是在您的List中设置选择,但这似乎不是必需的:

List(self.elements, id: \.identifier, selection:$selectedElementId) {

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

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