I am working on a firebase project and decided to make some changes in the data structure. Now, I decided it would be better to add a placeholder image to the node below (Before, I used a dummy image to serve as a placeholder). Is there a way I can still get the dates below? and add the placeholder URL to another structure?
So what I would want is an array dates = ["20180203","20180204","20180205"] and then another array containing placeholders = ["https.googleapis.whateverIsInTheDay80180203", "https.dummydefaultimage","https.dummydefaultimage"]
public func getAvailableDates(spotTitle:String, handler: #escaping (_ dateList:[String])->())
{
var datesList:[String] = []
let formatter = DateFormatter()
formatter.dateFormat = "yyyyMMdd"
let allowedDays = 30
ref.child("ImageLocationDates").child(spotTitle).observe(DataEventType.value, with: { (snapshot) in
if snapshot.childrenCount > 0 {
for mydata in snapshot.children.allObjects as! [DataSnapshot]
{
let date = mydata.key
if date.count == 8 {
let testDate = formatter.date(from: date)
let cal = Calendar.current
let components = cal.dateComponents([.day], from: testDate!, to: cal.startOfDay(for: Date()))
let dateDistance = components.day! // integer of distance between today and date provided
if dateDistance < allowedDays {
if !datesList.contains(convertDate(stringDate: date)){
datesList.insert(convertDate(stringDate: date), at: 0)
}
else {
print("getting duplicate data")
}
}
}
}
handler(datesList)
}
})
}
In Firebase real time database you can have a few ways to structure you data. Keep in mind you want your data not to get too deep. Usually my limit is 3 folds not more. To keep things shallow then, you can normalize and distribute your data which includes a bit of duplication sometimes but it can speed up queries and will decrease the amount of data users download each time.
Look at three structures below :
(A)
D_street:
2012491203 :
placeholder: "https://...."
2012491203 :
placeholder: "https://...."
...
(B)
D_street_dates:
2012491203 : true
2012491203 :
...
D_street_placeholder:
2012491203 : "https://...."
2012491203 : "https://...."
...
(C)
D_street_dates:
id1 :
date: 2012491203
placeholder: "https://...."
id2 :
date: 2012491203
placeholder: "https://...."
...
Each one of structures above can be great depending on your use case. in A you can query by the date (key) and once you get the results you have actually downloaded the placeholders too because they are the values.
In B you only download date when you query D_street_dates and then you have to do another query to get the placeholder per each date from D_street_placeholder.
In C you structure data per an ID or userID or something that both date and placeholders are properties of and whenever you query ids you get both dates and placeholder for that.
Related
I'm trying to get multiple types of health data all with the same timeframe. My problem relies on the way that I should handle all the data that I get from those queries. Currently my model is as follows:
struct DailyData {
var steps: Int?
var distance: Int?
var calories: Int?
var exercise: Int?
}
class UserData {
let healthManager = HealthKitManager.shared
var dailyData = [DailyData]?
.
.
.
}
if I'm not mistaken, I can only query only one HKQuantityIdentifier at a time, so that means I need to call my getData() function from my HealthKitManager.swift once for every HKQuantityType that I have in my model:
func getData(type: HKQuantityTypeIdentifier, unit: HKUnit, days: Int, completed: #escaping (Result<[Int], Error>) -> Void) {
let calendar = NSCalendar.current
let interval = NSDateComponents()
interval.day = 1
let quantityType = HKQuantityType.quantityType(forIdentifier: type)!
var anchorComponents = calendar.dateComponents([.day, .month, .year], from: NSDate() as Date)
anchorComponents.hour = 0
let anchorDate = calendar.date(from: anchorComponents)
// Define 1-day intervals starting from 0:00
let query = HKStatisticsCollectionQuery(quantityType: quantityType,
quantitySamplePredicate: nil,
options: .cumulativeSum,
anchorDate: anchorDate!,
intervalComponents: interval as DateComponents)
query.initialResultsHandler = {query, results, error in
if let error = error {
completed(.failure(error))
return
}
let endDate = NSDate()
let startDate = calendar.date(byAdding: .day, value: -(days - 1), to: endDate as Date, wrappingComponents: false)
var completeDataArray: [Int] = []
if let myResults = results{
myResults.enumerateStatistics(from: startDate!, to: endDate as Date) { statistics, stop in
if let quantity = statistics.sumQuantity(){
let dayData = quantity.doubleValue(for: unit)
completeDataArray.append(Int(dayData))
}
}
}
completed(.success(completeDataArray))
}
healthStore.execute(query)
}
My problem is that I can't find a way to correctly set the received data into my model. Could someone point me in the right direction? I believe my model could be wrong, because as for what I have gather online, it's impossible to query multiple HKQuantityTypes in one query. Meaning that I would definitely have to set my model one [Int] of a HKtype at a time.
But with what I currently have, that would mean that when the first query returns I have to create multiple DailyData objects with almost all of the variables nil except the one I'm setting. Then, when the other queries return, I should do some array checking of dailyData matching the .count values with the one I got from the query, and that just feels wrong in my opinion. Not very elegant.
I tried another approach. Instead of having an array of a custom type, having a custom type that inside has an array of ints for every HKType that I need. But this has another problem: How could I "Keep in sync" the data from every HKType? having different arrays for every type would be, in my opinion, difficult to handle for example in a tableView. How could I set number of rows in section? Which type should I prefer? Also I would be accessing data over array indexes which may lead to bugs difficult to solve.
struct WeekData {
var steps: [Int]?
var distance: [Int]?
var calories: [Int]?
var exercise: [Int]?
}
class UserData {
let healthManager = HealthKitManager.shared
var weekData = WeekData()
.
.
.
}
Hi Guys I briefly explain my problem.
From my database I get an array of HistoryItem, a custom type that contains a simple Date property inside it:
struct HistoryItem {
let date: Date
let status: Status // Not important for my problem
}
I want to group this data by year and month, I thought the best way was a dictionary with key DateComponents:
// ungroupedHistory is already fetched and is of type [HistoryItem]
var groupedHistory: [DateComponents : [HistoryItem]]
groupedHistory = Dictionary(grouping: ungroupedHistory) { (historyItem) -> DateComponents in
let calendar = Calendar.current
let components = calendar.dateComponents([.year, .month], from: hisoryItem.date)
return components
}
The result is as expected but the problem is that it is unsorted, and it is obvious that this is the case since the dictionary by definition is an unsorted collection.
How can i get a sorted by date copy of this dictionary?
I've tried something like this:
let sortedDict = groupedHistory.sorted {
$0.key.date.compare($1.key.date) == .orderedDescending
}
But I just get an array with keys of type:
[Dictionary<DateComponents, [HistoryItem]>.Element]
Thanks in advance!
You need to do this in 2 steps, first get an array with the dictionary keys sorted
let sortedKeys = groupedHistory.keys.sorted {
$0.year! == $1.year! ? $0.month! < $1.month! : $0.year! < $1.year!
}
and then use that array to access the values in your dictionary in a sorted manner
for key in sortedKeys {
print(groupedHistory[key])
}
A dictionary is unordered by definition.
To get a sorted array you could create a wrapper struct
struct History {
let components : DateComponents
let items : [HistoryItem]
}
then sort the keys and map those to an array of History
let sortedKeys = groupedHistory.keys.sorted{($0.year!, $0.month!) < ($1.year!, $1.month!)}
let history = sortedKeys.map{History(components: $0, items: groupedHistory[$0]!)}
What I have: A snapshot of all users with a bunch of if statements that eventually returns an array of users that get displayed.
What I need: The array of end users to be used in a .query in the line preceding the snapshot.
Why do I need this: This line is so that the entire database of users is not run on the client.
More specifically, what do I need to query for: A) Users who have a child "caption"(timestamp) with a timestamp that is in today, AND, B) who are 3000 miles from the current user.
JSON of DB
"people" : {
"02PdiNpmW3MMyJt3qPuRyTpHLaw2" : {
"Coordinates" : {
"latitude" : -25.809620667034363,
"longitude" : 28.321706241781342
},
"PhotoPosts" : "https://firebasestorage.googleapis.com/v0/b/daylike-2f938.appspot.com/o/images%2F02PdiNpmW3MMyJt3qPuRyTpHLaw2%2FPhotoPosts?alt=media&token=24fee778-bcda-44e3-aa26-d7c2f8509740",
"caption" : 1602596281762, /// this is timestamp
"postID" : "02PdiNpmW3MMyJt3qPuRyTpHLaw2"
},
"e1" : “cvvvv666",
"e2" : "aol.com",
" "postID" : "0RnqWV7Gd9Z0bUW9nUvizMQOjK73",
"users" : "cvvvv666#aol.com"
},
.
var dict = CLLocation()
...
dict = CLLocation(latitude: lat, longitude: lon)
...
let thisUsersUid = Auth.auth().currentUser?.uid
//this line below is where the refArtists2 query should go. in other words send all users to device that meet the 2 if statements, which is represented by self.people.append(peopl)//
let refArtists2 = Database.database().reference().child("people").queryOrdered(byChild: "caption").queryEqual(toValue: ANY Timestamp in today).queryOrdered(byChild:Coordinates). queryEqual(toValue:ThoseCoordinates which make the distance to current user less than 3000 miles)
refArtists2.observe(DataEventType.value, with: { snapshot in
if snapshot.childrenCount>0{
self.people.removeAll()
for people in snapshot.children.allObjects as! [DataSnapshot] {
if people.key != thisUsersUid {
let peopleObject = people.value as? [String: AnyObject]
let peopleCoordinates = peopleObject?["Coordinates"] as? String
let peoplecaption = peopleObject?["caption"] as? Int //is timestamp
let peoplepostID = peopleObject?["postID"] as? String
let coordSnap = people.childSnapshot(forPath: "Coordinates")
guard let lat = coordSnap.childSnapshot(forPath: "latitude").value as? CLLocationDegrees else { return }
guard let lon = coordSnap.childSnapshot(forPath: "longitude").value as? CLLocationDegrees else { return }
let locCoord = CLLocation(latitude: lat, longitude: lon)
let coordSnap12 = people.childSnapshot(forPath: "caption").value as? Int ?? 0
let date = Date(timeIntervalSince1970: TimeInterval(coordSnap12)/1000.0)
//let secondsInDay = 86400
**if Calendar.current.isDateInToday(date)** {
let distance = locCoord.distance(from: self.dict)
print(distance, "distancexy")
**if distance/1609.344 < 3000**{
let peopl = Userx(Coordinates: peopleCoordinates, distance:distance, caption: peoplecaption, postID: peoplepostID)
self.people.append(peopl)
let d = people.key as! String
self.printPersonInfo(uid:d) ///////This is used to reload the data
} else {
print ("w")
}
} else {
print ("alphaaa")
}
}
print("aaaaaaaa", self.people.map {$0.distance})
}
self.people.sort { ($0.distance ?? 0) < ($1.distance ?? 0) } ////////This sorting with distance is used with returning the cell. people is used as uid array to return the cell.
}
})
} else {
print("no")
}
})
Ancillary caveat: the self.people.sort { ($0.distance ?? 0) < ($1.distance ?? 0) }sorting is important, so the queries should not impede that. I am a bit concerned with using queryOrdered in that it orders the array of users in the wrong order. If it does, a C) query should be: The order of the users must be with the closest users to the logged in user first. The furthest from the logged in user must go last in the array.
Another way of asking this would be: Instead of running a snapshot of all users, how do you query the snapshot's 'end result sort' when making the snapshot?
The timestamp is seconds since 1970
My attempt at the date query below. I took the code and tried to put the code that gets the date before the actual query(currently the code that gets the date is after the snapshot of all users).
var ppp: String! ////this should be the uid of all users in db
let people = Database.database().reference().child("people").child(self.ppp).child("captions")
people.observe(DataEventType.value, with: { snapshot in
let captionss = snapshot.value as? Int ?? 0
let date = Date(timeIntervalSince1970: TimeInterval(captionss)/1000.0)
let query1 = Database.database().reference().child("people").queryOrdered(byChild: "caption").where?(isDateInToday(date))
Edit: This answer is in Firestore, not Realtime Database. However, the concepts are the same.
The question is several questions in one; asking about distance, compound queries and how to query Firebase in general. I will post this answer to address the second two and distance queries are addressed in the comment to the question.
Once the query pattern is understood, they become easier and most importantly; it becomes more obvious that how your data is structured depends on what queries you want to run against that data.
Suppose we have a users collection with user documents - each documentId is the users uid
users
uid_0
name: "Leroy"
and then we have the posts for the users - each post contains some text, a timestamp of the post, the uid of the user that posted it, what the topic is and a url of a picture that appears in the post. Notice I am storing posts in a separate collection; why read in a bunch of user data when we only want to know about their post.
posts
post_id
postText: "pretty flowers"
postDate: "20201103"
postUrl: "www....."
postUid: "uid_0"
postTopic: "flowers"
Let suppose we want to get posts from today that are about flowers, and then also get the posters name and output who posted the message and what they said.
To do this we will need a compound query and then a subquery to retrieve the posters name as well.
func getTodaysPostsAboutFlowers() {
let postsCollection = self.db.collection("posts")
let query = postsCollection.whereField("postDate", isEqualTo: "20201103").whereField("postTopic", isEqualTo: "flowers")
query.getDocuments(completion: { snapshot, error in
if let err = error {
print(err.localizedDescription)
return
}
guard let docs = snapshot?.documents else { return }
for doc in docs {
let postText = doc.get("postText") as? String ?? "No text"
guard let postersUid = doc.get("postUid") as? String else { return }
self.outputPostTextAndUserName(withText: postText, andUid: postersUid)
}
})
}
The above performs a compound query on both the postDate field as the postTopic field.
The above then calls another function to retrieve the users name and output both the name and what they said
func outputPostTextAndUserName(withText: String, andUid: String) {
let usersCollection = self.db.collection("users")
let theUserDoc = usersCollection.document(andUid)
theUserDoc.getDocument(completion: { documentSnapshot, error in
if let err = error {
print(err.localizedDescription)
return
}
if let doc = documentSnapshot {
let postersName = doc.get("name") as? String ?? "No Name"
print("\(postersName) posted: \(withText)")
}
})
}
and the output
Leroy posted: pretty flowers
As you can see, there's no need to load all of the users, no need to iterate over results etc. Even if you have a billion users, this will only return a subset of that data which is a best practice when working with huge data sets; only get the data you're interested in.
Edit. The OP is asking about querying for nodes containing today. The simple solution is to have one child node containing a timestamp which would contains specific date data and then another child node just containing today data in YYYYMMDD format.
people
uid_x
timetamps: 9023490823498 //Date(timeIntervalSince1970:
todaystamp: "20201106" // yyyymmdd format
that makes querying for nodes that contain today very simple.
I am working on a project with NSManagedObjects where each object is a user entry that has a value and a date. The app will be running a 7 day average and a 14 day average of the values the user enters. I have already set it up to do the appropriate fetch requests, sum the values, and divide by 7 and 14, respectively. However, I am realizing that when the user first begins using the app these running average values will be very misleading, so I would like to set up a function that will evaluate the number of days out of the last 7 and 14 that do not have any entries so I can subtract that value from the denominator in these calculations. I am a relative beginner and am having a hard time getting my head around how to write this function though, so any help would be appreciated. Thanks in advance.
Edit in response to Drekka:
The code I'm working from is below. I'm sorry for the broad question but I can't quite figure out where to start with structuring a looping function for what I'm trying to do and I haven't been able to come up with any examples or analogues in the searching I've done. Basically I'm pulling all of the values entered in the last seven days but I'm trying to figure out a way to evaluate for days where no values were entered within the span of this fetch request.
func sevenDayFetch() {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Entry")
var calendar = Calendar.current
calendar.timeZone = NSTimeZone.local
let sevenDaysAgo = calendar.date(byAdding: .day, value: -7, to: Date())
let dateFrom = calendar.startOfDay(for: sevenDaysAgo!)
let dateTo = Date()
let fromPredicate = NSPredicate(format: "entryDate > %#", dateFrom as NSDate)
let toPredicate = NSPredicate(format: "entryDate <= %#", dateTo as NSDate)
let sevenDayPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [fromPredicate, toPredicate])
fetchRequest.predicate = sevenDayPredicate
do {
entryArray = try managedContext.fetch(fetchRequest)
var sevenDayArray: [Int] = []
for i in entryArray as [NSManagedObject] {
sevenDayArray.append(i.value(forKey: "Value") as! Int)
}
let sevenDaySum = sevenDayArray.reduce(0, +)
let sevenDayAverage = sevenDaySum/7
sevenDayAverageLabel.text = String(sevenDayAverage)
I find it helpful to save the install date of the app in UserDefaults.
func installDate() -> NSDate {
var installDate: Date
if let date = UserDefaults.standard.object(forKey: UserDefaultsKeys.dateInstalled) as? Date {
installDate = date
} else {
installDate = Date()
UserDefaults.standard.set(installDate, forKey: UserDefaultsKeys.dateInstalled)
}
return installDate
}
Core data is not well suited for storing a single global value. And you cannot infer the install date from core-data; not having a value for a date does not mean that the was not install then.
Once you know the install date you can adjust calculations and your UI, if it is less than 14 or 7 days.
I have three arrays that have the data to populate the complication timeline with entries.
When I scroll through time travel, the complication does not change so I know I must be doing something wrong.
func getTimelineEntriesForComplication(complication: CLKComplication, afterDate date: NSDate, limit: Int, withHandler handler: (([CLKComplicationTimelineEntry]?) -> Void)) {
for headerObject in headerArray! {
for body1Object in body1Array! {
for body2Object in body2Array! {
let headerTextProvider = CLKSimpleTextProvider(text: headerObject as! String)
let body1TextProvider = CLKSimpleTextProvider(text: body1Object as! String)
let body2TextProvider = CLKRelativeDateTextProvider(date: body2Object as! NSDate, style: .Offset, units: .Day)
print("HeaderTextProvider: \(headerTextProvider)")
print("Body1TextProvider: \(body1TextProvider)")
print("Body2TextProvider: \(body2TextProvider)")
let template = CLKComplicationTemplateModularLargeStandardBody()
template.headerTextProvider = headerTextProvider
template.body1TextProvider = body1TextProvider
template.body2TextProvider = body2TextProvider
let timelineEntry = CLKComplicationTimelineEntry(date: body2Object as! NSDate, complicationTemplate: template)
entries.append(timelineEntry)
print("TimeEnt: \(entries)")
print("TimeEntCount: \(entries.count)")
}
}
}
handler(entries)
}
My thinking:
Loop through the three arrays
Set the template with the results of the array loops
Set the timeline entry with the date of the object in body2Array
The output on my console is:
HeaderTextProvider: <CLKSimpleTextProvider: 0x78e3f800>
Body1TextProvider: <CLKSimpleTextProvider: 0x78e4eb30>
Body2TextProvider: <CLKRelativeDateTextProvider: 0x78e4f050>
TimeEnt: [<CLKComplicationTimelineEntry: 0x78e4edd0> date = 2016-03-21 05:00:00 +0000, template = <CLKComplicationTemplateModularLargeStandardBody: 0x78e4edf0>, animationGroup = (null), <CLKComplicationTimelineEntry: 0x78e4f520> date = 2016-10-01 17:00:00 +0000, template = <CLKComplicationTemplateModularLargeStandardBody: 0x78e4f540>, animationGroup = (null)]
TimeEntCount: 2
Why time travel isn't working the way you expect:
Time travel only supports a 48-hour sliding window. Any timeline entries outside the complication server's latestTimeTravelDate will be ignored.
When constructing your timeline, do not create any entries after this date. Doing so is a waste of time because those entries will not be displayed right away.
You can't time travel over six months ahead to October 1, so your Mar 21 entry would never change to show the October 1 entry.
Other issues:
You probably don't mean to iterate through every body object for each header object.
You also want to start with an empty entries array within this method, so you're not inadvertently appending to an array with any existing (backwards) timeline entries.
Change your loop to look something like this:
// Unwrap optional arrays. You can also check counts if there's the possibility that they are not equally sized.
guard let headers = headerArray, texts = body1Array, dates = body2Array else { return handler(nil) }
var entries = [CLKComplicationTimelineEntry]()
for (index, header) in headers.enumerate() {
let text = texts[index]
let date = dates[index]
...
}
print("TimeEntCount: \(entries.count)")
This will give you headerArray.count timeline entries, instead of headerArray.count x body1Array.count x body2Array.count entries.
You may also want to specify the type of objects in each array so you don't have to constantly use as!. This will provide type safety and let the compiler type-check your code.
It also might make your code more readable and maintainable if you keep the data in an array of structs (with header, text, and date properties), instead of using multiple arrays.