UITableView doesn't show the realm objects that it should show - ios

I have a problem with the content of a tableView. It doesn't show me the realm objects, that I asked for. Here's my code:
At the top of the class i declared this variable:
let newPlan = TrainingPlan()
Then I have this button action, which is copying the exercise objects, put them in an array an add this to realm. I did this, because I only want to change this copied array:
#IBAction func savePlanAction(_ sender: AnyObject) {
if planNameTextField.text!.isEmpty{
planNameFehlerLabel.isHidden = false
}
else{
newPlan.name = planNameTextField.text!
newPlan.creationDate = NSDate() as Date
let selectedExcercises = loadSelectedExcercises()
if selectedExcercises != nil{
for var i in (0..<selectedExcercises!.count){
excerciseCopies.append(selectedExcercises![i])
do{
try realm.write{
realm.add(excerciseCopies[i])
}
}
catch{
print(error)
}
}
try! realm.write{
realm.add(newPlan)
}
for object in excerciseCopies {
do{
try realm.write{
newPlan.excercises.append(object)
}
}
catch{
print(error)
}
}
performSegue(withIdentifier: "savePlan", sender: self)
}
else{
uebungFehlerLabel.isHidden = false
}
}
}
Then I give the newPlan object to another ViewController:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier! == "savePlan" {
let tct = segue.destination as! TrainingPlanConfTableViewController
tct.plan = newPlan
}
}
Now in the next class I want to show all exercise objects from the copied array in a tableView, but the tableView doesn't show anything:
class TrainingPlanConfTableViewController: UITableViewController {
//Properties
let realm = try! Realm()
var excercisesFromPlan: Results<Excercise>?{
didSet{
tableView.reloadData()
}
}
var plan: TrainingPlan?
//Lifecycle
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
loadExcercisesFromPlan()
}
//Request
func loadExcercisesFromPlan(){
if plan != nil{
let predicate = NSPredicate(format: "trainingsplan = %#", plan!)
excercisesFromPlan = realm.objects(Excercise.self).filter(predicate)
}
}
//Tableview Funktionen
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if excercisesFromPlan != nil{
return excercisesFromPlan!.count
}
return 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: TrainingPlanConfTableViewCell = tableView.dequeueReusableCell(withIdentifier: "PlanConfCell") as! TrainingPlanConfTableViewCell
let excercise = excercisesFromPlan![indexPath.row]
cell.nameLabel.text = excercise.name
return cell
}
}
I'm pretty new in Swift and I can't find the issue. Is the predicate seated wrong? I would be thankful for any help! If you need any more information, for example the data model etc, pls feel free to ask

Since you didn't include the code for your models, there's no way to know for sure if your predicate is correct or not.
What I can tell is the TrainingPlan has a exercises property on it that you should use instead of querying for the same thing.
You would just need to change excercisesFromPlan to be a List instead of a Results
var excercisesFromPlan: List<Excercise>?
and change loadExcercisesFromPlan to get it
func loadExcercisesFromPlan() {
excercisesFromPlan = plan?.excercises
}

Related

How can I delete data in coreData and Tableview with one Button?

