Realm Swift - save a reference to another object - ios

I thought this would be pretty straightforward after reading here and here but I'm a bit stuck.
I have a 'favouriteWorkout' object that looks like this :
class FavouriteObject: Object {
#objc dynamic var favouriteWorkoutName = ""
#objc dynamic var workoutReference = WorkoutSessionObject()
override class func primaryKey() -> String? {
return "favouriteWorkoutName"
}
}
What I'm trying to do here is reference a WorkoutSessionObject in Realm that links from a WorkoutName when a workout is saved as a favourite.
My WorkoutSessionObject has a primary key of workoutID which is a UUID string. It looks like this :
class WorkoutSessionObject: Object {
#objc dynamic var workoutID = UUID().uuidString
#objc dynamic var workoutType = ""
let exercises = List<WorkoutExercise>()
#objc dynamic var totalExerciseCount = 0
#objc dynamic var rounds = 0
#objc dynamic var favourite : Bool = false
override class func primaryKey() -> String? {
return "workoutID"
}
}
I've then tried to save using this :
let favouriteWorkout = FavouriteObject()
favouriteWorkout.favouriteWorkoutName = favouriteName
favouriteWorkout.workoutReference = (realm.object(ofType: WorkoutSessionObject.self, forPrimaryKey: self.workoutID))!
do {
try realm.write {
realm.add(favouriteWorkout)
}
} catch {
print ("Error adding favourite")
}
but i get a crash when I run of :
'RLMException', reason: 'The FavouriteObject.workoutReference property must be marked as being optional.
However, when I then try to make it optional (by adding ?) it says
"Cannot use optional chaining on non-optional value of type 'WorkoutSessionObject"!
Summary
I want to save a reference of the workoutID of a WorkoutSessionObject in my FavouriteObject which is an actual link to the WorkoutSessionObject (so the properties can be accessed from favourites)
Update
using the answers below I've now sorted the problem of the workout reference. This is now showing in Realm as the proper format () under "workoutReference". However, I'm now getting "nil" in "workoutReference" when trying to save. I know the workoutID is coming through correctly as I am printing it in the console.

You need to change the declaration of workoutReference. First of all, you need to make it Optional by writing ? after the type. Secondly, you shouldn't assign a default value to it, it needs to be Optional for a reason. The linked docs clearly state that
to-one relationships must be optional
, and workoutReference is clearly a to-one relationship.
class FavouriteObject: Object {
#objc dynamic var favouriteWorkoutName = ""
#objc dynamic var workoutReference:WorkoutSessionObject?
override class func primaryKey() -> String? {
return "favouriteWorkoutName"
}
}

In property-cheatsheet you can see that a non-optional Object-property is not allowed, so you have to change it like the following:
class FavouriteObject: Object {
#objc dynamic var favouriteWorkoutName = ""
// here you have to make the property optional
#objc dynamic var workoutReference: WorkoutSessionObject?
override class func primaryKey() -> String? {
return "favouriteWorkoutName"
}
}

Related

How to get data from list type in the transferred object? (RealmSwift)

I have a realm database model object that I transfer from one controller to another. On another controller, I need to get data from this object, which stores the object of the type List. I need it in 'cellForRowAt' method.
This is my model:
class Route: Object {
#objc dynamic var routeImage: Data?
#objc dynamic var routeName: String?
#objc dynamic var numberOfPersons = 0.0
#objc dynamic var dateOfDeparture: String?
#objc dynamic var dateOfArrival: String?
let placeToVisit = List<Place>()
let person = List<Person>()
}
class Place: Object {
#objc dynamic var placeName = ""
convenience init(placeName: String) {
self.init()
self.placeName = placeName
}
}
on second VC I created:
var currentRoute: Route?
and in viewDidLoad I set:
currentRoute = UserSelectedRoute.shared.selectedRoute!
I can get data from other properties but not from the list type. I tried to implement 'reduce' method, but it doesn't work. It returns list type too. I think I need convert list to type Results but I don't know how I can return values from current object?
cellForRowAt image
As Jay suggested to me, the following solution helped me:
cell.textLabel!.text = currentRoute?.placeToVisit[indexPath.row].placeName

Why does adding a convenience init to a Realm object declaration mess with private values?

