How To Save Only One Instance Of Class In Realm - swift

So instead of using user defualts I want to persist some settings using Realm.
I've created a class for the settings
import Foundation
import RealmSwift
class NutritionSettings: Object {
#objc dynamic var calories: Int = 0
#objc dynamic var proteins: Int = 0
#objc dynamic var carbohydrates: Int = 0
#objc dynamic var fats: Int = 0
}
But in my view controller I don't know how to save just one instance of it
I've tried
let realm = try! Realm()
let settings = NutritionSettings()
do {
try realm.write{
settings.calories = cals!
settings.carbohydrates = carbs!
settings.fats = fats!
settings.proteins = proteins!
}
} catch {
print("error saving settings")
}
Since I know doing realm.add would just add another NutritionSettings object which is not what I want. I was unable to clarify anything using the documentation. Any help would be appreciated thanks.

I faced a similar issue in my project when I tried to save a user session object. If you want to save a unique object, override the primaryKey() class method and set the unique key for it.
#objcMembers class NutritionSettings: Object {
static let uniqueKey: String = "NutritionSettings"
dynamic var uniqueKey: String = NutritionSettings.uniqueKey
dynamic var calories: Int = 0
override class func primaryKey() -> String? {
return "uniqueKey"
}
}
Then to receive the object just use the unique key.
// Saving
let settings = NutritionSettings()
settings.calories = 100
do {
let realm = try Realm()
try realm.write {
realm.add(settings, update: .modified)
}
} catch {
// Error handling
}
// Reading
var settings: NutritionSettings?
do {
let realm = try Realm()
let key = NutritionSettings.uniqueKey
settings = realm.object(ofType: NutritionSettings.self, forPrimaryKey: key)
} catch {
// Error handling
}
if let settings = settings {
// Do stuff
}
Hope it will help somebody.

If you look at the example realm provides https://realm.io/docs/swift/latest you can see that in order to only save one object you still have to do an add. Once you have added the object to the database you can fetch that object and do a write that modifies the internal properties
let realm = try! Realm()
let settings = NutritionSettings()
settings.id = 1
do {
try realm.write{
realm.add(settings)
}
} catch {
print("error saving settings")
}
Next you can fetch and modify that single instance that you saved
let realm = try! Realm()
let settings = realm.objects(NutritionSettings.self).filter("id == 1").first
do {
try realm.write{
settings.calories = cals!
settings.carbohydrates = carbs!
settings.fats = fats!
settings.proteins = proteins!
}
} catch {
print("error saving settings")
}

Related

Saving objects to Realm

I try to save some objects to a Realm, but even after setting the member variables and saving it - after reading the Realm, the objects have their default values.
class Person: Object {
#objc dynamic var Name: String = "test"
#objc dynamic var Age: Int = 0;
}
do {
let realm = try Realm();
var p = Person();
p.Name = "Poirot"
p.Age = 55
try realm.write {
realm.add(p)
print("Person:",p.Name); // <-- Here its correct: "Poirot"
}
} catch let error {
print(error.localizedDescription)
}
do {
let realm = try Realm()
let data = realm.objects(Person.self)
for persons in data {
print("Person:", persons.Name); // <-- Here its wrong: "test"
}
} catch let error {
print(error.localizedDescription)
}
After running this, it would print:
Person: Poirot
Person: test
Can someone explain me this behavior and tell me what I´m doing wrong?
Thank you!
If you run your code in foreground it is fine.
If you are on a background thread (a thread without a RunLoop) than you need to refresh your realm.
let realm = try Realm()
realm.refresh()
let data = realm.objects(Person.self)
for persons in data {
print("Person:", persons.Name); // <-- Here its wrong: "test"
}
Have a look at the Realm threading documentation and scroll down to Refreshing Realms

Swift Realm change only one object value

class User: Object {
#objc dynamic var id = ""
#objc dynamic var dateFirstStart:TimeInterval = 0
//dates
#objc dynamic var dateLastStart:TimeInterval = 0
#objc dynamic var dateLastAppClose:TimeInterval = 0
#objc dynamic var dateLastDataUpdateCheck:TimeInterval = 0
#objc dynamic var dateLastFilesUpdateCheck:TimeInterval = 0
override class func primaryKey() -> String? {
return "id"
}
}
Do I really have to create a function for each value to change? Like this:
func updateUserDateFirstStart(date:Date){
do {
let realm = try Realm()
try realm.write {
let user = getUser()
user. dateLastStart = Date().timeIntervalSince1970
}
} catch let error as NSError {
print("ERROR \(error.localizedDescription)")
}
}
What I want is something like
let user = getUser()
user.dateLastStart = Date().timeIntervalSince1970
dataManager.updateUser(user)
And in my DataManager:
func updateUser(user:User){
do {
let realm = try Realm()
try realm.write {
realm.add(user, update: true)
}
} catch let error as NSError {
print("ERROR \(error.localizedDescription)")
}
}
But if I do it as you can see in my wishtohave solution I always get an Attempting to modify object outside of a write transaction error.
I tried to create a complete new Object and use the id from the object I want to change. This works but would need even more lines of code.
You can use KVO to update one value in realm object
how to call
let user = getUser()
self.update(ofType:user, value: Date().timeIntervalSince1970 as AnyObject, key: "dateLastStart")
Helper func
func update(ofType:Object,value:AnyObject,key:String)->Bool{
do {
let realm = try Realm()
try realm.write {
ofType.setValue(value, forKeyPath: key)
}
return true
}catch let error as NSError {
fatalError(error.localizedDescription)
}
return false
}