I'm actually doing an app for recipe, I already did the persistent data to save my ingredients in the list but when I want to delete my ingredients with my button it works at the first time but come back when I restart my app.
Here's my code :
class AddIngredientController: UIViewController, ShowAlert {
#IBOutlet weak var textField: UITextField!
#IBOutlet weak var ingredientsTableView: UITableView!
var itemArrayIngredient = [Item]()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
override func viewDidLoad() {
super.viewDidLoad()
let dataFilePath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
print(dataFilePath)
loadItems()
}
func saveItems() {
do {
try context.save()
} catch {
print("Error saving context \(error)")
}
}
func loadItems(with request: NSFetchRequest<Item> = Item.fetchRequest()) {
do {
itemArrayIngredient = try context.fetch(request)
} catch {
print("Error fetching data from context \(error)")
}
}
#IBAction func clearButton(_ sender: UIButton) {
context.delete() //Here the problem
itemArrayIngredient.removeAll()
saveItems()
ingredientsTableView.reloadData()
}
#IBAction func addButton(_ sender: UIButton) {
if textField.text!.isEmpty {
showAlert(title: "No Ingredients", message: "Please, add some ingredients to your list.")
} else {
newIngredientAdded()
}
}
func newIngredientAdded() {
let newItem = Item(context: context)
newItem.title = textField.text!
itemArrayIngredient.append(newItem)
saveItems()
ingredientsTableView.reloadData()
textField.text! = ""
}
}
extension AddIngredientController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return itemArrayIngredient.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "customSearchCell", for: indexPath)
let item = itemArrayIngredient[indexPath.row]
cell.textLabel?.text = item.title
cell.textLabel?.textColor = UIColor.white
cell.textLabel?.font = UIFont(name: "Marker Felt", size: 19)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
saveItems()
tableView.deselectRow(at: indexPath, animated: true)
}
}
context.delete must be called with an argument. For example
#IBAction func clearButton(_ sender: UIButton) {
itemArrayIngredient.forEach { context.delete($0) }
itemArrayIngredient.removeAll()
saveItems()
ingredientsTableView.reloadData()
}
You need to delete a specific object from core data. Please refer below code
let fetchRequest = NSFetchRequest(entityName: "EntityName")
if let result = try? context.fetch(fetchRequest) {
for object in result {
//Please check before delete operation
if object.id == Your_Deleted_Object_ID{
context.delete(object)
}
}
}
You can delete all data from particular entity by using NSBatchDeleteRequest. See following code. The main advantage of NSBatchDeleteRequest is, you don't need to enumerate on array of object.
let fetch = NSFetchRequest<NSFetchRequestResult>(entityName: "EntityName")
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetch)
do {
try context.execute(deleteRequest)
} catch {
print(error.localizedDescription)
}

Tableview using realm won't reload when deleted in swift

