Realm Swift - variable number of values in an object - ios

In a situation where a random 'workout' might have any number of exercises from say, 5 - 20, is there a way to save a 'workout object' in Realm where there is a variable number of values (exercises)?
e.g. I currently have something like this as my object model:
#objc dynamic var workoutID = UUID().uuidString
#objc dynamic var workoutName = ""
#objc dynamic var totalExercisesCount = 0
#objc dynamic var exerciseOne = ""
#objc dynamic var repsExerciseOne = 0
#objc dynamic var exerciseTwo = ""
#objc dynamic var repsExerciseTwo = 0
#objc dynamic var exerciseThree = ""
#objc dynamic var repsExerciseThree = 0
#objc dynamic var exerciseFour = ""
#objc dynamic var repsExerciseFour = 0
#objc dynamic var exerciseFive = ""
#objc dynamic var repsExerciseFive = 0
#objc dynamic var exerciseSix = ""
#objc dynamic var repsExerciseSix = 0
This works fine but it also frequently populates many fields with 'null' (for example if there are only 3 exercises in a specific workout and 6 dynamic var's as per above).
It just feels like quite an inelegant solution but not sure if there is a better way?

You should be using a List to store a dynamic number of variables attached to your workout session. You should create a WorkoutExercise class that contains the reps and name of the exercise and store a list of WorkoutExercises in your WorkoutSession model.
class WorkoutExercise: Object {
#objc dynamic var name = ""
#objc dynamic var reps = 0
}
class WorkoutSession: Object {
#objc dynamic var workoutID = UUID().uuidString
#objc dynamic var workoutName = ""
let exercises = List<WorkoutExercise>()
var totalExerciseCount: Int {
return exercises.count
}
}
I'd also recommend changing totalExerciseCount to a computed property (which will also make it ignored, since you can't store computed properties in Realm), since its value should always reflect the number of elements in exercises and there's no need to make it a persisted property, because you can always recompute it without a big computational cost.

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

How to access the realm model list in filter in swift 4?

