Realm Filter nested Array - ios

I have three object
class YearDatabaseModel: Object {
#Persisted var year: Int
#Persisted var months = List<MonthsDatabaseModel>()
override static func primaryKey() -> String? {
return "year"
}
}
final class MonthsDatabaseModel: Object {
#Persisted var id: String
#Persisted var media = List<Model>()
override static func primaryKey() -> String? {
return "id"
}
}
public class Model: Object {
#Persisted public var id: String
#Persisted public var type: String
}
I need to return all the Year models with filter nested Month. Model items with their type for example just return models items in month with type == "audio"
I did use this SUBQUERY but it just filter the year list not filter the model list
let predicate = NSPredicate(
format: "SUBQUERY(months.media, $media, $media.type = %#).#count > 0",
"audio")
And I don't want to get the realm result then filter models by flat mapping the list of models I want to use realm query power to filter neseted objects from big model
Here's a diagram

This is a very challenging query due to the depth and dealing with Lists, but the subquery is likely the best solution unless you want to leverage high-level Swift functions.
I think this will get your result but it's a two part query. As is, the current SwiftSDK doesn't have a (good) implementation for subqueries per se so I'm using an old school NSPredicate combined with the newer type safe query to get the result
let predicate = NSPredicate(format: "SUBQUERY(media, $m, $m.type == 'm0 type 0').#count > 0")
let months = realm.objects(MonthsDatabaseModel.self).filter(predicate)
let years = realm.objects(YearDatabaseModel.self).where { $0.months.containsAny(in: months) }
print(years)
We use the subquery to get all of the months MonthsDatabaseModel that have a Model with type = "audio"
Then we use those results to get the YearDatabaseModel that have months in their list that match.
Edit:
Here's a sample set of data (that matches the diagram in the question, noting there were two Year 2's so I used 2022 and 2023)
year 2021
month: year 2021 month 1
media: video
media: image
media: video
month: year 2021 month 2
media: image
media: image
media: image
month: year 2021 month 3
media: video
media: image
media: video
year 2022 (note, no video media values in the child list)
month: year 2022 month 1
media: image
media: image
media: image
year 2023
month: year 2023 month 1
media: video
media: image
media: image
and when the code above is run against that data this it returns year 2021 and year 2023, omitting year 2022 because it does not have any video child values in the media List
year 2021
year 2023

Related

Check if a given date is the closest to the current date and time compared to an array of dates [duplicate]

This question already has answers here:
how to find/filter the array element with the smallest positive difference
(7 answers)
Closed 12 months ago.
I have seen questions like these, but from like 6 years ago and do not directly answer the question in my opinion.
I have an array of 'events' which follow the Model of:
struct Event: Identifiable{
var id = UUID().uuidString
var eventTitle: String
var eventDescription: String
var eventDate: Date
}
And example data of:
#Published var storedEvents: [Event] = [
Event(eventTitle: "Go shopping", eventDescription: "Eggs, flour, butter", eventDate: .init(timeIntervalSince1970: 1646078400)),
Event(eventTitle: "Lecture with bob", eventDescription: "Room 453", eventDate: .init(timeIntervalSince1970: 1646078708))
]
For any given event, I'd like the event's date to be passed to a function that takes Date and returns true or false whether the given events date is the closest/current event at the current date time compared to all other event's dates (i.e. if I had two events, one for 8:15 and one for 8:30 and the current time is 8:21, then the first event should be the current one, and therefore returned true from my function.
I have a function working that only takes into account the hour, so if two events have the same hour then they will both be selected and both return true:
func isCurrentHour(date: Date)-> Bool{
let calendar = Calendar.current
let hour = calendar.component(.hour, from: date)
let currentHour = calendar.component(.hour, from: Date())
return hour == currentHour
}
I have tried sorting the list of dates from the earliest to latest and then comparing each to the current time but I'm not sure how to return true or false on the right event being compared as I have no way of identifying which date belongs to an event.
I think you may be going about this the hard way. It seems what you really want is the earliest event from all events. If so, that can be accomplished like this:
First, make your Event struct conform to Comparable by date:
struct Event: Identifiable, Comparable {
var id = UUID().uuidString
var eventTitle: String
var eventDescription: String
var eventDate: Date
static func < (lhs: Event, rhs: Event) -> Bool {
lhs.eventDate < rhs.eventDate
}
}
Then you can return the minimum element in the array. The following will return the earliest event in time, but remember that there is no check in it to make sure that it is the earliest future event, if that is what you need.
private func earliestEvent(events: [Event]) -> Event? {
return events.min()
}
This returns an optional Event as it could fail, such as in the case where the array is empty.
Edit:
private func earliestEvent(event: Event, events: [Event]) -> Bool {
guard let earliestEvent = events.min() else { return false }
return event == earliestEvent
}
I suggest as a solution to just take the intervals from the dates so far and then compare the two intervals.
func timeIntervalSince(_ date: Date) -> TimeInterval
Documentation here

