Search code examples
swiftmvvmnsmanagedobject

How do set a relationship between managed objects within an MVVM pattern?


I'm building an app and trying to use the MVVM pattern. While there's a plethora of information about how to wire up things up for data to flow form the model to the view model to the controller to the view, I'm having a very hard time learning how to do other things while sticking to MVVM principles. One thing I'm really struggling with is setting a relationships between two managed objects after segueing to different view controllers. Let me explain...

When my app starts, it presents the first view controller which is backed by a view model which talks to a newly created NSManagedObject of type LiftEvent. From here, the user can segue to a Settings view controller which also has a view model, and from there they can segue to a table view, which also has a view model, and make a selection that needs to change an NSManaged var property of the model object. This property is a relationship to another NSManagedObject. Here's the flow:

enter image description here

I'm passing the managedObjectContext from the first view controller to the Settings view controller:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
   if let nav = segue.destinationViewController as? UINavigationController {
     let vc = nav.topViewController as! SettingsViewController
     vc.dismissalDelegate = self

     let moc = viewModel.model.context
     vc.moc = moc
   }

and then pass it again to the last view controller where the user makes a selection and I want to set the relationship. It works, but I don't like it because I now have to import CoreData in the view controllers I'm passing it to.

The view controller on the far right is a UITableViewController. When the user selects one of the rows, I want to use that selection to grab the corresponding managed object and set it as one of the properties on the object that was created when the app launched. Here's the didSelectRowAtIndexPath method:

  override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    tableView.deselectRowAtIndexPath(indexPath, animated: true)

    let defaultFormula = NSUserDefaults.formula()
    guard indexPath.row != defaultFormula.rawValue else { return }

    if let newDefaultFormula = FormulaName(rawValue: indexPath.row) {
        NSUserDefaults.setDefaultFormulaName(newDefaultFormula)
    }

    if let selectedFormula = formulasArray[indexPath.row] {
       // now what? 
    }
  }

FormulaName is a enum of the possible names. The data source is formulasArray, an array of Formula managed objects. I initialize the array in viewDidLoad:

  override func viewDidLoad() {
    super.viewDidLoad()

    formulasArray = dataManager.fetchSelectableFormulas(moc)
  }

I see a few possible solutions in my head but I have no idea which of them, if any, are good choices:

  1. pass the Formula object I picked out of the array back to the first view controller in unwind segues and set the relationship there
  2. somehow get a hold of the LiftEvent from here, set the relationship, then tell the view model of the first view controller that the object has changed.
  3. pass a reference to the LiftEvent object around instead of passing just the managedObjectContext property

I've spent countless hours trying to find examples, lessons, git repositories, SO threads, etc to figure this out that I don't know where to go from here. I'm not asking anyone to write the code for me, just steer me in a good direction and I'd be forever grateful.


Solution

  • I too have had issues finding concrete examples of MVVM in iOS, so i've kind of developed my own approach.

    I have a couple recommendations (this is opinion to be clear).

    1. Keep the NSManagedObject context in a CD access singleton for access on the main thread is really helpful. Assuming you're not using multiple databases or doing a bunch of things in the background, (which you can handle as well with the accessor if necessary) you will have 1 context with no context passing.

    2. When using Segues, dependency injection is awesome. Rather than giving your new VC the minimum information it needs to find the data and set things up, simply set it's ViewModel in the Segue and build the VC to work only off of that. This is what makes ViewModels work so well. your View/ViewController doesn't need to to know anything about your model.

    When dealing with unwinds, my approach is to simply make a "selectedItem" property in the desired VC and leave the responsibility of what to do to the VC to which it is unwinding. If necessary it can grab that value when the unwind occurs. This allows your choosing VC to operate the same way regardless of the behavior around it.