Swiftui Coredata主详细问题

发布于 2025-02-03 20:36:49 字数 6686 浏览 2 评论 0原文

我正在尝试与Coredata建立主细节关系。我有一个用于选择主的设置选项卡(它是全局的,用户不经常完成)。还有另一个标签显示当前主的详细信息。

主人有一个字段,名称,一个字符串和详细信息数组。细节有一个字段,名称,一个字符串。我正在使用uuid()。uuidstring填充示例的名称。

我遇到的问题是,当我选择“详细信息”选项卡时,它显示了当前主人的详细信息。如果我添加详细信息(单击 +按钮),则直到更改主(设置 - > select master)之前,它们不会出现。如果我编辑详细信息并删除了一些详细信息,则列表条目会消失,但是当我完成编辑时,它们会立即返回。我可以切换主人,然后返回到编辑的主体,数据看起来正确(我必须更改ActiveMaster已发布的属性)。

我认为已发布的属性不会强迫更新到详细信息视图,因为Swift没有看到主变量更改。我也可能不会正确添加或删除详细信息。

  1. 如何将详细信息添加到通常完成的主人(这里是一笔详细的一项详细信息)中
  2. 如何删除通常完成的大师的详细信息?
  3. 由于已发表的属性而不是“发布”有关如何更好地做到这一点的任何想法,数据是否没有显示?

谢谢。

代码在下面。

这是全局应用程序数据:

import Foundation
import CoreData
import SwiftUI

class ApplicationData: ObservableObject
{
    let container: NSPersistentContainer

    @Published var activeMaster: Master?
    
    init(preview: Bool = false)
    {
        container = NSPersistentContainer(name: "MasterDetail")
        if (preview)
        {
            container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
        }
        container.loadPersistentStores(completionHandler:
            { storeDescription, error in
                if let error = error as NSError?
                {
                    fatalError("Unresolved error \(error), \(error.userInfo)")
                }
        })
    }
}

持久性和单个可选的活动主。应用程序数据是在应用程序代码中创建的,并将设置为环境对象:


import SwiftUI

@main
struct MasterDetailApp: App
{
    @StateObject var appData = ApplicationData()

    var body: some Scene {
        WindowGroup {
            MainView()
            .environmentObject(appData)
            .environment(\.managedObjectContext, appData.container.viewContext)
        }
    }
}

“选项卡视图:

import Foundation
import SwiftUI

struct MainView: View
{
    @AppStorage("selectedTab") var selectedTab: Int = 0
    
    @EnvironmentObject var appData: ApplicationData
    
    var body: some View
    {
        TabView(selection: $selectedTab)
        {
            DetailView()
                .tabItem({Label("Detail", systemImage: "house")})
                .tag(0)
            SettingsView()
                .tabItem({Label("Settings", systemImage: "gear")})
                .tag(1)
        }
        .environment(\.managedObjectContext, appData.container.viewContext)
    }
}

详细信息”允许用户添加详细信息并编辑列表:


import Foundation
import SwiftUI
import CoreData

struct DetailView: View
{
    @Environment(\.managedObjectContext) private var viewContext
    @Environment(\.dismiss) var dismiss
    @EnvironmentObject var appData: ApplicationData

    var body: some View
    {
        NavigationView
        {
            List
            {
                ForEach(appData.activeMaster?.wrappedDetail ?? [])
                {
                    detail in Text(detail.name ?? "None")
                }
                .onDelete(perform: { indexes in Task(priority: .high) { await deleteDetails(indexes: indexes) } } )
            }
            .toolbar
            {
                ToolbarItem(placement: .navigationBarTrailing)
                {
                    EditButton()
                }
                ToolbarItem(placement: .navigationBarTrailing)
                {
                    Button
                    {
                        let detail = Detail(context: viewContext)
                        
                        detail.name = UUID().uuidString
                        detail.master = appData.activeMaster
                        
                        do
                        {
                            try viewContext.save()
                        }
                        catch
                        {
                            print("Error adding master")
                        }
                    } label: { Image(systemName: "plus") }
                    .disabled(appData.activeMaster == nil)
                }
            }
        }
    }
    
    /*
     * Delete indexes - assumes that appData.activeWeapon is set.
     */
    private func deleteDetails(indexes: IndexSet) async
    {
        await viewContext.perform
        {
            for index in indexes
            {
                print(index)
                viewContext.delete(appData.activeMaster!.wrappedDetail[index])
            }
            do
            {
                try viewContext.save()
            }
            catch
            {
                print("Error deleting dope entry")
            }
        }
    }
}

