Update MKMapView constraints but keep annotations in same location on screen - ios

I've got an mkmapview which has annotations on it. I want to resize the mapview (by changing it's height constraint's constant from 200 to 400), but I don't want the map's center coordinate to move at all.
Currently, when the map is resized, the center coordinate moves so that it stays in the center of the map (from (x, 100) to (x, 200) if the map height goes from 200 to 400).
It seems the only solution to this is executing code like this:
var coordinate = self.mapView.centerCoordinate
coordinate.latitude += self.mapView.region.span.latitudeDelta * 0.25;
self.mapView.setCenterCoordinate(coordinate, animated: true)
This does work, but the issue I'm encountering is mainly with animation. I want to update the constraint in an animated fashion as well:
mapHeightConstraint.constant = 400
UIView.animateWithDuration(0.35) {
self.view.layoutIfNeeded()
}
This results in the map not being animated regardless of whether I animate the constraint before or after I adjust the center.
I'd like both animations to occur simultaneously and take the same amount of time or find another solution.

Related

set current location icon lower side in MKMapView

I want to show my current location lower in the map (iOS 6 and iOS 7) as per below screen shot to user can see further view [google default app with google map].
Right now, the cursor that shows center in the view as per below image [my app with apple map] .
Therefore the largest part of the screen is used to display what's behind, while it cannot look forward very far.
In the first image and second image, I compare to Google Navigation, which shows the current position much lower in the screen, for about the same rotation angle. I've added some arrows to show what I'm talking about.
i tried below code for set center because i cannot find to set lower.
mapView.userTrackingMode = MKUserTrackingModeFollow;
and also try below method
[mapView setCenterCoordinate:currentLocation.coordinate animated:YES];
Setting layoutMargins on MKMapView works just fine.
According to docs,
The default spacing to use when laying out content in the view.
In iOS 11 and later, use the directionalLayoutMargins property to
specify layout margins instead of this property.
To offset camera's centering point to be centered in bottom half of the MKMapView, just set UIEdgeInsets.top to half of MKMapView's height.
class MapViewController: UIViewController {
#IBOutlet var mapView: MKMapView!
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let layoutMargins = UIEdgeInsets(top: self.mapView.bounds.size.height / 2, left: 0, bottom: 0, right: 0)
self.mapView.layoutMargins = layoutMargins
}
Don't set the userTrackingMode or the map's centerCoordinate.
Instead, you can try pointing the MKMapCamera to a coordinate slightly ahead of the user's in the direction they are heading. This will automatically put the user's location lower on the screen.
Calculating the coordinate "a short distance ahead" is not currently built into the iOS SDK so you'd have to calculate it manually.
One way to do it is using the method shown in:
Calculate new coordinate x meters and y degree away from one coordinate.
Using the coordinateFromCoord:atDistanceKm:atBearingDegrees: method from that answer, you could set the camera like this:
MKMapCamera *cam = [MKMapCamera camera];
CLLocationCoordinate2D coordinateAhead =
[self coordinateFromCoord:currentLocation.coordinate
atDistanceKm:0.15
atBearingDegrees:currentLocation.course];
//adjust distance (0.15 km) as needed
cam.centerCoordinate = coordinateAhead;
cam.heading = currentLocation.course;
cam.pitch = 80; //adjust pitch as needed (0=look straight down)
cam.altitude = 100; //adjust as needed (meters)
[mapView setCamera:cam animated:YES];
Try this with the "City Bicycle Ride", "City Run", or "Freeway Drive" in the simulator.
You may need to adjust some of the numbers to get the perspective you want.
A simple workaround that should work is to make the MKMapView frame bigger than the screen of the device.
Something like:
MapViewHeight = (WindowHeight - desiredOffsetFromBottom)*2

Why am I unable to detect when my UIView I push off screen using UIKit Dynamics is no longer visible?

