首页 > 解决方案 > 如何使用 swiftui 将文本字段存储到核心数据中

问题描述

我无法完成在输入时将值与核心数据一起存储TextField并在进入视图时再次显示的任务。可能吗?

我需要存储namesurname. 为此,我创建了ProfileData数据模型,但找不到任何相关信息。关于如何使其正常工作。

请在下面找到代码:

import SwiftUI
import CoreData

struct ProfileView: View {
    @State private var name: String = ""
    @State private var surname: String = ""
    @Environment(\.managedObjectContext) var managedObjectContext
    @FetchRequest(
        entity: ProfileData.entity(),
        sortDescriptors: [
            NSSortDescriptor(keyPath: \ProfileData.name, ascending: true),
            NSSortDescriptor(keyPath: \ProfileData.surname, ascending: true),

        ]
    ) var profile: FetchedResults<ProfileData>
    @EnvironmentObject var profile1: ProfileData

    var body: some View {
        VStack {
            HStack {
                VStack {
                    HStack {
                        Text("Meno:")
                            .font(.headline)
                            .padding()
                        TextField("", text: $name, onCommit: {
                        self.profile1.name = self.name
                        try? self.managedObjectContext.save()})
                    .onAppear {
                        self.name = self.profile.name != nil ? "\(self.profile.name!)" : "Zadajte meno" //here I get error Value of type 'FetchedResults<ProfileData>' has no member 'name'
                    }
                    .onDisappear {
                        self.profile1.name = self.name
                        try? self.managedObjectContext.save()
                    }
                        }   .padding(.leading, 37)
                    }
                    HStack {
                        Text("Priezvisko:")
                            .font(.headline)
                            .padding()
                        TextField("Zadajte priezvisko", text: $surname)
                    }
                }
            }
        }
        .navigationBarTitle(Text("Profil"))
    }
}

这是我的 ProfileData+CoreClassData.swift:

import Foundation
import CoreData

@objc(ProfileData)
public class ProfileData: NSManagedObject {

}

这是我的 ProfileData+CoreDataProperties.swift

//
//  ProfileData+CoreDataProperties.swift
//  UcmMap
//
//  Created by Jakub Adamec on 06/01/2020.
//  Copyright © 2020 Jakub Adamec. All rights reserved.
//
//

import Foundation
import CoreData


extension ProfileData {

    @nonobjc public class func fetchRequest() -> NSFetchRequest<ProfileData> {
        return NSFetchRequest<ProfileData>(entityName: "ProfileData")
    }

    @NSManaged public var name: String?
    @NSManaged public var surname: String?

}

标签: swiftcore-dataswiftui

解决方案


这是完整的代码:

import SwiftUI
import CoreData

@objc(ProfileData)
public class ProfileData: NSManagedObject, Identifiable {
    public var id = UUID()
}


extension ProfileData {


    @NSManaged public var name: String?
    public var wrappedName: String{
        get{name ?? "NoName"}
        set{name = newValue}
    }
    @NSManaged public var surname: String?
    public var wrappedSurname: String{
        get{surname ?? "NoSurname"}
        set{surname = newValue}
    }
}

struct ProfileView: View {
    @State private var name: String = ""
    @State private var surname: String = ""
    @Environment(\.managedObjectContext) var moc: NSManagedObjectContext // it will need you to add new examples of youre entities and save all changes
    @FetchRequest(
        entity: ProfileData.entity(),
        sortDescriptors: [
            NSSortDescriptor(keyPath: \ProfileData.name, ascending: true),
            NSSortDescriptor(keyPath: \ProfileData.surname, ascending: true),

        ]
    ) var profileList: FetchedResults<ProfileData>
    //fetchRequest is a list of all objects off type ProfileData - saved and unsaved

    var body: some View {
        NavigationView{

            List{
                ForEach(profileList){profile in
                    NavigationLink(destination: profileUpdateView(profile: profile)){
                        Text("\(profile.wrappedName) \(profile.wrappedSurname) ")
                    }
                }
                HStack{
                    Image(systemName: "plus.circle.fill")
                        .foregroundColor(.green)
                        .imageScale(.large)
                    Button("add a new profile"){
                        let newProfile = ProfileData(context: self.moc)
                        newProfile.wrappedName = "Name"
                        newProfile.wrappedSurname = "Surname"
                        }

                }
             }

                .navigationBarTitle(Text("Profile"))
                .navigationBarItems(trailing: Button("save"){
                if self.moc.hasChanges{
                    do{try self.moc.save()}
                    catch{print("Cant save changes: \(error)")}
                }
            })

        }
    }
}
struct profileUpdateView: View {
    @ObservedObject var profile: ProfileData
    var body: some View{
        VStack {
              HStack {
                  Text("Meno:")
                      .font(.headline)
                      .padding()
                TextField("Zadajte meno", text: $profile.wrappedName)
              }
              HStack {
                  Text("Priezvisko:")
                      .font(.headline)
                      .padding()
                    TextField("Zadajte priezvisko", text: $profile.wrappedSurname)
              }
          }
    }
}
struct ProfileView_Previews: PreviewProvider {
    static var previews: some View {
        let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
        let newProfile = ProfileData(context: context)
        newProfile.wrappedName = "Name"
        newProfile.wrappedSurname = "Surname"
        return ProfileView().environment(\.managedObjectContext, context)
    }
}

注意几件事:

  1. FetchRequest返回您定义的类型的实体列表。可能有一个项目,几个项目或可能没有。
  2. 如果你获取一些东西,你需要有ForEach循环来显示结果(大部分时间)。
  3. 为此,我id在您的实体中添加了一个。它没有连接到CoreData,每次FetchRequest得到新的结果,id都会改变,但没关系。它的唯一目的 - 是让ForEach 知道,循环的哪一部分与对象完全连接。因此 ForEach可以更改它并向您显示更新
  4. 不要忘记将codegen数据模型中实体的属性更改为manual/none. 为此,请打开 DataModel,选择您的实体并转到数据模型检查器(屏幕右侧)。如果您不这样做,编译器将在编译本身中创建这些文件,并且此类将被定义两次。
  5. 如果您想创建您的类型的新对象 - 您需要使用 MOC。它是创建类型对象的唯一方法NSManagedObject
  6. 在 TextField 中,您可以放置​​一个 BindableObject。您尝试为此目的使用@State,但您不需要它。你可以像这样修改普通对象的属性:TextField("Zadajte meno", text: $profile.wrappedName)符号$是让这个属性被包裹@Binding。这意味着,在其中所做的所有更改都 View将立即转换为该对象。
  7. 你不能只放@NSManaged属性,因为它们中的大多数都有像 String? 这样的可选类型。我创建了一个计算属性 wrappedName,以便在 Views 中简单地使用它。
  8. 我在更新视图中使用@ObservedObject包装器。它喜欢@State,但对于类。当您想要View在此对象更改时立即更新时使用它。它还有助于创建一个Binding. 你的类必须满足ObservableObject协议要求,但NSManagedObject已经满足了,所以你不必担心。所有@NSManaged属性都已经@Published.
  9. 即使您使用 CoreData,也有一种使用 Canvas 的方法。只需从中获取上下文AppDelegate,创建任何测试实体。但请记住,保存的更改将添加到您的预览中。
  10. 最后,您需要使用moc.save(). 它可以抛出异常,所以你必须在try-catch. 如果确实有未保存的更改,检查它是一个很好的做法。

祝你学习 SwiftUI 好运。没有太多关于使用 SwiftUI 和 CoreData 的信息。尝试在 hackingwithswift上查看它,有很多有用的信息。


推荐阅读