annotations not appearing on MapKit - ios

Has anyone experienced opening a map in your app and although it shows the area you are in it doesn't show the blue dot. Also I'm using localSearch to find supermarkets in my app. When you originally open the map it shows nothing. When you close and open the app again the annotations magically appear. Am I missing something really obvious?
Also I am receiving these errors in the console:
Wrapped around the polygon without finishing... :-(
List has 10 nodes:
7 8 9 10 4 3 0 1 2 6
2022-02-20 17:55:28.093927+0900 GoferList[15460:5478085] [VKDefault] Building failed to triangulate!
2022-02-20 17:55:28.265877+0900 GoferList[15460:5478094] [Font] Failed to parse font key token: hiraginosans-w6
Here is the view controller I'm using:
import CoreLocation
import MapKit
import UIKit
class MapViewController: UIViewController {
static func createViewController() -> MapViewController? {
UIStoryboard(name: "Main", bundle: Bundle.main)
.instantiateInitialViewController() as? MapViewController
}
#IBOutlet private var mapView: MKMapView!
private let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
configureLocationManager()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
locationManager.startUpdatingLocation()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
locationManager.stopUpdatingLocation()
}
}
// MARK: - Location Manager
extension MapViewController: CLLocationManagerDelegate {
func configureLocationManager() {
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = kCLDistanceFilterNone
}
func locationManager(
_ mgr: CLLocationManager,
didUpdateLocations locations: [CLLocation]
) {
guard let location = locations.first else {
return
}
let region = MKCoordinateRegion(
center: location.coordinate,
latitudinalMeters: 1000,
longitudinalMeters: 1000
)
mapView.setRegion(region, animated: false)
findPOIPlaces(query: "supermarket", region: region)
mgr.stopUpdatingLocation()
}
}
// MARK: - MapViewDelegate
extension MapViewController: MKMapViewDelegate {
func findPOIPlaces(query: String, region: MKCoordinateRegion) {
let searchRequest = MKLocalSearch.Request()
searchRequest.naturalLanguageQuery = query
searchRequest.resultTypes = .pointOfInterest
searchRequest.region = region
let search = MKLocalSearch(request: searchRequest)
search.start { [weak self] res, error in
guard let res = res,
let self = self,
error == nil
else {
print("\(String(describing: error))")
return
}
for item in res.mapItems {
let annotation = SuperMarketAnnotation(item: item)
self.mapView.addAnnotation(annotation)
}
}
}
func mapView(
_ mapView: MKMapView,
viewFor annotation: MKAnnotation
) -> MKAnnotationView? {
guard annotation is SuperMarketAnnotation else {
return nil
}
if let view = mapView.dequeueReusableAnnotationView(
withIdentifier: SuperMarketAnnotationView.reuseID)
{
return view
}
let annotationView = SuperMarketAnnotationView(annotation: annotation)
annotationView.delegate = self
return annotationView
}
}
// MARK: - SuperMarketAnnotationViewDelegate
extension MapViewController: SuperMarketAnnotationViewDelegate {
func onTapPin(annotation: SuperMarketAnnotation) {
mapView.setCenter(annotation.coordinate, animated: true)
}
func onTapInfo(annotation: SuperMarketAnnotation) {
if let url = annotation.webURL {
// if place has an web site
if let vc = WebViewController.createViewController() {
vc.url = url
vc.name = annotation.title
present(vc, animated: true, completion: nil)
}
} else if let url = annotation.externalURL {
UIApplication.shared.open(
url, options: [:],
completionHandler: nil
)
}
}
}
Can anyone help? Thank you.

A few observations:
Regarding not seeing the blue dot, the question is whether you set showsUserLocation (either programmatically or in the NIB/storyboard).
Regarding annotations not showing up, you need to narrow down the problem. I would temporarily remove the mapView(_:viewFor:) and see whether you see annotations at that point.
If they do, then the problem is either in mapView(_:viewFor:) (see below) or your custom annotation view subclass.
If they do not appear, then the problem is either in your logic of how/when you added annotation or in your custom annotation class.
With mapView(_:viewFor:) removed, you could then try adding a MKPointAnnotation, rather than your SuperMarketAnnotation. That will help you figure out whether the problem is in your annotation class or the the logic about when and where you add annotations.
FWIW, the mapView(_:viewFor:) has a little bug (which may or may not be related). If you successfully dequeue an existing annotation view, you just return it and never update its annotation property. You must update the annotation of any dequeued annotation view or else it will point to the old annotation (and, specifically, its old coordinate).
if let view = mapView.dequeueReusableAnnotationView(withIdentifier: SuperMarketAnnotationView.reuseID) {
view.annotation = annotation // make sure you update the annotation for dequeued annotation view
return view
}