This is my object/model
import RealmSwift
class IBPChapters: Object {
#objc dynamic var ibp_chapter_id = 0
#objc dynamic var chapter = ""
#objc dynamic var chapter_president = ""
#objc dynamic var office_no = ""
#objc dynamic var email_address = ""
#objc dynamic var services_offered = ""
var service = List<Services>()
#objc dynamic var locid = 0
#objc dynamic var loc:Locations?
#objc dynamic var status = 0
override static func primaryKey() -> String {
return "ibp_chapter_id"
}
}
Services Model
import RealmSwift
class Services: Object{
#objc dynamic var service_id = 0
#objc dynamic var service = ""
#objc dynamic var status = 0
#objc dynamic var ibp_service_filter = true
#objc dynamic var pao_service_filter = true
#objc dynamic var lac_service_filter = true
override static func primaryKey() -> String {
return "service_id"
}
}
Example Data
1. legal aid,outreach,counseling,case handling
2. legal aid,counseling
3. outreach,counseling
Appending Data
if chapter.service[0].ibp_service_filter == true , append all data having legal aid
Output will be 1 and 2 //Expected OUTPUT
if chapter.service[1].ibp_service_filter == true, append all data having counseling but if the id is already exist ignore that index and continue to the next data
Output will be 1 and 2 and 3 //Expected OUTPUT
func addData(){
var ibpArray:[IBPChapters] = []
let chapters = realm.objects(IBPChapters.self)//condition for filter here
for chapter in chapters{
print(chapter.service)
ibpArray.append(chapter)
//Legal Aid service[0]
if chapter.service[0].ibp_service_filter == true{
ibpArray.append(chapter)
}
//Counseling service[1]
if chapter.service[1].ibp_service_filter == true{
ibpArray.append(chapter)
}
//Outreach service[2]
if chapter.service[2].ibp_service_filter == true{
ibpArray.append(chapter)
}
//Case Handling service[3]
if chapter.service[3].ibp_service_filter == true{
ibpArray.append(chapter)
}
}
}
MY OUTPUT 1,2,3,1,2,3,1,2,3,1,2,3//<- wrong output
only 1 and 2 has legal aid , and all of them 1,2 and 3 has counseling so I dont need to append 1 and 2 again since I already appended them on the array on the first loop
How can I access the list on the realm for my filter, so I can manipulate the data or Do you have any idea or alternative way to generate my expected output
Can I use array index to filter? ex. .filter("services[0].ibp.filtered == true)
Correct Output
func addData(){
let chapters = realm.objects(IBPChapters.self)
for chapter in chapters{
let filteredServices = chapter.service.filter("ibp_service_filter == true")
for service in filteredServices{
let service_index = filteredServices.index(of: service)
if chapter.service.contains(filteredServices[service_index!]){
if !ibpArray.contains(where: {$0.ibp_chapter_id == chapter.ibp_chapter_id}){
ibpArray.append(chapter)
}
}
}
}
}
but I still want to know if there is a much better answer or shorter than my answer or lets say less loops and conditions
Realm support .filter() function based on NSPredicate. So, first you should get ibp_service_filter == true values:
let filteredServices = chapter.service.filter("ibp_service_filter == true")
and then save only uniq ids (basic idea):
for service in filteredServices where !history_ibp.contains(where: { $0. service_id == service.service_id }) {
history_ibp.append(service)
}

Realm Reserved Words

I'm having an issue with Realm Swift. I have an object which is suppose to store information about the user created character. However certain properties are not saving. If I switch the name of the object just by one letter it saves and reads back correctly. The first example refuses to save anything but the default value for the race property, but the second example saves any value to the racea property with no issue. What is causing this?
Example 1
class Character: Object {
//MARK: Properties
dynamic var id: Int = 1
dynamic var name: String = "John Appleseed"
dynamic var level: Int = 1
dynamic var exp: Int = 0
dynamic var race: Int = 0
dynamic var career: Int = 0
dynamic var currentHealth: Int = 100
dynamic var inventory: Inventory? = Inventory()
//MARK: Realm
override static func primaryKey() -> String? {
return "id"
}
}
Example 2
class Character: Object {
//MARK: Properties
dynamic var id: Int = 1
dynamic var name: String = "John Appleseed"
dynamic var level: Int = 1
dynamic var exp: Int = 0
dynamic var racea: Int = 0
dynamic var career: Int = 0
dynamic var currentHealth: Int = 100
dynamic var inventory: Inventory? = Inventory()
//MARK: Realm
override static func primaryKey() -> String? {
return "id"
}
}
Extension
extension Character {
func getRace() -> String {
return Fitventure.species[race]
}
}
If the getRace() function was causing the issue, then it sounds like to me that this might be an unfortunate collision between Swift and the Objective-C runtime reflection features that Realm uses to work.
Realm dynamically generates its own accessors for each of its properties in order to explicitly manage how data is saved and retrieved from disk. As a result, it's not possible to override the accessors of Realm properties, and doing so will create strange behavior.
Best practice is what you've already discovered: when creating another method that transforms the Realm property somehow, you need to make sure the function doesn't have a name that might have been implicitly generated by the Objective-C runtime.

How to filter one to many relationship

I have two models
class Survey: Object {
dynamic var id = 0
dynamic var campaign: Campaign?
dynamic var lat = 0.0
dynamic var lng = 0.0
dynamic var duration = ""
dynamic var week = ""
dynamic var desc = ""
override static func primaryKey() -> String? {
return "id"
}
}
class Campaign: Object {
dynamic var id = 0
dynamic var name = ""
dynamic var date_start = ""
dynamic var date_end = ""
dynamic var desc = ""
let surveys = List<Survey>()
override static func primaryKey() -> String? {
return "id"
}
}
They are populated in a table view controller, with a UISearchBar on top.
Section header = campaign name, row cell are the info of the survey.
How can I filter/query from my Realm so that Campaigns that have 0 survey won't be shown(including quick search query).
At the moment my app still shows the section with 0 row.
I did
self.campaigns = self.realm.objects(Campaign).filter("surveys.#count > 0")
But however, this will not work for filtering :(
If you're using the version of Realm earlier than v0.96, you should use v0.96 or later. Since Collection Keypath Queries (#count, #sum, etc.) has been supported from v0.96.
Keypath collection queries using #count, #min, #max, #sum and #avg are now supported on RLMArray/List properties. See our handy NSPredicate Cheatsheet for more details on how to use these.
https://realm.io/news/realm-objc-swift-0.96.0/

Sort Realm objects with the count of List property

I have two Realm data models classes like this:
class TaskList: Object {
dynamic var name = ""
dynamic var createdAt = NSDate()
let tasks = List<Task>()
}
And:
class Task: Object {
dynamic var name = ""
dynamic var createdAt = NSDate()
dynamic var notes = ""
dynamic var isCompleted = false
}
Now I need to query TaskList and sort them with number of tasks in each of them. I tried to use something like this but it crashes the app because its not supported:
realm.objects(TaskList).sorted("tasks.count")
Another workaround is:
Introduce taskCount property in TaskList, and make always sync taskCount and tasks.count.
class TaskList: Object {
dynamic var name = ""
dynamic var createdAt = NSDate()
let tasks = List<Task>()
dynamic var taskCount = 0
}
Then, you can use
realm.objects(TaskList).sorted("taskCount")
Since Realm does not support sorting on key paths, currently.
If you'd like to sync taskCount and tasks.count automatically, you can do like the following:
(Don't use tasks.append() directly, use addTask() method instead.)
class TaskList: Object {
dynamic var name = ""
dynamic var createdAt = NSDate()
private let tasks = List<Task>()
dynamic var taskCount = 0
func addTask(task: Task) {
func add() {
tasks.append(task)
taskCount = tasks.count
}
if let realm = realm where !realm.inWriteTransaction {
try! realm.write{
add()
}
} else {
add()
}
}
}
Like this:
realm.objects(TaskList).sort { $0.tasks.count < $1.tasks.count }
EDIT: have no idea about Realm, this only works when objects returns a CollectionType and List has a count property.

Resources