设置视图仅具有导航链接到一个视图以选择Master和一个添加按钮以添加主人:

import Foundation
import SwiftUI

struct SettingsView: View
{
    @Environment(\.managedObjectContext) private var viewContext
    @EnvironmentObject var appData: ApplicationData

    var body: some View
    {
        NavigationView
        {
            Form
            {
                Section(header: Text("Masters"))
                {
                    NavigationLink(destination: SelectMastersView(selectedMaster: $appData.activeMaster), label:
                    {
                        Text(appData.activeMaster?.name ?? "Select Master")
                    })
                    Button
                    {
                        let master = Master(context: viewContext)
                        
                        master.name = UUID().uuidString
                        
                        do
                        {
                            try viewContext.save()
                        }
                        catch
                        {
                            print("Error adding master")
                        }
                    } label: { Image(systemName: "plus") }
                }
            }
        }
    }
}

选择主的视图只有一个提取请求来获取所有主体,并将选定的一个分配给全局应用程序数据发布的属性:

import Foundation
import SwiftUI

struct SelectMastersView: View
{
    @Environment(\.dismiss) var dismiss

    @FetchRequest(entity: Master.entity(), sortDescriptors: [], animation: .default)
    var masters: FetchedResults<Master>

    @Binding var selectedMaster: Master?

    var body: some View
    {
        List
        {
            ForEach(masters)
            { master in
                Text(master.name ?? "None")
                .onTapGesture
                {
                    selectedMaster = master
                    dismiss()
                }
            }
        }
        .navigationBarTitle("Masters")
    }
}

编辑以将扩展名添加到主我忘了发布。


import Foundation

extension Master
{
    var wrappedDetail: [Detail]
    {
        detail?.allObjects as! [Detail]
    }
}

I'm trying to create a master detail relationship with CoreData. I have a settings tab that is used to select the master (it's global and not done very often by the user). There is another tab that shows the detail entries for the current master.

The master has one field, name, a string and the details array. The detail has one field, name, a string. I'm using UUID().uuidString to populate the names for the example.

The problem I'm having is that when I select the detail tab, it shows the details for the current master. If I add details (click the + button) they do not appear until I change the master (settings -> select master). If I edit the details and delete some, the list entries go away but when I finish editing, they immediately come back. I can switch masters and then go back to the edited master and the data looks correct (I have to change the activeMaster published property).

I'm thinking that the published property isn't forcing the update to the details view because swift doesn't see the master variable change. I may also not be adding or deleting the details correctly.

  1. How is adding details to a master typically done (here master is one to many details)
  2. How is deleting details from a master typically done?
  3. Is the data no showing up due to the published property not "publishing" Any ideas on how to better do this?

Thanks.

Code is below.

Here's the global application data:

import Foundation
import CoreData
import SwiftUI

class ApplicationData: ObservableObject
{
    let container: NSPersistentContainer

    @Published var activeMaster: Master?
    
    init(preview: Bool = false)
    {
        container = NSPersistentContainer(name: "MasterDetail")
        if (preview)
        {
            container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
        }
        container.loadPersistentStores(completionHandler:
            { storeDescription, error in
                if let error = error as NSError?
                {
                    fatalError("Unresolved error \(error), \(error.userInfo)")
                }
        })
    }
}

Just persistence and a single optional active master. The application data is created in the application code and set as an environment object:


import SwiftUI

@main
struct MasterDetailApp: App
{
    @StateObject var appData = ApplicationData()

    var body: some Scene {
        WindowGroup {
            MainView()
            .environmentObject(appData)
            .environment(\.managedObjectContext, appData.container.viewContext)
        }
    }
}

The tab view:

import Foundation
import SwiftUI

struct MainView: View
{
    @AppStorage("selectedTab") var selectedTab: Int = 0
    
    @EnvironmentObject var appData: ApplicationData
    
    var body: some View
    {
        TabView(selection: $selectedTab)
        {
            DetailView()
                .tabItem({Label("Detail", systemImage: "house")})
                .tag(0)
            SettingsView()
                .tabItem({Label("Settings", systemImage: "gear")})
                .tag(1)
        }
        .environment(\.managedObjectContext, appData.container.viewContext)
    }
}

The detail tab allows the user to add details and to edit the list:


import Foundation
import SwiftUI
import CoreData

struct DetailView: View
{
    @Environment(\.managedObjectContext) private var viewContext
    @Environment(\.dismiss) var dismiss
    @EnvironmentObject var appData: ApplicationData