Related

Timer is not allowing to add annotation on mapView in iOS swift

I have a situation where I have to call an API to fetch some Vehicles Locations objects in an array after getting the user current location. After fetching vehicles, I have to get the address also from Vehicles Locations data, so for 'n' Vehicles, there will be an 'n' API call and then add annotations on Map.
After that, I have to refresh the Vehicles data every 1 min. So, I created a timer but even after getting the API response, annotations are not displaying on map. Kindly look into this issue.
Below is Map View
import MapKit
class MapViewController: UIViewController, MKMapViewDelegate {
#IBOutlet private var mapView: MKMapView!
var currentLocation: CLLocation?
var user: User?
lazy var vehicleViewModel = {
VehicleViewModel()
}()
var locationUpdateTimer: Timer?
override func viewDidLoad() {
super.viewDidLoad()
configureLocationManager()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
stopTimer()
}
func configureLocationManager() {
LocationManager.shared().delegate = self
LocationManager.shared().initializeLocationManager()
}
func configureTimer() {
if locationUpdateTimer == nil {
locationUpdateTimer = Timer.scheduledTimer(timeInterval: 60, target: self, selector: #selector(runLocationTimer), userInfo: nil, repeats: true)
}
}
#objc func runLocationTimer() {
fetchVehiclesLocation()
}
func resetMap() {
let annotations = mapView.annotations
mapView.removeAnnotations(annotations)
mapView = nil
}
func initializeMapView() {
mapView = MKMapView(frame: CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: self.view.frame.size.height))
mapView.delegate = self
}
func configureMapView() {
let mapDetail = vehicleViewModel.getLatitudeLongitudeLatitudeDeltaLongitudeDelta()
if let latitude = mapDetail.0, let longitude = mapDetail.1, let latitudeDelta = mapDetail.2, let longitudeDelta = mapDetail.3 {
let region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: latitude, longitude: longitude), latitudinalMeters: latitudeDelta, longitudinalMeters: longitudeDelta)
let scaledRegion: MKCoordinateRegion = mapView.regionThatFits(region)
mapView.setRegion(scaledRegion, animated: true)
mapView.setCameraBoundary(
MKMapView.CameraBoundary(coordinateRegion: region),
animated: true)
let zoomRange = MKMapView.CameraZoomRange(maxCenterCoordinateDistance: 100000)
mapView.setCameraZoomRange(zoomRange, animated: true)
mapView.register(
VehicleAnnotationView.self,
forAnnotationViewWithReuseIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier)
}
}
func fetchVehiclesLocation() {
configureTimer()
initViewModel {
DispatchQueue.main.async {
self.resetMap()
self.initializeMapView()
self.configureMapView()
}
if let user = self.user {
self.vehicleViewModel.fetchVehicleAddress(user: user, completion: { status in
if self.vehicleViewModel.vehicleAnnotationItems.count == 0 {
self.alertWithTitleAndMessageWithOK(("Alert" , "error while fetching vehicle locations"))
} else {
DispatchQueue.main.async {
self.mapView.addAnnotations(self.vehicleViewModel.vehicleAnnotationItems)
}
}
})
}
}
}
func initViewModel(completion: #escaping () -> Void) {
if let user = self.user, let userId = user.userId {
vehicleViewModel.getVehiclesLocation(userId: userId) { (vehicleApiResponse, error) in
if vehicleApiResponse != nil {
completion()
} else {
self.alertWithTitleAndMessageWithOK(("Alert" , error?.localizedDescription ?? "error while fetching vehicles"))
}
}
}
}
func stopTimer() {
if locationUpdateTimer != nil {
locationUpdateTimer!.invalidate()
locationUpdateTimer = nil
}
}
deinit {
stopTimer()
}
}
//MARK: - LocationManagerDelegate methods
extension MapViewController: LocationManagerDelegate {
func didFindCurrentLocation(_ location: CLLocation) {
currentLocation = location
if let currentLocation = currentLocation, (currentLocation.horizontalAccuracy >= 0) {
mapView.showsUserLocation = true
fetchVehiclesLocation()
}
}
}
LocationManager Extension class
import CoreLocation
protocol LocationManagerDelegate: AnyObject {
func didFindCurrentLocation(_ location: CLLocation)
func didFailedToFindCurrentLocationWithError(_ error: NSError?)
func alertLocationAccessNeeded()
}
/**
This class acts as a Singleton for getting location manager updates across the application.
*/
class LocationManager: NSObject {
var manager: CLLocationManager!
private static var sharedNetworkManager: LocationManager = {
let networkManager = LocationManager()
return networkManager
}()
private override init() {
super.init()
manager = CLLocationManager()
}
class func shared() -> LocationManager {
return sharedNetworkManager
}
weak var delegate: LocationManagerDelegate?
//Entry point to Location Manager. First the initialization has to be done
func initializeLocationManager() {
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.distanceFilter = kCLDistanceFilterNone
manager.delegate = self
manager.requestWhenInUseAuthorization()
manager.allowsBackgroundLocationUpdates = false
startUpdating()
}
//Start updating locations
func startUpdating() {
manager.startUpdatingLocation()
}
//Check for whether location services are disabled.
func locationServicesEnabled() -> Bool {
let isAllowed = CLLocationManager.locationServicesEnabled()
return isAllowed
}
}
//MARK: - CLLocation Manager delegate methods
extension LocationManager: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else {
return
}
manager.stopUpdatingLocation()
delegate?.didFindCurrentLocation(location)
// manager.delegate = nil
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
delegate?.didFailedToFindCurrentLocationWithError(error as NSError?)
}
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
switch manager.authorizationStatus {
case .notDetermined:
self.manager.requestWhenInUseAuthorization()
break
case .authorizedWhenInUse, .authorizedAlways:
if locationServicesEnabled() {
self.startUpdating()
}
case .restricted, .denied:
delegate?.alertLocationAccessNeeded()
#unknown default:
print("Didn't request permission for location access")
}
}
}
Your code has a number of problems.
Neither your initializeMapView() function nor your resetMap() function make any sense.
You should add an MKMapView to your Storyboard, then connect it to your mapView outlet, and then don't assign a new value to mapView. Don't set it to nil, and don't replace the map view in the outlet with a brand new map view you create (like you're doing in initializeMapView().) Both of those things will prevent your map from displaying.
You also never create a timer except in your fetchVehiclesLocation() function, which doesn't seem right.
You also don't show how you're setting up your location manager and asking for location updates. (You call a function initializeLocationManager(). I don't believe that is an Apple-provided function. I'm guessing you added it in an extension to the location manager, but you don't show that code.)
You need to ask if the user has granted permission to use the location manager, and trigger a request for permission if not.
Once you have permission to use the location manager, you need to ask it to start updating the user's location.
You don't show any of that code.
Maybe you're doing that in code you didn't show? It also looks like you don't have your CLLocationManagerDelegate methods defined correctly. I don't know of any delegate method didFindCurrentLocation(_:). You will likely need to implement one of the delegate methods locationManager(_:didUpdateLocations:) or locationManager(_:didUpdateTo:from:).
I suggest searching for a tutorial on using the location manager to display the user's location on a map. It's a little involved, and requires some study to set up.

UI Tableview won't display or update search results in cells, Swift

I am an intermediate Swift developer and I am creating an app that involves using a search function to find an address and pinpoint said address on a map. I followed a tutorial on how to achieve this and everything is functional besides the search function itself. Whenever I type in the UIsearchbar my search results tableview controller is instantiated, however, the table view is blank and does not update as I type. An address API call should be present.
Below is my code for the search table
import UIKit
import MapKit
class LocationSearchTable : UITableViewController {
var resultSearchController:UISearchController? = nil
var handleMapSearchDelegate:HandleMapSearch? = nil
var matchingItems:[MKMapItem] = []
var mapView: MKMapView? = nil
#IBOutlet var searchTableView: UITableView!
}
extension LocationSearchTable : UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
searchController.showsSearchResultsController = true
}
func updateSearchResultsForSearchController(searchController: UISearchController) {
guard let mapView = mapView,
let searchBarText = searchController.searchBar.text else { return }
let request = MKLocalSearch.Request()
request.naturalLanguageQuery = searchBarText
request.region = mapView.region
let search = MKLocalSearch(request: request)
search.start { response, _ in
guard let response = response
else {
return
}
self.matchingItems = response.mapItems
self.tableView.reloadData()
}
}
}
extension LocationSearchTable {
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return matchingItems.count
}
func tableView(_ tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")!
let selectedItem = matchingItems[indexPath.row].placemark
cell.textLabel?.text = selectedItem.name
cell.detailTextLabel?.text = ""
return cell
}
}
extension LocationSearchTable {
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let selectedItem = matchingItems[indexPath.row].placemark
handleMapSearchDelegate?.dropPinZoomIn(placemark: selectedItem)
dismiss(animated: true, completion: nil)
}
}
This is my code for my initial view controller
import UIKit
import MapKit
import CoreLocation
protocol HandleMapSearch {
func dropPinZoomIn(placemark:MKPlacemark)
}
//for some reason my location delegate is not working (resolved)
class locationViewController: UIViewController, UISearchResultsUpdating, UISearchBarDelegate {
func updateSearchResults(for searchController: UISearchController) {
return
}
var selectedPin:MKPlacemark? = nil
var resultSearchController:UISearchController? = nil
#IBOutlet weak var eventLocationMapView: MKMapView!
#IBOutlet weak var backButton: UIButton!
let locationManager = CLLocationManager()
let regionInMeters: Double = 10000
override func viewDidLoad() {
super.viewDidLoad()
resultSearchController?.searchResultsUpdater = self
let locationSearchTable = storyboard!.instantiateViewController(withIdentifier: "LocationSearchTable") as! LocationSearchTable
resultSearchController = UISearchController(searchResultsController: locationSearchTable)
resultSearchController?.searchResultsUpdater = locationSearchTable
resultSearchController?.searchBar.delegate = self
locationSearchTable.mapView = eventLocationMapView
let searchBar = resultSearchController!.searchBar
searchBar.sizeToFit()
searchBar.placeholder = "Search for places"
//this is the search bar
navigationItem.titleView = resultSearchController?.searchBar
resultSearchController?.hidesNavigationBarDuringPresentation = false
resultSearchController?.obscuresBackgroundDuringPresentation = true
definesPresentationContext = true
locationSearchTable.handleMapSearchDelegate = self
//all of this is in regards to the search functionality
locationManager.startUpdatingLocation()
self.checkLocationAuthorization()
self.checkLocationServices()
self.navigationController?.isNavigationBarHidden = false
// Do any additional setup after loading the view.
locationManager.requestWhenInUseAuthorization()
//this pushes the user request. The issue was most likely in the switch statement
locationManager.delegate = self
locationManager.requestLocation()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
//solved it but we will probably have to go back to this
self.centerViewOnUserLocation()
}
#IBAction func backButtonTapped(_ sender: Any) {
self.transitionBackToCreateEventVC()
}
func transitionBackToCreateEventVC (){
let createEventViewController = self.storyboard?.instantiateViewController(identifier: "createEventVC")
self.view.window?.rootViewController = createEventViewController
self.view.window?.makeKeyAndVisible()
}
func centerViewOnUserLocation() {
if let location = locationManager.location?.coordinate {
let region = MKCoordinateRegion.init(center: location, latitudinalMeters: regionInMeters, longitudinalMeters: regionInMeters)
eventLocationMapView.setRegion(region, animated: true)
//this function centers the map onto the location of the user
}
}
func setUpLocationManager(){
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
}
func checkLocationServices(){
if CLLocationManager.locationServicesEnabled(){
setUpLocationManager()
checkLocationAuthorization()
} else {
//show alert letting the user know they have to turn this on
}
}
func checkLocationAuthorization() {
let locationManager = CLLocationManager()
switch locationManager.authorizationStatus {
case .authorizedWhenInUse:
eventLocationMapView.showsUserLocation = true
// We want the users location while the app is in use
break
case .denied:
//show alert instructing how to turn on permissions
break
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
return
case .restricted:
//show an alert
break
case .authorizedAlways:
// we don't want this
break
#unknown default:
return
//I dont know if we need this code
}
}
/*
// 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.destination.
// Pass the selected object to the new view controller.
}
*/
}
extension locationViewController: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedWhenInUse {
locationManager.requestLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let location = locations.first {
let region = MKCoordinateRegion(center: location.coordinate, latitudinalMeters: 0.05 , longitudinalMeters: 0.05)
eventLocationMapView.setRegion(region, animated: true)
}
}
public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("error:: \(error)")
}
}
extension locationViewController: HandleMapSearch {
func dropPinZoomIn(placemark:MKPlacemark){
// cache the pin
selectedPin = placemark
// clear existing pins
eventLocationMapView.removeAnnotations(eventLocationMapView.annotations)
let annotation = MKPointAnnotation()
annotation.coordinate = placemark.coordinate
annotation.title = placemark.name
if let city = placemark.locality,
let state = placemark.administrativeArea {
annotation.subtitle = "\(city) \(state)"
}
eventLocationMapView.addAnnotation(annotation)
let span = MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
let region = MKCoordinateRegion(center: placemark.coordinate, span: span)
eventLocationMapView.setRegion(region, animated: true)
}
}
I think the you should make a property to hold the MKLocalSearch object in your LocationSearchTable class.
class LocationSearchTable : UITableViewController {
private var search: MKLocalSearch?
// ...
}
extension LocationSearchTable : UISearchResultsUpdating {
func updateSearchResultsForSearchController(searchController: UISearchController) {
guard let mapView = mapView,
let searchBarText = searchController.searchBar.text else { return }
// if there is an ongoing search, cancel it first.
self.search?.cancel()
let request = MKLocalSearch.Request()
request.naturalLanguageQuery = searchBarText
request.region = mapView.region
let search = MKLocalSearch(request: request)
search.start { response, error in
guard let response = response
else {
return
}
self.matchingItems = response.mapItems
self.tableView.reloadData()
}
// make sure `search` is not released.
self.search = search
}
}
The problem of the original code is that, the search object, instance of MKLocalSearch class will be released when finishes executing updateSearchResultsForSearchController method, since there is no strong reference to it, and start's callback will never be called, so your table view will never be reloaded.
What we do is just make a strong ref to it, and make sure it is not released before completion handler is called.