Finding difference of 2 DateTime Objects with different timezones?

I need to set the other object's timezone to match now object which has utc timezone.
I'm comparing two datetime objects but the 'difference' value does not match the expected value. Most likely down to the fact that both objects have different Time Zones (Utc & Bst).
void main() {
var now = new DateTime.now().toUtc();
print(now);
print(now.timeZoneName);
var other = DateTime.parse("2020-05-22 18:27:32.608069");
print(other);
print(other.timeZoneName);
var diff = now.difference(other);
print(diff);
}
output:
2020-05-22 19:26:39.169Z
UTC
2020-05-22 18:27:32.608
British Summer Time
1:59:06.561000
You don't want to convert, you want to read in a date/time as UTC.
Change
var other = DateTime.parse("2020-05-22 18:27:32.608069");
to
var other = DateTime.parse("2020-05-22 18:27:32.608069z");
If other is already constructed, you need to make a new object with DateTime.utc()
https://api.dart.dev/stable/2.8.2/dart-core/DateTime/DateTime.utc.html
var newDate = new DateTime.utc(other.year, other.month, other.day, other.hour, other.minute, other.second, other.millisecond, other.microsecond);

How to write NSFetchRequest with multiple sort descriptors?

I have a Student Entity which has a dateOfBirth attribute.
I want to fetch all Students from the Student Entity in ascending order of their date fo birth but grouped by the decending order of the month.
for example: students which belong to the same Month are grouped together in ascending order but all the groups are in descending order.
Section 1: December 2019
-- student 1: DOB - 1 December 2019
-- student 2: DOB - 2 December 2019
-- student 3: DOB - 3 December 2019
Section 2: November 2019
-- student 1: DOB - 1 November 2019
-- student 2: DOB - 2 November 2019
-- student 3: DOB - 3 November 2019
Section 3: October 2019
-- student 1: DOB - 1 October 2019
-- student 2: DOB - 2 October 2019
-- student 3: DOB - 3 October 2019
Is it possible to write a NSFetchRequest to support this query? I have to use NSFetchResultsController to populate UITableView but I am unable to write a fetch request.
To group the students by their dateOfBirth I have an extra attribute which is a yearMonthString with dateFormat yyyyMM. I am using yearMonthString attribute to group students.
let studentFetchRequest: NSFetchRequest<Student> = Student.fetchRequest()
studentFetchRequest.predicate = NSPredicate(format: "%K != nil AND %K != nil", #keyPath(Student.dateOfBirth), #keyPath(Student.yearMonthString))
let dateSortDescriptor = NSSortDescriptor(key: #keyPath(Student.dateOfBirth), ascending: false)
studentFetchRequest.sortDescriptors = [dateSortDescriptor]
let frc = NSFetchedResultsController<Student>(fetchRequest: studentFetchRequest,
managedObjectContext: managedContext,
sectionNameKeyPath: #keyPath(Student.yearMonthString),
cacheName: nil)
If I give dateSortDescriptor as ascending then all section also become ascending and if I give dateSortDescriptor as descending then students within the section becomes descending.
What is the correct way to write this fetch request? Is it possible to give different sort descriptor for the sections?
Use two sort descriptors, one to sort by the year/month (descending), then another to sort by date of birth (ascending):
let groupSortDescriptor = NSSortDescriptor(key: #keyPath(Student.yearMonthString), ascending: false)
let dateSortDescriptor = NSSortDescriptor(key: #keyPath(Student.dateOfBirth), ascending: true)
studentFetchRequest.sortDescriptors = [groupSortDescriptor, dateSortDescriptor]

Inserting into Array and comparing Dates Swift iOS Code

I am attempting to create an array that will store 365 integers, it must be filled completely. I am using Healthkit to figure out the users steps from a year back, hence the array size. Every integer represents 1 day.
I have done this in android already and it worked perfectly, I got 365 integers back with 0's for the days with no steps, however, the problem is with iOS health kit I get nothing from days with no data, which I need. In order to do this I thought I would compare the date variable I get with the date of the current day + 1 and loop through the array to see if it find any matching cases, if not put a 0 into it at the end.
So in order to do this I created an array of 365, at the line var ID = 0 is where I attempt to store the integers correctly into the array. I am using Swift 4.
struct stepy {
static var step = [365]
}
This is where I enumerate through the stepData, first at var ID I attempt to compare the date I get in the enumerate loop with the current date (basically index 0 in the array, which represents the first day, the current day).
However I got a problem, currently I believe I would overwrite the days which already has been inputted into the date at the second step enumeration? Also I can't get the date code to compile properly, I just get the Date has no valid member called "add"
stepsQuery.initialResultsHandler = { query, results, error in
let endDate = NSDate()
let startDate = calendar.date(byAdding: .day, value: -365, to: endDate as Date, wrappingComponents: false)
if let myResults = results{
myResults.enumerateStatistics(from: startDate!, to: endDate as Date) { statistics, stop in
if let quantity = statistics.sumQuantity(){
var date = statistics.startDate
let steps = quantity.doubleValue(for: HKUnit.count())
var id = 0
var dateToInsert = date
var today = Date()
var todaytwo = Date()
for index in 0..<stepy.step.count {
if dateToInsert != today {
id = index + 1
today.(Calendar.current.date(byAdding: .day, value: -1, to: today)
stepy.step.append(0)
}
if date == dateToInsert as Date {
today.add(Calendar.current.date(byAdding: .day, value: -1, to: today)
stepy.step.append(Int(steps))
id = index + 1
}
}
}
}
}
}
static var step = [365]
The above doesn't make sense. It does not create an array of 365 integers, it creates an array with one integer in it that is 365. What you need is
static var step: [Int] = []
which creates an empty array you can just append your results to
currently I believe I would overwrite the days which already has been inputted into the date at the second step enumeration?
because your code appends to the array, which is the same as in Java: myArrayList.add(element), this is not a problem.
Also I can't get the date code to compile properly, I just get the Date has no valid member called "add"
Correct, it doesn't. Also this line:
today.(Calendar.current.date(byAdding: .day, value: -1, to: today)
does not make any sense. That should be causing a compiler error to.
Anyway, I don't see what the point of all that is. Your outer loop presumably loops through the statistics, one per day, so just do your calculation and append to the array. It'll be oldest first, but you can then just reverse the array to get newest first.

Core Data - filter entity and its relationship field

Suppose I have a core data model like this one:
This are the NSManagedObject:
class Day: NSManagedObject {
#NSManaged var date: String
#NSManaged var daynews: NSSet
}
class News: NSManagedObject {
#NSManaged var content: String
#NSManaged var type: String
#NSManaged var day: Day
}
So I have a one to many relationship between Day and News: a day is related with some news. Each news has a type:
morning
evening
I want to be able to fetch all days with news of a specific type, and on these day filter the daynews field so that it contains only the news of this specific type.
For example:
Day1 has News1{type:"morning", content:"..."} and News2{type:"evening", content:"..."}.
Day2 has News3{type:"morning", content:"..."} and News4{type:"morning", content:"..."}.
Day3 has News5{type:"morning", content:"..."} and News6{type:"evening", content:"..."}.
In this case if a try to filter with type="evening", I want to get Day1 and Day3, and in the field daynews have only News2 for Day1 and News6 for Day3
When fetching days, use this predicate in your fetch request:
let filter = NSPredicate(format: "ANY daynews.type = %#", "morning")
request.predicate = filter
When displaying the news for the day, use
day.daynews.filteredSetUsingPredicate(filter)
You could put a convenience method for this into you managed object subclass.

Resources