I have 2 annotations to display on the mapview, but unable to set the maprect to show all of them on screen without requiring users to zoom out.
I tried with showAnnotations but no luck. Anyone has been able to do this in Swift and Xcode 6.1.1?
Here is my code:
class ViewController: UIViewController, MKMapViewDelegate {
#IBOutlet var map: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
var mapView = map
// 1
let point1 = CLLocationCoordinate2D(latitude: 38.915565, longitude: -77.093524)
let point2 = CLLocationCoordinate2D(latitude: 38.890693, longitude: -76.933318)
//2
let annotation = MKPointAnnotation()
annotation.setCoordinate(point1)
annotation.title = "point1"
map.addAnnotation(annotation)
let annotation2 = MKPointAnnotation()
annotation2.setCoordinate(point2)
annotation2.title = "point2"
map.addAnnotation(annotation2)
//3
// option1: set maprect to cover all annotations, doesn't work
var points = [annotation, annotation2]
var rect = MKMapRectNull
for p in points {
let k = MKMapPointForCoordinate(p.coordinate)
rect = MKMapRectUnion(rect, MKMapRectMake(k.x, k.y, 0.1, 0.1))
println("result: x = \(rect.origin.x) y = \(rect.origin.y)")
}
map.setVisibleMapRect(rect, animated: true)
// option 2: using showAnnotations, doesn't work
//map.showAnnotations(points, animated: true)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
This is what I got currently:
This is what I expected to see:
Thanks for your help.
I finally found out why the pins of the annotations had not been displayed in the visible region of the screen. I think the MapKit framework behaves a bit different than in the previous versions. Since I use autolayout to allow the map to expand to the entire screen for all devices (iPhones, iPad), setVisibleMapRect or mapView.showAnnotations of the map should be invoked in mapViewDidFinishLoadingMap, not in viewDidLoad of the view controller
For example:
func mapViewDidFinishLoadingMap(_ mapView: MKMapView) {
// this is where visible maprect should be set
mapView.showAnnotations(mapView.annotations, animated: true)
}
I had this same problem when I called
viewDidLoad() {
mapView.showAnnotations(myAnnotations, animated: false)
}
However, moving the call to viewDidLayoutSubviews() also seems to fix the problem (not that isInitialLoad is initialized to true in viewDidLoad).
viewDidLayoutSubviews() {
if isInitialLoad {
mapView.showAnnotations(myAnnotations, animated: false)
isInitialLoad = false
}
}
The difference (I think it is an advantage) of putting the call in viewDidLayoutSubviews is that the map hasn't actually displayed yet, so your initial display is that area defined by the annotations. However, it seems that it is called every time the map zooms, so you need to be sure to only call it the first time.
For me using showing annotations after map did finish loading did not work.
func mapViewDidFinishLoadingMap(mapView: MKMapView!) {
// this is where visible maprect should be set
mapView.showAnnotations(mapView.annotations, animated: true)
}
Besides showing the annotation, I needed to calculate polylines to connect the annotations and map finished loading was triggered too early.
Instead I tried mapViewDidFinishRenderingMap and it worked perfectly fine. See example below:
//MARK: - Show all objects after adding them on the map
func mapViewDidFinishRenderingMap(mapView: MKMapView, fullyRendered: Bool) {
mapView.showAnnotations(mapStages, animated: true)
}
You can try this.
I created an extension to show all the annotations using some code from here and there in swift 2.3. This will not show all annotations if they can't be shown even at maximum zoom level.
import MapKit
extension MKMapView {
func fitAllAnnotations() {
var zoomRect = MKMapRectNull;
for annotation in annotations {
let annotationPoint = MKMapPointForCoordinate(annotation.coordinate)
let pointRect = MKMapRectMake(annotationPoint.x, annotationPoint.y, 0.1, 0.1);
zoomRect = MKMapRectUnion(zoomRect, pointRect);
}
setVisibleMapRect(zoomRect, edgePadding: UIEdgeInsetsMake(20, 20, 20, 20), animated: true)
}
}
Related
My IOS map isn't rendering the NOAA tiles on top of it. Can someone give my a clue what is wrong? Here is my viewcontroler. It's just a standard mapkit viewer. Anyways, it is not working and is just giving me a standard satellite map when I run this code. Does anyone know what I am doing wrong?
Thanks for any help you can give me.
Have a great rest of your weekend.
import UIKit
import MapKit
class ViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
#IBOutlet weak var mapView: MKMapView!
//Linking MapView
override func viewDidLoad() {
super.viewDidLoad()
self.mapView.delegate = self
self.mapView.mapType = MKMapType.satellite
let centre = CLLocationCoordinate2D(latitude: 39.2189, longitude: -76.0690)
let span = MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
let region = MKCoordinateRegion(center: centre, span: span)
self.mapView.setRegion(region, animated: false)
self.mapView.regionThatFits(region)
let template = "https://tileservice.charts.noaa.gov/tiles/50000_1/{z}/{x}/{y}.png"
let carte_indice = MKTileOverlay(urlTemplate:template)
carte_indice.isGeometryFlipped = true
carte_indice.canReplaceMapContent = false
self.mapView.addOverlay(carte_indice)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func mapView(
mapView: MKMapView!, rendererFor overlay: MKOverlay!) -> MKOverlayRenderer!
{
if overlay is MKTileOverlay{
var renderer = MKTileOverlayRenderer(overlay:overlay)
renderer.alpha = 0.8
return renderer
}
return nil
}
}
After a lot of mulling over it, I solved my issue. Posting the solution if anyone needs it.
Thanks to GJ Nilsen for helping me figure this out. The problem was that the NOAA tiles didn't have coverage for everywhere, so I solved it by simplify forcing it to load.
if overlay is MKTileOverlay{
var renderer = MKTileOverlayRenderer(overlay:overlay)
renderer.alpha = 0.8
return renderer
}
else{
var renderer = MKTileOverlayRenderer(overlay:overlay)
renderer.alpha = 0.8
return renderer
}
The NOAA RNC Map Tile server is being taken out of commission, and some of the tiles my app uses started being disrupted around the same time you posted this question. This is part of the larger transfer from Raster to Electronic charting, and NOAA's discontinuation of paper charts.
https://nauticalcharts.noaa.gov/updates/coast-survey-to-shut-down-the-raster-navigational-chart-tile-service-and-other-related-services/
I am working to find a similar solution, but all of the new services I have found require a pay model of some sort (MBTile conversion, ArcGIS, etc) ... I miss the good old free NOAA map tiles!
(This is my first stack overflow question haha)
UPDATE:
From this link - Center MKMapView BEFORE displaying callout
I implemented the solution from Thermometer, however selecting and deselecting the annotation makes it look like my application is glitching.
I think the best way would be to delay the callOut (detail pop up) from appearing by a few seconds so the map has time to move first.
Here is my code:
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
guard let annotation = view.annotation else {
return
}
let currentAnnotation = view.annotation as? MapMarker
Global.currentAnnotation = currentAnnotation
findRelatinoshipLines()
if Global.showLifeStoryLines {
var locations = lifeStoryAnnotations.map { $0.coordinate }
let polyline = MKPolyline(coordinates: &locations, count: locations.count)
Global.finalLineColor = Global.lifeStoryColor
mapView.addOverlay(polyline)
}
if Global.showFatherLines {
var fatherLocations = fatherTreeAnnotations.map { $0.coordinate }
let fatherPolyline = MKPolyline(coordinates: &fatherLocations, count: fatherLocations.count)
Global.finalLineColor = Global.fatherLineageColor
mapView.addOverlay(fatherPolyline)
}
if Global.showMotherLines {
var motherLocations = motherTreeAnnotations.map { $0.coordinate }
let motherPolyline = MKPolyline(coordinates: &motherLocations, count: motherLocations.count)
Global.finalLineColor = Global.motherLineageColor
mapView.addOverlay(motherPolyline)
}
if Global.showSpouseLines {
var locations = spouseAnnotations.map { $0.coordinate }
let polyline = MKPolyline(coordinates: &locations, count: locations.count)
Global.finalLineColor = Global.spouseColor
mapView.addOverlay(polyline)
}
if Global.zoomChange == true {
Global.zoomChange = false
} else {
let currentRegion = mapView.region
let span = currentRegion.span
let location = currentAnnotation!.coordinate
let region = MKCoordinateRegion(center: location, span: span)
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
mapView.setCenter(annotation.coordinate, animated: true)
//mapView.setRegion(region, animated: true)
}
}
}
CONTINUED:
Basically I'm working on a family genealogy application that displays events from relatives on a map.
When I click an annotation (event) the details (who event belongs to, where and when, etc) pops up above with an information button to show the selected person.
I have it set up to set the MKMapView region so that the selected annotation is centered each time a new annotation is clicked.
The problem is when I click an event that is on the edge of the screen, my annotation title/description pops up off centered so that it fits on my screen because it doesn't know that I plan on re-centering the map view around said annotation.
I was wondering if there was any way to make the title/description appear centered directly above the selected annotation so that when I move the map everything is centered and fits on the screen.
Here are some screenshots of what I'm talking about:
Before and After
Solved it by calling setCenter with a slight delay in mapView(_:didSelect:):
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
guard let annotation = view.annotation else {
return
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
mapView.setCenter(annotation.coordinate, animated: true)
}
}
Update: Dose it have to do anything with language? as my app is sporting English and Arabic.
Hi everyone I am struggling with this since days and I have tried everything but can not find any solution, the problem is when I run the app through xCode it all works fine like in the following screenshot.
Then when i unplug the device and reopen the app it stops working, the app is running fine but i dont see any map but just the markers like the following screenshot.
Any help will be appreciated.
AppDelegate
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
GMSServices.provideAPIKey(googleApiKey)
GMSPlacesClient.provideAPIKey(googleApiKey)
return true
}
ViewController
//Step 1
#IBOutlet weak var mapview: GMSMapView!
//Step 2
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.mapview.settings.scrollGestures = true
self.mapview.settings.zoomGestures = true
self.mapview.isMyLocationEnabled = true
self.mapview.settings.myLocationButton = true
self.mapview.delegate = self
self.mapview.layoutIfNeeded()
}
}
//Step 3 After we have the current location items are loaded from server by user location and the markers are added in GetBusinesses function
extension ItemsList: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
guard status == .authorizedWhenInUse else {
return
}
self.locationManager.startUpdatingLocation()
self.mapview.camera = GMSCameraPosition(target: (self.locationManager.location?.coordinate)!, zoom: 14, bearing: 0, viewingAngle: 0)
self.GetBusinesses(data: "&isOpen=2&cat=\(Prefrences.getSelectedCategoryId())")
}
}
to show map in custom views you have to do it that way,
let camera = GMSCameraPosition.camera(withLatitude: lat , longitude: long, zoom: 15.0)
let mapView = GMSMapView.map(withFrame: CGRect(x: 2, y: 2, width: self.MapView.frame.width - 4, height: self.MapView.frame.height - 2), camera: camera)
self.MapView.addSubview(mapView)
self.MapView.clipsToBounds = true
self.MapView.contentMode = .center
you need to create a mapView using the created mapView on the storyboard frame and add it as a subView in the main MapView you created
Okay so after struggling many days and trying every stupid possible way to fix it, I finally found a solution to it. The main problem was if your app is supporting multiple languages then google map will show this kind of behaviour. Following are the steps to fix it.
Step 1:
If you have the GMSMapView view in your viewController, break the outlets of your GMSMapView since it will be created dynamically.
Step 2:
Set the application language as per the user settings.
Step 3:
Create custom GMSMapView object set delegate methods and other necessary settings and add it to its container.
That's it, you are good to go. What I understood from this is that you have to set your app language first before creating the GMSMapView (I can be wrong as well). If anyone knows why this method is working kindly explain it.
//Our Custom MapView Var
var mapview: GMSMapView!
//View viewDidLoad() function
override func viewDidLoad() {
super.viewDidLoad()
/*
TODO:
Get selected language from preferences and set accordingly.
*/
UserDefaults.standard.set(["en"], forKey: "AppleLanguages")
UserDefaults.standard.synchronize()
let camera = GMSCameraPosition.camera(withLatitude: 55.277397,
longitude: 25.199514,
zoom:12)
self.mapview = GMSMapView.map(withFrame: self.mMapContainer.bounds, camera: camera)
self.mapview.delegate = self
self.mapview.isMyLocationEnabled = true
self.mapview.settings.myLocationButton = true
self.mapview.isMyLocationEnabled = true
self.mapview.autoresizingMask = [.flexibleWidth, .flexibleHeight]
mMapContainer.addSubview(self.mapview)
}
I'm trying to add a search bar to a navigation map for MapBox on iOS, but having some trouble. I'm trying to do it programmatically in this case, as I don't have a great handle on storyboard. I've tried messing around with the zIndex as well, but still no search bar shows up for me.
Here's the code I've got so far:
class ViewController: UIViewController, UISearchBarDelegate, MGLMapViewDelegate {
var mapView: NavigationMapView!
lazy var directions: DirectionsManager = DirectionsManager()
var searchBar: UISearchBar!
override func viewDidLoad() {
super.viewDidLoad()
// Setup mapbox
let styleURL = URL(string: "style")
mapView = NavigationMapView(frame: view.bounds, styleURL: styleURL)
view.addSubview(mapView)
mapView.delegate = self
mapView.showsUserLocation = true
mapView.setUserTrackingMode(.follow, animated: true)
mapView.localizeLabels()
self.searchBar = UISearchBar()
mapView.addSubview(searchBar)
}
#erikpartridge, It's not an issue with mapbox, you need to set frame for UISearchBar like the below,
self.searchBar = UISearchBar(frame: CGRect(x: 15, y: 50, width: (view.bounds.width-30), height: 50))
mapView.addSubview(searchBar)
I'm trying to build an app that when you open it it zooms to your current location. Below is my code so far.
class MapVC: UIViewController {
#IBOutlet weak var mapView: MKMapView!
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
mapView.zoomToUserLocation()
}
}
extension MKMapView {
func zoomToUserLocation() {
print(userLocation.location?.coordinate)
guard let coordinate = userLocation.location?.coordinate else { return }
let region = MKCoordinateRegionMakeWithDistance(coordinate, 10000, 10000)
setRegion(region, animated: true)
}
}
This code does not solve the problem. I believe this is because the app doesn't have time after receiving location authorisation to obtain the user location. I'm looking to write a function that would wait for the app to have a location and then call the zoomToUserLocation function. I've already tried this with a do-while loop which didn't work. I could set a delay but that would mean the zoom was done at a specific time and instead I want the zoom to be done as soon as possible. I've found solutions to this in objective C but couldn't translate it.
This method might be helpful setUserTrackingMode(_:animated:), but I think that's not what you are looking for.
The solution that may fit best for you is to implement locationManager(_:didChangeAuthorization:) and locationManager(_:didUpdateLocations:) on the CLLocationManager's delegate. Then you call zoomToUserLocation() in didUpdateLocations.
Another observation, is that you shouldn't animate the map inside viewDidLoad() 'cause the view it not on screen yet. This should be done in viewDidAppead().
you need both a span and a region...the span allows you to set how far you want to zoom in...
let span = MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5)
if let center = location?.coordinate {
let region = MKCoordinateRegion(center: center, span: span)
self.mapView.setRegion(region, animated: true)
self.mapView.showsUserLocation = true