How to display customer callout screens on a map using Swift and Xcode

I am fairly new to IOS development, and I am working on creating a map to display different venues from a JSON file which works like a charm. Now, what I want to do is, once a user clicks a pin, I want a callout view to be displayed at the bottom of the map that shows the Venue's logo on the left, and the following on the right:
a phone number that's clickable so call them directly, a website url that's clickable to access the website, and finally, a directions button to open the map to show directions to the Venue.
Once a user clicks a different pin, then the information about that new venue is displayed.
Here is the portion of the code that I think needs edits, and below is the full code
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?
{
if let annotation = annotation as? Venue {
let identifier = "pin"
var view: MKPinAnnotationView
if let dequeuedView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as? MKPinAnnotationView {
dequeuedView.annotation = annotation
view = dequeuedView
} else {
view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
view.canShowCallout = true
view.calloutOffset = CGPoint(x: -5, y: 5)
view.rightCalloutAccessoryView = UIButton(type: .detailDisclosure) as UIView
}
return view
}
return nil
}
Please help me with step by step instructions. I am having a hard time getting around Swift.
THANKS A BUNCH!
import UIKit
import CoreLocation
import MapKit
//import SwiftyJSON
struct Place: Codable {
let id: Int
let name, address, number, imageURL: String
let lat, long: Double
enum CodingKeys: String, CodingKey {
case id, name, address, number
case imageURL = "imageUrl"
case lat, long
}
}
class YogaViewController: UIViewController {
// MARK: - Properties
var locationManager: CLLocationManager!
var mapView: MKMapView!
let centerMapButton: UIButton = {
let button = UIButton(type: .system)
button.setImage(#imageLiteral(resourceName: "location-arrow-flat").withRenderingMode(.alwaysOriginal), for: .normal)
button.addTarget(self, action: #selector(handleCenterLocation), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
// MARK: - Init
override func viewDidLoad() {
super.viewDidLoad()
configureLocationManager()
configureMapView()
enableLocationServices()
// mapView.addAnnotations(venues)
// //JSON STUFF
let jsonUrlString = "https://www.elev8dfw.com/Gyms.json"
// let jsonUrlString = "https://www.elev8dfw.com/Venues.json"
// let jsonUrlString = "https://api.letsbuildthatapp.com/jsondecodable/course"
guard let url = URL(string: jsonUrlString) else {return}
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { return }
do {
let places = try JSONDecoder().decode([Place].self, from: data)
for place in places {
// print(place.lat)
// print(place.long)
let sampleStarbucks = Venue(title: place.name, locationName: place.address, coordinate: CLLocationCoordinate2D(latitude: place.lat, longitude: place.long))
self.mapView.addAnnotation(sampleStarbucks)
}
} catch let jsonErr{
print("Error serializing json: ", jsonErr)
}
}.resume()
mapView.delegate = self
}
// MARK: - Selectors
#objc func handleCenterLocation() {
centerMapOnUserLocation()
centerMapButton.alpha = 0
}
// MARK: - Helper Functions
func configureLocationManager() {
locationManager = CLLocationManager()
locationManager.delegate = self
}
func configureMapView() {
mapView = MKMapView()
mapView.showsUserLocation = true
mapView.delegate = self
mapView.userTrackingMode = .follow
view.addSubview(mapView)
mapView.frame = view.frame
view.addSubview(centerMapButton)
centerMapButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -100).isActive = true
centerMapButton.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -20).isActive = true
centerMapButton.heightAnchor.constraint(equalToConstant: 50).isActive = true
centerMapButton.widthAnchor.constraint(equalToConstant: 50).isActive = true
centerMapButton.layer.cornerRadius = 50 / 2
centerMapButton.alpha = 0
}
func centerMapOnUserLocation() {
guard let coordinate = locationManager.location?.coordinate else { return }
let region = MKCoordinateRegion(center: coordinate, latitudinalMeters: 4000, longitudinalMeters: 4000)
mapView.setRegion(region, animated: true)
}
}
// MARK: - MKMapViewDelegate
extension YogaViewController: MKMapViewDelegate {
func mapView(_ mapView: MKMapView, regionWillChangeAnimated animated: Bool) {
UIView.animate(withDuration: 0.5) {
self.centerMapButton.alpha = 1
}
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?
{
if let annotation = annotation as? Venue {
let identifier = "pin"
var view: MKPinAnnotationView
if let dequeuedView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as? MKPinAnnotationView {
dequeuedView.annotation = annotation
view = dequeuedView
} else {
view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
view.canShowCallout = true
view.calloutOffset = CGPoint(x: -5, y: 5)
view.rightCalloutAccessoryView = UIButton(type: .detailDisclosure) as UIView
}
return view
}
return nil
}
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
let location = view.annotation as! Venue
let launchOptions = [MKLaunchOptionsDirectionsModeKey : MKLaunchOptionsDirectionsModeDriving]
location.mapItem().openInMaps(launchOptions: launchOptions)
}
}
// MARK: - CLLocationManagerDelegate
extension YogaViewController: CLLocationManagerDelegate {
func enableLocationServices() {
switch CLLocationManager.authorizationStatus() {
case .notDetermined:
print("Location auth status is NOT DETERMINED")
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
centerMapOnUserLocation()
case .restricted:
print("Location auth status is RESTRICTED")
case .denied:
print("Location auth status is DENIED")
case .authorizedAlways:
print("Location auth status is AUTHORIZED ALWAYS")
case .authorizedWhenInUse:
print("Location auth status is AUTHORIZED WHEN IN USE")
locationManager.startUpdatingLocation()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
centerMapOnUserLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
self.mapView.showsUserLocation = true
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
guard locationManager.location != nil else { return }
centerMapOnUserLocation()
}
}
If you are attempting to open a custom UIView coming up from the bottom upon tapping a annotation you could utilize didSelect and do something like the following:
final func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
let location = view.annotation as! Venue
// Update your new custom view with the Venue here.
// Now lets show the view
self.bottomViewConstraint.constant = 0
UIView.animate(withDuration: 0.2) {
self.view.layoutIfNeeded()
}
}
So in your storyboard. Create a UIView, set its height and width constraints. Set its bottom constraint to be a negative number so it is not showing on the screen. Create an IBOutlet to the bottom constraint (I named it bottomViewConstraint in the example code). This is a very basic example, untested, to get you started.

