Update realm IgnoredProperties While App Running - ios

class place :Object{
#objc dynamic var name : String = ""
#objc dynamic var address :String = ""
#objc dynamic var lat :Double = 0.0
#objc dynamic var long :Double = 0.0
var marker = GMSMarker()
var cllCorrdinate : CLLocationCoordinate2D {
return CLLocationCoordinate2D(latitude: lat, longitude: long)
}
}
when i add a place to an array of FirstViewController
let place = Place()
place.name = "123"
place.address = "123 street"
place.lat = lat
place.long = lon
let marker = GMSMarker()
marker.position = place.cllCorrdinate
marker.icon = UIImage(imageLiteralResourceName: "123")
place.marker = marker
place.marker.map = mapView
saveToRealm(place: place)
I am Sharing data between two ViewController with singleton class and keep updating it through viewWillDisappear and viewDidApear
class anotherController:UITableViewController{
var places:List<PLace>?
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let place = places[indexPath.row]
let marker = place.marker
}}
Value of marker always showing nil.
is it possible to update ignore property only while app is running ?
places are saving and updating fine in realm

Related

how to get rid of the 'The API method must be called from the main thread' problem when retrieving data from an API to use them in arrays? Swift

I come to you because I have the following problem:
I work with the 'GoogleMaps' cocoapods and I need to place several markers in a map by using the latitude, longitude and a codeID that I get from an API. I will present you guys 2 cases: the one that works (that uses 3 hard coded arrays mentioned before) and the one that I try to get from the API and that crashes no matter what I do. OK, the first case (the one that works) is this one:
import UIKit
import GoogleMaps
class ViewController: UIViewController {
// MARK: - Constants and variables
let lat: Double = 38.739429 // User's Latitude
let lon: Double = -9.137115 // User's Longitude
let zoom: Float = 15.0
// MARK: - Elements in the storyboard
#IBOutlet weak var googleMap: GMSMapView!
// MARK: - ViewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
googleMapsStuff()
}
// MARK: - Google maps method
func googleMapsStuff() {
googleMap.delegate = self
self.googleMap.isMyLocationEnabled = true // User's current position (blue dot on the map)
let arrayLat: [Double] = [38.739, 38.74, 38.741, 38.732, 38.7325, 38.733]
let arrayLon: [Double] = [-9.136, -9.135, -9.134, -9.137, -9.1375, -9.138]
//var arrayCompanyZoneID: [Int] = []
let camera: GMSCameraPosition = GMSCameraPosition.camera(withLatitude: lat, longitude: lon, zoom: self.zoom)
googleMap.camera = camera
for index in 0..<arrayLon.count {
let marker = GMSMarker()
marker.position = CLLocationCoordinate2D(latitude: arrayLat[index], longitude: arrayLon[index])
marker.title = "Marker number: \(index)"
marker.snippet = "Marker's Lat: \(arrayLat[index]), Marker's Lon: \(arrayLon[index])"
marker.map = self.googleMap
print("Index: \(index)")
}
}
}
And as you can see in the image, it all goes smoothly well
The problem comes in the second case, when I try to fill the empty arrays (which it seems to do) when I connect to an API to get that data. This is the "failure" case:
struct MyInfo: Codable {
let id: String
let name: String
let x: Double // Longitude
let y: Double // Latitude
let licencePlate: String?
let range: Int?
let batteryLevel: Int?
let seats: Int?
let model: String?
let resourceImageId: String?
let pricePerMinuteParking: Int?
let pricePerMinuteDriving: Int?
let realTimeData: Bool?
let engineType: String?
let resourceType: String?
let companyZoneId: Int
let helmets: Int?
let station: Bool?
let availableResources: Int?
let spacesAvailable: Int?
let allowDropoff: Bool?
let bikesAvailable: Int?
}
class ViewController: UIViewController {
// MARK: - Constants and variables
let lat: Double = 38.739429 // User's Latitude
let lon: Double = -9.137115 // User's Longitude
let zoom: Float = 15.0
var arrayLat: [Double] = [] // [38.7395, 38.739, 38.74, 38.741, 38.732, 38.7325, 38.733]
var arrayLon: [Double] = [] // [-9.1365, -9.136, -9.135, -9.134, -9.137, -9.1375, -9.138]
var arrayCompanyZoneID: [Int] = [] // [1, 2, 3, 4, 5, 6, 7]
// MARK: - Elements in the storyboard
#IBOutlet weak var googleMap: GMSMapView!
// MARK: - ViewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
googleMap.delegate = self
self.googleMap.isMyLocationEnabled = true // User's current position (blue dot on the map)
let camera: GMSCameraPosition = GMSCameraPosition.camera(withLatitude: self.lat, longitude: self.lon, zoom: self.zoom)
googleMap.camera = camera
guard let urlAPI = URL(string: "https://apidev.meep.me/tripplan/api/v1/routers/lisboa/resources?lowerLeftLatLon=38.711046,-9.160096&upperRightLatLon=38.739429,-9.137115") else { return }
let task = URLSession.shared.dataTask(with: urlAPI) {(data, response, error) in
if error == nil {
guard let urlContent = data else { return }
do {
let JSONResult = try JSONDecoder().decode([MyInfo].self, from: urlContent) //JSONSerialization.jsonObject(with: urlContent, options: .mutableContainers)
print("JSON Result:", JSONResult)
for jsonData in JSONResult {
self.arrayLon.append(jsonData.x)
self.arrayLat.append(jsonData.y)
self.arrayCompanyZoneID.append(jsonData.companyZoneId)
}
print("-----------------")
print(type(of: JSONResult))
print("-----------------")
print("ArrayLon:", self.arrayLon)
print("ArrayLat:", self.arrayLat)
print("companyZoneId: ", self.arrayCompanyZoneID)
print("Count zoneid: ", self.arrayCompanyZoneID.count)
print("-----------------")
// MARK: - Place the multiple markers on the map
for index in 0..<self.arrayCompanyZoneID.count {
let marker = GMSMarker()
marker.position = CLLocationCoordinate2D(latitude: self.arrayLat[index], longitude: self.arrayLon[index])
marker.title = "Marker number: \(index)"
marker.snippet = "Marker's Lat: \(self.arrayLat[index]), Marker's Lon: \(self.arrayLon[index])"
marker.map = self.googleMap
print("Index: \(index)")
}
} catch {
print("JSON processing failed.")
}
} else {
print("Error serializing JSON:", error!)
}
}
task.resume()
}
And it doesn't matter what I do, the console always says:
"Terminating app due to uncaught exception 'GMSThreadException', reason: 'The API method must be called from the main thread' "
I also tried using the method
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
but it also says that the API method most be called from the main thread.
I'm so stuck here and I invested several hours to this issue but It only fails over and over.
I appreciate your advice and wisdom.
Thanks in advance.
You need
DispatchQueue.main.async {
// MARK: - Place the multiple markers on the map
for index in 0..<self.arrayCompanyZoneID.count {
let marker = GMSMarker()
marker.position = CLLocationCoordinate2D(latitude: self.arrayLat[index], longitude: self.arrayLon[index])
marker.title = "Marker number: \(index)"
marker.snippet = "Marker's Lat: \(self.arrayLat[index]), Marker's Lon: \(self.arrayLon[index])"
marker.map = self.googleMap
print("Index: \(index)")
}
}
As URLSession.shared.dataTask callback is in a background thread