I have created a Realm object that needs to store an enum value. To do that I use a method outlined in this question which involves declaring a private property of type String, and then declaring another property of type Enum that sets/reads the private property using getters and setters.
For ease of reference here is the code for that:
#objcMembers
class PlaylistRealmObject: Object {
dynamic var id: String = UUID().uuidString
dynamic var created: Date = Date()
dynamic var title: String = ""
private dynamic var revisionTypeRaw: String = RevisionType.noReminder.rawValue
var revisionType: RevisionType {
get { return RevisionType(rawValue: revisionTypeRaw)! }
set { revisionTypeRaw = newValue.rawValue }
}
let reminders = List<ReminderRealmObject>()
let cardsInPlaylist = List<CardRealmObject>()
override static func primaryKey() -> String? {
return "id"
}
}
I have noticed though that if I add a convenience init to the class declaration (to make it a bit easier to initialise the object) the revisionType properties on the objects I end up with adopt the default value declared in the class, and NOT the revision type value passed to the class using the convenience init.
Here is the class declaration with a convenience init
#objcMembers
class PlaylistRealmObject: Object {
dynamic var id: String = UUID().uuidString
dynamic var created: Date = Date()
dynamic var title: String = ""
private dynamic var revisionTypeRaw: String = RevisionType.noReminder.rawValue
var revisionType: RevisionType {
get { return RevisionType(rawValue: revisionTypeRaw)! }
set { revisionTypeRaw = newValue.rawValue }
}
let reminders = List<ReminderRealmObject>()
let cardsInPlaylist = List<CardRealmObject>()
convenience init(title: String, revisionType: RevisionType) {
self.init()
self.title = title
self.revisionType = revisionType
}
override static func primaryKey() -> String? {
return "id"
}
}
And - to make things even more perplexing - if I simply remove the word 'private' from the revisionTypeRaw property, everything works fine!
I am confused. 1) Why does adding a convenience init have this effect? 2) Why does making the property 'public' resolve the issue?
I have created a demo Xcode project to illustrate the issue and can share it if anyone needs it.
Update:
I found the problem. It has nothing to do with the convenience init. I am using #objcMembers at the top of the class as per the Realm docs: https://realm.io/docs/swift/latest/#property-attributes
If you remove this and place #objc in front of the private keyword, everything works as would be expected. I guess the question then is: what explains this behaviour?
This is a good question but I think the issue is elsewhere in the code. Let's test it.
I created a TestClass that has a Realm managed publicly visible var, name, as well as a non-managed public var visibleVar which is backed by a Realm managed private var, privateVar. I also included a convenience init per the question. The important part is the privateVar is being set to the string "placeholder" so we need to see if that is overwritten.
class TestClass: Object {
#objc dynamic var name = ""
#objc private dynamic var privateVar = "placeholder"
var visibleVar: String {
get {
return self.privateVar
}
set {
self.privateVar = newValue
}
}
convenience init(aName: String, aString: String) {
self.init()
self.name = aName
self.visibleVar = aString
}
}
We then create two instances and save them in Realm
let a = TestClass(aName: "some name", aString: "some string")
let b = TestClass(aName: "another name", aString: "another string")
realm.add(a)
realm.add(b)
Then a button action to load the two objects from Realm and print them.
let testResults = realm.objects(TestClass.self)
for test in testResults {
print(test.name, test.visibleVar)
}
and the output:
some name some string
another name another string
So in this case, the default value of "placeholder" is being overwritten correctly when the instances are being created.
Edit:
A bit more info.
By defining your entire class with #objMembers, it exposes your propertites to Objective-C, but then private hides them again. So that property is not exposed to ObjC. To reverse that hiding, you have to say #objc explicitly. So, better practice is to define the managed Realm properties per line with #objc dynamic.

One-to-one relationship in Realm swift

