I want the users' location to be updated in cloudkit but my code just save a new record every time. How can the existing record be modified or replaced by the new one?
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
{
let location = locations.last
let center = CLLocationCoordinate2D(latitude: location!.coordinate.latitude, longitude: location!.coordinate.longitude)
let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01))
self.mapView.setRegion(region, animated: true)
self.locationManager.stopUpdatingLocation()
locationRecord.setObject(location, forKey: "location")
let publicData = CKContainer.defaultContainer().publicCloudDatabase
publicData.saveRecord(locationRecord) { record, error in
}
if error == nil
{
print("Location saved")
self.loc1 = location!
}
}
To Modify a record, you need to pull in your orignal record - update the details and then save the record.
I would do this by caching a reference to the original recordID eg
var locationRecordforUser = (whatever your CKrecord was when you created it)
then in your location method above, just grab it, make changes and perform save
record.setValue(locationbits, forKey: locationField)
self. publicData.saveRecord(record, completionHandler: { (savedRecord, error) in
dispatch_async(dispatch_get_main_queue()) {
completion(savedRecord, error)
}
})
Related
I try to save a map with the users location to a post, but I get Value of type 'MKMapView?' has no member 'MKMapView' as an error all the time...
The following shows my code but I leave out any background code to the images and labels as everything there works fine, I just include them in here so you know how I save the post informations... Do you know what my error is and how I can solve it?
var takenMap: MKMapView!
#IBAction func postPressed(_ sender: Any) {
if textView.text != "" && takenImage != nil && userLocation.text != "" {
// Create and save a new job
let newJob = Job(text: textView.text, jobImage: takenImage!, addedByUser: (userLabel?.text)!, userImage: UserImage, location: userLocation.text, map: takenMap.MKMapView)
newJob.save()
}
//MARK:- CLLocationManager Delegates
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let lastLocation = locations.last {
let geoCoder = CLGeocoder()
let center = CLLocationCoordinate2D(latitude: lastLocation.coordinate.latitude, longitude: lastLocation.coordinate.longitude)
let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01))
map.setRegion(region, animated: true)
self.map = takenMap
geoCoder.reverseGeocodeLocation(lastLocation) { (placeMarks, error) in
if error == nil {
if let firstLocation = placeMarks?[0] {
self.locationManager.stopUpdatingLocation()
if let cityName = firstLocation.locality,
let street = firstLocation.thoroughfare {
self.scanLocation = "\(street), \(cityName)"
print("This is the current city name", cityName)
print("this is the current street address", street)
self.takenLocation = self.scanLocation!
self.userLocation.text = self.takenLocation
}
}
}
}
}
}
Job.swift:
var map: String?
init(map: String? = nil) {
self.map = map
ref = Database.database().reference().child("jobs").childByAutoId()
}
init(snapshot: DataSnapshot){
ref = snapshot.ref
if let value = snapshot.value as? [String : Any] {
map = value["location"] as? String
}
}
func save() {
let newPostKey = ref.key
// save jobImage
if let imageData = jobImage?.jpegData(compressionQuality: 0.5) {
let storage = Storage.storage().reference().child("jobImages/\(newPostKey)")
storage.putData(imageData).observe(.success, handler: { (snapshot) in
self.downloadURL = snapshot.metadata?.downloadURL()?.absoluteString
let postDictionary = [
"map" : self.map!
] as [String : Any]
self.ref.setValue(postDictionary)
})
}
}
I left out any code for labels or whatever out so the snippet won't be too long
The code takenMap.MKMapView should probably just be takenMap.
when I run the code below testing using my phone connected to my mac I have a GPX file and on the debugger I select "Locations" everything works fine, every region I have on the GPX file gets triggered, the notification comes up with no problem.
The issue is when I take my phone for a car ride and I pass the exactly regions I have on my GPX file , the notifications don't get triggered just the first one.
On the method
func locationManager(_ manager: CLLocationManager, didStartMonitoringFor region: CLRegion) {
print("Hi \(region.identifier)")
}
I can see all the regions had been started for monitoring
I've been stuck on this for quite few days now... Could you please someone help me .
Code follows below.
func GetAllILocations(){
if let url = URL(string: "http://www.goemobile.com/mobile/liquidnitro/getlocations.php"){
var request = URLRequest(url:url)
request.httpMethod = "POST";// Compose a query string
let postString = ""
request.httpBody = postString.data(using: String.Encoding.utf8)
let task = URLSession.shared.dataTask(with:request) { data, response, error in
if error != nil{
return
}
do {
if let convertedJson = try JSONSerialization.jsonObject(with: data!, options: []) as? [[String:Any]] {
for location in convertedJson {
if ((location["locationid"] as? Int)! > 0) {
let latitude = location["latitude"] as! Double
let longitude = location["longitude"] as! Double
let title = location["locationtitle"] as! String
let subtitle = location["locationsubtitle"] as? String
let annotation = MKPointAnnotation()
annotation.title = title
annotation.subtitle = subtitle
annotation.coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
self.mapView.addAnnotation(annotation)
let center = CLLocationCoordinate2DMake(latitude, longitude)
let region = CLCircularRegion.init(center: center, radius: 0.5, identifier: title)
region.notifyOnEntry = true;
region.notifyOnExit = false;
self.locationManger.startMonitoring(for: region)
}
}
}
}
catch let error as NSError {
print(error.localizedDescription)
}
}
task.resume()
}
}
func locationManager(_ manager: CLLocationManager, didStartMonitoringFor region: CLRegion) {
print("Hi \(region.identifier)")
}
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
lblRegion.text = region.identifier
if region is CLCircularRegion {
scheduleNotification(inSeconds: 0.5, storeName:region.identifier, completion: {success in
if !success{
let alert = UIAlertController(title: "Error Alert", message: region.identifier, preferredStyle: .alert)
let action = UIAlertAction(title: "OK", style: .cancel, handler: nil)
alert.addAction(action)
self.present(alert, animated: true, completion: nil)
}
})
}
}
Originally I was trying to save a CLLocation as a NSUserDefault value before I stored it as a CKRecord in CLoudkit but I got the error: "defaults.setObject(locationRecord.recordID, forKey: "locationRecordID")" with the reason being "Attempt to set a non-property-list object as an NSUserDefaults/CFPreferences value for key locationRecordID". So now I am trying to save the lat and long as a default and replace the old location in Cloudkit. I am currently getting a 'Thread 1 SIGABRT' error on the line "publicDB.fetchRecordWithID((defaults.objectForKey("Location") as! CKRecordID),completionHandler: " with the reason being "Could not cast value of type '__NSCFDictionary' (0x1a1bec968) to 'CKRecordID'."
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
{
let location = locations.last
self.loc1 = location!
let center = CLLocationCoordinate2D(latitude: location!.coordinate.latitude, longitude: location!.coordinate.longitude)
let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 0.004, longitudeDelta: 0.004))
self.mapView.setRegion(region, animated: true)
self.locationManager.stopUpdatingLocation()
self.locationManager.startMonitoringSignificantLocationChanges()
self.getRecordToUpdate(locations)
let lat: CLLocationDegrees = center.latitude
self.lat1 = lat
let long: CLLocationDegrees = center.longitude
self.long1 = long
locationRecord.setObject(location, forKey: "location")
let publicData = CKContainer.defaultContainer().publicCloudDatabase
publicData.saveRecord(locationRecord) { record, error in
}
if error == nil
{
print("Location saved")
}
}
func getRecordToUpdate(locations:[CLLocation])
{
let defaults = NSUserDefaults.standardUserDefaults()
var locationRecord:CKRecord
if defaults.objectForKey("Location") == nil{
locationRecord = CKRecord(recordType: "location")
let locationDict = ["lat": lat1, "lng": long1]//
defaults.setObject(locationDict, forKey: "Location")//
self.updateLocationRecord(locationRecord, locations: locations)
print("new")
}else{
let publicDB = CKContainer.defaultContainer().publicCloudDatabase
publicDB.fetchRecordWithID((defaults.objectForKey("Location") as! CKRecordID),completionHandler: {
(record, error) in
if error == nil{
self.updateLocationRecord(record!, locations: locations)
print("fetched record")
}else{
print("Error fetching previous record")
}
})
}
}
func updateLocationRecord(locationRecord:CKRecord, locations:[CLLocation])
{
let location = locations.last
locationRecord.setObject(location, forKey: "location")
let publicData = CKContainer.defaultContainer().publicCloudDatabase
publicData.saveRecord(locationRecord) { record, error in
}
if error == nil
{
print("Location saved")
}
}
Try this
You can save NSData to NSUserDefaults. So all you need is to convert your CLLocation object to NSData as follow:
// saving your CLLocation object
let locationData = NSKeyedArchiver.archivedDataWithRootObject(your location here)
NSUserDefaults.standardUserDefaults().setObject(locationData, forKey: "locationData")
// loading it
if let loadedData = NSUserDefaults.standardUserDefaults().dataForKey("locationData") {
if let loadedLocation = NSKeyedUnarchiver.unarchiveObjectWithData(loadedData) as? CLLocation {
println(loadedLocation.coordinate.latitude)
println(loadedLocation.coordinate.longitude)
}
}
I am trying to have the users' location be updated by this function. Originally it was saving the users' location every time, but I added the code that will be below the comment. How can I make sure the record that is being deleted is the old location?
let locationRecord = CKRecord(recordType: "location")
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
{
let location = locations.last
let center = CLLocationCoordinate2D(latitude: location!.coordinate.latitude, longitude: location!.coordinate.longitude)
let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01))
self.mapView.setRegion(region, animated: true)
self.locationManager.stopUpdatingLocation()//
locationRecord.setObject(location, forKey: "location")
let publicData = CKContainer.defaultContainer().publicCloudDatabase
publicData.saveRecord(locationRecord) { record, error in
}
if error == nil
{
print("Location saved")
self.loc1 = location!
}
//testing code below
let operation = CKModifyRecordsOperation(recordsToSave: nil, recordIDsToDelete: [locationRecord.recordID])
operation.savePolicy = .AllKeys
operation.modifyRecordsCompletionBlock = { added, deleted, error in
if error != nil {
print(error)
} else {
print("location updated")
}
}
CKContainer.defaultContainer().publicCloudDatabase.addOperation(operation)
}
The best way to do this would be to update the existing one, as #Michael pointed out. The easy way to do this would be the first time you create the record, save the recordID to the defaults. For example,
func getRecordToUpdate(locations:[CLLocation])->CKRecord?{
let defaults = NSUserDefaults.standardUserDefaults()
var locationRecord:CKRecord
if defaults.objectForKey("locationRecordID") == nil{
//Create a new record and save its id
locationRecord = CKRecord(recordType: "location")
defaults.setObject(locationRecord.recordID, forKey: "locationRecordID")
self.updateLocationRecord(locationRecord, locations)
}else{
//Fetch the already existing record
let publicDB = CKContainer.defaultContainer().publicCloudDatabase
publicDB.fetchRecordWithID((defaults.objectForKey("locationRecordID") as! CKRecordID), completionHandler{
(record, error) in
if error == nil{
self.updateLocationRecord(record, locations)
}else{
print("Error fetching previous record")
}
}
}
}
Create a new function,
func updateLocationRecord(locationRecord:CKRecord, locations:[CLLocation]){
//Put your code from "let location..." to just above the testing code
//You may want to fix the block syntax in the call to saveRecord
}
Then in the original locationManager method, take out everything and put
self.getRecordsToUpdate(locations)
These are my two function and i want to transform the information from function to the other without need to declare a variable to store the information because always when i start my app the initial variable is the one that show up
func action(gestureRecognizer: UIGestureRecognizer) {
var touchPoint = gestureRecognizer.locationInView(self.mapView)
var location:CLLocationCoordinate2D = mapView.convertPoint(touchPoint, toCoordinateFromView: self.mapView)
var latDelta:CLLocationDegrees = 0.01
var lonDelta:CLLocationDegrees = 0.01
var span:MKCoordinateSpan = MKCoordinateSpan(latitudeDelta:latDelta, longitudeDelta: lonDelta)
var region:MKCoordinateRegion = MKCoordinateRegion(center: location, span: span)
mapView.setRegion(region, animated: true)
var annotation = MKPointAnnotation()
annotation.coordinate = location
annotation.title = ""
}
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
var userLocation:CLLocation = locations[0] as! CLLocation
CLGeocoder().reverseGeocodeLocation(userLocation, completionHandler: { (placeMarks, error) -> Void in
if error != nil
{
println("error: \(error)")
}
else {
let place = CLPlacemark(placemark: placeMarks?[0] as! CLPlacemark)
}
})
}