Two Functions are calling in Swift

I am using GoogleMaps to show the location marker on screens after fetching the location from Firestore database but the problem is I have three functions.
First function is showing all the list of users on the google maps, I called it in viewDidLoad() method.
func showListOfAllUsers() {
for document in snapshot!.documents {
print(document.data())
let marker = GMSMarker()
self.location.append(Location(trackingData: document.data()))
print(self.location)
guard let latitude = document.data()["Latitude"] as? Double else { return }
guard let longitude = document.data()["longitude"] as? Double else { return }
marker.position = CLLocationCoordinate2D(latitude: latitude as! CLLocationDegrees , longitude: longitude as! CLLocationDegrees)
marker.map = self.mapView
marker.userData = self.location
marker.icon = UIImage(named: "marker")
bounds = bounds.includingCoordinate(marker.position)
print("Data stored in marker \(marker.userData!)")
}
}
Now I presented a list of users in which I am passing the selected user co-ordinates to show the markers on the GoogleMaps.
func getAllLocationOfSelectedUserFromFirestore() {
for document in snapshot!.documents {
print(document.data())
let marker = GMSMarker()
self.location.append(Location(trackingData: document.data()))
print(self.location)
guard let latitude = document.data()["Latitude"] as? Double else { return }
guard let longitude = document.data()["longitude"] as? Double else { return }
marker.position = CLLocationCoordinate2D(latitude: latitude as! CLLocationDegrees , longitude: longitude as! CLLocationDegrees)
marker.map = self.mapView
marker.userData = self.location
bounds = bounds.includingCoordinate(marker.position)
print("Data stored in marker \(marker.userData!)")
}
}
I used delegate method to pass the selected user information.
extension MapViewController: ShowTrackingSalesMenListVCDelegate {
func didSelectedFilters(_ sender: ShowTrackingSalesMenListViewController, with userID: String) {
self.selectedUserID = userID
self.userLogButton.isHidden = false
print("The selected UserID is \(selectedUserID)")
self.getAllLocationOfSelectedUserFromFirestore() // called here the second function
}
Here is GMSMapViewDelegate function in which I am passing the user informations in userData.
func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool {
print("didTap marker")
self.view.endEditing(true)
self.mapView.endEditing(true)
if let _ = self.activeMarker {
self.infoWindowView.removeFromSuperview()
self.activeMarker = nil
}
self.infoWindowView = MarkerInfoView()
let point = mapView.projection.point(for: marker.position)
self.infoWindowView.frame = CGRect(x: (point.x-(self.infoWindowView.width/2.0)), y: (point.y-(self.infoWindowView.height+25.0)), width: self.infoWindowView.width, height: self.infoWindowView.height)
self.activeMarker = marker
for mark in location {
self.infoWindowView.storeNameLabel?.text = mark.name
}
print(self.infoWindowView.storeNameLabel?.text as Any)
if let data = marker.userData as? [String:Any] {
print(data)
self.storeMapData = data
print(self.storeMapData)
var name = "N/A"
if let obj = data["name"] as? String {
name = obj
}
} else {
}
infoWindowView.delegate = self
self.mapView.addSubview(self.infoWindowView)
return true
}
It is showing the marker of the selected user on GoogleMaps. Now the problem is GMSMapViewDelegate function is same for both the above functions and it is showing the markers from both the functions on map. But I want to show only the selected user information on Maps. The red marker showing the selected user locations. How can I do this?
Just put a boolean flag and when you select the user set it to true and check it in the delegate and clear map overlay and put your marker only

swift: what is the correct way of working with current location

I am quite new to swift and IOS development.
I would like to ask experienced members what is the correct way of creating mapvie and tableview in one viewcontroller and populating them.
The logic of application is the following:
get current location of user
read plist file with POI coordinates
for each POI run a function wich calculates distance between user and point
populate table with data from plist file plus newly calculated data.
Both mapview and tableview are in the same viewcontroller.
in viewDidLoad I am getting users location.
in viewwillappear I am running functions to read plist file and calculate distances between POIs and user.
Everything is working but it is not stable ... sometimes it might show user's location but table will be empty. So I doubt that I am doing everything correctly. Also probably it is not correct to put both map and table inside one class?
Update
Here is the code:
import UIKit
import MapKit
import CoreLocation
class MapViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var theMapView: MKMapView!
#IBOutlet weak var tableView: UITableView!
var locationManager: CLLocationManager!
var branches = [Branch]()
override func viewDidLoad() {
super.viewDidLoad()
locationManager = CLLocationManager()
self.locationManager.requestAlwaysAuthorization()
self.locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.startUpdatingLocation()
}
tableView.dataSource = self
tableView.delegate = self
locationManager.startUpdatingLocation()
locationManager.startUpdatingHeading()
//mapview setup to show user location
theMapView.delegate = self
theMapView.showsUserLocation = true
theMapView.mapType = MKMapType(rawValue: 0)!
theMapView.userTrackingMode = MKUserTrackingMode(rawValue: 2)!
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
readFromPlist()
}
//MARK: UITableView methods
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return branches.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: MapCustomCell = tableView.dequeueReusableCellWithIdentifier("mapCell") as! MapCustomCell
let brnch = branches[indexPath.row]
cell.mapSetupCell(brnch.cityName, AddressLabel: brnch.address, DistanceLabel: brnch.distance)
return cell
}
//MARK: FUNC TO CALCULATE DISTANCE
func calculateDistance (lat1 lat1: Double, lon1: Double, lat2: Double, lon2: Double) -> String {
return "100km"
}
func locationManager (manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let myCoordinates = locations.last
let myLat = myCoordinates!.coordinate.latitude
let myLong = myCoordinates!.coordinate.longitude
let myCoordinates2D = CLLocationCoordinate2DMake(myLat, myLong)
let myLatDelta = 0.10
let myLongDelta = 0.10
let mySpan = MKCoordinateSpanMake(myLatDelta, myLongDelta)
let myRegion = MKCoordinateRegion(center: myCoordinates2D, span: mySpan)
theMapView.setRegion(myRegion, animated: true)
let myAnno = MKPointAnnotation()
myAnno.coordinate = myCoordinates2D
self.locationManager.stopUpdatingLocation()
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
print("error:" + error.localizedDescription)
}
func readFromPlist() {
//read plist file to extract banks coordinates
let path = NSBundle.mainBundle().pathForResource("poi", ofType: "plist")
let POIarrays = NSArray(contentsOfFile: path!)
for arr in POIarrays! {
var ctName : String!
var brnchAddress : String!
var wrkngHours : String!
var lat : Double!
var long : Double!
ctName = arr.objectForKey("cityName")! as! String
brnchAddress = arr.objectForKey("address")! as! String
wrkngHours = arr.objectForKey("workingHours")! as! String
lat = Double(arr.objectForKey("latitude")! as! String)
long = Double(arr.objectForKey("longitude")! as! String)
let latitude: CLLocationDegrees = lat
let longitude : CLLocationDegrees = long
let bankLocation : CLLocationCoordinate2D = CLLocationCoordinate2DMake(latitude, longitude)
let annotation = MKPointAnnotation()
annotation.coordinate = bankLocation
annotation.title = bnkName
annotation.subtitle = brnchAddress
self.theMapView.addAnnotation(annotation)
let myLatitude = self.locationManager.location?.coordinate.latitude
let myLongitude = self.locationManager.location?.coordinate.longitude
if myLatitude != nil {
let dist = calculateDistance(lat1: latitude, lon1: longitude, lat2: myLatitude!, lon2: myLongitude!)
let b = Branch(cityName: ctName!, address: brnchAddress!, distance: dist)
branches.append(b)
}
}
}
}
Sometimes I got an error "error:The operation couldn’t be completed. (kCLErrorDomain error 0.)" and current location doesn't appear on my map.