Using Realm with MPMediaQuery

I want to build an Audiobookplayer which can set Bookmarks. Loading the Audiobooks from my Library with MPMediaQuery works fine, but when I take an audiobook off through iTunes, it stays in my realmfile.
I would like realm to delete the entry automatically when the playlist is updated through iTunes, but I can't seem to figure out how.
Here is my code.
class Books: Object {
dynamic var artistName: String?
dynamic var albumTitle: String?
dynamic var artwork: NSData?
dynamic var albumUrl: String?
dynamic var persistentID: String?
let parts = List<BookParts>()
override static func primaryKey() -> String? {
return "persistentID"
}
override class func indexedProperties() -> [String] {
return ["albumTitle"]
}
convenience init(artistName: String, albumTitle: String, albumUrl: String) {
self.init()
self.artistName = artistName
self.albumTitle = albumTitle
self.albumUrl = albumUrl
}
class BookQuery {
let realm = try! Realm()
var bookItems = Array<Books>()
var partItems = Array<BookParts>()
func getBooks() {
let query: MPMediaQuery = MPMediaQuery.audiobooks()
query.groupingType = .album
let collection: [MPMediaItemCollection] = query.collections!
try! realm.write {
for allbooks in collection {
let item = allbooks.representativeItem
let book = Books()
let id = item?.value(forProperty: MPMediaItemPropertyAlbumPersistentID) as! Int
book.artistName = item?.artist
book.albumTitle = item?.albumTitle
book.albumUrl = item?.assetURL?.absoluteString
book.artwork = Helper.getArtwork(item?.artwork) as NSData?
book.persistentID = id.stringValue
realm.add(book, update: true)
guard realm.object(ofType: Books.self, forPrimaryKey: "persistentID") != nil else {
continue
}
bookItems.append(book)
}
}
}
}
I calling the MediaQuery in "viewDidLoad" in my LibraryViewController.
I am pretty new to coding and are trying to solve this for a while.
Thanks for any help.
The high level thing you'll need to do is to have a way to detect when the iTunes playlist is updated and then delete the removed items' corresponding objects from the Realm.
A general approach to this is to get all the "persistent ID"s currently in the Realm at the start of the for loop, put those in an array, remove each ID it sees from the array, then delete objects with the persistent ID in the array that's left, since those weren't in the collection.

Swift Remove Object from Realm

I have Realm Object that save list from the JSON Response. But now i need to remove the object if the object is not on the list again from JSON. How i do that?
This is my init for realm
func listItems (dic : Array<[String:AnyObject]>) -> Array<Items> {
let items : NSMutableArray = NSMutableArray()
let realm = try! Realm()
for itemDic in dic {
let item = Items.init(item: itemDic)
try! realm.write {
realm.add(item, update: true)
}
items.addObject(item)
}
return NSArray(items) as! Array<Items>
}
imagine your Items object has an id property, and you want to remove the old values not included in the new set, either you could delete everything with just
let result = realm.objects(Items.self)
realm.delete(result)
and then add all items again to the realm,
or you could also query every item not included in the new set
let items = [Items]() // fill in your items values
// then just grab the ids of the items with
let ids = items.map { $0.id }
// query all objects where the id in not included
let objectsToDelete = realm.objects(Items.self).filter("NOT id IN %#", ids)
// and then just remove the set with
realm.delete(objectsToDelete)
I will get crash error if I delete like top vote answer.
Terminating app due to uncaught exception 'RLMException', reason: 'Can only add, remove, or create objects in a Realm in a write transaction - call beginWriteTransaction on an RLMRealm instance first.'
Delete in a write transaction:
let items = realm.objects(Items.self)
try! realm!.write {
realm!.delete(items)
}
func realmDeleteAllClassObjects() {
do {
let realm = try Realm()
let objects = realm.objects(SomeClass.self)
try! realm.write {
realm.delete(objects)
}
} catch let error as NSError {
// handle error
print("error - \(error.localizedDescription)")
}
}
// if you want to delete one object
func realmDelete(code: String) {
do {
let realm = try Realm()
let object = realm.objects(SomeClass.self).filter("code = %#", code).first
try! realm.write {
if let obj = object {
realm.delete(obj)
}
}
} catch let error as NSError {
// handle error
print("error - \(error.localizedDescription)")
}
}
What you can do is assign a primary key to the object you are inserting, and when receiving a new parsed JSON you verify if that key already exists or not before adding it.
class Items: Object {
dynamic var id = 0
dynamic var name = ""
override class func primaryKey() -> String {
return "id"
}
}
When inserting new objects first query the Realm database to verify if it exists.
let repeatedItem = realm.objects(Items.self).filter("id = 'newId'")
if !repeatedItem {
// Insert it
}
The first suggestion that comes to mind is to delete all objects before inserting new objects from JSON.
Lear more about deleting objects in Realm at https://realm.io/docs/swift/latest/#deleting-objects
do {
let realm = try Realm()
if let obj = realm.objects(Items.self).filter("id = %#", newId).first {
//Delete must be perform in a write transaction
try! realm.write {
realm.delete(obj)
}
}
} catch let error {
print("error - \(error.localizedDescription)")
}
There is an easier option to remove 1 object:
$item.delete()
remember to have the Observable object like:
#ObservedRealmObject var item: Items
var realm = try! Realm()
open func DeleteUserInformation(){
realm.beginWrite()
let mUserList = try! realm.objects(User.self)
realm.delete(mUserList)
try! realm.commitWrite()
}

Realm with ObjectMapper

I load a list of objects from my server and save them to Realm using ObjectMapper. Each object contains an url defining where to load the image for the object. I load the image and save the imagedata in the realm object so that I don't need to reload it every time. But unfortunately the image data is lost if I reload the data.
I use a primary key and my thought was that when the JSON. I fear that ObjectMapper doesn't update existing objects in Realm but overwrites them. So the imagedata is nil and must be refetched from server. Is there something I can do do prevent this?
Here is my simplified ObjectMapping-File:
import Foundation
import ObjectMapper
import RealmSwift
class OverviewItem: PersistentObject {
override var hashValue : Int {
get {
return self.overviewID.hashValue
}
}
dynamic var overviewID: Int = 0
dynamic var titleDe: String = ""
dynamic var imageUrl: String = ""
dynamic var imageData: NSData?
required convenience init?(_ map: Map) {
self.init()
}
//computed properties
dynamic var image: UIImage? {
get {
return self.imageData == nil ? nil : UIImage(data: self.imageData!)
}
set(newImage){
if let newImage = newImage, data = UIImagePNGRepresentation(newImage){
self.imageData = data
}
else{
self.imageData = nil
}
}
}
//image is a computed property and should be ignored by realm
override class func ignoredProperties() -> [String] {
return ["image"]
}
override func mapping(map: Map) {
overviewID <- map["infoid"]
titleDe <- map["titleDe"]
imageUrl <- map["imageurl"]
}
override class func primaryKey() -> String {
return "overviewID"
}
}
And here how I fetch the image and update the object:
func fetchImage(item: OverviewItem, successHandler: UIImage? ->(), errorHandler: (ErrorType?) -> ()){
AlamofireManager.Configured
.request(.GET, item.imageUrl)
.responseData({ (response: Response<NSData, NSError>) in
if let error = response.result.error{
logger.error("Error: \(error.localizedDescription)")
errorHandler(error)
return
}
if let imageData = response.result.value{
successHandler(UIImage(data: imageData))
let overviewID = item.overviewID
let queue = NSOperationQueue()
let blockOperation = NSBlockOperation {
let writeRealm = try! Realm()
do{
if let itemForUpdate = writeRealm.objects(OverviewItem).filter("overviewID = \(overviewID)").first{
try writeRealm.write{
itemForUpdate.imageData = imageData
writeRealm.add(itemForUpdate, update: true)
}
}
}
catch let err as NSError {
logger.error("Error with realm: " + err.localizedDescription)
}
}
queue.addOperation(blockOperation)
}
})
}
You would need to pull the existing image data by primary key before you add / upsert your object to the Realm. You could do that e.g. in a method, which abstracts ObjectMapper's mapping, but still allows providing a Realm instance.
But in general, I wouldn't recommend to store images in the Realm itself for that purpose. There are some really good image caching frameworks out there, which allow you to cache images on disk and in memory. These allow you in addition to organize the cache by size, they assist you with fast decompression and they allow you to manage expiry times.