How can I model one-to-one relationship between objects?
For example, I have models for user_infoA, user_infoB and user_profile.
user_profile has
user_id (PK)
name
age
user_infoA has
info_a_id (PK)
user_profile
user_infoB has
info_b_id (PK)
user_profile
user_profile (P) have relationship with both user_infoA (A) and user_infoB(B). When A is deleted, also will P be deleted or not? Will P be deleted only if when related A and B are deleted?
And how can I model this with realm swift?
Many-to-one relationship needs optional property, and it makes me use force unwrapping optional. :(
[EDITED]
class RealmMyProfile: Object {
#objc dynamic var id: Int64 = 0
#objc dynamic var profile = RealmUserProfile()
}
class RealmUserProfile: Object {
#objc dynamic var userId: Int64 = 0
#objc dynamic var name: String = ""
override static func primaryKey() -> String? {
return "userId"
}
}
Exception 'The RealmMyProfile.profile property must be marked as being optional.' occurred. It should be optional.
To-one relationships (links) in Realm cannot enforce that a link is always present. So they always have to be marked as optional because there's no way to prevent nil from being stored for a link in the file format.
Therefore, we require that Realm models defined in Swift explicitly mark to-one relationships as Optional.
class RealmMyProfile: Object {
#objc dynamic var id: Int64 = 0
#objc dynamic var profile:RealmUserProfile?
}
class RealmUserProfile: Object {
#objc dynamic var userId: Int64 = 0
#objc dynamic var name: String = ""
override static func primaryKey() -> String? {
return "userId"
}
}
You can do this solution which may save you from using the unwrapping value
Realm Issue 2814
dynamic var profile_:RealmUserProfile? = nil
var profile: RealmUserProfile {
get {
return profile_ ?? RealmUserProfile()
}
set {
profile_ = newValue
}
}

Add a non realm object as ignored property to realm object in swift?

I am trying to add a non-realm class object to realm object something like this.
class TrainTripItinerary: Object {
dynamic var departStationName: String?
dynamic var departStationCode: String?
var runningStatus: TrainRunningStatus?
override static func ignoredProperties() -> [String] {
return ["runningStatus"]
}
}
While TrainRunningStatus is not a realm class.
class TrainRunningStatus {
var trainDataFound: String?
var startDate: String?
var startDayDiff: String?
}
I am not able to update runningstatus property now. Anyone know how it works? I fetch separately runnningstatus and assign it to the realm object later but it stays nil even after the assignment.
eg.
let runningStatus = TrainRunningStatus()
trainTripItinerary.runningStatus = runningStatus
This line is not working, trainTripItinerary runningStatus property is not set properly its always nil.
As suggested in comments make sure you use the same instance of TrainTripItinerary because ignored properties won’t automatically update their value across different instances.
See an example code below that demonstrates how ignored properties work
let realm = try! Realm()
try! realm.write {
realm.deleteAll()
}
let runningStatus = TrainRunningStatus()
var trainTripItinerary = TrainTripItinerary()
trainTripItinerary.runningStatus = runningStatus
assert(trainTripItinerary.runningStatus != nil)
try! realm.write {
realm.add(trainTripItinerary);
}
assert(trainTripItinerary.runningStatus != nil)
trainTripItinerary = realm.objects(TrainTripItinerary.self).first!
assert(trainTripItinerary.runningStatus == nil)
Firstly, your code is not correct.
class TrainTripItinerary: Object {
dynamic var departStationName: String?
dynamic var departStationCode: String?
var runningStatus: TrainRunningStatus?
override static func ignoredProperties() -> [String] {
return ["runningStatus"]
}
}
func ignoredProperties() -> [String] is only used on Realm properties. Since your property var runningStatus: TrainRunningStatus? does not begin with dynamic, it is not a Realm property. You don't need to use func ignoredProperties() -> [String] here.
var runningStatus: TrainRunningStatus? here is called a "transient property" in Realm. Usually a transient property is something calculated basing on current date or on Realm properties, Realm won't do anything on transient properties and you should maintain them yourself.
So if you just want to use runningStatus as a transient property, you can simply remove the code override static func ignoredProperties() -> [String].

Realm object as member is nil after saving

I'm facing an issue where a Realm object has another Realm object as member which is always nil after adding to the database.
class MedPack: Object {
dynamic var uuid = NSUUID().UUIDString
dynamic var medicine: Medicine?
convenience init(medicine: Medicine) {
self.init()
self.medicine = medicine
}
override static func primaryKey() -> String? {
return "uuid"
}
}
The reference to the object Medicine is always nil after adding.
class Medicine: Object {
var uuid = NSUUID().UUIDString
var name: String?
override static func primaryKey() -> String? {
return "uuid"
}
}
Creation of object
let medPack = MedPack(medicine: med)
Adding to database
static let sharedInstance = DBHelper()
var realmDb: Realm!
private init() {
realmDb = try! Realm()
}
func store(object: Object) {
try! self.realmDb.write {
self.realmDb.add(object)
}
}
After comparing this code to one of the Realm sample projects, it would appear that simply setting an Object as a child of another does not implicitly write it to the database as well.
Instead, you may need to refactor your code slightly, but make sure you explicitly add your Medicine object to Realm in a write transaction, before you set its relation to MedPack and then write MedPack to the database.

Resources