How can I add a GPS that leads to pins on map?

I am making an app that displays pins ( that the user adds) on a map and saves them in a tableview and I would like to have a GPS lead the user when they tap on a button that opens the GPS so they can get to that place, how could I do this, this is the code I have in the table view:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
cell.textLabel?.text = places[indexPath.row]["name"]
return cell
}
override func tableView(tableView: UITableView, willSelectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath? {
activePlace = indexPath.row
return indexPath
}
and this is the code I have in the map view:
func action(gestureRecognizer:UIGestureRecognizer) {
if gestureRecognizer.state == UIGestureRecognizerState.Began {
var touchPoint = gestureRecognizer.locationInView(self.Map)
var newCoordinate = self.Map.convertPoint(touchPoint, toCoordinateFromView: self.Map)
var location = CLLocation(latitude: newCoordinate.latitude , longitude: newCoordinate.longitude)
CLGeocoder().reverseGeocodeLocation(location, completionHandler: { (placemarks, error) -> Void in
var title = ""
if (error == nil) {
if let p = CLPlacemark(placemark: placemarks?[0] as! CLPlacemark) {
var subThoroughfare: String = ""
var thoroughfare: String = ""
if p.subThoroughfare != nil {
subThoroughfare = p.subThoroughfare
}
if p.thoroughfare != nil {
thoroughfare = p.thoroughfare
}
title = "\(subThoroughfare) \(thoroughfare)"
}
}
if title == "" {
title = "added \(NSDate())"
}
places.append(["name":title,"lat":"\(newCoordinate.latitude)","lon":"\(newCoordinate.longitude)"])
I have been trying the code below to add the GPS but I have a predefined destination so I suppose I have to change the coordinates to something else but I dont know exactly what, thanks for the help !
UIApplication.sharedApplication().openURL(NSURL(string: "http://maps.apple.com/maps?daddr=34.539250,-117.222025")!)
I am also using this piece of code for the user location
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
var userLocation:CLLocation = locations[0] as! CLLocation
var latitude = userLocation.coordinate.latitude
var longitude = userLocation.coordinate.longitude
var coordinate = CLLocationCoordinate2DMake(latitude, longitude)
var latDelta:CLLocationDegrees = 0.01
var lonDelta:CLLocationDegrees = 0.01
var span:MKCoordinateSpan = MKCoordinateSpanMake(latDelta, lonDelta)
var region:MKCoordinateRegion = MKCoordinateRegionMake(coordinate, span)
self.Map.setRegion(region, animated: true)
To piggyback off of #Mingebag 's answer, in response to your comment there:
You need to set the variables in the openMapForPlace() method he provided to the ones that correspond to the gps position you want to get directions to in the apple maps app.
Wherever you call this method, you can pass it the variables you need. For this to work, you really just need to give it Lat / Lon somehow.
You could do that by making it be:
func openMapsForPlace(lat: Double, lon: Double) {}
or whatever format you have those in.
You can also just put this code wherever you want if you have a better place for it to execute from:
let regionDistance:CLLocationDistance = 10000
//set the coordinates with your variables
var coordinates = CLLocationCoordinate2DMake(newCoordinates.latitude, newCoordinates.longitude)
let regionSpan = MKCoordinateRegionMakeWithDistance(coordinates, regionDistance, regionDistance)
var options = [
MKLaunchOptionsMapCenterKey: NSValue(MKCoordinate: regionSpan.center),
MKLaunchOptionsMapSpanKey: NSValue(MKCoordinateSpan: regionSpan.span)
]
//now your placemark will have the lat long you put in above
var placemark = MKPlacemark(coordinate: coordinates, addressDictionary: nil)
var mapItem = MKMapItem(placemark: placemark)
mapItem.name = "\(self.venueName)"
//this line then launches the app for you
mapItem.openInMapsWithLaunchOptions(options)
Let us assume you have location enabled so you just need the "user location" that you have saved in your table then try this:
Just pass your lang,lat and that should do it ^^
func openMapForPlace() {
var lat1 : NSString = self.venueLat
var lng1 : NSString = self.venueLng
var latitute:CLLocationDegrees = lat1.doubleValue
var longitute:CLLocationDegrees = lng1.doubleValue
let regionDistance:CLLocationDistance = 10000
var coordinates = CLLocationCoordinate2DMake(latitute, longitute)
let regionSpan = MKCoordinateRegionMakeWithDistance(coordinates, regionDistance, regionDistance)
var options = [
MKLaunchOptionsMapCenterKey: NSValue(MKCoordinate: regionSpan.center),
MKLaunchOptionsMapSpanKey: NSValue(MKCoordinateSpan: regionSpan.span)
]
var placemark = MKPlacemark(coordinate: coordinates, addressDictionary: nil)
var mapItem = MKMapItem(placemark: placemark)
mapItem.name = "\(self.venueName)"
mapItem.openInMapsWithLaunchOptions(options)
}
If there are any question feel free to ask

MKPinAnnotation to Detailed view

I have an object array from a PFQuery and then add MKPinAnnotations in my mapView with the following function:
func addAnnotation() {
for (var i = 0; i < self.objectArray.count; i++) {
var location = object.objectForKey("location") as PFGeoPoint
var annotation = MKPointAnnotation()
annotation.setCoordinate(CLLocationCoordinate2D(latitude: location.latitude, longitude: location.longitude))
annotation.title = object.objectForKey("title") as NSString
var priceFormatter = NSNumberFormatter()
self.mapView.addAnnotation(annotation)
}
}
I have want to be able to click on the pin (or the information tab that shows when the pin is tapped) and segue to a detail view controller and pass the information associated with that pin or that index place in the Array...
I only need to know how to get the index number of the tapped pin so I can perform the segue...I've been stuck in this piece of code for the past 3 days and gave up and decided to ask the masters :P
Hope you can help me out please.
Thanks
Create a custom class for your annotation where you can store additional information such as the array index:
class PinAnnotation : NSObject, MKAnnotation {
private var coord: CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: 0, longitude: 0)
var coordinate: CLLocationCoordinate2D {
get {
return coord
}
}
var title: String = ""
var subtitle: String = ""
var index: Int = 0
func setCoordinate(newCoordinate: CLLocationCoordinate2D) {
self.coord = newCoordinate
}
}
later in your code you can set the array index:
func addAnnotation() {
for (var i = 0; i < self.objectArray.count; i++) {
var location = object.objectForKey("location") as PFGeoPoint
var annotation = PinAnnotation()
annotation.setCoordinate(CLLocationCoordinate2D(latitude: location.latitude, longitude: location.longitude))
annotation.title = object.objectForKey("title") as NSString
annotation.index = i
var priceFormatter = NSNumberFormatter()
self.mapView.addAnnotation(annotation)
}
}
You have access to all the properties when you are working with the annotation object

Resources