    var body: some View
    {
        NavigationView
        {
            List
            {
                ForEach(appData.activeMaster?.wrappedDetail ?? [])
                {
                    detail in Text(detail.name ?? "None")
                }
                .onDelete(perform: { indexes in Task(priority: .high) { await deleteDetails(indexes: indexes) } } )
            }
            .toolbar
            {
                ToolbarItem(placement: .navigationBarTrailing)
                {
                    EditButton()
                }
                ToolbarItem(placement: .navigationBarTrailing)
                {
                    Button
                    {
                        let detail = Detail(context: viewContext)
                        
                        detail.name = UUID().uuidString
                        detail.master = appData.activeMaster
                        
                        do
                        {
                            try viewContext.save()
                        }
                        catch
                        {
                            print("Error adding master")
                        }
                    } label: { Image(systemName: "plus") }
                    .disabled(appData.activeMaster == nil)
                }
            }
        }
    }
    
    /*
     * Delete indexes - assumes that appData.activeWeapon is set.
     */
    private func deleteDetails(indexes: IndexSet) async
    {
        await viewContext.perform
        {
            for index in indexes
            {
                print(index)
                viewContext.delete(appData.activeMaster!.wrappedDetail[index])
            }
            do
            {
                try viewContext.save()
            }
            catch
            {
                print("Error deleting dope entry")
            }
        }
    }
}

The settings view just has a navigation link to a view to select the master and an add button to add masters:

import Foundation
import SwiftUI

struct SettingsView: View
{
    @Environment(\.managedObjectContext) private var viewContext
    @EnvironmentObject var appData: ApplicationData

    var body: some View
    {
        NavigationView
        {
            Form
            {
                Section(header: Text("Masters"))
                {
                    NavigationLink(destination: SelectMastersView(selectedMaster: $appData.activeMaster), label:
                    {
                        Text(appData.activeMaster?.name ?? "Select Master")
                    })
                    Button
                    {
                        let master = Master(context: viewContext)
                        
                        master.name = UUID().uuidString
                        
                        do
                        {
                            try viewContext.save()
                        }
                        catch
                        {
                            print("Error adding master")
                        }
                    } label: { Image(systemName: "plus") }
                }
            }
        }
    }
}

The view for selecting the master just has a fetch request to get all masters and assign the selected one to the global app data published property:

import Foundation
import SwiftUI

struct SelectMastersView: View
{
    @Environment(\.dismiss) var dismiss

    @FetchRequest(entity: Master.entity(), sortDescriptors: [], animation: .default)
    var masters: FetchedResults<Master>

    @Binding var selectedMaster: Master?

    var body: some View
    {
        List
        {
            ForEach(masters)
            { master in
                Text(master.name ?? "None")
                .onTapGesture
                {
                    selectedMaster = master
                    dismiss()
                }
            }
        }
        .navigationBarTitle("Masters")
    }
}

Edited to add extension to Master I forgot to post.


import Foundation

