Swift - 为相关的 CoreData 实体赋值

Posted

技术标签:

【中文标题】Swift - 为相关的 CoreData 实体赋值【英文标题】:Swift - assign value to related CoreData entity 【发布时间】:2015-02-24 17:29:00 【问题描述】:

我正在构建一个具有两个 CoreData 实体的应用 - 锻炼和锻炼。两者的关系是多对多的。

该应用程序是一对基本的 tableViewControllers,允许您将锻炼 (workoutName) 添加到 Workouts 实体,然后在下一个 tableViewController 中将锻炼添加到该 Workout。我正在努力解决的是如何将每个练习分配回它源自 CoreData 的锻炼。本质上,我要做的是在向练习实体添加 newExercise(使用练习名称变量)时在锻炼实体中设置锻炼名称值。

我通过来自 Workouts tableViewController 的 segue 将锻炼名称作为 var 锻炼传递给锻炼 tableViewController。

我也有多对多关系,并在 NSManagedObjects 文件中设置为 NSSet,但不知道如何使用它们。

这是练习设置的 tableViewController:

import UIKit
import CoreData

class ExerciseMasterTableViewController: UITableViewController 

// Declare workout variable
var workout: Workouts!

// Create an empty array of Exercises
var exercises = [Exercises]()

// Retreive the managedObjectContext from AppDelegate
let managedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext

override func viewDidLoad() 
    super.viewDidLoad()

    // Use optional binding to confirm the managedObjectContext
    if let moc = self.managedObjectContext 
    

    fetchExercises()


func fetchExercises() 
    let fetchRequest = NSFetchRequest(entityName: "Exercises")

    // Create a sort descriptor object that sorts on the "exerciseName"
    // property of the Core Data object
    let sortDescriptor = NSSortDescriptor(key: "exerciseName", ascending: true)

    // Set the list of sort descriptors in the fetch request,
    // so it includes the sort descriptor
    fetchRequest.sortDescriptors = [sortDescriptor]

    if let fetchResults = managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as? [Exercises] 
        exercises = fetchResults
    


override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
    // How many rows are there in this section?
    // There's only 1 section, and it has a number of rows
    // equal to the number of exercises, so return the count
    return exercises.count


override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell 
    let cell = self.tableView.dequeueReusableCellWithIdentifier("Exercise Cell", forIndexPath: indexPath) as UITableViewCell

    // Get the Exercises for this index
    let exercise = exercises[indexPath.row]

    // Set the title of the cell to be the title of the exercise
    cell.textLabel!.text = exercise.exerciseName
    cell.detailTextLabel!.text = "\(exercise.sets)x\(exercise.reps)"
    cell.accessoryType = UITableViewCellAccessoryType.DisclosureIndicator
    return cell


override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) 
    if(editingStyle == .Delete ) 
        // Find the Exercise object the user is trying to delete
        let exerciseToDelete = exercises[indexPath.row]

        // Delete it from the managedObjectContext
        managedObjectContext?.deleteObject(exerciseToDelete)

        // Refresh the table view to indicate that it's deleted
        self.fetchExercises()

        // Tell the table view to animate out that row
        tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
        save()
    


// MARK: UITableViewDelegate
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) 
    let exercise = exercises[indexPath.row]


let addExerciseAlertViewTag = 0
let addExerciseTextAlertViewTag = 1


@IBAction func addExerciseButton(sender: AnyObject) 
    var namePrompt = UIAlertController(title: "Add Exercise",
        message: "Enter Exercise Name",
        preferredStyle: .Alert)

    var exerciseNameTextField: UITextField?
    namePrompt.addTextFieldWithConfigurationHandler 
        (textField) -> Void in
        exerciseNameTextField = textField
        textField.placeholder = "Exercise Name"
    

    namePrompt.addAction(UIAlertAction(title: "Ok",
        style: .Default,
        handler:  (action) -> Void in
            if let textField = exerciseNameTextField 
                self.saveNewItem(textField.text, workoutName: workouts.workoutName)
            
    ))

    self.presentViewController(namePrompt, animated: true, completion: nil)


func saveNewItem(exerciseName : String, workoutName: String) 

    // Create the new exercise item
    var newExercise = Exercises.createExerciseInManagedObjectContext(self.managedObjectContext!, exerciseName: exerciseName, workoutName: workoutName)

    // Update the array containing the table view row data
    self.fetchExercises()

    // Animate in the new row
    // Use Swift's find() function to figure out the index of the newExercise
    // after it's been added and sorted in our Exercises array
    if let newExerciseIndex = find(exercises, newExercise) 
        // Create an NSIndexPath from the newExerciseIndex
        let newExerciseIndexPath = NSIndexPath(forRow: newExerciseIndex, inSection: 0)
        // Animate in the insertion of this row
        tableView.insertRowsAtIndexPaths([ newExerciseIndexPath ], withRowAnimation: .Automatic)
        save()
    



func save() 
    var error : NSError?
    if(managedObjectContext!.save(&error) ) 
        println(error?.localizedDescription)
    