I'm using UIKit Dynamics to push a UIView off screen, similar to how Tweetbot performs it in their image overlay.
I use a UIPanGestureRecognizer, and when they end the gesture, if they exceed the velocity threshold it goes offscreen.
[self.animator removeBehavior:self.panAttachmentBehavior];
CGPoint velocity = [panGestureRecognizer velocityInView:self.view];
if (fabs(velocity.y) > 100) {
self.pushBehavior = [[UIPushBehavior alloc] initWithItems:#[self.scrollView] mode:UIPushBehaviorModeInstantaneous];
[self.pushBehavior setTargetOffsetFromCenter:centerOffset forItem:self.scrollView];
self.pushBehavior.active = YES;
self.pushBehavior.action = ^{
CGPoint lowestPoint = CGPointMake(CGRectGetMinX(self.imageView.bounds), CGRectGetMaxY(self.imageView.bounds));
CGPoint convertedPoint = [self.imageView convertPoint:lowestPoint toView:self.view];
if (!CGRectIntersectsRect(self.view.bounds, self.imageView.frame)) {
NSLog(#"outside");
}
};
CGFloat area = CGRectGetWidth(self.scrollView.bounds) * CGRectGetHeight(self.scrollView.bounds);
CGFloat UIKitNewtonScaling = 5000000.0;
CGFloat scaling = area / UIKitNewtonScaling;
CGVector pushDirection = CGVectorMake(velocity.x * scaling, velocity.y * scaling);
self.pushBehavior.pushDirection = pushDirection;
[self.animator addBehavior:self.pushBehavior];
}
I'm having an immense amount of trouble detecting when my view actually completely disappears from the screen.
My view is setup rather simply. It's a UIScrollView with a UIImageView within it. Both are just within a UIViewController. I move the UIScrollView with the pan gesture, but want to detect when the image view is off screen.
In the action block I can monitor the view as it moves, and I've tried two methods:
1. Each time the action block is called, find the lowest point in y for the image view. Convert that to the view controller's reference point, and I was just trying to see when the y value of the converted point was less than 0 (negative) for when I "threw" the view upward. (This means the lowest point in the view has crossed into negative y values for the view controller's reference point, which is above the visible area of the view controller.)
This worked okay, except the x value I gave to lowestPoint really messes everything up. If I choose the minimum X, that is the furthest to the left, it will only tell me when the bottom left corner of the UIView has gone off screen. Often times as the view can be rotating depending on where the user pushes from, the bottom right may go off screen after the left, making it detect it too early. If I choose the middle X, it will only tell me when the middle bottom has gone off, etc. I can't seem to figure out how to tell it "just get me the absolute lowest y value.
2. I tried CGRectIntersectsRect as shown in the code above, and it never says it's outside, even seconds after it went shooting outside of any visible area.
What am I doing wrong? How should I be detecting it no longer being visible?
If you take a look on UIDynamicItem protocol properties, you can see they are center, bounds and transform. So UIDynamicAnimator actually modifies only these three properties. I'm not really sure what happens with the frame during the Dynamics animations, but from my experience I can tell it's value inside the action block is not always reliable. Maybe it's because the frame is actually being calculated by CALayer based on center, transform and bounds, as described in this excellent blog post.
But you for sure can make use of center and bounds in the action block. The following code worked for me in a case similar to yours:
CGPoint parentCenter = CGPointMake(CGRectGetMidX(self.view.bounds), CGRectGetMidY(self.view.bounds));
self.pushBehavior.action = ^{
CGFloat dx = self.imageView.center.x - parentCenter.x;
CGFloat dy = self.imageView.center.y - parentCenter.y;
CGFloat distance = sqrtf(dx * dx + dy * dy);
if(distance > MIN(parentCenter.y + CGRectGetHeight(self.imageView.bounds), parentCenter.x + CGRectGetWidth(self.imageView.bounds))) {
NSLog(#"Off screen!");
}
};

Moving an MKMapView to a specific location relative to the superview

I have a few annotations added to a MKMapView, and when the user clicks on one of the annotations, it displays a UICalloutView with a right accessory button which adds a UIView to the map, displaying some information about that specific location. This UIView is centred in the superview of the map, and in order to show that the information in that view is relative to the annotation, I would like to shift the visible map rect down (on the y axis), and center it on the x axis so that the annotation is directly under the view.
I am doing the following to centre the annotation, however, I don't know how to move the annotation down on the y axis so that it sits under the added UIView. Please can you tell me how I can do so?
[self.mapView setCenterCoordinate:[annotation coordinate] animated:YES];
If you want to shift the map down so it's centered on a particular coordinate, but shift it down, say, 40%, so you have space for something above it, you could do something like the following:
CLLocationCoordinate2D center = coordinate;
center.latitude -= self.mapView.region.span.latitudeDelta * 0.40;
[self.mapView setCenterCoordinate:center animated:YES];
You can get the size of the information view, then you know how much you want to use the map (based on the difference between its size and the map view size). Now you know the offset, you can calculate the point (in the view coordinate system) that should be moved to the centre so that the annotation is moved down). Then you can use convertPoint:toCoordinateFromView: to find the coordinate for that point to use with setCenterCoordinate:animated:.

New foursquare venue detail map

I really love the way foursquare designed venue detail view. Especially the map with venue location in the "header" of view ... How was it done? Details are obviously some uiscrollview (maybe uitableview?) and behind it (in the header) there is a map so when you scroll up the map is beeing uncovered as the scroll view bounces... does anyone has an idea how to do this?
Here's the way I manage to reproduce it:-
You need a UIViewController with a UIScrollView as its view. Then, the content of the UIView you add to your scrollview should look like this :-
- The frame of the MKMapView have a negative y position. In this case, we can only see 100pts of the maps in the default state (before dragging).
- You need to disable zooming and scrolling on your MKMapView instance.
Then, the trick is to move down the centerCoordinate of the MKMapView when you drag down, and adjust its center position.
For that, we compute how much 1point represent as a delta latitude so that we know how much the center coordinate of the map should be moved when being dragged of x points on the screen :-
- (void)viewDidLoad {
[super viewDidLoad];
UIScrollView* scrollView = (UIScrollView*)self.view;
[scrollView addSubview:contentView];
scrollView.contentSize = contentView.frame.size;
scrollView.delegate = self;
center = CLLocationCoordinate2DMake(43.6010, 7.0774);
mapView.region = MKCoordinateRegionMakeWithDistance(center, 1000, 1000);
mapView.centerCoordinate = center;
//We compute how much latitude represent 1point.
//so that we know how much the center coordinate of the map should be moved
//when being dragged.
CLLocationCoordinate2D referencePosition = [mapView convertPoint:CGPointMake(0, 0) toCoordinateFromView:mapView];
CLLocationCoordinate2D referencePosition2 = [mapView convertPoint:CGPointMake(0, 100) toCoordinateFromView:mapView];
deltaLatFor1px = (referencePosition2.latitude - referencePosition.latitude)/100;
}
Once those properties are initialized, we need to implement the behavior of the UIScrollViewDelegate. When we drag, we convert the move expressed in points to a latitude. And then, we move the center of the map using the half of this value.
- (void)scrollViewDidScroll:(UIScrollView *)theScrollView {
CGFloat y = theScrollView.contentOffset.y;
// did we drag ?
if (y<0) {
//we moved y pixels down, how much latitude is that ?
double deltaLat = y*deltaLatFor1px;
//Move the center coordinate accordingly
CLLocationCoordinate2D newCenter = CLLocationCoordinate2DMake(center.latitude-deltaLat/2, center.longitude);
mapView.centerCoordinate = newCenter;
}
}
You get the same behavior as the foursquare app (but better: in the foursquare app, the maps recenter tends to jump, here, changing the center is done smoothly).
The example above is nice. If you need more help, I think they're using something very similar to RBParallaxTableViewController. https://github.com/Rheeseyb/RBParallaxTableViewController
It's essentially the same effect that Path uses for its header photo.
Yonel's answer is nice, but I found a problem as I have a pin at the center of the map. Because the negative Y, the point is hidden under my UINavigationBar.
Then, I didn't set the Negative Y, and I correct my mapView.frame according the scroll offset.
My mapView is 320 x 160
_mapView.frame = CGRectMake(0, 160, 320, -160+y);
Hope this helps someone.

Zooming out with RMMapView constrained to coordinates

I'm trying to setup an offline map in an iPhone app, but the result is not very good.
I'm using the route-me framework, I've got an offline file .db (created by tiles2sqlite) and the map view is constrained with coordinates (using setConstraintsSW:NE:).
My problem is appearing when zooming out (pinch gesture), this error message "Zooming will move map out of bounds: no zoom" is always present and it's very difficult to zoom out when you are not near the real center of the map.
Is there a solution to have the same result as in Offmaps (iOS app) where the map has a nice scrollview behavior?
Cheers.
Cyril
I had to edit RMMapView.m source code to make a quick fix. Find - (void)zoomByFactor: (float) zoomFactor near:(CGPoint) center animated:(BOOL)animated method (near line 300). It has constrain logic, I turned it off:
- (void)zoomByFactor: (float) zoomFactor near:(CGPoint) center animated:(BOOL)animated
{
if ( _constrainMovement && false ) // turn constraint checks off
{
//check that bounds after zoom don't exceed map constraints
//the logic is copued from the method zoomByFactor,
float _zoomFactor = [self.contents adjustZoomForBoundingMask:zoomFactor];
float zoomDelta = log2f(_zoomFactor);
...
}
...
}
Now map is zoomed smoothly, but this fix could have side effects.
Instead you can use of setting setConstraintsSW:NE: we can set RMMapview as,
RMDBMapSource *mapSrc = [[[RMDBMapSource alloc] initWithPath:#"mapDB.sqlite"] autorelease];
[[RMMapContents alloc] initWithView:mapView tilesource:mapSrc centerLatLon:mapSrc.centerOfCoverage zoomLevel:17 maxZoomLevel:18 minZoomLevel:15 backgroundImage:image screenScale:10 ];
This will enable your zooming acording to your set parameter

Resources