location manager is not on main thread after segue? - ios

I have a serious problem about use location manage and mapview.
I create a app simple show where the location I am now, it runs ok when only one viewcontroller, but after I added a viewController and segue, and after segue first viewController to map viewController, it has some error message:
location manager (0x101f3d870) was created on a dispatch queue executing on a thread other than the main thread
after that, the App crash with this error message :
MKMapView must be initialized on the main thread
But I really move the code into the main thread, and it does ok before I added first viewcontroller and segue!!! The code at below:
import UIKit
import MapKit
import CoreLocation
class Map_ViewController: UIViewController,MKMapViewDelegate,CLLocationManagerDelegate,UISearchBarDelegate {
#IBOutlet weak var mapView: MKMapView!
var cloaction = CLLocationManager()
var span = MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01)
var currentLat : Double = 0
var currentLon : Double = 0
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
// Start get the location on viewWillAppear
DispatchQueue.main.async {
self.mapView.delegate = self
self.mapView.showsScale = true
self.mapView.showsPointsOfInterest = true
self.mapView.showsUserLocation = true
self.cloaction.requestAlwaysAuthorization()
self.cloaction.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
self.cloaction.delegate = self
self.cloaction.desiredAccuracy = kCLLocationAccuracyBest
self.cloaction.startUpdatingLocation()
}
setCenter()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let location = locations.last {
mapView.region.center = CLLocationCoordinate2D(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
mapView.setRegion(mapView.region, animated: false)
}
}
func setCenter() {
let center = CLLocationCoordinate2D(latitude: mapView.userLocation.coordinate.latitude, longitude: mapView.userLocation.coordinate.longitude)
mapView.region.center = center
mapView.region.span = span
mapView.setRegion(mapView.region, animated: true)
}
}
The segue in the main viewController :
override func viewDidLoad() {
super.viewDidLoad()
self.performSegue(withIdentifier: "goToMap", sender: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
print("goToMapViewController")
}

Related

Thread 1: EXC_BREAKPOINT (code=1, subcode=...) When using MKMap View

So I recently encountered a problem with my MKMapView. Whenever I click a button to go to the view with the map in it I get the above error centered around this line.
Updated with full script. error is let region = MKCoordinateRegion.init(center: (userLocation.location?.coordinate)!, latitudinalMeters: 5000, longitudinalMeters: 5000)
mapView.setRegion(region, animated: true) }
#IBOutlet weak var mapView: MKMapView!
#IBAction func mapTypeChanged(_ sender: Any) {
switch ((sender as AnyObject).selectedSegmentIndex) {
case 0:
mapView.mapType = .standard
case 1:
mapView.mapType = .hybridFlyover
default:
mapView.mapType = .standard
}
}
#IBAction func zoomIn(_ sender: Any) {
let userLocation = mapView.userLocation
let region = MKCoordinateRegion.init(center: (userLocation.location?.coordinate)!, latitudinalMeters: 5000, longitudinalMeters: 5000)
self.mapView.setRegion(region, animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
mapView.showsUserLocation = true
mapView.delegate = self
let userLocation = mapView.userLocation
let region = MKCoordinateRegion.init(center: (userLocation.location?.coordinate)!, latitudinalMeters: 5000, longitudinalMeters: 5000)
mapView.setRegion(region, animated: true)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
There was a formatting problem.
I have no clue why this is as it wasnt happening before. Could anybody help out? Any help will be greatly appreciated
You can try this
if let coor = userLocation.location?.coordinate {
let region = MKCoordinateRegion(center:coor, latitudinalMeters: 5000, longitudinalMeters: 5000)
}
it will work if there is a current value for coor , maybe you click it before getting a location so request for location updates again in else , or initially inside viewDidLoad
//
Use this method
var once = true
//
func mapView(_ mapView: MKMapView,
didUpdate userLocation: MKUserLocation) {
if once {
let region = MKCoordinateRegion.init(center: userLocation.location!.coordinate, latitudinalMeters: 5000, longitudinalMeters: 5000)
mapView.setRegion(region, animated: true)
once = false
}
}
of the delegateMKMapViewDelegate to get the correct value

Unrecognized selector sent to instance.. 'NSInvalidArgumentException' for MapView delegate

Swift 4/Google Maps.
The crash I get is..
Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: '-[UIView setDelegate:]:
unrecognized selector sent to instance 0x7fa1eb50d7e0'
Yes, I know there are a lot of threads on this, but looking through them has not helped me. I have debugged enough where I have a suspect, but do not know exactly how to resolve.
Here's the git repo
Some detail.. The MapViewController has three UIViews.
MapViewController
Main View (default one that comes with the controller)
-- Menu (sub view of Main View)
-- Map (sub view of Main View)
I believe I've narrowed down the crash to the statement
self.mapView.delegate = self
Here's most of the code for the MapViewController.
import UIKit
import GoogleMaps
import FirebaseDatabase
class MapViewController: UIViewController, GMSMapViewDelegate, CLLocationManagerDelegate {
#IBOutlet var mapView: GMSMapView!
var locationOfInterestMarker = GMSMarker()
var userMarker = GMSMarker()
var locationManager = CLLocationManager()
let zoom:Float = 15
let locationOfInterestImage:String = "hhouseicon"
let userMarkerImage:String = "witchicon"
override func viewDidLoad() {
super.viewDidLoad()
self.getLocations()
self.locationManager.delegate = self
self.locationManager.requestAlwaysAuthorization()
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startMonitoringSignificantLocationChanges()
/**
The next line causes crash. I believe I need to set the delegate to something other than self. Set the delegate to the View named Map. However I'm not sure about the actual code for that.
**/
self.mapView.delegate = self //Crashes
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
guard status == .authorizedWhenInUse else {
return
}
self.locationManager.startUpdatingLocation()
self.mapView.isMyLocationEnabled = true
self.mapView.settings.myLocationButton = true
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.first else {
return
}
self.locationManager.stopUpdatingLocation()
self.mapView.camera = GMSCameraPosition(target: location.coordinate, zoom: 15, bearing: 0, viewingAngle: 0)
self.placeMarker(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude, marker: self.userMarker, imageName: userMarkerImage)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?){
if let destination = segue.destination as? LocationDetialViewController {
destination.coordinate = locationManager.location?.coordinate
} else {
return
}
} . . .
func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool {
performSegue(withIdentifier: "locationDetailSegue", sender: self)
return false
}
}//MapViewController
After looking at your code and storyboard.
I can see that you have connected the
#IBOutlet var mapView: GMSMapView!
To the parent view not to actual GMSMapView so please recheck the IBOutlet connections.
I had a similar issue, but the problem was i didn't inherit the module from target

Google maps change mapType on segmented control click

Here is the problem. In my view controller, I have placed a segmented control on the top.
On controller load it displays the map, circle and the marker. Now, when I click the segmented control to change the map type it goes into the code block on select but never updates the Map type.
Please see the code here.
class MapController: UIViewController, CLLocationManagerDelegate , GMSMapViewDelegate {
var locationManager:CLLocationManager!
let marker = GMSMarker()
var roundCircle: GMSCircle!
var mapView = GMSMapView()
private var didPerformGeocode = false
enum maptype:NSInteger
{
case standardmap = 0
case satellitemap = 1
}
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func segmentedControlAction(_ sender: UISegmentedControl)
{
switch sender.selectedSegmentIndex {
case maptype.standardmap.rawValue:
mapView.mapType = .normal
**no change in the map type**
case maptype.satellitemap.rawValue:
mapView.mapType = .satellite
**no change in the map type**
default:
break
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
determineMyCurrentLocation()
}
func determineMyCurrentLocation() {
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.startUpdatingLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let userLocation:CLLocation = locations[0] as CLLocation
guard !didPerformGeocode else { return }
didPerformGeocode = true
locationManager.stopUpdatingLocation()
let camera = GMSCameraPosition.camera(withLatitude: userLocation.coordinate.latitude, longitude: userLocation.coordinate.longitude, zoom: 13.0)
let mapView = GMSMapView.map(withFrame: CGRect.zero, camera: camera)
mapView.delegate = self
self.view = mapView
let markerImage = UIImage(named: "mapMarker")!.withRenderingMode(.alwaysTemplate)
let markerView = UIImageView(image: markerImage)
markerView.tintColor = UIColor.red
marker.iconView = markerView
marker.position = CLLocationCoordinate2D(latitude: userLocation.coordinate.latitude, longitude: userLocation.coordinate.longitude)
marker.map = mapView
roundCircle = GMSCircle(position: camera.target, radius: 1500) //0.96 Miles
roundCircle.fillColor = UIColor.red.withAlphaComponent(0.3)
roundCircle.strokeColor = nil
roundCircle.map = mapView
}
}
Honestly, I don't use Google Maps
However, to change MapTypes in Apple Maps I use
#IBAction func Whatever(_ sender: UISegmentedControl) {
mapView.mapType = MKMapType.init(rawValue: UInt(sender.selectedSegmentIndex)) ?? .normal
}
Maybe it will work

How do I add a marker when I press the done button?

Okay, so I want to add a marker when I click the done button. I want to add my current locations marker to the MapVC class when I click the done button in AddPinPointVC. What would I have to do to do this?
Here's the relevant code:
MapsVC
class MapsVC: UIViewController {
weak var googleMaps: GMSMapView!
var locationManager = CLLocationManager()
var currentLocation: CLLocation?
var placesClient: GMSPlacesClient!
var zoomLevel: Float = 15.0
override func viewDidLoad() {
super.viewDidLoad()
let camera = GMSCameraPosition.camera(withLatitude: 39.9533, longitude: -75.1593, zoom: 15.0)
let mapView = GMSMapView.map(withFrame: CGRect.zero, camera: camera)
mapView.isMyLocationEnabled = true
mapView.settings.myLocationButton = true
self.view = mapView
locationManager = CLLocationManager()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
locationManager.distanceFilter = 50
locationManager.startUpdatingLocation()
locationManager.delegate = self as? CLLocationManagerDelegate
placesClient = GMSPlacesClient.shared()
if( CLLocationManager.authorizationStatus() == CLAuthorizationStatus.authorizedWhenInUse ||
CLLocationManager.authorizationStatus() == CLAuthorizationStatus.authorizedAlways){
currentLocation = locationManager.location
}
}
static var latitude: CLLocationDegrees?
}
AddPinPointVC
class AddPinPointVC: UIViewController, UICollectionViewDelegateFlowLayout,UIPickerViewDelegate, UIPickerViewDataSource {
var pickerdata: [String] = [String]()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(handleCancel))
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Done", style: .plain, target: self, action: #selector(handleCancel))
//function for cancel button
func handleCancel() { dismiss(animated: true, completion: nil) }
func handleDone(){
dismiss(animated: true, completion: nil)
let userMarker = GMSMarker()
userMarker.position = CLLocationCoordinate2D(latitude: CLLocationDegrees, longitude: CLLocationDegrees)
// userMarker.title = typeOfPlaces[row]
userMarker.snippet = ""
}
}
If there are any further questions, please let me know! Thank you.
You should make a delegate for this with a function that fires when the Done button is pressed.
Protocol
protocol AddPinPointDelegate: class {
func addPinPointWillDismiss(_ marker: GMSMarker)
}
MapsVC
class MapsVC: UIViewController, AddPinPointDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let camera = GMSCameraPosition.camera(withLatitude: 39.9533, longitude: -75.1593, zoom: 15.0)
mapView = GMSMapView.map(withFrame: CGRect.zero, camera: camera)
[...]
}
[...]
var mapView: GMSMapView!
//assuming you open AddPinPointVC via a segue
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "presentAddPinPoint" { //the identifier you gave for the segue
(segue.destination as! AddPinPointVC).delegate = self
}
}
func addPinPointWillDismiss(_ marker: GMSMarker) {
marker.map = mapView
}
}
AddPinPointVC
class AddPinPointVC: UIViewController, UICollectionViewDelegateFlowLayout, UIPickerViewDelegate, UIPickerViewDataSource {
[...]
weak var delegate: AddPinPointDelegate?
func handleDone() {
let userMarker = GMSMarker()
userMarker.position = CLLocationCoordinate2D(latitude: CLLocationDegrees, longitude: CLLocationDegrees)
//userMarker.title = typeOfPlaces[row]
userMarker.snippet = ""
delegate?.addPinPointWillDismiss(userMarker)
dismiss(animated: true)
}
}

