Saving Array to Realm in Swift? - swift

Is it possible to save an array of objects to Realm? Anytime I make a change to the array it should be saved to Realm.
My current solution is to save object for object with a for loop. For append/modifying objects calling save() will do the job, but not when I remove an object from it.
class CustomObject: Object {
dynamic var name = ""
dynamic var id = 0
override static func primaryKey() -> String? {
return "id"
}
}
struct RealmDatabase {
static var sharedInstance = RealmDatabase()
var realm: Realm!
let object0 = CustomObject()
let object1 = CustomObject()
var array = [object0, object1]
init() {
self.realm = try! Realm()
}
func save() {
for object in self.array {
try! self.realm.write {
self.realm.add(object, update: true)
}
}
}
}

To save lists of objects you have to use a Realm List, not a Swift Array.
let objects = List<CustomObject>()
Then, you can add elements:
objects.append(object1)
Take a look at to many relationships and Collections sections of the official docs.

Swift 3
func saveRealmArray(_ objects: [Object]) {
let realm = try! Realm()
try! realm.write {
realm.add(objects)
}
}
And then call the function passing an array of realm 'Object's:
saveRealmArray(myArray)
Note: realm.add(objects) has the same syntax of the add function for a single object, but if you check with the autocompletion you'll see that there is: add(objects: Sequence)

Related

How can I append an new Element to an Array in the Userdefaults (SwiftUI)

I wanna have an String Array in the Userdefaults, which I can edit. That means I want to append new Items to this Array.Here is some example Code
var defaults = UserDefaults.standard
defaults.array(forKey: "myArray")?.append("NewElement")
But I get this Error Code:
Cannot use mutating member on immutable value: function call returns immutable value
How can I handle that? Do you need more information? Thanks for your time
Boothosh
You need to assign the result of the function to variable before you can modify it
if var array = defaults.array(forKey: "myArray") {
array.append("NewElement")
}
You can extend UserDefaults and create a computed property with a getter and a setter. This way you can directly access your array straight from disk:
extension UserDefaults {
var myStringArray: [String] {
get { stringArray(forKey: "myStringArray") ?? [] }
set { set(newValue, forKey: "myStringArray") }
}
}
Usage:
UserDefaults.standard.myStringArray.append("NewElement")
UserDefaults.standard.myStringArray // ["NewElement"]
You can save or read an array, but you should make your job finish before saving it, and then you can start using or adding new data after reading.
PS: I used SwiftUI for show case, but saveData() and readData() functions are usable in UIKit as well.
import SwiftUI
struct ContentView: View {
var body: some View {
VStack(spacing: 20) {
Button("saveData") { saveData() }
Button("readData") { readData() }
}
.font(Font.body.weight(Font.Weight.bold))
}
}
func saveData() {
var array: [String] = [String]()
array.append("a")
array.append("b")
array.append("c")
UserDefaults.standard.set(array, forKey: "array")
print("saved!")
}
func readData() {
if let unwrappedData = UserDefaults.standard.stringArray(forKey: "array") {
var array: [String] = unwrappedData
array.append("d")
print(array)
}
}

Local Realm dbs for multiple users