I have a simple tableView with saved data. I created a delete button that lets me multi-delete from realm. That part works, it is when the tableview is suppose to reload that it seems to not work. I have seen a lot of answers that say you should reload it on the main thread, or view or whatever, using dispatchQueue.main.async
using just normal tableView.reloadData() didn't reload the tableview but when I use the dispatchQueue version it does delete a value but usually the last value in the tableView.
For example my tableView has the strings Uno and Un in that descending order. If I chose to delete Uno when I press the delete button the tableview does reload leaving only one value but that value is Uno, but realm Database tells me I deleted Uno and when I go back to that view it shows Un. It just isn't reloading correctly.
I have tried to place the reloadData in the dispatch at many different locations, but it still doesn't reload correctly. I am curious what I am doing wrong.
this is the viewController with the tableview where I delete the data in the tableView:
import UIKit
import Realm
import RealmSwift
class OtherViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var otherTableView: UITableView!
var realm: Realm!
var realmedData = ""
var realmList: Results<Realmed> {
get {
return realm.objects(Realmed.self)
}
}
let deleteBtn = UIBarButtonItem()
var testingBool = false
var realmArr = [String]()
var idValue = [Int]()
var idArr = [Int]()
var spanArrValue: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
otherTableView.reloadData()
realm = try! Realm()
self.otherTableView.delegate = self
self.otherTableView.dataSource = self
self.otherTableView.reloadData()
deleteBtnInfo(btn: deleteBtn)
self.navigationItem.rightBarButtonItem = deleteBtn
}
func deleteBtnInfo(btn: UIBarButtonItem) {
btn.title = "Delete"
btn.style = .plain
btn.target = self
btn.action = #selector(didTapDeleteBtn(sender:))
testingBool = false
}
#objc func didTapDeleteBtn(sender: AnyObject) {
testingBool = !testingBool
if testingBool == true {
deleteBtn.title = "Remove"
otherTableView.allowsMultipleSelection = true
otherTableView.allowsMultipleSelectionDuringEditing = true
} else if testingBool == false {
deleteBtn.title = "Delete"
didPressRemove()
DispatchQueue.main.async {
self.otherTableView.reloadData()
}
otherTableView.allowsMultipleSelection = false
otherTableView.allowsMultipleSelectionDuringEditing = false
}
}
func didPressRemove() {
if idValue.count == 0 {
print("Please Select what to Delete")
} else {
deleteRealm(idInt: idValue)
}
}
func deleteRealm(idInt: [Int]) {
do {
try realm.write {
for deleteIndex in idInt {
let deleteValue = realm.objects(RealmTwo.self).filter("id == %#", deleteIndex as Any)
print(deleteIndex)
realm.delete(deleteValue)
}
}
} catch {
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var counted = realm.objects(RealmTwo.self).filter("realmLbl == %#", realmedData)
return counted.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "otherCell", for: indexPath) as! OtherTableViewCell
var celledItem = realm.objects(Realmed.self)
for item in celledItem {
for items in item.realmTwo {
self.idArr.append(items.id)
self.realmArr.append(items.spanish)
}
}
cell.otherLbl.text = "\(realmArr[indexPath.row])"
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if testingBool == false {
print(realmArr[indexPath.row])
} else {
self.idValue.append(idArr[indexPath.row])
print(spanArrValue)
}
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
if testingBool == true {
if let index = idValue.index(of: idArr[indexPath.row]) {
idValue.remove(at: index)
print(spanArrValue)
}
}
}
}
this is the realm class for the data that I am trying to delete.
import Foundation
import UIKit
import Realm
import RealmSwift
class RealmTwo: Object {
#objc dynamic var id = Int()
#objc dynamic var realmLbl = String()
#objc dynamic var spanish = String()
#objc dynamic var french = String()
let realmed = LinkingObjects(fromType: Realmed.self, property: "realmTwo")
convenience init(id: Int, realmLbl: String, spanish: String, french: String) {
self.init()
self.id = id
self.realmLbl = realmLbl
self.spanish = spanish
self.french = french
}
}
As I said above, I placed reloadData() in different places and these are where I placed them, just in case you want to know:
func didPressRemove() {
if idValue.count == 0 {
print("Please Select what to Delete")
} else {
deleteRealm(idInt: idValue)
DispatchQueue.main.async {
self.otherTableView.reloadData()
}
}
}
func deleteRealm(idInt: [Int]) {
do {
try realm.write {
for deleteIndex in idInt {
let deleteValue = realm.objects(RealmTwo.self).filter("id == %#", deleteIndex as Any)
print(deleteIndex)
realm.delete(deleteValue)
DispatchQueue.main.async {
self.otherTableView.reloadData()
}
}
}
} catch {
}
}
I am just not sure where the reloadData is suppose to go, or if that is the real problem. Thank you for the help, and ask if there is anything else I can do.
There are a couple of issues but the main issue is that you're deleting the object from realm but that object is still hanging around in your dataSource tableView array, realmArr.
There are a whole bunch of solutions but the simplest is to add an observer to the realm results and when an item is added, changed or removed, have that update your dataSource array and then reload the tableview. One option also here is to use those results as the dataSource instead of a separate array. Realm Results objects behave very similar to an array and are great a a dataSource.
Conceptually the realm code is similar to
notificationToken = results.observe { [weak self] (changes: RealmCollectionChange) in
guard let tableView = self?.tableView else { return }
switch changes {
case .initial:
tableView.reloadData() //this is when the realm data is intially loaded.
case .update(_, let deletions, let insertions, let modifications):
//handle add, edit and modify per event.
// with an add, add the provided object to your dataSource
// same thing for remove and modify
case .error(let error):
// An error occurred while opening the Realm file on the background worker thread
fatalError("\(error)")
}
//reload the tableView now the dataSource has been updated
}
There are several options of handling those events and they are all covered in the Realm documentation. See Realm Notifications for further details about setting up the notifications.
A second option is to manually keep things in sync; e.g. when deleting the item from Realm, also delete the item from your dataSource array
This is how I managed to solve this problem.
import UIKit
import Realm
import RealmSwift
class OtherViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var notificationToken: NotificationToken? = nil
#IBOutlet weak var otherTableView: UITableView!
var realm: Realm!
var realmedData = ""
var realmList: Results<RealmTwo> {
get {
return realm.objects(RealmTwo.self).filter("%# == realmLbl", realmedData)
}
}
var realmingList: Results<RealmTwo> {
get {
return realm.objects(RealmTwo.self)
}
}
let deleteBtn = UIBarButtonItem()
var testingBool = false
var realmArr = [String]()
var idValue = [Int]()
var idArr = [Int]()
var spanArrValue: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
otherTableView.allowsMultipleSelectionDuringEditing = true
realm = try! Realm()
notificationToken = realmList.observe { [weak self] (changes: RealmCollectionChange) in
guard let tableView = self?.otherTableView else {return}
switch changes {
case .initial:
tableView.reloadData()
case .update(_, let deletions, let insertions, let modifications):
tableView.beginUpdates()
tableView.insertRows(at: insertions.map({ IndexPath(row: $0, section: 0) }),
with: .automatic)
tableView.deleteRows(at: deletions.map({ IndexPath(row: $0, section: 0)}),
with: .automatic)
tableView.reloadRows(at: modifications.map({ IndexPath(row: $0, section: 0) }),
with: .automatic)
tableView.endUpdates()
case .error(let error):
fatalError("\(error)")
}
}
self.otherTableView.delegate = self
self.otherTableView.dataSource = self
self.otherTableView.reloadData()
deleteBtnInfo(btn: deleteBtn)
self.navigationItem.rightBarButtonItem = deleteBtn
}
func deleteBtnInfo(btn: UIBarButtonItem) {
btn.title = "Delete"
btn.style = .plain
btn.target = self
btn.action = #selector(didTapDeleteBtn(sender:))
testingBool = false
}
#objc func didTapDeleteBtn(sender: AnyObject) {
testingBool = !testingBool
if testingBool == true {
deleteBtn.title = "Remove"
} else if testingBool == false {
deleteBtn.title = "Delete"
}
}
func didPressRemove() {
if testingBool == false {
print("Select what to Delete")
} else {
deleteRealm(idInt: idValue)
otherTableView.isEditing = false
}
}
#IBAction func pressEdit(_ sender: Any) {
testingBool = !testingBool
if testingBool == true {
otherTableView.isEditing = true
} else if testingBool == false {
otherTableView.isEditing = false
}
}
#IBAction func pressDelete(_ sender: Any) {
deleteRealm(idInt: idValue)
}
func deleteRealm(idInt: [Int]) {
do {
try realm.write {
for deleteIndex in idInt {
let deletingValue = realmList.filter("id == %#", deleteIndex as Any)
print("DeleteValue: \(deletingValue)")
realm.delete(deletingValue)
}
}
} catch {
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return realmList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "otherCell", for: indexPath) as! OtherTableViewCell
cell.otherLbl.text = realmList.filter("%# == realmLbl", realmedData)[indexPath.row].spanish
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if otherTableView.isEditing == false {
} else {
let idArr = realmList.filter("%# == realmLbl", realmedData)[indexPath.row].id
self.idValue.append(idArr)
print("ID: \(idValue)")
}
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
if otherTableView.isEditing == true {
let idArr = realmList.filter("%# == realmLbl", realmedData)[indexPath.row].id
if let index = idValue.index(of: idArr) {
idValue.remove(at: index)
print("ID: \(idValue)")
}
}
}
deinit {
notificationToken?.invalidate()
}
}
Thank you

