Reading snapshots from firebase is fairly simple, although transferring the information to arrays is more complicated. I have this snapshot
Snap (01-08-2019) {
Sleep = "6.25 hrs";
"Time Uploaded" = "07:10 AM";
}
Snap (01-09-2019) {
Sleep = "6.72 hrs";
"Time Uploaded" = "07:19 AM";
}
Snap (01-10-2019) {
Sleep = "6.55 hrs";
"Time Uploaded" = "07:10 AM";
}
How would I be able to make one array for the date, one for the sleep, and one for the time uploaded.
I think you should reconsider how you store your data in firebase. To Look something similar to this.
Also I would consider to create a data model for day that looks something like this.
class Day {
var date: String
var sleep: String
var timeUploaded: String
init(date: String, sleep: String, timeUploaded: String) {
self.date = date
self.sleep = sleep
self.timeUploaded = timeUploaded
}
}
Then you can just fetch your snapshots like this.
var days = [Day]()
private func fetchDays() {
print(days.count)
let ref = Database.database().reference().child("days")
ref.observeSingleEvent(of: .value) { (snapshot) in
guard let days = snapshot.value as? [String: Any] else { return }
for (_,value) in days.enumerated() {
guard let dayDict = value.value as? [String: String] else { return }
let date = dayDict["date"] ?? ""
let sleep = dayDict["sleep"] ?? ""
let timeUploaded = dayDict["time_uploaded"] ?? ""
//If you really want 3 different arrays just add them here
// dateArray.append(date) and so on for the other two arrays
let day = Day(date: date, sleep: sleep, timeUploaded: timeUploaded)
self.days.append(day)
}
print(self.days.count)
}
}
}
Hope this helps. Couldn't comment to ask how your data was structured.
I would suggest not keeping the data in different arrays, it may be better to store the data from each node within a class, and then keep an array of those classes.
Let's start with a class that keeps all of the data
class ChronoClass {
var node_id = ""
var sleep = ""
var time_uploaded = ""
init(withSnapshot: DataSnapshot) {
let nodeId = withSnapshot.key
let someSleep = withSnapshot.childSnapshot(forPath: "sleep").value as? String ?? ""
let someTime = withSnapshot.childSnapshot(forPath: "time_uploaded").value as? String ?? ""
self.node_id = nodeId
self.sleep = someSleep
self.time_uploaded = someTime
}
}
and then a class array to keep all of the classes
var sleepArray = [ChronoClass]()
and finally the code to read in each node, populate the class and store the classes in an array.
func readFirebaseDataAndPopulateArray() {
let sleepNode = self.ref.child("sleep_node")
sleepNode.observeSingleEvent(of: .value, with : { snapshot in
for child in snapshot.children {
let snap = child as! DataSnapshot
let aChrono = ChronoClass(withSnapshot: snap)
self.sleepArray.append(aChrono)
}
for x in self.sleepArray { //just prints out what's in the array
print(x.node_id, x.sleep, x.time_uploaded)
}
})
}
and the output based on your structure
01-08-2019 6.25 hrs 07:10 AM
01-09-2019 6.72 hrs 07:19 AM
01-10-2019 6.55 hrs 07:10 AM
The advantage with using a class is you can sort, search, extrapolate or do a variety of other functions on the objects instead of working with three separate arrays.
Related
I'm tryin' to obtain a list of activities ("dd/mm/YY: goal achieved/missed goal") which has to be setted every week. The problem is that I obtain a list of activities with the same date and the same result of the previous one. For example:
28/02/2022: goal achieved
28/02/2022: goal achieved
28/02/2022: goal achieved
and the next day:
01/03/2022: missed goal
01/03/2022: missed goal
01/03/2022: missed goal
01/03/2022: missed goal
I want to obtain, instead, a list like:
28/02/2022: goal achieved
01/03/2022: missed goal
02/03/2022: goal achieved...
These are useful structs:
struct Persistent {
#AppStorage("goalAchieved") static var goalAchieved : Bool = false
#AppStorage("activityList") static var activityList : [String] = []
}
struct obj {
static var currentDate = Date()
static var stringDate = ""
static var activity = Activity(date:Persistent.lastUpdatedDate)
}
This is the ActivityListView:
import SwiftUI
func activitystring(activity:Activity) -> String{
var output = ""
output = "\(activity.date): \(activity.reachedobj(goalAchieved: Persistent.goalAchieved))"
return output
}
struct Activity: Identifiable{
let id = UUID()
let date: String
func reachedobj(goalAchieved: Bool) -> String {
var output = ""
if Persistent.goalAchieved == false { output = "Missed goal" }
if Persistent.goalAchieved == true { output = "Goal Achieved!"}
return output
}
}
struct ActivityRow: View{
var activity: Activity
var body: some View{
Text(activitystring(activity: activity))
Divider()
}
}
struct ActivityListView: View {
var body: some View {
ScrollView{
Text("Week summary").font(.system(size: 15)).foregroundColor(Color.green)
Text("")
ForEach(Persistent.activityList, id: \.self) { activity in
let activity = Activity(date: Persistent.lastUpdatedDate)
ActivityRow(activity: activity)
}
}
}
}
Finally this is the useful code in the ApplicationApp file (main) where I update activity list:
MenuView().onAppear(){
if Persistent.activityList.count>7{
Persistent.activityList.removeAll()
}
obj.currentDate = Date()
let formatter = DateFormatter()
formatter.dateFormat = "dd/MM/YY"
obj.stringDate = formatter.string(from:obj.currentDate)
if Persistent.lastUpdatedDate != obj.stringDate{
Persistent.goalAchieved = false
let activity = Activity(date: Persistent.lastUpdatedDate)
Persistent.activityList.append(activitystring(activity: activity))
Persistent.lastUpdatedDate = obj.stringDate
}
}
What's wrong on this?
You're calling obj.activity in your ForEach and ActivityRow, that's why it repeats that same static property all over the place.
You better just drop your struct obj and try again without it
In your Persistent object you have an array of many activities, called activitylist , but one single boolean that tells if the goal is achieved - goalachieved indeed.
Your view is iterating through the array of Persistent.activitylist, so you will have many lines for one single result - achieved or not achieved. You might actually want to iterate over an array of Persistent objects - meaning that somewhere you should probably store [Persistent] in some variable. In this way, you will see one line only for each result.
If I also may suggest: use the conventions for naming variables, Swift uses "camelCaseConventionForVariables", easier to read than "thewholevariableislowercase"
Edit:
Let me try to change a little bit your code (I would personally change it more radically, but that's not the scope of the answer).
Instead of having only one goalAchieved for all elements on the array activityList, make it a dictionary:
struct Persistent {
// Drop this variable
// #AppStorage("goalAchieved") static var goalAchieved : Bool = false
// Make this a dictionary, the date will be the key and the goalAchieved will be the value
#AppStorage("activityList") static var activityList : [String: Bool] = [:]
}
Add values to the dictionary (#meomeomeo is right, you don't need obj):
MenuView().onAppear() {
if Persistent.activityList.count > 7 {
Persistent.activityList.removeAll()
}
let currentDate = Date()
let formatter = DateFormatter()
formatter.dateFormat = "dd/MM/YY"
let stringDate = formatter.string(from: currentDate)
if Persistent.lastUpdatedDate != stringDate {
let activity = Activity(date: Persistent.lastUpdatedDate)
Persistent.activityList[activitystring(activity: activity))] = false // Will create something like ["01/03/2022": false]
Persistent.lastUpdatedDate = stringDate
}
}
Iterate on the dictionary in your ForEach; for more info: read here.
Basically, instead of pulling the most recent saved object, I'd like to pull out previously saved objects. How would I go about doing this? What the code is doing is setting the text of a label depending on the object's text pulled from the array, but I want to pull older saves.
// deleteAllRecords() // ---- uncomment deleteAllRecords() here to delete all records saved.
// FETCH BLOCK
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Tasks")
//request.predicate = NSPredicate(format: "age = %#", "12")
request.returnsObjectsAsFaults = false
do {
let result = try context.fetch(request)
for data in result as! [Tasks] {
print(data.value(forKey: "taskName"))
if let reminders = data.value(forKey: "taskName") as? [Reminder] {
for reminder in reminders {
// Now you have your single object Reminder and you can print his variables
print("Your reminder description is \(reminder.reminderDescription), and his length is \(reminder.reminderLength)")
self.reminderDisplay.text = reminder.reminderDescription
if reminderDisplay.text == reminder.reminderDescription {
self.testLabel1.text = reminder.reminderDescription
reminderCount = 1
}
}
}
}
How would I go about doing this?
Thanks in advance. - Sav.
EDIT:
// SAVE BLOCK
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "Tasks", in: context)
let newTask = Tasks(entity: entity!, insertInto: context)
newTask.setValue(reminderList, forKey: "taskName")
do {
try context.save()
} catch {
print("Failed saving")
}
}
EDIT2:
request.predicate = NSPredicate(format: "taskName = %#", "Lock the door.")
EDIT3:
Here's the object code;
public class Reminder: NSObject, NSCoding {
var reminderDescription : String? = nil
var reminderLength : Int? = nil// in days
public required init?(coder aDecoder: NSCoder) {
self.reminderDescription = aDecoder.decodeObject(forKey: "reminderDescription") as? String
self.reminderLength = aDecoder.decodeObject(forKey: "reminderLength") as? Int
}
public func encode(with aCoder: NSCoder) {
aCoder.encode(reminderDescription, forKey: "reminderDescription")
aCoder.encode(reminderLength, forKey: "reminderLength")
}
init (chosenReminderDescription: String, chosenReminderLength: Int) {
reminderDescription = chosenReminderDescription
reminderLength = chosenReminderLength
}
}
Data Model
If possible I'd like to query in relation to when they were made, like the first one, then the 2nd one. Would I have to give the object a date property and query through that?
EDIT4:
request.predicate = NSPredicate(format: "orderNo = %#", "2")
request.returnsObjectsAsFaults = false
do {
let result = try context.fetch(request)
for data in result as! [Tasks] {
print(data.value(forKey: "taskName"))
if let reminders = data.value(forKey: "taskName") as? [Reminder] {
for reminder in reminders {
// Now you have your single object Reminder and you can print his variables
print("Your reminder description is \(reminder.reminderDescription), and his length is \(reminder.reminderLength)")
self.reminderDisplay.text = reminder.reminderDescription
if reminderDisplay.text == reminder.reminderDescription {
self.testLabel1.text = reminder.reminderDescription
reminderCount = 1
}
}
}
}
Your code seems to call a request at entity Task and get the results in an Array correctly.
I would suggest you to look if you are actually saving the previous attempts to store value at your Core Data entity Task before asking for queries like this.
Don't forget that sometimes Core Data decides to postpone actual storing of information on its DB.
So try forcing savings into CoreDB before querying. For this to happen, make sure you are calling ".save()" to guarantee that Core Data sends (and executes) a saving request right away.
A sample code to force saving right away is down below:
do {
try context.save()
} catch {
print("Failure to save context: \(error)")
}
}
–
Edit (24/11/2018 03:07AM GMT+01:00):
By reading your comment, it looks like you just want to filter the queries for a specific value, instead of querying the most recent ones.
To do so, just simply uncomment the code:
//request.predicate = NSPredicate(format: "age = %#", "12")
To:
request.predicate = NSPredicate(format: "age = %#", "12")
Inside the NSPredicate's format method calling is supposed to be what you want as the statement of your filtering. If you want to filter it by age, your example is already done for you.
I'll also attach an example of other codes of mine.
request.predicate = NSPredicate(format: "(time >= %#) AND (time <= %#)", fromDateUnformatted as NSDate, toDateUnformatted as NSDate)
Now if you want to do relationship-related queries (double or more queries at once to dig information further inside Entities' relationships), then there are other methods of doing so.
Let me know if I'm going in the right direction to find an answer.
How can I update a single value inside of a struct. Currently I'm fetching all of the data inside multiple documents of a collection with the below function. the data structure is as follows:
People - collection
DocumentID
Name: "Joe"
Friends (Object)
1 (Object)
Name: "Sally"
2 (Object)
Name: "Sam"
DocumentID
Name: "Emily"
Friends (Object)
1 (Object)
Name: "Peter".
If I run the below code it jut creates a new array, whereas I would like to merge the two together. Any help is greatly appreciated. Many thanks!!
func loadData() {
let userRef = db.collection("Users").document(user!)
let docRef = userRef.collection("People")
docRef.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
if let snapshot = querySnapshot {
for document in snapshot.documents {
let data = document.data()
let name = data["name"] as? String ?? ""
let newPeople = People(name: name, friends: [:])
self.peopleArray.append(newPeople)
if let friends = data["Friends"] as? [String: Any] {
for (key, _) in friends {
let number = friends[key] as? [String: Any] ?? [:]
let friendsName = number["name"] as? String ?? ""
\\ The code in which I want to update the value of friendsName into the previous set array
let newFriends = People(name: name, friendsName: friendsName)
self.peopleArray.append(newFriends)
}
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
}
}
What I would like the array to look like:
[ name: Joe, friends: ["name": Sally, "name": Sam]], [name: Emily, friends: [name: Peter]]
Updated Code
var friendsName: [String: [String: Int]] = [:]
var friendsArray = [String: Int]()
func loadData() {
let userRef = db.collection("Users").document(user!)
let docRef = userRef.collection("People")
docRef.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
if let snapshot = querySnapshot {
for document in snapshot.documents {
let data = document.data()
let name = data["name"] as? String ?? ""
if let friends = data["Friends"] as? [String: Any] {
for friend in friends {
let key = friend.key
let values = friend.value
let friendsDict = friends[key] as? [String: Any] ?? [:]
let friendsNameString = friendsDict["name"] as? String ?? ""
self.friendsArray.updateValue(friendsNameString, forKey: key)
}
self.friendsName.updateValue(self.friendsArray, forKey: "name")
let newPeople = People(name: name, friends: self.friendsName)
self.peopleArray.append(newPeople)
self.friendsArray.removeAll()
self.friendsName.removeAll()
}
}
}
}
}
}
The way you are doing it seems a little too complex to what you require. Firestore has quite fast queries so you can have different collections for people and friends.
Lets say for example that in your "people" collection you have the names of all the users and other properties that you may need... Email, age, gender, etc etc.
Then in a second collection you can add the relations for each user. For example, "Friends" contains documents with fields friendA: AAA, FriendB: BBB. Then you won't have any duplicates in your data.
The way you are doing it creates too many duplicated that you don't need. For example, in your Joe document you have set that Sally is a friend. But then in your Sally document you will have to set that Joe is a friend. This makes the database more difficult to maintain and with a lot of duplicates.
If you make two different collections for People and Friends the code for fetching also becomes simpler.
I am trying to parse the JSON below (actual data is 20x the format listed)
{
message = "";
result = (
{
Ask = "4.8e-05";
BaseVolume = "32.61025363";
Bid = "4.695e-05";
Created = "2017-06-06T01:22:35.727";
High = "5.44e-05";
Last = "4.69e-05";
Low = "4.683e-05";
MarketName = "BTC-1ST";
OpenBuyOrders = 293;
OpenSellOrders = 4186;
PrevDay = "4.76e-05";
TimeStamp = "2018-02-20T00:00:31.863";
Volume = "662575.93818332";
},
This is the code that I have right now. It successfully prints the value "Last" to the console but when I incorporate the Dispatch.Queue, I get a Thread 1: signal SIGBRT not printing the value to the label.
let myJson = try JSONSerialization.jsonObject(with: content) as! [String:Any]
if let info = myJson["result"] as! [[String:Any]]?
{
for i in 0..<20 {
if i == 1
{
if let dict = info[i] as? [String:Any]
{
if let price = dict["Last"]
{
print(price)
//DispatchQueue.main.async
//{
// self.label1.text = price as String
//}
}
}
}
Any help is greatly appreciated!
Most likely your self.label1 outlet isn't connected. Fix that connection.
You should also update the if let that gets the value for the "Last" key as follows:
if let price = dict["Last"] as? String{
print(price)
DispatchQueue.main.async {
self.label1.text = price
}
}
There is some other cleanup you can do as well:
if let myJson = try JSONSerialization.jsonObject(with: content) as? [String:Any] {
if let info = myJson["result"] as? [[String:Any]] {
for (index, dict) in info.enumerated() {
if index == 1 {
if let price = dict["Last"] as? String {
print(price)
DispatchQueue.main.async {
self.label1.text = price
}
} // else no "Last" or not a String
}
}
} // else "result" doesn't contain expected array of dictionary
} // else content isn't a valid JSON dictionary
Avoid all of those forced casts. Especially avoid force casting to an optional.
JSON doesn't use the = sign or the semicolon. Change every = to a colon and every semicolon to a comma, so that
Ask = "4.8e-05";
BaseVolume = "32.61025363";
Bid = "4.695e-05";
Becomes
Ask: "4.8e-05",
BaseVolume: "32.61025363",
Bid: "4.695e-05",
I am new to Swift and i'm trying to mess around with some UITableViews and Arrays.
I have an array of type [Day], named daysArray.
I'm initiating the array in ViewDidLoad(), daysArray has 7 "days" in it.
Also, I have UITableView, which is being populated with the daysArray.
When I'm trying to change one day (one element in the array), the whole daysArray days are changed and consequently, all cells in the UITableView are the same.
This is so peculiar, I really don't know what is wrong (:
The Day class:
import Foundation
class Day {
private var _name: String!
private var _starts: Double!
private var _ends: Double!
var name: String! {
get {
if _name == nil {
_name = "Sunday"
}
return _name
}
set {
_name = newValue
}
}
var starts: Double! {
get {
if _starts == nil {
_starts = 8.00
}
return _starts
}
set {
_starts = newValue
}
}
var ends: Double! {
get {
if _ends == nil {
_ends = 20.00
}
return _ends
}
set {
_ends = newValue
}
}
init(dayDict: Dictionary<String, AnyObject>) {
if let dictName = dayDict["name"] as! String! {
self._name = dictName
}
if let dictIsWorking = dayDict["isWorking"] as! Bool! {
self._isWorking = dictIsWorking
}
if let dictStarts = dayDict["starts"] as! Double! {
self._starts = dictStarts
}
if let dictEnds = dayDict["ends"] as! Double! {
self._ends = dictEnds
}
}
}
The code that seems to be problematic is:
import UIKit
let GeneralDayDict: [String : AnyObject] = ["name" : "Sunday" as AnyObject, "starts": 8.00 as AnyObject, "ends" : 20.00 as AnyObject]
let someDay: Day = Day(dayDict: GeneralDayDict)
class ViewController: UIViewController {
var daysArray: [Day]! = []
override func viewDidLoad() {
super.viewDidLoad()
for i in 0...6 {
let generalDay: Day = someDay
daysArray.append(generalDay)
}
changeArray()
// Do any additional setup after loading the view, typically from a nib.
}
func changeArray() {
daysArray[5].starts = 6.00
for day in daysArray {
print("Each day in the array starts at: ", day.starts)
}
}
}
The print command in changeArray prints this:
Each day in the array starts at: 6.0
Each day in the array starts at: 6.0
Each day in the array starts at: 6.0
Each day in the array starts at: 6.0
Each day in the array starts at: 6.0
Each day in the array starts at: 6.0
Each day in the array starts at: 6.0
As I said, very very peculiar...
Thank you for just reading my question, and also for answering of course (:
In your loop you instantiate objects with the same reference.
You have to replace :
let generalDay: Day = someDay
by this :
let generalDay = Day(dayDict: GeneralDayDict)
Then you will be able to change attributes of generalDay individually
Classes in Swift are passed by reference. Meaning each item in your array points to the same thing. So when you update one, you update them all.
Try doing this with a struct instead of using a class and you will see the difference.
See this Documentation for a better explanation and the reasoning behind this.
Set the class of Day to be NSObject
class Day : NSObject {
}
for i in 0...6 {
let generalDay: Day = someDay
daysArray.append(generalDay.copy())
}
then just append the copy of the generalDay Object
Hope it helps!!