Sorry if the topic is not that accurate. I am trying to build a map route from point to point, and the key thing is that both points are defined by the user. So I want to pass a source mapItem and destination mapItem from the interface builder and make a route between them.
What I did:
I have a suggestion table view controller which suggests places for search table view controller when something is typed in search bar; then search TVC passes map annotation with name and address to the result map View.
On my result map view I have two buttons: Source and Destination. So when address is passed from search TVC, mapView suggests two buttons to press. If Destination button is pressed, then it means that source map item is nil and it builds the route from my current location to destination map item. No problem here, but when Source button is pressed, source map Item is not stored and when searching for destination map item and pressing Destination button, app builds route again from my current location, ignoring that before that I pressed the Source button and I need a route not from my current location, but from source mapItem.
So in code, I have defined variables:
var mapItems: [MKMapItem]?
private var sourceMapItem: MKMapItem!
#IBOutlet weak var selectPointBmessage: UILabel!
#IBOutlet weak var resultMapView: MKMapView!
#IBOutlet weak var resultName: UILabel!
#IBOutlet weak var resultAddress: UILabel!
two functions: findMe(), which searches for my current location, and buildRoute(destinationMapItem: MKMapItem), which builds a route from my current location, if source map item is nil, to the destinationMapItem.
and two buttons:
#IBAction func sourceButton(_ sender: UIButton) {
// add searched mapItems to Array of MapItems
sourceMapItem = mapItems!.first
// message saying the user to find destination is source button is pressed
selectPointBmessage.isHidden = false
}
#IBAction func buildRouteButton(_ sender: UIButton) {
// add searched mapItems to Array of MapItems
let destinationMapItems = mapItems!.compactMap { (mapItem) -> MKMapItem in
let destinationMapItem = mapItem
return destinationMapItem
}
// for each if several or for one if one - build a route(s) to it
for destinationMapItem in destinationMapItems {
buildRoute(sourceMapItem: sourceMapItem, destinationMapItem: destinationMapItem)
}
}
(mapItems are defined and recorded in Search Table View Controller. Here we unpack them using compact map and start interacting with them.)
and other stuff as adding annotations, route line to mapView, two Swift files with Suggest TVC and Search TVC.
Also added the printScreen of StoryBoard to show how it is organized.
storyBoard printScreen
Please type if some more details are required.
So:
How can I make my app remember the sourceMapItem and when Destination button is pressed build a route from point to point defined by the user?
So I created an Array of mapItems, stored mapItems in it when table row is selected. Then when calling my buttons, using that array, I've assigned values to my source and destination points.
I created the picker as follows:
let placePicker = GSMPlacePickerViewController(config: config)
placePicker.delegate?.placePicker(viewController: GSMPlacePickerViewController, didPick: GSMPlace)
In the second line of the code above, how can I store the place the user picks inside a variable of my own so later I can access its properties like the coordinates?
I am confused on how to implement GSMPlacePickerViewController method and couldn't find examples online.
As from the GSMPlacePickerViewController docs, it says that I don't have to implement the first parameter and I could leave it empty if I understood it correctly
Implementations of this method should dismiss the view controller as
the view controller will not dismiss itself.
So my problem now, is storing the user selected location in a variable of my own.
How should I go to achieving that?
Add GMSPlacePickerViewControllerDelegate to your view controller like so:
class YourViewController: GMSPlacePickerViewControllerDelegate {
}
Then add the delegate method within said VC which will be called when the user picks the place. You then dismiss and access place data like below:
func placePicker(_ viewController: GMSPlacePickerViewController, didPick place: GMSPlace) {
placePicker.dismiss(animated: true, completion: nil)
let location = CLLocation(latitude: place.coordinate.latitude, longitude: place.coordinate.longitude)
}
I've got a google map and i'm setting marker.iconView to a custom UIView.
marker.iconView = customUIView()
func customUIView()-> UIView
{
var theView:UIView
theView.isUserInteractionEnabled = true
let gesture = UITapGestureRecognizer(target: self, action:
#selector (self.popAction(sender:)))
theView.addGestureRecognizer(gesture)
}
The UIView is pretty much just a textbox and when the UIView is clicked I won't a popup happening from popAction.
But I can't get the gesture functionality to work and I wonder if it's because the gesture is being registered on the google map and not on the UIView.
**** Update *****
After reading the google docs i'm pretty sure i can't add a gesture to a marker.iconView because the UIView is just a "snapshot"
Instead i'm implementing GMSMapViewDelegate.
func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool {
print("markerTapped")
return true
}
This function works but what i'd like to do is pass data from my UIView that is the marker.iconView into the above func mapView and not entirely sure how.
update
So marker.iconView = customUIView(sport: sportEvent) and each sportEvent object has title, date and description. The customUIView looks like a textbox with the info.
There is a loop through an array so that each marker on the map has specific sportEvent info.
Ideally, I'd like to add a gesture to the customUIView so that when the "marker" is clicked an alert pops up with the details of sportEvent title, date and description. But the way google renders marker.iconView means user input is lost from the customUIView so I can't set a gesture on it.
So i'm looking for a workaround where I can get a particular marker clicked and then an alert pops with that sportEvent info.
So I used GMSMapViewDelegate and then when the marker is clicked func mapView fires. That's cool but I can't get the sportEvent info associated with each particular marker.
So I tried to pass the sportEvent object into func mapView but then the function doesn't seem to execute and the marker is no longer clickable.
Thx.
The way I approached it was creating a custom GMSMarker. This will create the points on your mapView.
You want to pass in your sportEvent object when initializing the custom marker (i.e. something like CustomMarker(with: sportEvent))
The GMSMarker object has some properties you can set which you can find here but, some of the notable ones include:
title, snippet (text beneath title) which is what you want to set from your sportEvent object.
So, in your custom marker you can do:
Class CustomMarker: GMSMarker {
var title: String
var desc: String
...
init(with sportEvent: SportEvent) {
// do some validation before setting this assumes all values are set correctly
self.title = sportEvent.title //(assuming `.title` is a string)
self.desc = sportEvent.description
...
super.init()
title = self.title
snippet = self.desc
}
}
Note that the custom GMSMarker may need a position property set which is just a CLLocationCoordinate2D i.e. (position = CLLocationCoordinate2D(latitude: sportEvent.latitude, longitude: sportEvent.longitude)
This will by default show you a mini marker info window with your sportEvent object.
If you want to further customize the marker info window you can take a look at markerInfoWindow
It returns a view if it exists. What I did was create my view programatically and I passed in my custom marker into the view and set everything accordingly there.
Like so:
func mapView(_ mapView: GMSMapView, markerInfoWindow marker: GMSMarker) -> UIView? {
let customMarker = marker as! CustomMarker
return myCustomView(with: customMarker)
}
Where myCustomView is a method that returns a UIView`
func myCustomView(with customMarker: CustomMarker) -> UIView {
// Set view programatically here with your customMarker
}
Hope it helps!
I'm new on Swift Programming, and I'm trying to build an app that I can get the coordinates of the center of the view with MapKit and Swift 2.
I already can get the current location, but I need if I move on the map, the location set to the new point which will be the center of the screen.
Can you help me with this please?.
Regards,
You can use the regionDidChangeAnimated delegate method and call mapView.centerCoordinate. It will look like the following:
func mapView(mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
var center = mapView.centerCoordinate
}
Also make sure that your Class is extending MKMapViewDelegate and you are calling
self.mapView.delegate = self in your viewDidLoad() function.
I have several MKAnnotations (and their corresponding views) in my map, and it sometimes gets really crowded. Now, the annotations in my app come in two flavors: some are bound to stay where they are, while others will move as time goes on. I'd prefer to have the more stable ones visually in the background and the moving ones to always pass in front of them.
One would think, perhaps, that the annotations most recently added to the map would end up to the front (or alternatively at the very back, at least) but this just doesn't seem to be the rule. As far as I can tell, I create and add ALL the non-moving annotations first, and then add some newly-instantiated moving annotations, but many of them (although not all!) end up drawn under the perpetually stock-still ones.
Interestingly, when time goes by, and yet new moving annotations are created, they tend to gravitate more to the top than the first ones - even if all moving annotation objects were created only after the nonmoving parts were already added to the map.
Does anyone know a trick to alter this strange natural order of the annotation views on the map? I tried to search the Map Kit API, but it doesn't seem to speak of such a thing.
Ok, so for solution use method from MKMapViewDelegate
- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views
In this method you should rearrange AnnotationView after it was added to mapKit View.
So, code may looks like this:
- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views {
for (MKAnnotationView * annView in views) {
TopBottomAnnotation * ann = (TopBottomAnnotation *) [annView annotation];
if ([ann top]) {
[[annView superview] bringSubviewToFront:annView];
} else {
[[annView superview] sendSubviewToBack:annView];
}
}
}
This works for me.
Under iOS 11 the implementation of displayPriority broke all the solutions which use bringSubviewToFront or zPosition.
If you override the annotation view's CALayer, you can wrestle control of zPosition back from the OS.
class AnnotationView: MKAnnotationView {
/// Override the layer factory for this class to return a custom CALayer class
override class var layerClass: AnyClass {
return ZPositionableLayer.self
}
/// convenience accessor for setting zPosition
var stickyZPosition: CGFloat {
get {
return (self.layer as! ZPositionableLayer).stickyZPosition
}
set {
(self.layer as! ZPositionableLayer).stickyZPosition = newValue
}
}
/// force the pin to the front of the z-ordering in the map view
func bringViewToFront() {
superview?.bringSubviewToFront(toFront: self)
stickyZPosition = CGFloat(1)
}
/// force the pin to the back of the z-ordering in the map view
func setViewToDefaultZOrder() {
stickyZPosition = CGFloat(0)
}
}
/// iOS 11 automagically manages the CALayer zPosition, which breaks manual z-ordering.
/// This subclass just throws away any values which the OS sets for zPosition, and provides
/// a specialized accessor for setting the zPosition
private class ZPositionableLayer: CALayer {
/// no-op accessor for setting the zPosition
override var zPosition: CGFloat {
get {
return super.zPosition
}
set {
// do nothing
}
}
/// specialized accessor for setting the zPosition
var stickyZPosition: CGFloat {
get {
return super.zPosition
}
set {
super.zPosition = newValue
}
}
}
Try to setup annotation view layer's zPosition (annotationView.layer.zPosition) in:
- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views;
Swift 3:
I get pin locations from API and I was having similar issues, the pins that had to be on top weren't. I was able to solve it like this.
var count = 0 // just so we don't get the same index in bottom pins
func mapView(_ mapView: MKMapView, didAdd views: [MKAnnotationView]) {
for view in views {
view.layer.zPosition = CGFloat(count)
}
count += 1
if count > 500 {
count = 250 // just so we don't end up with 999999999999+ as a value for count, plus I have at least 30 pins that show at the same time and need to have lower Z-Index values than the top pins.
}
}
Hope this helps
In the delegate function, you can select the pin to force it on top:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?` {
...
if my annotation is the special one {
annotationView.isSelected = true
}
...
}
I'm finding that this reordering the annotation views causes the callout that pops up when one is clicked to no longer be on top of all the annotations. I've even tried refining it so that instead of bringSubviewToFront and sendSubviewToBack, I use insertSubview:aboveSubview and insertSubview:belowSubview: where the second argument is the first annotationView in the list. This would seem to cause much less front to back scattering, but the call outs still pop up under some annotations.
I really needed to do this, and none of the (current) answers seemed to provide a reliable implementation. They sort of worked, but panning the map, selecting annotations, or zooming in could mess up the order again.
The final, well behaved solution wasn't so trivial, so I'll just outline the steps I took here. The annotation ordering that MKMapView uses doesn't respect the added order, or even the order of an overriden annotations property. So...
Steps
• Create a CADisplayLink
• Every frame, reorder annotations using both the layer zPosition, and the view's ordering in the superview's subviews array.
• If the view is selected, promote it to the front in your ordering scheme
• Tapping on annotations still respects internal MKMapView ordering, despite the already made changes. To counter this, add an MKMapViewDelegate
• In the delegate object's mapView:didSelect: method, check if the selected annotation is what you'd like it to be
• You can figure out the correct/prioritised annotation by running hit tests on the annotations yourself, with your own ordering taken into account
• If the selected annotation is correct, great. If not, manually select the correct annotation using selectAnnotation:animated:
And there you have it. The above method seems to work well, and the performance hit from running this each frame isn't too bad. You could also look at switching to MapBox, which I believe supports annotation ordering, but this isn't always an option for various reasons.