How do I check app connectivity correctly?

I'm trying to create a function to check the Connectivity when the App loads. If a internet connection is detected, the app should download the JSON data and save the array in UserDefaults, then proceed to the UITableView methods. However, if the internet connection is not found, the app should recover the array on USerDefault to populate another array, then proceed to UItableView methods.
The problem I'm facing, is that when the compiler goes trough UserDefault line to be able to save the array, the app crashes immediately. What I'm doing wrong?
Compiler Error:
) for key backupSaved'
*** First throw call stack: (0x18257ad8c 0x1817345ec 0x18257ac6c 0x1825b1d08 0x1824e730c 0x1824e5a60 0x1825b2080 0x182515cec
0x1825b2080 0x1825b2304 0x182518d6c 0x182518588 0x182518c54
0x1825bc218 0x1825bf8a0 0x182edaaf4 0x102a794c0 0x102a7452c
0x102e8d314 0x102e45b7c 0x103da11dc 0x103da119c 0x103da5d2c
0x182523070 0x182520bc8 0x182440da8 0x184425020 0x18c45d758
0x102a756b0 0x181ed1fc0) libc++abi.dylib: terminating with uncaught
exception of type NSException (lldb)
[FIXED]ViewController:
import UIKit
import Kingfisher
import Alamofire
var arrCerveja = [Cerveja]()
var arrBackup = [Cerveja]()
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
//TableView DataSource
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if Connectivity.isConnectedToInternet {
return arrCerveja.count
} else {
return arrBackup.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellID") as! TableViewCell
if Connectivity.isConnectedToInternet {
let model = arrCerveja[indexPath.row]
cell.labelName.text = model.name
cell.labelDetail.text = "Teor alcoólico: \(model.abv)"
let resource = ImageResource(downloadURL: URL(string: "\(model.image_url)")!, cacheKey: model.image_url)
cell.imageViewCell.kf.setImage(with: resource, placeholder: UIImage(named: "icons8-hourglass-48"), options: nil, progressBlock: nil, completionHandler: nil)
return cell
} else {
let model = arrBackup[indexPath.row]
cell.labelName.text = model.name
cell.labelDetail.text = "Teor alcoólico: \(model.abv)"
let resource = ImageResource(downloadURL: URL(string: "\(model.image_url)")!, cacheKey: model.image_url)
cell.imageViewCell.kf.setImage(with: resource, placeholder: UIImage(named: "icons8-hourglass-48"), options: nil, progressBlock: nil, completionHandler: nil)
return cell
}
}
//TableView Delegate
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if Connectivity.isConnectedToInternet {
performSegue(withIdentifier: "segueId", sender:arrCerveja[indexPath.row])
} else {
performSegue(withIdentifier: "segueId", sender:arrBackup[indexPath.row])
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "segueId" {
let des = segue.destination as? TableViewDetalhes
//.item possui uma propriedade instanciada na TelaDetalheProdutos
des?.item = (sender as? Cerveja)
//Segue para CollectionView Categorias
}
}
struct Connectivity {
static let sharedInstance = NetworkReachabilityManager()!
static var isConnectedToInternet:Bool {
return self.sharedInstance.isReachable
}
}
override func viewDidAppear(_ animated: Bool) {
if Connectivity.isConnectedToInternet {
print("Connected")
getApiData { (cerveja) in
arrCerveja = cerveja
//Backup
do{
let data = try JSONEncoder().encode(arrCerveja)
UserDefaults.standard.set(data, forKey: "backupSaved")
//
self.tableView.reloadData()
}catch{print(error)
}
}
} else {
print("No Internet")
do{
if let savedData = UserDefaults.standard.value(forKey: "backupSaved") as? Data {
arrBackup = try JSONDecoder().decode([Cerveja].self, from: savedData)
self.tableView.reloadData()
}
}catch{
print(error)
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
//SetupNavBarCustom
navigationController?.navigationBar.setupNavigationBar()
}
}
Model:
struct Cerveja:Decodable{
let name:String
let image_url:String
let description:String
let tagline:String
let abv:Double
let ibu:Double?
}
The array should be endcoded to Data before saving , as it's array of custom objects
do {
....... write
let data = try JSONEncoder().encode(arr)
UserDefaults.standard.set(data, forKey: "backupSaved")
// save as data
....... read
if let savedData = UserDefaults.standard.value(forKey: "backupSaved") as? Data {
let savedArr = try JSONDecoder().decode([Cerveja].self, from: savedData)
// use the array here
}
}
catch {
print(error)
}
//
Since now you encode & decode
struct Cerveja:Codable {--}
//
Also I don't vote for saving in userDefaults consider CoreData

iOS RealmSwift storage with Swift 4

I am trying to use RealmSwift in order to save items to the phone storage in Swift 4. I have two different Views; one for the save functionality and another which will display all saved items into a TableView. I have a buildable form coded but i am throwing an error Thread 1: signal SIGABRT specifically on the line when i call realm.add. When i am in my view which is saving, i am using a IBAction with a button to initiate the save functionality. Can anyone help me with this issue? I think the issue is when i set the var of realm however i am unsure.
UPDATE:
I have changed my implementation to reflect the idea given in this thread about my original issue. After doing so, when the call to add the item to the realm is called i crash EXC_BAD_ACCESS (code=EXC_I386_GPFLT) inside the source code of the API. Specifically I crash at this function of the API
//CODE EXCERPT FROM REALMSWIFT API
// Property value from an instance of this object type
id value;
if ([obj isKindOfClass:_info.rlmObjectSchema.objectClass] &&
prop.swiftIvar) {
if (prop.array) {
return static_cast<RLMListBase *>(object_getIvar(obj,
prop.swiftIvar))._rlmArray;
}
else { // optional
value = static_cast<RLMOptionalBase *>(object_getIvar(obj,
prop.swiftIvar)).underlyingValue; //CRASH OCCURS HERE!!!!!!!!
}
}
else {
// Property value from some object that's KVC-compatible
value = RLMValidatedValueForProperty(obj, [obj
respondsToSelector:prop.getterSel] ? prop.getterName : prop.name,
_info.rlmObjectSchema.className);
}
return value ?: NSNull.null;
import UIKit
import RealmSwift
class DetailsViewController: UIViewController {
var titleOfBook: String?
var author: String?
#IBAction func SavetoFavorites(_ sender: Any) {
DispatchQueue.global().async { [weak self] in
guard let strongSelf = self else { return }
guard let realm = try? Realm() else {
return
}
let newItem = Favorites()
newItem.title = strongSelf.titleOfBook
newItem.author = strongSelf.author
try? realm.write {
realm.add(newItem) // Crashes on this line
}
}
}
import UIKit
import RealmSwift
final class Favorites: Object {
var title: String?
var author: String?
}
class FavoritesTableViewController: UITableViewController {
var items: Array<Favorites> = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UITableViewCell.self, forCellReuseIdentifier:
"cell")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 0
}
override func tableView(_ tableView: UITableView?,
numberOfRowsInSection section: Int) -> Int {
return items.count
}
override func tableView(_ tableView: UITableView, cellForRowAt
indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell",
for: indexPath)
let item = items[indexPath.row]
cell.textLabel?.text = item.title
cell.detailTextLabel?.text = item.author
return cell
}
var selectedIndexPath: NSIndexPath = NSIndexPath()
override func tableView(_ tableView: UITableView, willSelectRowAt
indexPath: IndexPath) -> IndexPath? {
selectedIndexPath = indexPath as NSIndexPath
return indexPath
}
You have to wrap realm.add(newItem) inside a transaction:
try! realm.write {
realm.add(newItem)
}
Please note, that write transactions block the thread they are made on so if you're writing big portions of data you should do so on background thread (realm has to be instantiated on that thread too). You could do it like this:
#IBAction func saveToFavorites(_ sender: Any) {
DispatchQueue.global().async { [weak self] in
guard let strongSelf = self else { return }
guard let realm = try? Realm() else {
// avoid force unwrap, optionally report an error
return
}
let newItem = Favorites()
newItem.title = strongSelf.titleOfBook
newItem.author = strongSelf.author
try? realm.write {
realm.add(newItem)
}
}
}
Update: I haven't noticed that you have an issue with your model too – since Realm is written with Objective C you should mark your model properties with #objc dynamic modifiers:
final class Favorites: Object {
#objc dynamic var title: String?
#objc dynamic var author: String?
}
All changes to Realm managed objects (either creation, modification or deletion) need to happen inside write transactions.
do {
try realm.write {
realm.add(newItem)
}
} catch {
//handle error
print(error)
}
For more information, have a look at the writes section of the official docs.
Another problem you have in there is that in your Favorites class properties are missing #objc dynamic attributes. You can read about why you need that in realm docs.
Your code should look like this then:
final class Favorites: Object {
#objc dynamic var title: String?
#objc dynamic var author: String?
}

Realm Exception - updates on Realm objects, persisted or not

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()
}
}
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()

Resources