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?
}
Related
I would like to save this kind of arrays with Core Data:
let crypto1 = Cryptos(name: "Bitcoin", code: "bitcoin", symbol: "BTC", placeholder: "BTC Amount", amount: "0.0")
let crypto2 = Cryptos(name: "Bitcoin Cash", code: "bitcoinCash", symbol: "BCH", placeholder: "BCH Amount", amount: "0.0")
Is that even possible?
I know I can create an array to save like that...
let name = "Bitcoin"
let code = "bitcoin"
let symbol = "BTC"
let placeholder = "BTC Amount"
let amount = "0.0"
let cryptos = CryptoArray(context: PersistenceService.context)
cryptos.name = name
cryptos.code = code
cryptos.symbol = symbol
cryptos.placeholder = placeholder
cryptos.amount = amount
crypto.append(cryptos)
PersistenceService.saveContext()
...but this seems pretty inconvenient when a theoretical infinite number of arrays will be created by the user.
What would be the best way for me to save data, load it, edit it and delete it?
This is a question for a tutorial rather than a straight forward answer. I suggest you give some time to read about CoreData. Having said that, your question sounds generic, "Saving array to CoreData in Swift", so I guess it doesn't hurt to explain a simple implementation step by step:
Step 1: Create your model file (.xcdatamodeld)
In Xcode, file - new - file - iOS and choose Data Model
Step 2: Add entities
Select the file in Xcode, find and click on Add Entity, name your entity (CryptosMO to follow along), click on Add Attribute and add the fields you like to store. (name, code, symbol... all of type String in this case). I'll ignore everything else but name for ease.
Step 3 Generate Object representation of those entities (NSManagedObject)
In Xcode, Editor - Create NSManagedObject subclass and follow the steps.
Step 4 Lets create a clone of this subclass
NSManagedObject is not thread-safe so lets create a struct that can be passed around safely:
struct Cryptos {
var reference: NSManagedObjectID! // ID on the other-hand is thread safe.
var name: String // and the rest of your properties
}
Step 5: CoreDataStore
Lets create a store that gives us access to NSManagedObjectContexts:
class Store {
private init() {}
private static let shared: Store = Store()
lazy var container: NSPersistentContainer = {
// The name of your .xcdatamodeld file.
guard let url = Bundle().url(forResource: "ModelFile", withExtension: "momd") else {
fatalError("Create the .xcdatamodeld file with the correct name !!!")
// If you're setting up this container in a different bundle than the app,
// Use Bundle(for: Store.self) assuming `CoreDataStore` is in that bundle.
}
let container = NSPersistentContainer(name: "ModelFile")
container.loadPersistentStores { _, _ in }
container.viewContext.automaticallyMergesChangesFromParent = true
return container
}()
// MARK: APIs
/// For main queue use only, simple rule is don't access it from any queue other than main!!!
static var viewContext: NSManagedObjectContext { return shared.container.viewContext }
/// Context for use in background.
static var newContext: NSManagedObjectContext { return shared.container.newBackgroundContext() }
}
Store sets up a persistent container using your .xcdatamodeld file.
Step 6: Data source to fetch these entities
Core Data comes with NSFetchedResultsController to fetch entities from a context that allows extensive configuration, here is a simple implementation of a data source support using this controller.
class CryptosDataSource {
let controller: NSFetchedResultsController<NSFetchRequestResult>
let request: NSFetchRequest<NSFetchRequestResult> = CryptosMO.fetchRequest()
let defaultSort: NSSortDescriptor = NSSortDescriptor(key: #keyPath(CryptosMO.name), ascending: false)
init(context: NSManagedObjectContext, sortDescriptors: [NSSortDescriptor] = []) {
var sort: [NSSortDescriptor] = sortDescriptors
if sort.isEmpty { sort = [defaultSort] }
request.sortDescriptors = sort
controller = NSFetchedResultsController(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
}
// MARK: DataSource APIs
func fetch(completion: ((Result) -> ())?) {
do {
try controller.performFetch()
completion?(.success)
} catch let error {
completion?(.fail(error))
}
}
var count: Int { return controller.fetchedObjects?.count ?? 0 }
func anyCryptos(at indexPath: IndexPath) -> Cryptos {
let c: CryptosMO = controller.object(at: indexPath) as! CryptosMO
return Cryptos(reference: c.objectID, name: c.name)
}
}
All we need from an instance of this class is, number of objects, count and item at a given indexPath. Note that the data source returns the struct Cryptos and not an instance of NSManagedObject.
Step 7: APIs for add, edit and delete
Lets add this apis as an extension to NSManagedObjectContext:
But before that, these actions may succeed or fail so lets create an enum to reflect that:
enum Result {
case success, fail(Error)
}
The APIs:
extension NSManagedObjectContext {
// MARK: Load data
var dataSource: CryptosDataSource { return CryptosDataSource(context: self) }
// MARK: Data manupulation
func add(cryptos: Cryptos, completion: ((Result) -> ())?) {
perform {
let entity: CryptosMO = CryptosMO(context: self)
entity.name = cryptos.name
self.save(completion: completion)
}
}
func edit(cryptos: Cryptos, completion: ((Result) -> ())?) {
guard cryptos.reference != nil else {
print("No reference")
return
}
perform {
let entity: CryptosMO? = self.object(with: cryptos.reference) as? CryptosMO
entity?.name = cryptos.name
self.save(completion: completion)
}
}
func delete(cryptos: Cryptos, completion: ((Result) -> ())?) {
guard cryptos.reference != nil else {
print("No reference")
return
}
perform {
let entity: CryptosMO? = self.object(with: cryptos.reference) as? CryptosMO
self.delete(entity!)
self.save(completion: completion)
}
}
func save(completion: ((Result) -> ())?) {
do {
try self.save()
completion?(.success)
} catch let error {
self.rollback()
completion?(.fail(error))
}
}
}
Step 8: Last step, use case
To fetch the stored data in main queue, use Store.viewContext.dataSource.
To add, edit or delete an item, decide if you'd like to do on main queue using viewContext, or from any arbitrary queue (even main queue) using newContext or a temporary background context provided by Store container using Store.container.performInBackground... which will expose a context.
e.g. adding a cryptos:
let cryptos: Cryptos = Cryptos(reference: nil, name: "SomeName")
Store.viewContext.add(cryptos: cryptos) { result in
switch result {
case .fail(let error): print("Error: ", error)
case .success: print("Saved successfully")
}
}
Simple UITableViewController that uses the cryptos data source:
class ViewController: UITableViewController {
let dataSource: CryptosDataSource = Store.viewContext.dataSource
// MARK: UITableViewDataSource
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataSource.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return tableView.dequeueReusableCell(withIdentifier: "YourCellId", for: indexPath)
}
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let cryptos: Cryptos = dataSource.anyCryptos(at: indexPath)
// TODO: Configure your cell with cryptos values.
}
}
You cannot save arrays directly with CoreData, but you can create a function to store each object of an array. With CoreStore the whole process is quite simple:
let dataStack: DataStack = {
let dataStack = DataStack(xcodeModelName: "ModelName")
do {
try dataStack.addStorageAndWait()
} catch let error {
print("Cannot set up database storage: \(error)")
}
return dataStack
}()
func addCrypto(name: String, code: String, symbol: String, placeholder: String, amount: Double) {
dataStack.perform(asynchronous: { transaction in
let crypto = transaction.create(Into<Crypto>())
crypto.name = name
crypto.code = code
crypto.symbol = symbol
crypto.placeholder = placeholder
crypto.amount = amount
}, completion: { _ in })
}
You can show the objects within a UITableViewController. CoreStore is able to automatically update the table whenever database objects are added, removed or updated:
class CryptoTableViewController: UITableViewController {
let monitor = dataStack.monitorList(From<Crypto>(), OrderBy(.ascending("name")), Tweak({ fetchRequest in
fetchRequest.fetchBatchSize = 20
}))
override func viewDidLoad() {
super.viewDidLoad()
// Register self as observer to monitor
self.monitor.addObserver(self)
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.monitor.numberOfObjects()
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CryptoTableViewCell", for: indexPath) as! CryptoTableViewCell
let crypto = self.monitor[(indexPath as NSIndexPath).row]
cell.update(crypto)
return cell
}
}
// MARK: - ListObjectObserver
extension CryptoTableViewController : ListObjectObserver {
// MARK: ListObserver
func listMonitorWillChange(_ monitor: ListMonitor<Crypto>) {
self.tableView.beginUpdates()
}
func listMonitorDidChange(_ monitor: ListMonitor<Crypto>) {
self.tableView.endUpdates()
}
func listMonitorWillRefetch(_ monitor: ListMonitor<Crypto>) {
}
func listMonitorDidRefetch(_ monitor: ListMonitor<Crypto>) {
self.tableView.reloadData()
}
// MARK: ListObjectObserver
func listMonitor(_ monitor: ListMonitor<Crypto>, didInsertObject object: Switch, toIndexPath indexPath: IndexPath) {
self.tableView.insertRows(at: [indexPath], with: .automatic)
}
func listMonitor(_ monitor: ListMonitor<Crypto>, didDeleteObject object: Switch, fromIndexPath indexPath: IndexPath) {
self.tableView.deleteRows(at: [indexPath], with: .automatic)
}
func listMonitor(_ monitor: ListMonitor<Crypto>, didUpdateObject object: Crypto, atIndexPath indexPath: IndexPath) {
if let cell = self.tableView.cellForRow(at: indexPath) as? CryptoTableViewCell {
cell.update(object)
}
}
func listMonitor(_ monitor: ListMonitor<Crypto>, didMoveObject object: Switch, fromIndexPath: IndexPath, toIndexPath: IndexPath) {
self.tableView.deleteRows(at: [fromIndexPath], with: .automatic)
self.tableView.insertRows(at: [toIndexPath], with: .automatic)
}
}
Assuming that you have a CryptoTableViewCell with the function update registered to the CryptoTableViewController.
I want to initialize an instance of a struct called TaskList in my TableViewController, but I'm getting a "Use of unresolved identifier 'tasks'" error every place I used 'tasks'. It worked fine when I was declaring the var tasks within the class, but now that it's an initialization of a var declared in another .swift file, I'm getting that error. I'm just learning Swift so I suspect this has something to do with the architecture or messing up how to call an object from another file. Does anyone know what I need to do to fix it? Any help would be greatly appreciated! Thanks everybody!
Here's the UITableViewController code:
import UIKit
class LoLFirstTableViewController: UITableViewController {
//var tasks:[Task] = taskData
// Hashed out the above line (even though it worked) because replacing with
// the below line because trying to get instance of TaskList containing all
// properties instead of just the tasks as well as to allow multiple instances
// of TaskList
let exampleList = TaskList(buddy: exampleBuddy, phoneNumber: examplePhoneNumber, tasks: exampleTaskData)
override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 60.0
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tasks.count
}
#IBAction func cancelToLoLFirstTableViewController(_ segue:UIStoryboardSegue) {
}
#IBAction func saveAddTask(_ segue:UIStoryboardSegue) {
if let AddTaskTableViewController = segue.source as? AddTaskTableViewController {
if let task = AddTaskTableViewController.task {
tasks.append(task)
let indexPath = IndexPath(row: tasks.count-1, section: 0)
tableView.insertRows(at: [indexPath], with: .automatic)
}
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TaskCell", for: indexPath) as! TaskCell
let task = tasks[indexPath.row]
cell.task = task
if cell.accessoryView == nil {
let cb = CheckButton()
cb.addTarget(self, action: #selector(buttonTapped(_:forEvent:)), for: .touchUpInside)
cell.accessoryView = cb
}
let cb = cell.accessoryView as! CheckButton
cb.check(tasks[indexPath.row].completed)
return cell
}
func buttonTapped(_ target:UIButton, forEvent event: UIEvent) {
guard let touch = event.allTouches?.first else { return }
let point = touch.location(in: self.tableView)
let indexPath = self.tableView.indexPathForRow(at: point)
var tappedItem = tasks[indexPath!.row] as Task
tappedItem.completed = !tappedItem.completed
tasks[indexPath!.row] = tappedItem
tableView.reloadRows(at: [indexPath!], with: UITableViewRowAnimation.none)
}
}
And here's the code for the TaskList struct, if that helps:
import UIKit
struct TaskList {
var buddy: String?
var phoneNumber: String?
var tasks: [Task]
init(buddy: String?, phoneNumber: String?, tasks: [Task]) {
self.buddy = buddy
self.phoneNumber = phoneNumber
self.tasks = tasks
}
}
In your struct the tasks array is not optional it means you have to pass an initialised tasks Array. Pass an initialised array or change your task array to optional as you have done with buddy and phoneNumber.
import UIKit
struct TaskList {
var buddy: String?
var phoneNumber: String?
// add a question mark to make your array of tasks optional
var tasks: [Task]?
init(buddy: String?, phoneNumber: String?, tasks: [Task]) {
self.buddy = buddy
self.phoneNumber = phoneNumber
self.tasks = tasks
}
}
Note: when you're using struct you can leave out the initialzer it will generate one automatically
import UIKit
struct TaskList {
var buddy: String?
var phoneNumber: String?
var tasks: [Task]?
}
now in your viewController initialise the your example list
// as your tasks array is optional now even if you pass in a nil it will not crash but it will not have a tasks array
let exampleList = TaskList(buddy: exampleBuddy, phoneNumber: examplePhoneNumber, tasks: exampleTaskData)
remember to unwrap your array before using it otherwise your app can crash again if tasks array is nil. use if let or guard
if let tasks = exampleList.tasks {
// now you can use your tasks array
}
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
}
I have structured my 2D array data model after the example provided by the GroupedTableView example by Realm. However, I am trying to implement the feature to reorder tasks in my tasksByPriority array and am not sure about how to go about inserting at a specific array. At the moment, the Realm Results array is being sorted by dateCreated and I know only how to add new tasks to Realm at the end of the Realm object, not at a specific index.
import UIKit
import MGSwipeTableCell
import RealmSwift
import Realm
import AEAccordion
class TasksTableViewController: UITableViewController {
var realm: Realm!
var notificationToken: NotificationToken?
var priorityIndexes = [0, 1, 2, 3]
var priorityTitles = ["Urgent | Important", "Urgent | Not Important", "Not Urgent | Important", "Not Urgent | Not Important"]
var tasksByPriority = [Results<Task>]()
override func viewDidLoad() {
super.viewDidLoad()
realm = try! Realm()
notificationToken = realm.addNotificationBlock { [unowned self] note, realm in
self.tableView.reloadData()
}
for priority in priorityIndexes {
let unsortedObjects = realm.objects(Task.self).filter("priorityIndex == \(priority)")
let sortedObjects = unsortedObjects.sorted("dateCreated", ascending: true)
tasksByPriority.append(sortedObjects)
}
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return priorityIndexes.count
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return Int(tasksByPriority[section].count)
}
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return priorityTitles[section]
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("taskTableViewCell", forIndexPath: indexPath) as! TaskTableViewCell
// Configure the cell...
let task = self.taskForIndexPath(indexPath)
//configure right buttons
cell.rightButtons = [MGSwipeButton(title: "", icon: UIImage(named:"deleteTask.png"), backgroundColor: UIColor(red: 0, green: 0, blue: 0, alpha: 0), callback: {
(sender: MGSwipeTableCell!) -> Bool in
RealmHelper.deleteTask(self.taskForIndexPath(indexPath)!)
return true
})]
return cell
}
func taskForIndexPath(indexPath: NSIndexPath) -> Task? {
return tasksByPriority[indexPath.section][indexPath.row]
}
// MARK: Segues
#IBAction func unwindToTasksTableViewController(sender: UIStoryboardSegue) {
if let sourceViewController = sender.sourceViewController as? DisplayTaskViewController, task = sourceViewController.task, priorityIndex = sourceViewController.priorityIndex {
if let selectedIndexPath = tableView.indexPathForSelectedRow {
// Update an existing task.
if selectedIndexPath.section == priorityIndex { // not changing priority
RealmHelper.updateTask(taskForIndexPath(selectedIndexPath)!, newTask: task)
}
else { // changing priority
RealmHelper.addTask(task)
RealmHelper.deleteTask(taskForIndexPath(selectedIndexPath)!)
}
}
else {
RealmHelper.addTask(task)
}
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let identifier = segue.identifier {
if identifier == "editTaskDetailsSegue" {
let displayTaskViewController = segue.destinationViewController as! DisplayTaskViewController
// set task of DisplayTaskViewController to task tapped on
if let selectedTaskCell = sender as? TaskTableViewCell {
let indexPath = tableView.indexPathForCell(selectedTaskCell)!
let selectedTask = taskForIndexPath(indexPath)
displayTaskViewController.task = selectedTask
print("Task completed: \(selectedTask!.completed)")
displayTaskViewController.priorityIndex = indexPath.section
displayTaskViewController.completed = (selectedTask?.completed)!
}
}
else if identifier == "addNewTaskSegue" {
}
}
}
}
Realm Helper Class
import Foundation
import RealmSwift
class RealmHelper {
static func addTask(task: Task) {
let realm = try! Realm()
try! realm.write() {
realm.add(task)
}
}
static func deleteTask(task: Task) {
let realm = try! Realm()
try! realm.write() {
realm.delete(task)
}
}
static func updateTask(taskToBeUpdated: Task, newTask: Task) {
let realm = try! Realm()
try! realm.write() {
taskToBeUpdated.text = newTask.text
taskToBeUpdated.details = newTask.details
taskToBeUpdated.dueDate = newTask.dueDate
taskToBeUpdated.completed = newTask.completed
}
}
}
TL;DR
Objects in Realm are unordered. If you do want to sort the objects you can use List<T> type. A List can be mutated, you can insert(_:atIndex:), removeAtIndex(_:) and so on. Here is the List Class Reference, where you can find out how use it.
I am using Swift with Realm to build an unidirectional data flow App.
I am wondering why I can not use an object as current application state.
var people is always updated when I add new person but var oldestPerson is never updated.
This is my Store.swift file
class Person: Object {
dynamic var name: String = ""
dynamic var age: Int = 0
}
// MARK: Application/View state
extension Realm {
var people: Results<Person> {
return objects(Person).sorted("age")
}
var oldestPerson: Person? {
return objects(Person).sorted("age").first
}
}
// MARK: Actions
extension Realm {
func addPerson(name: String, age: Int) {
do {
try write {
let person = Person()
person.name = name
person.age = age
add(person)
}
} catch {
print("Add Person action failed: \(error)")
}
}
}
let store = try! Realm()
The state of oldest in my view layer never change but people change as it should.
import RealmSwift
class PersonsTableViewController: UITableViewController {
var notificationToken: NotificationToken?
var people = store.people
var oldest = store.oldestPerson
override func viewDidLoad() {
super.viewDidLoad()
updateView()
notificationToken = store.addNotificationBlock { [weak self] (_) in
self?.updateView()
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return people.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("PersonTableViewCell") as! PersonTableViewCell
cell.person = people[indexPath.row]
return cell
}
func updateView() {
print(oldest)
tableView.reloadData()
}
}
Change your declaration of
var oldest = store.oldestPerson
to:
var oldest: Person? { return store.oldestPerson }