1

I am using Realm in my app. I want to use the same viewController for update / insert a meal object.

Here is DayOverviewController, which displays meals that user had on a specific date.

This DayOverviewController segues to NewMealTableViewController, in two scenarios - a new meal is added, or a meal is clicked - to be edited. I get a Realm exception when a new meal should be added, more exactly I get it when I should return to DayOverviewController ( save button is pressed, meal is added to Realm, but mealTable.reloadData() - from viewWillAppear, in DayOverviewController crashes before calling cellForRowAtIndexPath.)

It seems that somehow transaction is not closed before calling popViewControllerAnimated - in NewMealTableViewController.

Exception: Terminating app due to uncaught exception 'RLMException', reason: 'Attempting to modify object outside of a write transaction - call beginWriteTransaction on an RLMRealm instance first.' I didn't managed to find out what line of code is causing this exception.

class DayOverviewController: UIViewController{
  @IBOutlet weak var mealTable: UITableView!
  let realm = try! Realm()
  var meals: Results<Meal>!
  var selectedMeal: Meal?

  override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
    getMealsFromDay(selectedDate){
      self.mealTable.reloadData()
    }
  }

  override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if segue.identifier == "NewMeal" {
      let meal = Meal()
      meal.date = selectedDate
      let newMealController = segue.destinationViewController as! NewMealTableViewController
      newMealController.meal = meal
      newMealController.kindOfController = .InserterController
    }

    if segue.identifier == "EditMeal" {
      if let meal = selectedMeal{
        let updaterController = segue.destinationViewController as! NewMealTableViewController
        updaterController.meal = meal
        updaterController.kindOfController = .UpdaterController
      }
    }
  } 
}

extension DayOverviewController: UITableViewDataSource, UITableViewDelegate{

  func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("mealCell", forIndexPath: indexPath) as! MealOverviewCell
    cell.typeOfMealLabel.text = meals[indexPath.row].dishType
    cell.foodItemsLabel.text = meals[indexPath.row].foodItems
    cell.feedbackLabel.text = EmonjiCalculator.getEmonji(Array(meals[indexPath.row].reactions))
    return cell
  }

  func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    selectedMeal = meals[indexPath.row]
    performSegueWithIdentifier("EditMeal", sender: self)
  }
}

extension DayOverviewController{
   func getMealsFromDay(selectedDate: NSDate, completionBlock : () -> Void ) {
     let dayStart = NSCalendar.currentCalendar().startOfDayForDate(selectedDate)
     let dayEnd: NSDate = {
       let components = NSDateComponents()
       components.day = 1
       components.second = -1
       return NSCalendar.currentCalendar().dateByAddingComponents(components, toDate: dayStart, options: NSCalendarOptions())! 
      }()
     self.meals = realm.objects(Meal).filter("date BETWEEN %@", [dayStart, dayEnd])
     completionBlock()
   }

   func deleteMeal(meal: Meal){
     realm.beginWrite()
     realm.delete(meal.reactions)
     realm.delete(meal)
     try! realm.commitWrite()
   }
}


enum TypeOfController{
  case UpdaterController
  case InserterController
}

class NewMealTableViewController: UITableViewController, UITextViewDelegate{

  let realm = try! Realm()
  var meal: Meal!
  @IBOutlet weak var foodItemsTextView: UITextView!
  var kindOfController: TypeOfController!

  override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    switch segue.destinationViewController {
      case let controller as MealSelectorTableViewController:
        controller.delegate = self
      case let controller as ReactionTableViewController:
        controller.reactionDelegate = self
        controller.meal = meal
      default: break
   }
}

  func saveMeal(saveButton: UIBarButtonItem){
    if kindOfController == .InserterController {
      insertNewMeal(){
        self.navigationController?.popViewControllerAnimated(true)
      }
    }
  }

  func textViewDidEndEditing(textView: UITextView) {
    if kindOfController == .UpdaterController{
      updateMeal{
        self.meal.foodItems = textView.text
      }
    } else {
      meal.foodItems = textView.text
    }
  }
}

extension NewMealTableViewController{

  func updateMeal(updateBlock: ()->()){
    try! realm.write(){
      updateBlock()
    }
  }

  func insertNewMeal(completionBlock: () -> ()){
    meal.id = NSUUID().UUIDString
    realm.beginWrite()
    realm.add(meal)
    try! realm.commitWrite()
    completionBlock()
  }
}
Laura Calinoiu
  • 704
  • 1
  • 8
  • 24

1 Answers1

1

It looks like it's this part of your code:

} else {
  meal.foodItems = textView.text
}

It should be inside the closure for method updateMeal().

EDIT 2:

I suggest:

} else {
    try! realm.write(){
        meal.foodItems = textView.text
    }
}

and

realm.beginWrite()
meal.id = NSUUID().UUIDString
realm.add(meal)
try! realm.commitWrite()
completionBlock()
Michal
  • 15,429
  • 10
  • 73
  • 104
  • Meal is not persisted in that moment. It's just a plain object of type Meal. Could you explain? Because I don't understand. – Laura Calinoiu Apr 25 '16 at 09:05
  • `self.meal.foodItems = textView.text` and `meal.foodItems = textView.text` are equivalent statements (in your code example), whether it's persisted or not. You are performing a write change, it should to be in a write block. – Michal Apr 25 '16 at 09:08
  • I am using NewMealTableViewController for adding a new meal or for updating. When updating - I use a persisted object, directly from Realm; when inserting I enter in segue with an empty Meal object: like `Meal()` – Laura Calinoiu Apr 25 '16 at 09:08
  • See the edit, I would do it this way, see if it helps. It's not exactly clear from the wall of text where the code could possibly fail. – Michal Apr 25 '16 at 09:11
  • It's this one as well `meal.id = NSUUID().UUIDString`. It has to be after `beginWrite()`. – Michal Apr 25 '16 at 09:13
  • Yes, I'm trying to modify as you said. – Laura Calinoiu Apr 25 '16 at 09:13
  • Glad to be helpful. Cheers. – Michal Apr 25 '16 at 09:26