Accessing final array after fetching from Firebase in Swift - arrays

I'm trying to fetch relational data from firebase in swift 3 and storing it in an array. It does fetch everything the way I want it to but i can't access the final array to work with.
I have tried everything I found online but can't make it work properly.
There are 3 child nodes I'm fetching, so every time it fetches it appends it to the array.
The output is:
success
success
success
I just want it to print "success" once.
Here is my code:
// Here the child with the relations is loaded
func fetchFollowingSection1IDs() {
guard let userID = FIRAuth.auth()?.currentUser?.uid else { return }
let reference = FIRDatabase.database().reference().child("interests").child("relations").child("userAndSection1").child(userID)
reference.observe(.childAdded, with: { (snapshot) in
// It's supposed to fetch the details of Section1 according to the childs from the relations (they are the IDs of Section1)
self.fetchSection1(section1ID: snapshot.key, completionHandler: { success in
guard success == true else {
return
}
print("success")
self.collectionView?.reloadData()
})
}, withCancel: nil)
}
// Here it gets the details from Firebase
func fetchSection1(section1ID: String, completionHandler: #escaping (Bool) -> ()) {
let ref = FIRDatabase.database().reference().child("interests").child("details").child("country").child("section1").child(section1ID)
ref.observeSingleEvent(of: .value, with: { (snapshot) in
self.collectionView?.refreshControl?.endRefreshing()
if let dictionary = snapshot.value as? [String: AnyObject] {
let section1 = Section1New(section1ID: section1ID, dictionary: dictionary)
self.section1s.append(section1)
}
completionHandler(true)
}) { (err) in
print("Failed to fetch section1s:", err)
}
}
My Firebase structure for the relations looks like this:
"interests" : {
"relations" : {
"userAndSection1" : {
"7fQvYMAO4yeVbb5gq1kEPTdR3XI3" : { // this is the user ID
"-KjS8r7Pbf6V2f0D1V9r" : true, // these are the IDs for Section1
"-KjS8tQdJbnZ7cXsNPm3" : true,
"-KjS8unhAoqOcfJB2IXh" : true
},
}
Everything loads properly and populates my collection views. It is just the problem that it is the wrong number of Section1s because of the triple appending to the array.
Thank you for your answers!

The code is doing exactly what you are telling it to do.
Your firebase event is .childAdded so it will iterate over each child node one at a time.
It first loads -KjS8r7Pbf6V2f0D1V9r and adds it to the section1s array - there is then one item in the array.
Then it loads -KjS8tQdJbnZ7cXsNPm3 and appends to the array. There's two items in the array and two lines output. etc.
The only thing we don't see in the code in your question is the line that actually prints the array, which is probably in your collectionView delegate methods.
Depending on your use case, you may want to read everything in with .value, and then iterate over that to populate your dataSource array.

Related

Updating array value using find object in the background is swift

I'm trying to retrieve data from Parse using findObjectsInbackground and store it in an array. I've already created outside the scoop of viewDidLoad().
I managed retrieving and printing the data, but they are not stored in the array and it keeps being empty!
I used self.articalsTableView.reloadData() but unfortunately the array myArray is still empty.
Any help please, this has been confusing me for two days!
import UIKit
import Parse
class FirstViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var articalsTableView: UITableView!
var myArray = [PFObject]()
override func viewDidLoad() {
super.viewDidLoad()
let query = PFQuery(className: "Gateshead")
query.findObjectsInBackground {(objects, error) -> Void in
if error != nil {
print(error)
} else {
if let articals = objects {
for artical in articals {
// print (articles) THIS IS WOKING WELL
self.myArray.append(artical)
self.articalsTableView.reloadData()
}
}
}
}
}
}
findObjectsInback is using a background thread and you should dispatch to mainThread whenever if you want to access UIKit stuff in this case updating self.articalsTableView.
Also self.articalsTableView.reloadData() should be called at the end (not in the loop). This to prevent a race condition on self.myArray being accessed by the main-thread (to update self.articalsTableView ) and by the background-thread (to append artical)
for artical in articals {
// print (articles) THIS IS WOKING WELL
self.myArray.append(artical)
}
DispatchQueue.main.async {
self.articalsTableView.reloadData()
}

How to pass data into swiftui view and access it

Im trying to pass data into a swiftui view to be displayed when it is initialized, but am having trouble getting it to work.
Here is my code which passes data into my 'MarkerInfoWindow.swift' view:
func mapView(_ mapView: GMSMapView, markerInfoContents marker: GMSMarker) -> UIView? {
print("Showing marker infowindow")
print("marker.userData: \(marker.userData)")
let mUserData = marker.userData as? [String:String]
print("mUserData?['name']", (mUserData?["name"]) ?? "mUserData[name] was nil")
let mInfoWindow = UIHostingController(rootView: MarkerInfoWindow(placedata: mUserData!))
return mInfoWindow.view
}
Here is the code to my 'MarkerInfoWindow.swift' view:
struct PlaceDataStruct {
var name : String
var place_id : String
}
struct MarkerInfoWindow: View {
var placedata: [PlaceDataStruct]
var body: some View {
//Here is where i keep getting errors regardless if i use this method or dot notation
Text(placedata["name"])
}
}
Im not sure if im implementing my PlaceDataStruct incorrectly.
Does anyone know what I'm doing wrong, so that I can display the right data every time my view is initialized?
You’re MarkerInfoWindow is expecting an array of structs but the data you are passing is a dictionary, in the form [String: String]
You should update your MarkerInfoWindow to accept a dictionary instead. Here is an example of how to do that.
struct MarkerInfoWindow: View {
var placedata: [String: String] // update to be dictionary
var body: some View {
Text(placedata["name", default: ""]) // use correct notation for accessing dictionary
}
}
You’re also force unwrapping the data before you pass it to your MarkerInfoWindow if that’s your intention that is ok. But note that if your data isn’t there then it will crash your app.

Swift: trouble with Arrays of classes

I'm trying to create an array of two items: a URL and a String. The index of the items is of significance.
The data is taken from QuartzFilterManager, which provides information about the Quartz Filters installed on a system.
import Cocoa
import Quartz
class myFilter {
var myURL: URL?
var name: String = ""
}
func getFilters() -> Array<String> {
var filterArray: Array<myFilter>
if let Filters = QuartzFilterManager.filters(inDomains: nil) {
for (index, eachFilter) in Filters.enumerated() {
filterArray[index].name.append((eachFilter as! QuartzFilter).localizedName()!)
filterArray[index].myURL!.append((eachFilter as! QuartzFilter).url()!)
}
}
}
Xcode complains about Type URL not having an append method. But the name property in the preceding line works. In short, how do I set specific properties in my array?
There are lots of issues.
Make myFilter a struct instead of class and name it properly as MyFilter.
You never initialize your filterArray, you only declare it.
You need to create a new instance of MyFilter for each url/name pair you want to add to the array.
You don't need to use enumerated in this case.
You have the wrong return type for your getFilter function (I think).
Use proper naming conventions.
Here's cleaned up code:
struct MyFilter {
var myURL: URL?
var name: String
}
func getFilters() -> [MyFilter] {
var filterArray = [MyFilter]()
if let filters = QuartzFilterManager.filters(inDomains: nil) {
for eachFilter in filters {
let filter = MyFilter(myURL: (eachFilter as! QuartzFilter).url(), name: (eachFilter as! QuartzFilter).localizedName()!)
filterArray.append(filter)
}
}
return filterArray
}
It's still not ideal. Having to cast eachFilter using as! QuartzFilter is clunky.
And other uses of ! are bad. Force-unwrapping the call to localizedName() can crash. Consider proper solutions.
Append is a method of the array struct, not of the URL/String.
You first need to create the array (you just declared it, you actually need to assign something to it)
You then need to create the object that you want to append into the array
You can now append this newly created object to the array
It should look something like this:
import Cocoa
import Quartz
class MyFilter {
var myURL: URL?
var name: String?
init(url: URL?, name: String?) {
self.myURL = url
self.name = name
}
}
func getFilters() -> Array<MyFilter> {
var filterArray = [MyFilter]()
if let filters = QuartzFilterManager.filters(inDomains: nil) {
for filter in filters {
let aFilter = MyFilter(url: filter.url(), name: filter.localizedName())
filterArray.append(aFilter)
}
}
return filterArray
}
Now the array returned by this method will have N MyFilter objects.
You can access every object in the array the way you did before, with
let aFilter = filterArray[index]
And to get the property inside of that object:
let url = aFilter.myURL
let name = aFilter.name
PS: I changed some names to fit the swift conventions (classes are written in PascalCase and variables in camelCase)
PpS: be careful with ! in swift, if it's used on something that happens to be nil will crash the app. Read more about optionals here
PpPs: I was just a few minutes late :D

Swift - Firebase multiple snapshots

So I need to capture the contents from different paths in my firebase database and save them into an array so that I can put them into a chart.
Is there any way I can get the snapshot.Value and save it into a variable that can then be accessed at a later point in the code?
This is the code which shows what I'm trying to do:
override func viewDidLoad() {
super.viewDidLoad()
leave.observeEventType(.Value, withBlock: { snapshot in
let vaiable = snapshot.value
})
remain.observeEventType(.Value, withBlock: { snapshot in
let variableTwo = snapshot.value
})
undecided.observeEventType(.Value, withBlock: { snapshot in
let vaiableThree = snapshot.value
})
let options = ["Remain", "Leave", "Undecided"]
let results = [variable, variableTwo, variableThree]
setChart(options, values: results)
But I believe that the firebase calls are made last of all, meaning that the variables are empty in the 'results' array.
Anything I'm missing?
First of all, you've got an issue with 'scope'. A variable declared inside of a function or a callback will never be accessible from the outside of that function or callback.
One solution to your problem could be like this:
var results: [FIRDataSnapshot] = []
override func viewDidLoad() {
super.viewDidLoad()
leave.observeEventType(.Value, withBlock: { snapshot in
self.results.append(snapshot.value)
})
remain.observeEventType(.Value, withBlock: { snapshot in
self.results.append(snapshot.value)
})
undecided.observeEventType(.Value, withBlock: { snapshot in
self.results.append(snapshot.value)
})
But now you don't really have a way of knowing wether or not your results array is actually populated with any data, or if the callbacks are still to complete.
A safer, although still a bit cumbersome way to do it could be to refactor your callbacks out into seperate functions, and then when your first callback completes, it calls the function for the second callback, which then call the function for the third callback and then you can do something with your data. Hope that makes sense

Query an array in Firebase SWIFT

using swift and firebase I have a list of data like so
{
"posts" : {
"-KHbULJcKqt9bSpxNO9k" : {
"author" : "Michele",
"postText" : "tags as arrays?",
"tag" : [ "HELLO", "WHAT'S UP?", "TEST" ]
}
with many more post. I want to search by tag and if a post contains the search term as a tag return the whole post. Ive been able to do this with strings using queryEqualToValue: but haven't been able to solve it for an array. Any point in the right direction is appreciated thank you.
This is what my save function looks like
func createNewPost(post: Dictionary<String, AnyObject>, tags: [String]) {
let firebaseNewPost = POST_REF.childByAutoId()
let firebaseTag = Firebase(url: "\(POST_REF)/\(firebaseNewPost.key)/tag")
print(firebaseTag)
firebaseNewPost.setValue(post)
for tag in tags {
let tagID = firebaseTag.childByAutoId()
tagID.setValue(tag)
}
}
Now I need help with searching through the data and retrieving certain post based on what a user searches for. This is what I was using when it was just one tag and it was a just a string.
func loadTaggedShit(searchTerm: String) {
DataService.dataService.POST_REF.queryOrderedByChild("tag").queryEqualToValue(seachTerm).observeEventType(.ChildAdded, withBlock: { snapshot in
self.posts = []
if let snapshots = snapshot.children.allObjects as? [FDataSnapshot] {
for snap in snapshots {
if let postDictionary = snap.value as? Dictionary<String, AnyObject> {
let key = snap.key
let post = Post(key: key, dictionary: postDictionary)
self.posts.insert(post, atIndex: 0)
}
}
}
self.tableView.reloadData()
})
}
also the data now looks like this
"posts":{
"-KHj_bDJmZJut7knKoUX" : {
"author" : "Michele",
"postText" : "what's up",
"tag" : {
"-KHj_bDJmZJut7knKoUY" : "HEY"
}
}
}
Firebase arrays are very situational and really should be avoided if possible.
Array's in code are very powerful data constructs but they just don't work well within a JSON structure. If they are modified, say, removing a node, that index will be removed as well. That leaves the array with 'holes' e.g. 0, 1, 2, 4, 5, 7 etc. So, create yourself a tags child node within your post node, and then child nodes with keys created with childByAutoId
"posts" : {
"-KHbULJcKqt9bSpxNO9k" : {
"author" : "Michele",
"postText" : "tags as arrays?",
"tags"
tag_id_0
tag_title: "HELLO"
tag_id_1
tag_title: "WHAT'S UP?"
tag_id_2
tag_title: "TEST"
}
You might even consider creating a separate tags node and reference it within the posts node - especially if they are going to be re-usable.
Edit:
Based on some additional information, a different structure will be in order because the OP needs to query for posts that contain a specific tag and the tags are in fact reusable and not specific to a post:
posts
-KHbULJcKqt9bSpxNO9k
author: "Michele"
postText: "tags as arrays?"
tags
tag_id_1: true
tag_id_2: true
-JPHJSojo91920LLK0J
author: "Larry"
postText: "NO, don't use arrays"
tags
tag_id_2: true
tags
tag_id_0
tag_title: "HELLO"
tag_id_1
tag_title: "WHAT'S UP?"
tag_id_2
tag_title: "TEST"
And then the code to receive all posts that use the TEST tag, tag_id_2
This is called a Firebase Deep Query
postsRef.queryOrderedByChild("tags/tag_id_2").queryEqualToValue(true)
.observeSingleEventOfType(.Value, withBlock: { snapshot in
print(snapshot)
})

Resources