GeoFire Swift 3 - Saving and Updating Coordinates - ios

I'm trying to store coordinates into Firebase Database using GeoFire.
I'm unsure how to update the new coordinates as they will be changed/updated every second. With the childByAutoId, it is generating a new unique ID for each Bike.
How do I reference this unique Bike ID? For instance, the user would be called by FIRAuth.auth()?.currentUser?.uid. Is this possible?
let geofireRef = FIRDatabase.database().reference().child("Bike").childByAutoId()
let geoFire = GeoFire(firebaseRef: geofireRef)
var data = geoFire?.setLocation(CLLocation(latitude: userIncrementLat, longitude: userIncrementLong), forKey: "BikeId")
My Firebase Database Structure will look like...
Root
1. Bike
2. UniqueUID Number (Firebase)
3. BikeId
4. g
l
5. 0:
1:

This is my Firebase DB structure for update users location by time and retrive the nearby users to show on map:
db-root
"users" : {
<userUID> : {
"someKey" : "someValue",
...
}
}
"users_location" : {
<userUID> : {
<geofireData> ...
}
}
Vars to use:
let ref = FIRDatabase.database().reference()
let geoFire = GeoFire(firebaseRef: ref.child("users_location"))
To update logged user location:
func updateUserLocation() {
if let myLocation = myLocation {
let userID = FIRAuth.auth()!.currentUser!.uid
geoFire!.setLocation(myLocation, forKey: userID) { (error) in
if (error != nil) {
debugPrint("An error occured: \(error)")
} else {
print("Saved location successfully!")
}
}
}
}
To find nearby user I use the findNearbyUsers function. It' useful to find the nearby users and save into nearbyUsers array the UID key of the the users. The observeReady function is executed after the query completion and uses the UID to retrieve the users details (I use this to show users details on map).
func findNearbyUsers() {
if let myLocation = myLocation {
let theGeoFire = GeoFire(firebaseRef: ref.child("users_location"))
let circleQuery = theGeoFire!.query(at: myLocation, withRadius: radiusInMeters/1000)
_ = circleQuery!.observe(.keyEntered, with: { (key, location) in
if !self.nearbyUsers.contains(key!) && key! != FIRAuth.auth()!.currentUser!.uid {
self.nearbyUsers.append(key!)
}
})
//Execute this code once GeoFire completes the query!
circleQuery?.observeReady({
for user in self.nearbyUsers {
self.ref.child("users/\(user)").observe(.value, with: { snapshot in
let value = snapshot.value as? NSDictionary
print(value)
})
}
})
}
}
Hope it helps

Related

How to check unique username in Firebase Database (swift)

