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)
}
}
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.
SO, I am new to swift and I made the conversion from current Lat and Long to City name and Country, it works fine like that:
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
{
if didFindLocation == false
{
didFindLocation = true
locationManager.stopUpdatingLocation()
userLocation = locations[0]
long = userLocation.coordinate.longitude;
lat = userLocation.coordinate.latitude;
print("\(lat),\(long)")
converLocationToCity()
}
}
func converLocationToCity()
{
let geoCoder = CLGeocoder()
userLocation = CLLocation(latitude: self.lat, longitude: self.long)
geoCoder.reverseGeocodeLocation(userLocation, completionHandler:
{
(placemarks, error) -> Void in
var placeMark: CLPlacemark!
placeMark = placemarks?[0]
if let city = placeMark.addressDictionary!["State"] as? String
{
self.city = city as String
} else
{
self.city = ""
}
if let country = placeMark.addressDictionary!["Country"] as? String
{
self.country = country as String
} else
{
self.country = ""
}
self.currentCity.name = ("\(self.city), \(self.country)" as String)
print("\(self.currentCity.name)")
self.fetchWeather.performCurrentWeatherFetch(forSelectedCity: self.currentCity.name)
DispatchQueue.main.async()
{
(self.superview as! UICollectionView).reloadData()
}
})
}
But when the device is set to other language, Russian for example it returns me the City Name and Country in Russian characters, but I need it to be only in english, please anybody some ideas or suggestions? Thank you!
Here is My Solution
While getting the location data i change `UserDefaults.standard.set(["base"], forKey: "AppleLanguages")'
and once I have received the dictionary in English i remove the Object
UserDefaults.standard.removeObject(forKey: "AppleLanguages")
which then sets applelanguage to default value
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let userLocation:CLLocation = locations[0] as CLLocation
// Call stopUpdatingLocation() to stop listening for location updates,
// other wise this function will be called every time when user location changes.
// manager.stopUpdatingLocation()
print("user latitude = \(userLocation.coordinate.latitude)")
print("user longitude = \(userLocation.coordinate.longitude)")
let geoCoder = CLGeocoder()
let location = CLLocation(latitude: userLocation.coordinate.latitude, longitude: userLocation.coordinate.longitude)
//location.accessibilityLanguage = "en-US"
UserDefaults.standard.set(["base"], forKey: "AppleLanguages")
geoCoder.reverseGeocodeLocation(location, completionHandler: { placemarks, error in
guard let addressDict = placemarks?[0].addressDictionary else {
return
}
print(addressDict)
// Print each key-value pair in a new row
addressDict.forEach { print($0) }
// Print fully formatted address
if let formattedAddress = addressDict["FormattedAddressLines"] as? [String] {
print(formattedAddress.joined(separator: ", "))
}
// Access each element manually
if let locationName = addressDict["Name"] as? String {
print(locationName)
}
if let street = addressDict["Thoroughfare"] as? String {
print(street)
}
var myCity:String = ""
if let city = addressDict["City"] as? String {
print(city)
if(city != "" ){
myCity = city
}
}
if let zip = addressDict["ZIP"] as? String {
print(zip)
}
var myCountry:String = ""
if let country = addressDict["Country"] as? String {
print(country)
if(country != "" ){
myCountry = country
}
MyGenericFunctions.sharedInstance.saveCountry(country: country)
}
manager.stopUpdatingLocation()
if(myCity != "" && myCountry != "" && self.isCurrLocAPICalled != true){
print("API Called")
self.isCurrLocAPICalled = true
self.callLocationSearch(strCity: myCity, strCountry: myCountry)
UserDefaults.standard.removeObject(forKey: "AppleLanguages")
}
})
//manager.stopUpdatingLocation()
}
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)
I am having trouble fetching the locations from cloudkit. The location gets uploaded, but when I try to have them printed out and loaded, they aren't downloaded. I don't get any errors.
This function uploads the location to CloudKit:
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.015, longitudeDelta: 0.015))
self.mapView.setRegion(region, animated: true)
self.locationManager.stopUpdatingLocation()//
let locationRecord = CKRecord(recordType: "location")
locationRecord.setObject(location, forKey: "location")
let publicData = CKContainer.defaultContainer().publicCloudDatabase
publicData.saveRecord(locationRecord) { record, error in
}
if error == nil
{
print("Location saved")
}
event1 = locations
}
This function fetches the locations from CloudKit:
func loadLocation()
{
let locations = [CKRecord]()
let publicData1 = CKContainer.defaultContainer().publicCloudDatabase
let query1 = CKQuery(recordType: "location", predicate: NSPredicate(format: "TRUEPREDICATE", argumentArray:nil))
publicData1.performQuery(query1, inZoneWithID: nil) { (results: [CKRecord]?, error: NSError?) -> Void in
if let locations = results
{
self.locations = locations
print(locations)
}
}
}
So to do this I made a unit test, that passes:
//
// CloudKitLocationsTests.swift
//
import XCTest
import UIKit
import CoreLocation
import CloudKit
class CloudKitLocationsTests: XCTestCase {
let locations = [ CLLocation(latitude: 34.4, longitude: -118.33), CLLocation(latitude: 32.2, longitude: -121.33) ]
func storeLocationToCloud(location:CLLocation) {
let locationRecord = CKRecord(recordType: "location")
locationRecord.setObject(location, forKey: "location")
let publicData = CKContainer.defaultContainer().publicCloudDatabase
publicData.saveRecord(locationRecord) { (records, error) in
if error != nil {
print("error saving locations: \(error)")
} else {
print("Locations saved: \(records)")
}
}
}
func fetchLocationsFromCloud(completion: (error:NSError?, records:[CKRecord]?) -> Void) {
let query = CKQuery(recordType: "Location", predicate: NSPredicate(value: true))
CKContainer.defaultContainer().publicCloudDatabase.performQuery(query, inZoneWithID: nil){
(records, error) in
if error != nil {
print("error fetching locations")
completion(error: error, records: nil)
} else {
print("found locations: \(records)")
completion(error: nil, records: records)
}
}
}
func testSavingLocations(){
let testExpectation = expectationWithDescription("saveLocations")
var n = 0
for location in self.locations {
let locationRecord = CKRecord(recordType: "Location")
locationRecord["location"] = location
let publicData = CKContainer.defaultContainer().publicCloudDatabase
publicData.saveRecord(locationRecord) { (records, error) in
if error != nil {
print("error saving locations: \(error)")
} else {
print("Locations saved: \(records)")
}
n += 1
if n >= self.locations.count {
testExpectation.fulfill()
}
}
}
// do something then call fulfill (in callback)
waitForExpectationsWithTimeout(10){ error in
if error != nil {
XCTFail("timed out waiting on expectation: \(testExpectation)")
}
}
}
func testFetchingLocations(){
let testExpectation = expectationWithDescription("FetchLocations")
fetchLocationsFromCloud(){ (error, records) in
if error != nil {
XCTFail("error fetching locations")
} else {
XCTAssertGreaterThan(records!.count, 0)
}
// do something then call fulfill (in callback)
testExpectation.fulfill()
}
waitForExpectationsWithTimeout(10){ error in
if error != nil {
XCTFail("timed out waiting on expectation: \(testExpectation)")
}
}
}
}
Note that you had case mismatch Location/location. Also, I am doing a subscript to set the field value.
Run this it works. Getting the location from the location manger callback has nothing to do with CloudKit so you should be able to plug this in as you require.
One other thing: I did turn on the option to allow you to query on ID field for the Location record type.
If your problem is to retrieve an array of CLLocation, try this:
publicData1.performQuery(query1, inZoneWithID: nil) { records, error in
var locations = [CLLocation]()
if let records = records {
for record in records {
if let location = record["location"] as? CLLocation {
locations.append(location)
}
}
}
}
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)
}
})
}