0
votes

I have 2 Controllers: TableViewController and ViewController. TableViewController is responsible for displaying all data, View Controller is responsible for creating new data. Now I want to make it possible to edit the current data also in ViewController. When we click on data, we need to switch to the ViewController and replace all default values with the current values. When we change and click save, we go back to TableViewController, where we already see the change.

class OperationsViewController: UITableViewController {

// MARK: - Stored Properties
var transactions: Results<Transaction>!
var sections = [(date: Date, items: Results<Transaction>)]()

// MARK: - UITableViewController Methods
override func viewDidLoad() {
    super.viewDidLoad()
    transactions = realm.objects(Transaction.self)
}

override func viewWillAppear(_ animated: Bool) {
    super .viewWillAppear(animated)
    assembleGroupedTransactions()
    tableView.reloadData()
  }
}

// MARK: - Navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if let indexPath = tableView.indexPathForSelectedRow {
        let section = sections[indexPath.section]
        let item = section.items[indexPath.row]
        print(item)
        if segue.identifier == "editOrDeleteOperationCell" {
            let addTableViewController = segue.destination as! AddTableViewController
            addTableViewController.defaultTransaction = item
        }
    }
  }
}

// MARK: - User Interface
extension OperationsViewController {

@discardableResult private func assembleGroupedTransactions() -> Array<Any> {
    // fetch all Items sorted by date
    let results = realm.objects(Transaction.self).sorted(byKeyPath: "date", ascending: false)

    sections = results
        .map { item in
            // get start of a day
            return Calendar.current.startOfDay(for: item.date)
        }
        .reduce([]) { dates, date in
            // unique sorted array of dates
            return dates.last == date ? dates : dates + [date]
        }
        .compactMap { startDate -> (date: Date, items: Results<Transaction>) in
            // create the end of current day
            let endDate = Calendar.current.date(byAdding: .day, value: 1, to: startDate)!
            // filter sorted results by a predicate matching current day
            let items = results.filter("(date >= %@) AND (date < %@)", startDate, endDate)
            // return a section only if current day is non-empty
            return (date: startDate, items: items)
    }
    return sections
}

But when I trying to send current data to next ViewController I get error:

*** 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 guess that I have problem with Category. Look on my model:

class Transaction: Object {

@objc dynamic var controlStatus = 0

@objc dynamic private var privateCategory: String = Category.consumption.rawValue
var category: Category {
    get { return Category(rawValue: privateCategory)! }
    set { privateCategory = newValue.rawValue }
}

@objc dynamic var amount = "0"
@objc dynamic var date = Date()
@objc dynamic var note = ""
}

controlStatus needs for monitors the status of the transaction that will be needed in the future. Where 0 is the expense, 1 is the income. The big problem I suppose is that I created categories by enum. I need to change the arrays with categories depending on the controlStatus. Now is this my model of Category:

indirect enum Category: String {
case income = "+"
case consumption = "-"

case salary = "salary"
case billingInterest = "billingInterest"
case partTimeJob = "partTimeJob"

etc.
}

extension Category: RawRepresentable {
typealias RawValue = String

init?(rawValue: RawValue) {
    switch rawValue {
    case "+": self = .income
    case "-": self = .consumption

    case "salary": self = .salary
    case "billingInterest": self = .billingInterest
    case "partTimeJob: self = .partTimeJob
    case "pleasantFinds": self = .pleasantFinds
    case "debtRepayment": self = .debtRepayment

    case "noCategories": self = .noCategories
    case "food": self = .food

    etc. 

    default:
        return nil
    }
}

var rawValue: RawValue {
    switch self {
    case .salary:
        return "salary"
    case .billingInterest:
        return "billingInterest"
    case .partTimeJob:
        return "partTimeJob"
    case .pleasantFinds:
        return "pleasantFinds"
    case .debtRepayment:
        return "debtRepayment"

    case .noCategories:
        return "noCategories"
    case .food:
        return "food"
    case .cafesAndRestaurants:
        return "cafesAndRestaurants"
    etc.
    }
}
}
2
realm.write { ... } - MadProgrammer
@vpoltave No, these are different questions. - Mikhail Tseitlin
So when exactly is the exception thrown: can you use the stack trace to show the line that causes it. I can't see any problems in this code. You're using addTableViewController.defaultTransaction = item to pass the item to the second VC which is fine, but this is now uneditable in the VC outside of realm.write block. I suspect the crash is caused by some editing code that isn't included above - what other code is in AddTableViewController? It may be in viewDidLoad or you may edit the defaultTransaction somewhere else - which you can't do. How is editData called? - Chris Shaw
@ChrisShaw I begin to guess what the problem may be. When I comment out the categories, the item is passed. Most likely I have a problem with the transfer Category. Can you help me with this? - Mikhail Tseitlin

2 Answers

2
votes

You need to enclose your realm write transactions in write block

try realm.write({ () -> Void in realm.add(object, update: true) })

1
votes

From Realm doc

try! realm.write {
    realm.add(myDog)
}