I'm trying to implement a user based realm db. So each time a new users registers, a Realm file is created with his UID (from Firebase), or if exists, accesses that Realm.
To do that i created a singletone for the Realm calls and setting the Realm Configuration.
My issue now is that, every time i switch the users, the realm path changes (i'm printing after users logged in), but the Results reading are the same, as the users didn't change, or the realm did not switch. To be more clear, the queries results are giving same data.
If i open the actual Realms, the data is different.
I'm seeing this behavior in my tableView, just by loading data.
What am i doing wrong?
RealmServices:
import Foundation
import RealmSwift
import Firebase
class RealmServices {
private init() {}
static let shared = RealmServices()
let userID = Auth.auth().currentUser!.uid
var realm = try! Realm(configuration: Realm.Configuration.init(
fileURL: Realm.Configuration().fileURL!.deletingLastPathComponent().appendingPathComponent("\(Auth.auth().currentUser!.uid).realm")))
func create<T:Object> (_ object: T) {
do {
try realm.write {
realm.add(object)
}
} catch {
post(error)
}
}
func update <T: Object>(_ object: T, with dictionary: [String : Any?]) {
do {
try realm.write {
for (key,value) in dictionary {
object.setValue(value, forKey: key)
}
}
} catch {
post (error)
}
}
func delete<T:Object> (_ object: T) {
do {
try realm.write{
realm.delete(object)
}
} catch {
post (error)
}
}
func post (_ error: Error) {
NotificationCenter.default.post(name: NSNotification.Name("RealmError"), object: error)
}
func observeRealmErrors (in vc: UIViewController, completion: #escaping (Error?)-> Void) {
NotificationCenter.default.addObserver(forName: NSNotification.Name("RealmError"), object: nil, queue: nil) { (Notification) in
completion(Notification.object as? Error)
}
}
func stopObservingError (in vc: UIViewController){
NotificationCenter.default.removeObserver(vc, name: NSNotification.Name("RealmError"), object: nil)
}
}
Here's what I think is going on (I could be totally off but let me give it a shot)
Your RealmServices is a singleton and initialized only once. It's not a subclass where each time one is created, it's initialized
So any var's are set up and populated on first run - the realm property is not going to re-initialize each time it's accessed. For example, take this singleton
final class MySingleton {
private init() {}
var someUuid = UUID().uuidString
static let sharedInstance = MySingleton()
}
as you can see, someUuId is a 'randomly' generated uuid. If you access that property twice, it presents the same value, there's nothing to force an update.
print(MySingleton.sharedInstance.someUuid)
print(MySingleton.sharedInstance.someUuid)
results in
EBE131DE-B574-4CE1-8D74-E680D80A577B
EBE131DE-B574-4CE1-8D74-E680D80A577B
So, the solution is to set the users uid property in your question to a different value each time you want it changed. Here's a quick example
final class RealmService {
private init() {}
var uid: String!
var usersRealm: Realm! {
let path = Realm.Configuration().fileURL!.deletingLastPathComponent().appendingPathComponent("\(uid!).realm")
let realm = try! Realm(configuration: Realm.Configuration.init(fileURL: path))
return realm
}
static let sharedInstance = RealmService()
}
and here's how it's called; in this example we write item0 to user0's realm and item1 to user1's realm:
RealmService.sharedInstance.uid = "uid_0"
let user0Realm = RealmService.sharedInstance.usersRealm!
let item0 = ItemClass(name: "item 0")
try! user0Realm.write {
user0Realm.add(item0)
}
RealmService.sharedInstance.uid = "uid_1"
let user1Realm = RealmService.sharedInstance.usersRealm!
let item1 = ItemClass(name: "item 1")
try! user1Realm.write {
user1Realm.add(item1)
}
the result is a realm being created for each user with item0 in user0's realm and item1 being in user1's realm

Why would I return an item from a function if I append to a global array within the function?

I'm writing an application from a tutorial that is a to-do list but I'm a little confused on why its setup this way and could use other perspectives.
Why would I return an item from a function if I append to a global array within the function? Why wouldn't we just query the array for this item. I'm not catching onto why this item is being returned or how its being used.
import Foundation
import UIKit
class ItemStore {
var allItems = [Item]()
func createItem() -> Item {
//use a constant to initialize a new item using the Item class's syntax
let newItem = Item.init(random: true)
//now append it to this class's allItems array
allItems.append(newItem)
//now according to this function we need to return an Item object. Now return newItem....but why are we returning it if we just appended it to the allItems array?? Can't we just query the array for this item? I wonder how this returned item is going to be used now
return newItem
}
//now I'm going to add a designated initializer to this class which will add five random items to the allItems array....arrgghh, whhyyy??
init() {
for _ in 0..<5 {
createItem()
}
}
}
Your createItem function should not do any appending. It should do just what is says - create and return a new item. Let the caller determine what to do with the new item. I would refactor your code as:
class ItemStore {
var allItems = [Item]()
func createItem() -> Item {
let newItem = Item(random: true)
return newItem
}
init() {
for _ in 0..<5 {
let item = createItem()
allItems.append(item)
}
}
}
Or you can refactor as:
class ItemStore {
var allItems = [Item]()
func loadItems() {
for _ in 0..<5 {
let item = Item(random: true)
allItems.append(item)
}
}
init() {
loadItems()
}
}
It's best to avoid non-obvious side effects. It's not obvious that a method named createItem would also add that newly created item to some list. Keep methods focused. Put functionality where it belongs.
One reason is to be able to setup other properties in the class.
Let's assume the class has an index property und you want to assign consecutive indices.
init() {
for i in 0..<5 {
let newItem = createItem()
newItem.index = i
}
}
I don't think this is a wrong approach the developer may want to use the added item directly like this
let store = ItemStore()
let item = store.createItem()
this may confuses with how init method is written put he may want the function to do more than one thing

How to update a list of objects in Realm DB Swift

I'm trying to find the right way to update a list in a Realm object. For example:
class Person: Object {
#objc dynamic var id: String = ""
let children = List<Child>()
}
//Adding an item to the list
func add(child: Child, _ completion: (DBError?) -> Void) {
do {
let ctx = try Realm()
if let person = ctx.objects(Person.self).last {
try ctx.write({
person.children.append(child)
completion(nil)
})
} catch {
completion(DBError.unableToAccessDatabase)
}
}
This seems to work for adding an element. So how do I update an individual element in the array as well as replace the whole array to ensure it persists?

How do I share variables inside a singleton class

I am trying to create a common class for storing and retrieving data in Parse. I made the ParseProcessing class a singleton class. From my main View Controller I load the data and store it into a dictionary in the ParseProcessing. I do this by creating a shared instance of the ParseProcessing class. From another view controller I try to access the data from the dictionary. I assumed that because ParseProcessing is a singleton class that I have a single copy of the dictionary. This does not appear to be correct. How should I declare the variables inside the ParseProcessing so that they are shared? The code is shown below:
import UIKit
var gSep = ","
class QwikFileViewController: UIViewController {
var loadData = ParseProcessing.sharedInstance
override func viewDidLoad() {
super.viewDidLoad()
// load data from Parse
loadData.loadCategorySubcategoryData()
loadData.loadRecordsFromParse()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
ParseProcessing Singleton Class
import UIKit
import Parse
class ParseProcessing: Parse {
var dictMenuList = [String:String]()
var noteTitle = [String]()
var notes = [String]()
var thumbnailFiles = [PFFile]()
var objectIds = [String]()
var noteImage = UIImage()
class var sharedInstance:ParseProcessing {
struct singleton {
static let instance:ParseProcessing = ParseProcessing()
}
return singleton.instance
}
// Load Category/Subcategory data from Parse Data Base
func loadRecordsFromParse () -> Bool{
var tmpFile = [PFFile]()
var loadComplete = false
var query = PFQuery(className:"Record")
query.findObjectsInBackgroundWithBlock {
(objects, error) -> Void in
if error == nil {
// The find succeeded.
println("Successfully retrieved \(objects!.count) items.")
for object in objects! {
self.noteTitle.append(object["title"] as! String)
self.notes.append(object["notes"] as! String)
self.thumbnailFiles.append(object["thumbnail"] as! PFFile)
self.objectIds.append(String(stringInterpolationSegment: object.objectId))
}
} else {
println("\(error)")
}
loadComplete = true
}
return loadComplete
}
// Load Category/Subcategory data from Parse Data Base
func loadCategorySubcategoryData () // -> Dictionary <String,String>
{
var success : Bool = false
var d : Dictionary <String,String> = ["":""]
var menu = PFQuery(className: "Classification")
println("ParseProcessing: loadCategory...")
menu.findObjectsInBackgroundWithBlock {
(objects, error) -> Void in
if error == nil {
var category = ""
var subcategory = ""
for object in objects! {
category = object["category"] as! String
println("ParseProcessing: category = \(category)")
subcategory = object["subcategory"] as! String
println("ParseProcessing: subcategory = \(subcategory)")
d[category] = subcategory
}
success = true
self.dictMenuList = d
return
} else {
println("ParseProcessing: error = \(error)")
success = false
}
}
return
}
}
Another View Controller to examine the data
import UIKit
class TestViewController: UIViewController {
var dictMenuList = [String:String]()
var loadData = ParseProcessing.sharedInstance
override func viewDidLoad() {
super.viewDidLoad()
dictMenuList = loadData.dictMenuList
println("dictMenuList: \(dictMenuList)")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
The problem is that findObjectsInBackgroundWithBlock is asynchronous method (i.e. it returns immediately but the closure is called later when the query is done). So you cannot return loadComplete in loadRecordsFromParse, for example. This background request will almost certainly never be done by the time loadRecordsFromParse returns.
Instead, you probably want to adopt the completionHandler pattern. For example, this sample loadRecords doesn't try to return anything immediately, but rather will call the completionHandler when the request is done.
func loadRecords(completionHandler:([SomeObject]?, NSError?) -> ()) {
let query = PFQuery(className: "SomeClass")
query.findObjectsInBackgroundWithBlock { objects, error in
// build some model object
completionHandler(objectArray, error)
}
}
And you'd call it like so:
loadData.loadRecords() { objects, error in
// use `objects` (and make sure `error` is `nil`) here
}
// but do not use those variables here, as the above closure probably has not run yet!
Frankly, I'd be inclined to get rid of those properties in your singleton altogether. When you're dealing with asynchronous code, to have public properties that are updated asynchronously is going to be a source of heartache. You can do it, but it wouldn't be my first choice.
For example, when TestViewController is presented, you cannot assume that the asynchronous fetch associated with dictMenuList is done yet. I look at this and wonder if it makes sense for TestViewController to initiate the fetch itself and then use dictMenuList in the completion handler. That's going to be easiest.
If you must initiate the asynchronous request from one view controller and then have another view controller be informed when that asynchronous request is done, then you might have to use some other pattern, such as notifications (e.g. use NSNotificationCenter, and have the singleton post notifications when the various requests are done, and then any view controller that needs to be informed of this fact can add themselves as observers for that notification).