override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) 
    if segue.identifier == "exerciseSettings" 
        let ExerciseSettingsDetailViewController = segue.destinationViewController as UIViewController
        let indexPath = tableView.indexPathForSelectedRow()!
        let exercise = exercises[indexPath.row]
        let destinationTitle = exercise.exerciseName
        ExerciseSettingsDetailViewController.title = destinationTitle
    


override func didReceiveMemoryWarning() 
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.



另外,这里是在我的练习类中定义的函数 createExerciseInManagedObjectContext 添加一个新的练习:

    class func createExerciseInManagedObjectContext(moc: NSManagedObjectContext, exerciseName: String, workoutName: String) -> Exercises 
    let newExercise = NSEntityDescription.insertNewObjectForEntityForName("Exercises", inManagedObjectContext: moc) as Exercises

    newExercise.exerciseName = exerciseName
    self.workouts.addObject(workoutName)

    return newExercise

我可以将所选锻炼 (workoutName) 的字符串传递给此函数,但不知道如何通过锻炼与锻炼实体的关系来设置它。

这是我的练习实体:

import Foundation
import CoreData

class Exercises: NSManagedObject 

@NSManaged var exerciseName: String
@NSManaged var sets: NSNumber
@NSManaged var reps: NSNumber
@NSManaged var repWeight: NSNumber
@NSManaged var barWeight: NSNumber
@NSManaged var incrementWeight: NSNumber
@NSManaged var workouts: NSSet

class func createExerciseInManagedObjectContext(moc: NSManagedObjectContext, exerciseName: String, workoutName: String) -> Exercises 
    let newExercise = NSEntityDescription.insertNewObjectForEntityForName("Exercises", inManagedObjectContext: moc) as Exercises

    newExercise.exerciseName = exerciseName
    newExercise.workouts.setByAddingObject(workoutName)

    return newExercise



这是我的锻炼实体:

import Foundation
import CoreData

class Workouts: NSManagedObject 

@NSManaged var workoutName: String
@NSManaged var sessions: NSSet
@NSManaged var exercises: NSSet

class func createWorkoutInManagedObjectContext(moc: NSManagedObjectContext, workoutName: String) -> Workouts 
    let newWorkout = NSEntityDescription.insertNewObjectForEntityForName("Workouts", inManagedObjectContext: moc) as Workouts
    newWorkout.workoutName = workoutName

    return newWorkout



【问题讨论】:

【参考方案1】:

如果您正确设置模型,两个实体将通过关系相互引用。您将实体添加到另一个实体,而不是它的名称(这是一个属性)。

Core Data 应该在您创建 NSManagedObject 子类时自动生成访问器。有了这些,在锻炼中添加新的(或现有的)锻炼非常简单:

workout.addExercisesObject(newExercise)

这假设你的关系被称为 exercises

所以实际上最好将实际的锻炼对象传递给函数而不是它的名称。不要忘记保存。

编辑: 为了使它起作用,您有两个选择。

您可以让 Xcode 在 Objective-C 中生成 NSManagedObject 子类并自动配置桥接头。然后您无需任何努力即可获得访问器。

或者你必须自己实现它们。例如:

@objc(Exercise)
class Exercise: NSManagedObject 

@NSManaged var workouts: NSSet

    func addWorkoutsObject(value: Workout!) 
        var mutableWorkouts = self.workouts.mutableCopy() as! NSMutableSet
        mutableWorkouts.addObject(value)
        self.workouts = mutableWorkouts as NSSet
    
 

请注意,我没有添加键值编码调用,因此除非您添加它们,否则 KVO 将无法工作。

【讨论】:

谢谢,这听起来很棒。我会将它添加到我的 createExerciseInManagedObjectContext 函数还是 saveNewItem 函数中? 嗯,刚刚在 saveNewItem 函数中尝试过这个,我得到“锻炼”没有名为“addExerciseObject”的成员? 这取决于关系名称。只需查看 Workout.swift。 我编辑了我的答案来解释如何去做。我以为你在使用 Objective-C 生成的子类。 感谢 Mundi,非常感谢您对此提供的帮助。我已经编辑了我的问题,并在 Workouts 和Exerces NSManaged 课程中添加了这些课程,这样您就可以看到它们是如何设置的。我已将您在上面描述的功能添加到练习类中,然后尝试实现练习。addWorkoutsObject(workout),但它不起作用。我收到一条错误消息,提示“锻炼”不能转换为“锻炼”?

以上是关于Swift - 为相关的 CoreData 实体赋值的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Swift 3 中将自定义类保存为 CoreData 实体的属性?

在 Swift 中使用 MagicalRecord 实例化的向下转型 CoreData 实体

删除核心数据对象时,如何在 Swift 5 中同时删除其所有相关对象?

Swift/IOS/CoreData:如何在自动生成的 CoreData 类中将 var 定义为枚举类型?

如何在 Swift 中将 NSSet 转换为 NSMutableSet

Swift 2:使用两个 CoreData 实体填充 TableView