MapKit map returns nil inside of function being called inside of different ViewController

I'm currently trying to implement a Map connected with a search function. For the overlay containing the table view, I've decided to go for a library called FloatingPanel.
I have to ViewControllers, namely MapViewController and SearchTableViewController - as the name already says, MapViewController contains a mapView. I assume since FloatingPanel adds SearchTableViewController (STVC) to MapViewController (MVC), that STVC is MVC's child.
Now whenever I want to call the MapViewController's function to add annotation inside of SearchTableViewController, MapViewController's mapView returns nil - calling it inside of MapViewController works fine.
class MapViewController: UIViewController, FloatingPanelControllerDelegate, UISearchBarDelegate {
var fpc: FloatingPanelController!
var searchVC = SearchResultTableViewController()
let locationManager = CLLocationManager()
let regionInMeters: Double = 10000
#IBOutlet private var mapView: MKMapView!
var mapItems: [MKMapItem]?
override func viewDidLoad() {
super.viewDidLoad()
checkLocationServices()
fpc = FloatingPanelController()
fpc.delegate = self
fpc.surfaceView.backgroundColor = .clear
fpc.surfaceView.cornerRadius = 9.0
fpc.surfaceView.shadowHidden = false
searchVC = (storyboard?.instantiateViewController(withIdentifier: "SearchPanel") as! SearchResultTableViewController)
fpc.set(contentViewController: searchVC)
fpc.track(scrollView: searchVC.tableView)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
fpc.addPanel(toParent: self, animated: true)
fpc.move(to: .tip, animated: true)
searchVC.searchController.searchBar.delegate = self
}
func checkLocationServices() {
if CLLocationManager.locationServicesEnabled() {
setupLocationManager()
checkLocationAuthorization()
} else {
}
}
func setupLocationManager() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
}
func checkLocationAuthorization() {
switch CLLocationManager.authorizationStatus() {
case .authorizedWhenInUse:
centerViewOnUserLocation()
break
case .denied:
break
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
break
case .restricted:
break
case .authorizedAlways:
break
#unknown default:
break
}
}
func centerViewOnUserLocation() {
if let location = locationManager.location?.coordinate {
let region = MKCoordinateRegion.init(center: location, latitudinalMeters: regionInMeters, longitudinalMeters: regionInMeters)
mapView.setRegion(region, animated: true)
}
}
#IBAction func click(_ sender: Any) {
}
func addPin(title: String, subtitle: String, coordinates: CLLocationCoordinate2D) {
let destination = customPin(pinTitle: title, pinSubTitle: subtitle, location: coordinates)
mapView.addAnnotation(destination)
}
func addAnnotationToMap() {
guard let item = mapItems?.first else { return }
guard let coordinates = item.placemark.location?.coordinate else { return }
addPin(title: item.name!, subtitle: "", coordinates: coordinates)
}
}
SearchTableViewController's function:
func passData() {
guard let mapViewController = storyboard?.instantiateViewController(withIdentifier: "map") as? MapViewController else { return }
guard let mapItem = places?.first else { return }
mapViewController.mapItems = [mapItem]
mapViewController.addAnnotationToMap()
}
This
guard let mapViewController = storyboard?.instantiateViewController(withIdentifier: "map") as? MapViewController else { return }
guard let mapItem = places?.first else { return }
creates a new seperate object other than the actual one that you have to access , use delegate to do it here
searchVC = (storyboard?.instantiateViewController(withIdentifier: "SearchPanel") as! SearchResultTableViewController)
searchVC.delegate = self // here
fpc.set(contentViewController: searchVC)
then declare
weak var delegate:MapViewController?
inside SearchViewController and use it
func passData() {
guard let mapItem = places?.first else { return }
delegate?.mapItems = [mapItem]
delegate?.addAnnotationToMap()
}
Several things:
When you use a third party library in your project and your readers might want to know about that library in order to understand your problem, you should include a link to the library in your question. I did a Google search and was able to find what I think is the correct library.
The sample code for that library has you call fpc.addPanel() in your view controller's viewDidLoad(), not in viewDidAppear().
The viewDidLoad() function is only called once in the lifetime of a view controller, but viewDidAppear() is called every time a view controller get's re-shown (like when it is redisplayed after being covered by a modal and then uncovered again.) The two are not interchangeable for that reason. I suggest moving that call back to viewDidLoad().
Next, as others have mentioned, your SearchTableViewController's passData() function is wrong. It creates a new, throw-away MapViewController every time it is called. It does not talk to the hosting MapViewController at all.
You should refactor your viewDidLoad() to set up the MapViewController as the delegate of the SearchTableViewController.
Define a protocol (possibly in a separate file
protocol SearchTableViewControllerDelegate {
var mapItems: [MapItem] //Or whatever type
func addAnnotationToMap()
}
Some changes to MapViewController
class MapViewController:
SearchTableViewControllerDelegate,
UIViewController,
FloatingPanelControllerDelegate,
UISearchBarDelegate {
//Your other code...
}
override func viewDidLoad() {
super.viewDidLoad()
checkLocationServices()
fpc = FloatingPanelController()
fpc.delegate = self
fpc.surfaceView.backgroundColor = .clear
fpc.surfaceView.cornerRadius = 9.0
fpc.surfaceView.shadowHidden = false
searchVC = (storyboard?.instantiateViewController(withIdentifier: "SearchPanel") as! SearchResultTableViewController)
//--------------------------
searchVC.delegate = self //This new line is important
//--------------------------
fpc.set(contentViewController: searchVC)
fpc.track(scrollView: searchVC.tableView)
fpc.addPanel(toParent: self) //Probably can't be animated at this point
}
And in SearchTableViewController:
class SearchTableViewController: UITableViewController, <Other protocols> {
weak var delegate: SearchTableViewControllerDelegate?
// other code...
func passData() {
guard let mapItem = places?.first else { return }
delegate?.mapItems = [mapItem]
delegate?.addAnnotationToMap()
}
}
You're instantiating a new MapViewController instead if passing data to the one that exists. There are three ways to do this:
Delegation
Closures
Notifications
An example using a closure, in SearchViewController:
var passData: ((MKMapItem) -> ())?
In MapViewController provide it a closure:
searchVC = (storyboard?.instantiateViewController(withIdentifier: "SearchPanel") as! SearchResultTableViewController)
searchVC.passData = { mapItem in
self.mapItems = [mapItem]
}
In SearchViewController call the closure:
passData?(mapItem)

locate user location in Swift

I try to add Current Location in to the map by using CLLocationmanager, but when I try to call the func mapView.StartUpdateLocation some how the delegate protocol " didUpdateLocation " is not to be called, any know how can I fix this ? Thanks
import UIKit
import MapKit
class mapViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {
#IBOutlet var mapView: MKMapView!
var restaurant:Restaurant!
var locationManager = CLLocationManager()
var current: CLPlacemark!
override func viewDidLoad()
{
super.viewDidLoad()
mapView.delegate = self
self.locationManager.requestAlwaysAuthorization()
// convert address to coordinate
let geoCoder = CLGeocoder()
geoCoder.geocodeAddressString(restaurant.location, completionHandler: { (placemarks, error) -> Void in
if error != nil
{
print(error)
return
}
if placemarks.count > 0
{
let placemark = placemarks[0] as CLPlacemark
// add annotation
let annotation = MKPointAnnotation()
annotation.coordinate = placemark.location.coordinate
annotation.title = self.restaurant.name
annotation.subtitle = self.restaurant.type
self.mapView.showAnnotations([annotation], animated: true)
self.mapView.selectAnnotation(annotation, animated: true)
self.mapView.showsUserLocation = true // add to show user location
}
})
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func currentLocation(sender: AnyObject)
{
if ( CLLocationManager.locationServicesEnabled())
{
println("Location service is on ")
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.stopUpdatingLocation()
locationManager.startUpdatingLocation()
}else
{
println("Location service is not on ")
}
}
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!)
{
CLGeocoder().reverseGeocodeLocation(manager.location, completionHandler: { (placemarks, error) -> Void in
if error != nil
{
print("error")
}
if placemarks.count > 0
{
self.current = placemarks[0] as CLPlacemark
let annotation = MKPointAnnotation()
annotation.coordinate = self.current.location.coordinate
self.mapView.showAnnotations([annotation], animated: true)
self.mapView.selectAnnotation(annotation, animated: true)
}
else
{
print("I dont know what the fuck is the error")
}
})
}
func locationManager(manager: CLLocationManager!, didFailWithError error: NSError!) {
println(error.localizedDescription)
}
func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView!
{
let indentifier = "My Pin"
if annotation.isKindOfClass(MKUserLocation){ return nil }
// Resuse Annotation if possiable
var annotationView = mapView.dequeueReusableAnnotationViewWithIdentifier(indentifier)
if annotationView == nil
{
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: indentifier)
annotationView.canShowCallout = true // tell annotation can display
}
let iconImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 53, height: 53)) // create left icon image for annotation view
iconImageView.image = UIImage(named: restaurant.image)
annotationView.leftCalloutAccessoryView = iconImageView
return annotationView
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
There is a change between iOS7 & 8 about GPS localisation. Please read this article.
http://matthewfecher.com/app-developement/getting-gps-location-using-core-location-in-ios-8-vs-ios-7/
Maybe you have not added these infos to your .plist
<key>NSLocationAlwaysUsageDescription</key>
<string>Your message goes here</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Your message goes here</string>
Please check once below keys entered in info.plist
NSLocationWhenInUseUsageDescription
NSLocationAlwaysUsageDescription
and set
var _locationManager = CLLocationManager()
_locationManager.delegate = self
_locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
_locationManager.requestAlwaysAuthorization()
in viewDidLoad()

Resources