extension Master
{
    var wrappedDetail: [Detail]
    {
        detail?.allObjects as! [Detail]
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

悲念泪 2025-02-10 20:36:49

我终于今天早上弄清楚了。我认为昨晚将示例代码放在一起有很大帮助。

我通过在详细信息视图中创建获取请求并将主人传递到Init()中的视图中,从而使其起作用。

这是标签视图的更新代码:

import Foundation
import SwiftUI

struct MainView: View
{
    @AppStorage("selectedTab") var selectedTab: Int = 0
    
    @EnvironmentObject var appData: ApplicationData
    
    var body: some View
    {
        TabView(selection: $selectedTab)
        {
            DetailView(master: appData.activeMaster)
                .tabItem({Label("Detail", systemImage: "house")})
                .tag(0)
            SettingsView()
                .tabItem({Label("Settings", systemImage: "gear")})
                .tag(1)
        }
        .environment(\.managedObjectContext, appData.container.viewContext)
    }
}

和更新的详细信息视图:

import Foundation
import SwiftUI
import CoreData

struct DetailView: View
{
    @Environment(\.managedObjectContext) private var viewContext
    @Environment(\.dismiss) var dismiss
    @EnvironmentObject var appData: ApplicationData

    @FetchRequest(entity: Detail.entity(), sortDescriptors: [])
    var details: FetchedResults<Detail>
    
    let master: Master?
    
    init(master: Master?)
    {
        self.master = master
        
        if master != nil
        {
            let predicate = NSPredicate(format: "%K == %@", #keyPath(Detail.master), master ?? NSNull())
            _details = FetchRequest(sortDescriptors: [], predicate: predicate)
        }
    }
    
    @ViewBuilder
    var body: some View
    {
        NavigationView
        {
            List
            {
                if master != nil
                {
                    ForEach(details)
                    {
                        detail in Text(detail.name ?? "None")
                    }
                    .onDelete(perform: { indexes in Task(priority: .high) { await deleteDetails(indexes: indexes) } } )
                }
            }
            .toolbar
            {
                ToolbarItem(placement: .navigationBarTrailing)
                {
                    EditButton().disabled(master == nil || details.isEmpty)
                }
                ToolbarItem(placement: .navigationBarTrailing)
                {
                    Button
                    {
                        let detail = Detail(context: viewContext)
                        
                        detail.name = UUID().uuidString
                        detail.master = appData.activeMaster
                        
                        do
                        {
                            try viewContext.save()
                        }
                        catch
                        {
                            print("Error adding master")
                        }
                    } label: { Image(systemName: "plus") }
                    .disabled(appData.activeMaster == nil)
                }
            }
        }
    }
    
    /*
     * Delete indexes - assumes that appData.activeWeapon is set.
     */
    private func deleteDetails(indexes: IndexSet) async
    {
        await viewContext.perform
        {
            for index in indexes
            {
                print(index)
                viewContext.delete(appData.activeMaster!.wrappedDetail[index])
            }
            do
            {
                try viewContext.save()
            }
            catch
            {
                print("Error deleting dope entry")
            }
        }
    }
}

这不像我想要的那样干净。我必须转到列表的视图构建。我希望能够创建一个空的获取请求,因此我不必使用视图构建器。

I finally figured it out this morning. I think putting the example code together last night helped quite a bit.

I got it work by creating fetch request in the detail view and passing the master into the view in init().

Here's the updated code for the tab view:

import Foundation
import SwiftUI

struct MainView: View
{
    @AppStorage("selectedTab") var selectedTab: Int = 0
    
    @EnvironmentObject var appData: ApplicationData
    
    var body: some View
    {
        TabView(selection: $selectedTab)
        {
            DetailView(master: appData.activeMaster)
                .tabItem({Label("Detail", systemImage: "house")})
                .tag(0)
            SettingsView()
                .tabItem({Label("Settings", systemImage: "gear")})
                .tag(1)
        }
        .environment(\.managedObjectContext, appData.container.viewContext)
    }
}

and the updated detail view:

import Foundation
import SwiftUI
import CoreData

struct DetailView: View
{
    @Environment(\.managedObjectContext) private var viewContext
    @Environment(\.dismiss) var dismiss
    @EnvironmentObject var appData: ApplicationData

    @FetchRequest(entity: Detail.entity(), sortDescriptors: [])
    var details: FetchedResults<Detail>
    
    let master: Master?
    
    init(master: Master?)
    {
        self.master = master
        
        if master != nil
        {
            let predicate = NSPredicate(format: "%K == %@", #keyPath(Detail.master), master ?? NSNull())
            _details = FetchRequest(sortDescriptors: [], predicate: predicate)
        }
    }
    
    @ViewBuilder
    var body: some View
    {
        NavigationView
        {
            List
            {
                if master != nil
                {
                    ForEach(details)
                    {
                        detail in Text(detail.name ?? "None")
                    }
                    .onDelete(perform: { indexes in Task(priority: .high) { await deleteDetails(indexes: indexes) } } )
                }
            }
            .toolbar
            {
                ToolbarItem(placement: .navigationBarTrailing)
                {
                    EditButton().disabled(master == nil || details.isEmpty)
                }
                ToolbarItem(placement: .navigationBarTrailing)
                {
                    Button
                    {
                        let detail = Detail(context: viewContext)
                        
                        detail.name = UUID().uuidString
                        detail.master = appData.activeMaster
                        
                        do
                        {
                            try viewContext.save()
                        }
                        catch
                        {
                            print("Error adding master")
                        }
                    } label: { Image(systemName: "plus") }
                    .disabled(appData.activeMaster == nil)
                }
            }
        }
    }
    
    /*
     * Delete indexes - assumes that appData.activeWeapon is set.
     */
    private func deleteDetails(indexes: IndexSet) async
    {
        await viewContext.perform
        {
            for index in indexes
            {
                print(index)
                viewContext.delete(appData.activeMaster!.wrappedDetail[index])
            }
            do
            {
                try viewContext.save()
            }
            catch
            {
                print("Error deleting dope entry")
            }
        }
    }
}

This is not as clean as I'd like. I had to move to a view build for the list. I'd like to be able to create an empty fetch request so I don't have to use a view builder.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文