Centering MKMapView on spot N-pixels below pin
The easiest technique is to just shift the map down, say 40% from where the coordinate
would be, taking advantage of the span
of the region
of the MKMapView
. If you don't need actual pixels, but just need it to move down so that the CLLocationCoordinate2D
in question is near the top of the map (say 10% away from the top):
CLLocationCoordinate2D center = coordinate;
center.latitude -= self.mapView.region.span.latitudeDelta * 0.40;
[self.mapView setCenterCoordinate:center animated:YES];
If you want to account for rotation and pitch of the camera, the above technique may not be adequate. In that case, you could:
Identify the position in the view to which you want to shift the user location;
Convert that to a
CLLocation
;Calculate the distance of the current user location from that new desired location;
Move the camera by that distance in the direction 180° from the current heading of the map's camera.
E.g. in Swift 3, something like:
var point = mapView.convert(mapView.centerCoordinate, toPointTo: view)
point.y -= offset
let coordinate = mapView.convert(point, toCoordinateFrom: view)
let offsetLocation = coordinate.location
let distance = mapView.centerCoordinate.location.distance(from: offsetLocation) / 1000.0
let camera = mapView.camera
let adjustedCenter = mapView.centerCoordinate.adjust(by: distance, at: camera.heading - 180.0)
camera.centerCoordinate = adjustedCenter
Where CLLocationCoordinate2D
has the following extension
:
extension CLLocationCoordinate2D {
var location: CLLocation {
return CLLocation(latitude: latitude, longitude: longitude)
}
private func radians(from degrees: CLLocationDegrees) -> Double {
return degrees * .pi / 180.0
}
private func degrees(from radians: Double) -> CLLocationDegrees {
return radians * 180.0 / .pi
}
func adjust(by distance: CLLocationDistance, at bearing: CLLocationDegrees) -> CLLocationCoordinate2D {
let distanceRadians = distance / 6_371.0 // 6,371 = Earth's radius in km
let bearingRadians = radians(from: bearing)
let fromLatRadians = radians(from: latitude)
let fromLonRadians = radians(from: longitude)
let toLatRadians = asin( sin(fromLatRadians) * cos(distanceRadians)
+ cos(fromLatRadians) * sin(distanceRadians) * cos(bearingRadians) )
var toLonRadians = fromLonRadians + atan2(sin(bearingRadians)
* sin(distanceRadians) * cos(fromLatRadians), cos(distanceRadians)
- sin(fromLatRadians) * sin(toLatRadians))
// adjust toLonRadians to be in the range -180 to +180...
toLonRadians = fmod((toLonRadians + 3.0 * .pi), (2.0 * .pi)) - .pi
let result = CLLocationCoordinate2D(latitude: degrees(from: toLatRadians), longitude: degrees(from: toLonRadians))
return result
}
}
So, even with the camera pitched and at a heading other than due north, this moves the user's location (which is centered, where the lower crosshair is) up 150 pixels (where the upper crosshair is), yielding something like:
Obviously, you should be conscious about degenerate situations (e.g. you're 1 km from the south pole and you try to shift the map up 2 km meters; you're using a camera angle pitched so far that the desired screen location is past the horizon; etc.), but for practical, real-world scenarios, something like the above might be sufficient. Obviously, if you don't let the user change the pitch of the camera, the answer is even easier.
Original answer: for moving the annotation n
pixels
If you have a CLLocationCoordinate2D
, you can convert it to a CGPoint
, move it x pixels, and then convert it back to a CLLocationCoordinate2D
:
- (void)moveCenterByOffset:(CGPoint)offset from:(CLLocationCoordinate2D)coordinate
{
CGPoint point = [self.mapView convertCoordinate:coordinate toPointToView:self.mapView];
point.x += offset.x;
point.y += offset.y;
CLLocationCoordinate2D center = [self.mapView convertPoint:point toCoordinateFromView:self.mapView];
[self.mapView setCenterCoordinate:center animated:YES];
}
You can call this by:
[self moveCenterByOffset:CGPointMake(0, 100) from:coordinate];
Unfortunately, this only works if the coordinate
is visible before you start, so you might have to go to the original coordinate first, and then adjust the center.
User Current Location on specific position on MAP
Here is the trick to use to position the map in such a way that UserLocation pin gets adjusted automatically :
func setUserLocationOnLowerPositiononMap(coordinate: CLLocationCoordinate2D) {
var region = MKCoordinateRegion(center: coordinate, span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01))
region.center = coordinate
self.map.setRegion(region, animated: true)
//self.map.moveCenterByOffSet(offSet: CGPoint(x: 0, y: -130), coordinate: coordinate)
self.map.moveCenterByOffSet(offSet: CGPoint(x: 0, y: SCREEN_HEIGHT - SCREEN_HEIGHT * 4/3 + 0 ), coordinate: coordinate)
}
Center an MKAnnotation in a specific MKMapRect
I stumbled across this post here, which massively helped: Centering MKMapView on spot N-pixels below pin.
As mentioned in the answer, you should work out the destination point by converting it out of the map view and into your view controller's view with a more or less standardised frame.
// define the rect in which to center the annotation
let visibleRect = CGRectMake(0, 0, CGRectGetWidth(view.frame), 200)
centerAnnotationInRect(someAnnotation, rect: visibleRect)
func centerAnnotationInRect(annotation: MKAnnotation, rect: CGRect) {
guard let mapView = mapView else {
return
}
let visibleCenter = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect))
let annotationCenter = mapView.convertCoordinate(annotation.coordinate, toPointToView: view)
let distanceX: CGFloat = visibleCenter.x - annotationCenter.x
let distanceY = visibleCenter.y - annotationCenter.y
mapView.scrollWithOffset(CGPoint(x: distanceX, y: distanceY), animated: true)
}
MKMapView centerCoordinate not exact
In your answer you fixed one particular scenario where the center coordinate appears to be off, but really is not because it is just partially obscured by the status bar. Unfortunately, it is not as simple as that. There are other edge cases where the center coordinate actually can get offset a bit. (I notice it when I change frame of map view as keyboard is presented and later dismissed.) This is a curious little map view idiosyncrasy.
Anyone know how to determine how "off" the center coordinate is so I can adjust?
Yes, just convert the centerCoordinate
to a CGPoint
and compare to the center
. The only trick is to make sure you do these in the same coordinate system (in the example below, the coordinate system of the map view):
func adjustOverlayCenterYConstraint() {
let mapCenter = mapView.convert(mapView.centerCoordinate, toPointTo: mapView)
let viewCenter = mapView.superview!.convert(mapView.center, to: mapView)
overlayCenterYConstraint.constant = mapCenter.y - viewCenter.y
}
In this example, I have a centerY
constraint, whose constant
I am adjusting, but it illustrates the idea of how to programmatically determine whether the center is offset a bit and how to adjust it. Theoretically, you might have to adjust the x coordinate, too, but the idea would be the same.
You said:
is still off (now its south and west of the true center)
If it is off both south and west, then the problem is likely just the coordinate system. Remember, center
is in the coordinate system of the superview
, so make sure to convert to a consistent coordinate system (either get coordinateCenter
in coordinate system of map view’s superview
and compare to center
, or get both in the coordinate system of the mapview, like shown above).
Move MKMapView point to pixel coordinates
EDIT:
Ok so I messed around with this a little and was able to to put something together that seems to work, hoping that I now actually understand what you're trying to achieve!
- (void)shiftToCorner{
//ignore "Annotation", this is just my own custom MKAnnotation subclass
Annotation *currentAnnotation = [[mapView selectedAnnotations] objectAtIndex:0];
[mapView setCenterCoordinate:currentAnnotation.coordinate];
CGPoint fakecenter = CGPointMake(20, 20);
CLLocationCoordinate2D coordinate = [mapView convertPoint:fakecenter toCoordinateFromView:mapView];
[mapView setCenterCoordinate:coordinate animated:YES];
}
Let me quickly explain what this does; Let's say you want your annotation to move to a position 20 points in from the right edge and 20 points up from the bottom edge of the map view. If you think about it, if the current annotation is at the center of the map view, then the distance to this point is the same as the distance to (20, 20). This means that we can send our annotation to this point by first centering our map view on the annotation, then animating to (20, 20).
Related Topics
How to Prompt the User to Turn on Location Services After User Has Denied Their Use
iOS - Parse Issues in Nsobjcruntime, Nszone, and Nsobject
How to Get an Iphone's Device Name
Mkmapview Mkpointannotation Tap Event
How to Change Uibutton Image in Swift
Cannot Set Searchbar as Firstresponder
Is a Date in Same Week, Month, Year of Another Date in Swift
iOS Enterprise Ota Distribution Unable to Download Application
How to Set Device (Ui) Orientation Programmatically
Swift - Uibutton with Two Lines of Text
How to Swizzle a Class Method on iOS
How to Convert an Int to Hex String in Swift
Minimum and Maximum Date in Uidatepicker
Hiding Uitabbar When Pushing a Uiview
In Swift, How to Let Other Apps Continue to Play Audio When My App Is Open