Swift delegate protocol not updating with locationManager

I have 2 views which the childView communicates with the parentView. I am using Google maps SDK to track a users location however if I startUpdatingLocation() using a button, the protocol does not work however if I initiate startUpdatingLocation() as soon as the app loads it works fine. Below are my Two controller files.
I am simply trying to display the location in a label but not sure why the protocol is not working after button click.
Child:
import Foundation
import UIKit
protocol LocationUpdateDelegate{
func delegateUserLocation(mapChildController:MapChildController, userLocation: String)
}
class MapChildController: UIViewController, CLLocationManagerDelegate
{
#IBOutlet weak var mapView: GMSMapView!
let locationManager = CLLocationManager()
var didFindMyLocation = false
var myLocations:[String] = []
var alreadyUpdatedLocation = false
var locationDelegate:LocationUpdateDelegate?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
var camera = GMSCameraPosition.cameraWithLatitude(-33.86, longitude: 151.20, zoom: 15, bearing: 0, viewingAngle: 45)
var mapView = GMSMapView.mapWithFrame(CGRectZero, camera: camera)
mapView.myLocationEnabled = true
self.view = mapView
if NSProcessInfo().isOperatingSystemAtLeastVersion(NSOperatingSystemVersion(majorVersion: 8, minorVersion: 0, patchVersion: 0)) {
println("iOS >= 8.0.0")
locationManager.requestWhenInUseAuthorization()
}
mapView.addObserver(self, forKeyPath: "myLocation", options: NSKeyValueObservingOptions.New, context: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<Void>) {
if !didFindMyLocation {
let myLocation: CLLocation = change[NSKeyValueChangeNewKey] as! CLLocation
var mapView = self.view as! GMSMapView
mapView.camera = GMSCameraPosition.cameraWithTarget(myLocation.coordinate, zoom: 15.0)
mapView.settings.myLocationButton = true
didFindMyLocation = true
}
}
func locationManager(manager:CLLocationManager, didUpdateLocations locations:[AnyObject]) {
var locValue:CLLocationCoordinate2D = manager.location.coordinate
myLocations.append("\(locValue.latitude)/\(locValue.longitude)")
self.locationDelegate?.delegateUserLocation(self, userLocation: "From child")
println("Child location: \(locValue.latitude)/\(locValue.longitude)")
}
func trackingToggle()
{
if !alreadyUpdatedLocation
{
locationManager.delegate = self
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
println("Tracking started")
alreadyUpdatedLocation = true
}
else
{
locationManager.stopUpdatingLocation()
println("Tracking stopped")
alreadyUpdatedLocation = false
}
}
}
Parent:
import UIKit
struct Constants {
static let embedSegue = "embedSegue"
}
class MapMainController: UIViewController, LocationUpdateDelegate
{
let mapChild = MapChildController()
var alreadyUpdatedLocation = false;
#IBOutlet weak var startTrackingButton: UIButton!
#IBOutlet weak var MapStatsView: UIView!
#IBOutlet weak var txtUserLocation: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == Constants.embedSegue {
let mapChildController = segue.destinationViewController as! MapChildController
mapChildController.locationDelegate = self
}
}
func delegateUserLocation(mapChildController: MapChildController, userLocation: String) {
println("Parent location: \(userLocation)");
txtUserLocation.text = userLocation
}
#IBAction func startTrackingTapped(sender: UIButton) {
mapChild.trackingToggle();
if !alreadyUpdatedLocation
{
alreadyUpdatedLocation = true
startTrackingButton.setTitle("Stop Tracking", forState: UIControlState.Normal)
}
else
{
alreadyUpdatedLocation = false
startTrackingButton.setTitle("Start Tracking", forState: UIControlState.Normal)
}
}
}
You are setting the mapChildController to self just before you segue to another view:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == Constants.embedSegue {
let mapChildController = segue.destinationViewController as! MapChildController
mapChildController.locationDelegate = self
After you just set the delegate to self your viewController went out of scope, now your delegate is nil again and will never be called.
You need to set the delegate in viewDidLoad

Resources