Fetch CloudKit records from current iCloud user - swift

I want to fetch all of the location records from a user that is currently logged in.
This creates the location record on CloudKit:
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations.last!
let center = CLLocationCoordinate2D(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
addCrumbPoint(center)
let locationRecord = CKRecord(recordType: "location")
locationRecord["location"] = location
let publicData = CKContainer.defaultContainer().publicCloudDatabase
publicData.saveRecord(locationRecord) { record, error in
}
}
But how would I go about pulling out the location records for only the currently logged in user?
I want to use this to create a breadcrumb of previous journeys on a map but just getting it to print a list would be a great start!
Here is my code so far:
func getLocationAsync(complete: (instance: CKRecordID?, error: NSError?) -> ()) {
let container = CKRecordID(recordName: "Location")
publicDB.fetchRecordWithID(location) { fetchedLocation, error in
if error != nil {
print(error!.localizedDescription)
complete(instance: nil, error: error)
} else {
print("fetched Location \(recordID?.recordName)")
complete(instance: recordID, error: nil)
}
}
}

If your goal is to save the breadcrumbs locations of an user and not sharing it to other users, then use the private database.
Like that you can retrieve all the location records of the user.
If you want to use the public DB, then add an entry of type CKReference to the Location record pointing to the user record. So that you can use a predicate based on the user recordID

Related

Display custom information according to user location FireStore

I'm trying to display custom information according to userLocation(i.g, ft the user is in Los Angeles, He will get Text("X"). If he is in San Diego, he will get Tex("Y")). For this, I'm using Firestore. There, I have a collection called TEST --> auto ID --> "Name": "Test", Los Angeles: :true
This is how I get userLocation:
class LocationViewModel: NSObject, ObservableObject, CLLocationManagerDelegate{
#Published var locationManager = CLLocationManager()
#Published var userLocation : CLLocation!
#Published var userAddress = ""
#Published var noLocation = false
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
switch manager.authorizationStatus {
case .authorizedWhenInUse:
self.noLocation = false
manager.requestLocation()
case .denied:
self.noLocation = true
default:
self.noLocation = false
locationManager.requestWhenInUseAuthorization()
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error.localizedDescription)
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
self.userLocation = locations.last
self.extractLocation()
}
func extractLocation(){
CLGeocoder().reverseGeocodeLocation(self.userLocation) { (res, err) in
guard let safeData = res else {return}
var address = ""
// address += safeData.first?.name ?? ""
// address += ", "
address += safeData.first?.locality ?? ""
self.userAddress = address
print(address)
}
}
}
Then, I have this code to getDocuments:
struct Testtt: Identifiable{
var id: String = UUID().uuidString
var name: String
}
class TesteViewModel: NSObject,ObservableObject{
#StateObject var LocationModel = LocationViewModel()
let db = Firestore.firestore()
#Published var testsss = [Testtt]()
func testeApp(){
db.collection("Test").whereField(LocationModel.userAddress, isEqualTo: true).getDocuments { (querySnapshot, err) in
guard let documents = querySnapshot?.documents else {return }
self.testsss = documents.map { (queryDocumentSnapshot) -> Testtt in
let data = queryDocumentSnapshot.data()
let name = data["Name"] as? String ?? ""
return Testtt(name: name)
}
}
}
}
Before, instead of LocationModel.userAddress, I manually added the location and it worked fine. But, after using it, the project launched but I got 2 error:
Purple error:
Accessing StateObject's object without being installed on a View. This will create a new instance each time.
Thread 1 error:
Invalid field path (). Paths must not be empty, begin with '.', end with '.', or contain '..'"`
On debug I got:
Terminating app due to uncaught exception 'FIRInvalidArgumentException', reason: 'Invalid field path (). Paths must not be empty, begin with '.', end with '.', or contain '..''
The app crashes right after I press the button that takes me to this class's view
If anyone have any links with more information, I would be grateful to receivw
Can you check that LocationModel.userAddress has the right content before it being used? If that string was empty or null, could cause this issue.

Get the location type (e.g. Hotel or Restaurant)

I want to get the type of place that the user is at, like if they are at a restaurant or at a hotel. I have tried to look into that and no one seems to have an answer. Even when printing the full description of the location it doesn't show that.
I finally found a solution to this answer. I just used the Yelp API and it gave me more than enough information. This is what I used...
https://medium.com/#khansaryan/yelp-fusion-api-integration-af50dd186a6e
Apple is providing this:
func getLocationAddress(completion: #escaping ([String]) -> Void) {
self.getLocation { locations in
let place = CLLocation(latitude: locations[0].latitude, longitude: locations[0].longitude)
CLGeocoder().reverseGeocodeLocation(place) { placemarks, error in
guard let placemark = placemarks?.first else {
let errorString = error?.localizedDescription ?? "Unexpected Error"
print("Unable to reverse geocode the given location. Error: \(errorString)")
return
}
var output: [String] = []
let reversedGeoLocation = ReversedGeoLocation(with: placemark)
output.append(reversedGeoLocation.formattedAddress)
completion(output)
}
}
}
Also, you should include MapKit
Here's a link!