I've Firebase Database where each user has own email and username. How to check unique username? I tried to make it like this, but my code doesn't work properly therefore different users can have the same username
usernameField.isHidden = false
let username:String = self.usernameField.text!
if (usernameField.text?.isEmpty == false){
ref.child("users").queryOrdered(byChild("username").queryEqual(toValue: username).observeSingleEvent(of: .value, with: { snapshot in
if snapshot.exists(){
print("username exist")
}else{
ref.root.child("users").child(userID).updateChildValues(["username": username])
}
})
}
I'm a little bit newbie in Firebase I store email and username for each user like this newUserReference.setValue(["username":String(), "email" : self.emailTextField.text!]). On next view, user can type username in usernameField.text and this value will be added in Firebase Database. But if the next user (user 2) will type the same username like previous user, it must be blocked, because username should be unique
You still need to indicate what property you want to order/filter on with queryOrdered(byChild:):
if (usernameField.text?.isEmpty == false){
ref.child("users").queryOrdered(byChild:"username").queryEqual(toValue: username).observeSingleEvent(of: .value, with: { snapshot in
if snapshot.exists(){
If you're trying to store your user's id on login do this when you receive a successful response to the login:
create a Shared Instance to store the ID
class userDataSource {
var id : String? // variable to store your users ID
static let sharedInstance = PageDataSource() // global access to this dataSource
private init() {}
}
Assign the id a value after successful login
func getIDFromLogin() {
if let user = Auth.auth().currentUser {
print(user.uid)
userDataSource.sharedInstance.id = user.uid
}
}
Then you can do this to view each id:
ref.child("users").observeSingleEvent(of: .value, with: { snapshot in
if let snapshots = snapshot.children.allObjects as? [DataSnapshot] {
for snap in snapshots {
print(snap.key) // you can compare userDataSource.sharedInstance.id to this value
}
}
})
Or if you just want that user's data do this:
ref.child("users").child(userDataSource.sharedInstance.id!).observeSingleEvent(of: .value, with: { snapshot in
if let snapshots = snapshot.children.allObjects as? [DataSnapshot] {
for snap in snapshots {
print(snap)
}
}
})
Edit to answer your question more accurately
Here is an answer more inline with your question. First thing I will recommend is for you to add a table to Firebase that only contains the usernames, and the .uid's that they belong to. You will need to first read through that table to make sure that no one else has that username, then update the table accordingly:
// This function will check through all of the usernames and return a true or false value in the completion handler
func checkUsernames(_ completion: #escaping(_ success: Bool) -> Void) {
ref.child("usernames").observeSingleEvent(of: .value, with: { snapshot in
if let snapshots = snapshot.children.allObjects as? [DataSnapshot] {
for snap in snapshots {
if snap.value == username {
completion(false)
}
}
completion(true)
} else {
completion(true) // TODO: check for errors before setting completion
}
})
}
// this function will set the username values in Firebase
func storeUsername() {
let usernameRef = ref.child("usernames")
usernameRef.updateChildValues(["\(userDataSource.sharedInstance.id!)" : username])
}
}
}
Assuming you have already handled your username variable and set it's value, you will call the functions like this:
checkUsernames({ (success) in
if success {
storeUsername()
// you may also want to update your "users" table here as well
} else { print("Duplicate Username") } // handle alert or something here
})

Can't Fetch Only One CloudKit RecordType

This app uses CloudKit and I sync the data to Core Data locally. I believe I have the basics working with one exception. The following code checks for changes in CloudKit and allows me to save to Core Data but I have been unable to limit the downloaded changes to one single recordType (I have two recordTypes, "Patient" and "PatientList"). I thought that CKFetchRecordZoneChangesOptions() should allow filtering by recordType but everything I have read and tried only allows filtering by desiredKeys across all recordTypes, which of course is not useful for my purpose. I can limit the Core Data saves by recordType but that seems to be the wrong approach.
Any guidance would be appreciated. Xcode 8.3.3 iOS10 Swift 3
func checkUpdates() {
let operation = CKFetchDatabaseChangesOperation(previousServerChangeToken: changeToken)
let myZone : CKRecordZone = CKRecordZone(zoneName: "myPatientZone")
var zonesIDs : [CKRecordZoneID] = []
//you ONLY want changes from myPatientZone
//you ONLY allow one custom zone and you ONLY share myPatientZone
operation.recordZoneWithIDChangedBlock = { (zoneID) in
if zoneID == myZone.zoneID {
zonesIDs.append(zoneID)
}//if
}//recordZoneWithIDChangedBlock
operation.changeTokenUpdatedBlock = { (token) in
self.changeToken = token
}
operation.fetchDatabaseChangesCompletionBlock = { (token, more, error) in
if error == nil && !zonesIDs.isEmpty {
self.changeToken = token
let options = CKFetchRecordZoneChangesOptions()
options.previousServerChangeToken = self.fetchChangeToken
let fetchOperation = CKFetchRecordZoneChangesOperation(recordZoneIDs: zonesIDs, optionsByRecordZoneID: [zonesIDs[0] : options])
fetchOperation.recordChangedBlock = { (record) in
let recordName = record.recordID.recordName
let request : NSFetchRequest<Patient> = Patient.fetchRequest()
request.predicate = NSPredicate(format: "recordName = %#", recordName)
do {
let result = try self.persistentContainer.viewContext.fetch(request)
//if the record is not in the local core data
if result.isEmpty {
let patient = Patient(context: self.persistentContainer.viewContext)
patient.recordName = recordName
patient.firstName = record.object(forKey: "firstname") as? String
//all the other fields here...
try self.persistentContainer.viewContext.save()
} else {
//if the record is in core data but has been updated
let patient = result[0]
//patient.recordName - don't change
patient.firstName = record.object(forKey: "firstname") as?
//all the other fields
}//if result.isEmpty
} catch {
//add the CRS custom error handler
print("Error")
}//do catch
}//fetchOperation
//fetchOperation.recordWithIDWasDeletedBlock = { (recordID, recordType) in
//fetchOperation.recordZoneChangeTokensUpdatedBlock = { (zoneID, token, data) in
database.add(operation)
}//checkUpdates

Firebase query using a list of ids (iOS)

I have an NSArray containing multiple ids. Is there a way in Firebase where I can get all the object with the ids in the array?
I am building a restaurant rating app which uses GeoFire to retrieve nearby restaurants. My problem is that GeoFire only returns a list of ids of restaurant that are nearby. Is there any way i can query for all the object with the ids?
No, you can't do a batch query like that in Firebase.
You will need to loop over your restaurant IDs and query each one using observeSingleEvent. For instance:
let restaurantIDs: NSArray = ...
let db = FIRDatabase.database().reference()
for id in restaurantIDs as! [String] {
db.child("Restaurants").child(id).observeSingleEvent(of: .value) {
(snapshot) in
let restaurant = snapshot.value as! [String: Any]
// Process restaurant...
}
}
If you are worried about performance, Firebase might be able to group all these observeSingleEvent calls and send them as a batch to the server, which may answer your original question after all ;-)
I know that this answer is considered accepted but I have had really good success using promise kit with the method frank posted with his javascript link Speed up fetching posts for my social network app by using query instead of observing a single event repeatedly and just wanted to share the swift version
So I have a list of users ids that are attached to a post like this:
also these methods are in my post class where I have access to the post id from firebase
// this gets the list of ids for the users to fetch ["userid1", "userid2"....]
func getParticipantsIds() -> Promise<[String]> {
return Promise { response in
let participants = ref?.child(self.key!).child("people")
participants?.observeSingleEvent(of: .value, with: { (snapshot) in
guard let snapshotIds = snapshot.value as? [String] else {
response.reject(FirebaseError.noData)
return
}
response.fulfill(snapshotIds)
})
}
}
// this is the individual query to fetch the userid
private func getUserById(id:String) -> Promise<UserData> {
return Promise { response in
let userById = dbRef?.child("users").child(id)
userById?.observeSingleEvent(of: .value, with: { (snapshot) in
guard let value = snapshot.value else {
response.reject(FirebaseError.noData)
return
}
do {
let userData = try FirebaseDecoder().decode(UserData.self, from: value)
response.fulfill(userData)
} catch let error {
response.reject(error)
}
})
}
}
// this is the where the magic happens
func getPostUsers(compeltion: #escaping (_ users:[UserData], _ error:Error?) -> ()){
getParticipantsIds().thenMap { (id) in
return self.getUserById(id: id)
}.done { (users) in
compeltion(users, nil)
}.catch({ error in
compeltion([], error)
})
}

loop in a loop not working

I'm working on an app for school project.
After accessing the user's contacts, I want to loop through and show only the contacts who are also users of the app. *Their username is their mobile number.
below are 3 functions.
the first one getAppUsers() works fine.
the third one getDisplayedUser() does not work. and i wonder why
the second one getUserContacts() works. but it is only there to check which part of my loop isn't working. :/
so apparently my loop in a loop has something wrong which i can't figure out (it didn't even get to the "you're HERE"). please help me out. THANKS!
var appUsers = [String]()
var contactStore = CNContactStore()
var userContacts = [CNContact]()
var displayedContacts = [name: phoneNumber]()
func getAppUsers() {
let appUsersQuery = PFUser.query()
appUsersQuery?.findObjectsInBackground { (objects, error) in
if error != nil {
print("WTF")
} else if let users = objects {
for object in users {
print("FYEAH!")
if let user = object as? PFUser {
self.appUsers.append(user.username!)
print(user.username)
}
}
}
}
}
func getUserContacts() {
for b in userContacts {
let c = (b.phoneNumbers[0].value).stringValue
let d = c.replacingOccurrences(of: "\\D", with: "", options: .regularExpression, range: c.startIndex..<c.endIndex)
print("you got here")
print(d)
}
}
func getDisplayedUser() {
for a in appUsers {
for b in userContacts {
let c = (b.phoneNumbers[0].value).stringValue
let d = c.replacingOccurrences(of: "\\D", with: "", options: .regularExpression, range: c.startIndex..<c.endIndex)
print("you're HERE")
print(d)
if a == d {
print("FOUND IT")
print(b.givenName + " " + b.familyName)
}
}
}
}
The getDisplayedUser should be call after the loop finished in in getAppUsers because it is executing in asynchronous mode. I added the row after finished loop below
func getAppUsers() {
let appUsersQuery = PFUser.query()
appUsersQuery?.findObjectsInBackground { (objects, error) in
if error != nil {
print("WTF")
} else if let users = objects {
for object in users {
print("FYEAH!")
if let user = object as? PFUser {
self.appUsers.append(user.username!)
print(user.username)
}
}
// Call it here please ..
self.getDisplayedUser()
}
}
}

Retrieving data with Firebase database in iOS

In my app I have a simple user base that looks like this:
What I'm trying to do is to simply fetch this list once, to check wether a username is valid when a new user signs up with a new username.
The thing is that the only ways I found to retrieve data utilize some sort of observer methods, which are not good for me.
The logic I'm trying to achieve (with the retrieving method that doesn't work) :
// When user tries to sign up with a new username
let username = nicknameField.text?.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
self.usersRef.observeEventType(.Value) { (snapshot: FIRDataSnapshot) in
let dict = snapshot.value as! NSDictionary
for val in dict.allValues {
if username == val as! String {
// Present alert
return
}
}
}
self.usersRef.child(username).setValue(username) { (error, dbRef) in
if error == nil {
// Continue
}
}
How can I simply just fetch the list of users once?
Thanks in advance!
I had to change the observeEventType method to observeSignleEventOfType.
I have also updated my code to make it work (regardless):
self.usersRef.observeSingleEventOfType(.Value) { (snapshot: FIRDataSnapshot) in
let dict = snapshot.value as! NSDictionary
for val in dict.allValues {
if username == val as! String {
// Present alert
return
}
else {
self.usersRef.child(username).setValue(username) { (error, dbRef) in
if error == nil {
// Continue
}
}
}

Resources