distance between current location and updated location

i want to get the distance between two location.
first location is my current location and the other location is the user updated location.
so, how to track user location and measure the distance between the current location and user updated location
i tried this code and the measure is not true.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let loc = locations.last else {return}
guard let startLocation = startLocation else {
self.startLocation = loc
return
}
guard let current = manager.location else { return }
let distance = current.distance(from: startLocation)
print("Distance: \((distance))")
}

GeoFire not showing entered region swift

I've tried for so many weeks to get it to work. I have read the documentation many times and there seem to be no examples of this online.
Goal:
All I want to be able to do is print to the console all the posts that are within a 10km radius of the current user's location.
Issues: I don't really understand what parameters need to placed into the geoFire.setLocation the user's locations or post location. The documentation only shows a manually entered coordinates. I want to pull mine from firebase and query it for when the user gets within 10km they are printed to the console. Currently, with the code I have nothing is being printed at all.
fileprivate func setupGeoFireLocation() {
let ref = Database.database().reference(withPath: "posts")
ref.observe(.childAdded, with: { (snapshot) in
guard let dictionary = snapshot.value as? [String: Any] else { return }
guard let latitude = dictionary["latitude"] as? String else { return }
guard let longitude = dictionary["longitude"] as? String else { return }
let postLat = (latitude as! NSString).doubleValue
let postLon = (longitude as! NSString).doubleValue
self.geoFire.setLocation(CLLocation(latitude: postLat, longitude: postLat), forKey: "posts")
//Not quite sure what's meant to be the "forKey:" parameter.
})
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let userLocation: CLLocation = locations[0] as CLLocation
ref = Database.database().reference()
geoFire = GeoFire(firebaseRef: ref)
let center = CLLocation(latitude: userLocation.coordinate.latitude, longitude: userLocation.coordinate.longitude)
let circleQuery = geoFire.query(at: center, withRadius: 10.0)
_ = circleQuery.observe(.keyEntered, with: { (key, location) in
print(key)
})
circleQuery.observeReady{
print("All initial data has been loaded and events have been fired for circle query!")
}
}
From your comments it seems like you never specifically set the location information through GeoFire's setLocation method. For GeoQueries to work you need to indeed set the location in GeoFire as well. It won't simply work from your existing lat and lon values.
GeoFire queries a separate location, where it associates keys with geohashes of the location info. Please follow the GeoFire documentation on setting a location for a key, retrieving a location, and querying.
I also recommend reading these questions that explain more about GeoFire and how it works:
Filtering results with Geofire + Firebase (shows the data structure that you're missing)
GeoFire query on User location (shows that there are two top-level nodes: one with the geo-info only, and one with the other information. You currently only have the latter.)
Using GeoFire queries in Swift does not give useful output
Android GeoFire onKeyEntered not trigged

How to store users location on CloudKit in swift?

I'm trying to store a users journey and upload it to CloudKit.
The following takes the users location whenever they move more than 5 meters and uploads as a string but I want it to be uploaded as a location list or similar so it can be pulled down later.
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
{
let location = locations.last
let center = CLLocationCoordinate2D(latitude: location!.coordinate.latitude, longitude: location!.coordinate.longitude)
addCrumbPoint(center)
let message = "{\"lat\":\(location!.coordinate.latitude),\"lng\":\(location!.coordinate.longitude), \"alt\": \(location!.altitude)}"
let newSweet = CKRecord(recordType: "Sweet")
newSweet["content"] = message
let publicData = CKContainer.defaultContainer().publicCloudDatabase
publicData.saveRecord(newSweet, completionHandler: { (record:CKRecord?, error:NSError?) -> Void in
if error == nil {
print("woo")
}else{
print(error)
}
})
}
The documentation on using Location and CloudKit is written in Objective-C so any help would be brilliant.
CloudKit
CLLocationManager
You create a CKRecordID and a CKRecord. Then you set the last retrieved CLLocation on the CKRecord.
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations.last!
let id = CKRecordID(recordName: "01")
let locationRecord = CKRecord(recordType: "location", recordID: id)
locationRecord.setObject(location, forKey: "location")
// or locationRecord["location"] = location
let publicData = CKContainer.defaultContainer().publicCloudDatabase
publicData.saveRecord(locationRecord) { record, error in
//
}
}