I am working with a GMSMapView that I have added as a custom UITableViewCell. When I first create the map and capture an initial user tap, my coordinates are based as though the user tapped near lat/lon 0/0 -- I get values like -1.800144/-1.867676 even though my map center is set to 43.50 and -116.22 (and the map displays around those coordinates).
However, when I move the map by panning it once, and then tapping on it, my coordinates are more along the line of what I expect. Also, if I add the following code to the GMSMapViewDelegate method mapView:idleAtCameraPosition: I get the correct coordinates the first time:
static dispatch_once_t onceToken ;
dispatch_once(&onceToken, ^{
GMSCameraUpdate *update = [GMSCameraUpdate setCamera:position] ;
[mapView moveCamera:update] ;
}) ;
But that seems like a hack that is hiding a more fundamental issue.
Here is the relevant code:
My custom table view cell creates the initial mapView in its constructor:
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Camera position is moot, just need something to instantiate the map view
GMSCameraPosition * cameraPosition = [GMSCameraPosition cameraWithLatitude:0 longitude:0 zoom:5];
GMSMapView * mapView = [GMSMapView mapWithFrame:CGRectZero camera:cameraPosition];
_mapView = mapView ;
}
return self;
}
In my view controller, I set the gestures enabled, set my delegate and my map bounds:
- (void) configureMap
{
[_mapView.settings setAllGesturesEnabled:YES];
[_mapView.settings setConsumesGesturesInView:YES] ;
_mapView.delegate = self ;
// _userTrip has among other things, a bounds for our map
GMSCoordinateBounds * bounds = _userTrip.bounds ;
DDLogInfo(#"Trip Bounds are sw(%f:%f) ne(%f:%f)", bounds.southWest.latitude, bounds.southWest.longitude, bounds.northEast.latitude, bounds.northEast.longitude) ;
GMSCameraUpdate *cameraUpdate = [GMSCameraUpdate fitBounds:bounds];
[_mapView moveCamera:cameraUpdate];
}
My delegate for camera position:
- (void) mapView:(GMSMapView *)mapView idleAtCameraPosition:(GMSCameraPosition *)position
{
DDLogInfo(#"User changed map size. Zoom is now set to %f", mapView.camera.zoom) ;
/* putting this in 'fixes' the problem
static dispatch_once_t onceToken ;
dispatch_once(&onceToken, ^{
GMSCameraUpdate *update = [GMSCameraUpdate setCamera:position] ;
[mapView moveCamera:update] ;
}) ;
*/
}
My delegate method for taps:
- (void) mapView:(GMSMapView *)mapView didTapAtCoordinate:(CLLocationCoordinate2D)coordinate
{
DDLogInfo(#"User did tap at coordinate %f, %f", coordinate.latitude, coordinate.longitude) ;
DDLogInfo(#"Map view center %f %f and zoom is %f", mapView.camera.target.latitude, mapView.camera.target.longitude, mapView.camera.zoom) ;
}
Sample output:
[] Trip Bounds are sw(43.409890:-116.435511) ne(43.597180:-116.023860)
[] map view zoom = 10.053131
[] User changed map size. Zoom is now set to 10.053131
--- At this point, the map is displayed and it shows the right location based on the bounds in the log statement above. Then, when I tap on the map:
[] User did tap at coordinate -0.262352, -0.900879
[] Map view center 43.503608 -116.229685 and zoom is 10.053131
--- As you can see above, the user coordinates are way off based on where the map thinks it is. Below, I have panned the map and then did another tap
[] User changed map size. Zoom is now set to 10.053131
[] User did tap at coordinate 43.447926, -116.340871
[] Map view center 43.426753 -116.377271 and zoom is 10.053131
As you can see, the user coordinates now appear to be a valid location on the map. I have verified through the debugger that the same map instance is being referenced throughout and it has the same gestureRecognizer.
I had a previous version of the same code that did not put the GMSMapView inside a table view cell, and it does not exhibit the same problem. So, I suspect there is an initialization issue caused by putting this in a UITableViewCell.
Anyone run across something like this and have any idea how to fix it?
Related
Am actually dealing with the Google Maps Framework for iOS, and I want to block scrolling out side a giving area.
What I tried to do at first, implement the delegate method : - (void)mapView:(GMSMapView *)mapView didChangeCameraPosition:(GMSCameraPosition *)position and compare the map position with the right/left corners.But what I did has many issues when scrolling or zooming.
Below an exemple of my implementation :
- (void)mapView:(GMSMapView *)mapView didChangeCameraPosition:(GMSCameraPosition *)position {
if (position.target.latitude > topLat) {
GMSCameraPosition *goBackCamera = [GMSCameraPosition cameraWithLatitude:topLat
longitude:position.target.longitude
zoom:position.zoom
bearing:220
viewingAngle:0];
[self.mapView animateToCameraPosition:goBackCamera];
}
if (position.target.latitude < bottomLat) {
GMSCameraPosition *goBackCamera = [GMSCameraPosition cameraWithLatitude:bottomLat
longitude:position.target.longitude
zoom:position.zoom
bearing:220
viewingAngle:0];
[self.mapView animateToCameraPosition:goBackCamera];
}
if (position.target.longitude > rightLong) {
GMSCameraPosition *goBackCamera = [GMSCameraPosition cameraWithLatitude:position.target.latitude
longitude:rightLong
zoom:position.zoom
bearing:220
viewingAngle:0];
[self.mapView animateToCameraPosition:goBackCamera];
}
if (position.target.longitude < leftLong) {
GMSCameraPosition *goBackCamera = [GMSCameraPosition cameraWithLatitude:position.target.latitude
longitude:leftLong
zoom:position.zoom
bearing:220
viewingAngle:0];
[self.mapView animateToCameraPosition:goBackCamera];
}
}
Do you no a way more efficient to deal with this?
PS: TopLat, RightLong ... mean top Latitude and Right longitude etc
Regards
One way I can think of is by setting up the bound in the map in you want maps to scroll using GMSCoordinateBounds.
Next thing you can do is to set up the scroll gesture to true just within that section of maps you have specified above bounds. For the rest of the map the scroll gesture should be false.
Use GMSCoordinateBounds(northeast co-ordinates, southwest co-ordinates) method.
This might be helpful
GMSCoordinateBounds Class Reference
I need to create a map view interface, which is something similar to the OLA Cabs Application in iOS. What I exactly wanna do is to fix an overlay on mapView and allow the user to scroll the map view across it. So that the overlay can be fixed at any location the User wants it to, I searched a lot about overlays, in iOS and MapKit, but couldn't make it possible. If some one can give me tips for achieving this I would be really grateful. Here is a snapshot of the screen
Here the annotation remains fixed and you can move the map view across it, So that when you stop the mapview, the overlay will be pointing to the new location, where you stopped
Click here to download demo...
Create a fix MKAnnotation and image view object to animating the location change effect in Map view.
#property (nonatomic, strong) CustomAnnotation *fixAnnotation;
#property (nonatomic, strong) UIImageView *annotationImage;
Add this code in viewDidLoad() method:
// Fix annotation
_fixAnnotation = [[CustomAnnotation alloc] initWithTitle:#"Fix annotation" subTitle:#"Location" detailURL:nil location:self.mapView.userLocation.coordinate];
[self.mapView addAnnotation:self.fixAnnotation];
// Annotation image.
CGFloat width = 64;
CGFloat height = 64;
CGFloat margiX = self.mapView.center.x - (width / 2);
CGFloat margiY = self.mapView.center.y - (height / 2) - 32;
// 32 is half size for navigationbar and status bar height to set exact location for image.
_annotationImage = [[UIImageView alloc] initWithFrame:CGRectMake(margiX, margiY, width, height)];
[self.annotationImage setImage:[UIImage imageNamed:#"mapannotation.png"]];
Now have to remove image when you drag a map view and add image which looks like an annotation. And after completion of that add annotation and remove image from Map View.
- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated {
NSLog(#"Region will changed...");
[self.mapView removeAnnotation:self.fixAnnotation];
[self.mapView addSubview:self.annotationImage];
}
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
NSLog(#"Region did changed...");
[self.annotationImage removeFromSuperview];
CLLocationCoordinate2D centre = [mapView centerCoordinate];
self.fixAnnotation.coordinate = centre;
[self.mapView addAnnotation:self.fixAnnotation];
}
Its not an map annotation overlay, its a normal UIImageView which has been placed over MKMapView, and it always used to get the lat-long for the center point of the map.
Hope this would be an easy way to achieve your goal.
#Kampai has added the same code for you.
it is my first time to use google map and i asked to let the user choose the location that he wanted to point by taping on the map. when the application start it will point on the device location how the user will change it to the location he wants?
First get the point of user's tap. Then, get coordinate value for that CGPoint and set it as mapView's center.
-(void)handlePan:(UIPanGestureRecognizer *)recognizer
{
// Get tap point
CGPoint tapPoint = [recognizer locationInView:[recognizer superView]];
// Convert CGPoint to CLLocationCoordinate2D
CLLocationCoordinate2D center = [self.mapView.projection coordinateForPoint:tapPoint];
// Set camera of mapView
GMSCameraPosition * camera = [GMSCameraPosition cameraWithLatitude:center.latitude longitude:center.longitude zoom:self.mapView.camera.zoom];
[self.mapView setCamera:camera];
}
Alternatively you can implement the GMSMapViewDelegate and use the - mapView:didTapAtCoordinate: method.
You can move the view port by doing something similar like this to move the center point of map view
GMSCameraPosition *sydney = [GMSCameraPosition cameraWithLatitude:-33.8683
longitude:151.2086
zoom:6];
[mapView_ setCamera:sydney];
(Google Map SDK called it camera position, as if you use a camera to target a portion of the whole map)
Checkout https://developers.google.com/maps/documentation/ios/views?hl=en Moving the camera section
So here, in order to finish your task with move use to the point where he tapped. You'll need to do following steps:
define a iVar or property to keep record of the tap point location(lat/lng):
CLLocationCoordinate2D *currentTapLocation;
In didTapAtCoordinate delegate, fetch that position
- (void) mapView:(GMSMapView *) mapView didTapAtCoordinate:(CLLocationCoordinate2D) coordinate{
currentTapLocation = coordinate;
}
Create a GMSCamera and set camera to currentTapLocation
- (void) mapView:(GMSMapView *) mapView didTapAtCoordinate:(CLLocationCoordinate2D) coordinate{
currentTapLocation = coordinate;
GMSCameraPosition *newCameraPosition = [GMSCameraPosition cameraWithLatitude:currentTapLocation.latitude
longitude:currentTapLocation.longitude
zoom:6];
[mapView_ setCamera:newCameraPosition];
}
Optional step: if you want animate to the new position instead setCamera directly,
you can do:
[mapView_ animateWithCameraUpdate:[mapView_
setCamera:newCameraPosition];];
instead of
[mapView_ setCamera:newCameraPosition];
I am using the HGMovingAnnotation and HGMovingAnnotationView code off of github to animate a MKAnnotation on an MKmap. When I run the example project from HG project everything works fine.
I have altred the original HG project to allow me to manually push a new coordinate to the HGMapPath and then move the annotation where I want it.
I have placed a button, for testing, to run the manual process and everything works fine. The annotation moves around the screen. The issue is, when I try to now call this manual method with data from a live socket.io connection, the map annotation won't move.
Also, when the map first loads the annotation won't show up until I move the map a little bit. The same thing for the moving annotation manually, it won't show the movement from the stream of data, until I zoom the map. But if I do the push button way, avoiding the io stream, the annotation moves without needing to zoom or pan the map?
PLACING THE VIEW ANNOTATIONS
if(doubleLat && doubleLng) {
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(doubleLat, doubleLng);
//Create path object
self.assignedAmbPath = [[HGMapPath alloc] initWithCoordinate:coordinate];
HGMovingAnnotation *movingObject = [[HGMovingAnnotation alloc] initWithMapPath:self.assignedAmbPath];
self.movingAssignedAmbObject = movingObject;
// add the annotation to the map
[self.mapView addAnnotation:movingObject];
// zoom the map around the moving object
MKCoordinateSpan span = MKCoordinateSpanMake(0.01, 0.01);
MKCoordinateRegion region = MKCoordinateRegionMake(MKCoordinateForMapPoint(self.movingAssignedAmbObject.currentLocation), span);
[self.mapView setRegion:region animated:YES];
// start moving the object
[movingObject start];
}
CODE THAT WORKS
- (IBAction)testMoveBtnPressed:(id)sender {
//TODO: move x and y
DLog(#"============== Test move button was pressed ================ ");
NSLog(#"");
int randn = (random() % 15)+15;
float pscale = (float)randn / 10000;
double lat = 39.9813855 + pscale;
double lng = -75.1502155 + pscale;
for (id<MKAnnotation> annotation in self.mapView.annotations){
MKAnnotationView* anView = [self.mapView viewForAnnotation: annotation];
if (![annotation isKindOfClass:[PhoneAnnotation class]]){
// Process annotation view
[((HGMovingAnnotation *)annotation) trackToNewPosition:CLLocationCoordinate2DMake(lat, lng)];
}
}
}
CODE THAT DOESN'T WORK
{
//TODO: move thing to new location
double doubleLat = [lat doubleValue];
double doubleLng = [lng doubleValue];
// NSLog(#"--------------- Jason it is ------------- Latitude being passed in is %f", doubleLat);
// NSLog(#"--------------- Jason it is ------------- Longitude being passed in is %f", doubleLng);
//
// [self.movingAssignedAmbObject trackToNewPosition:CLLocationCoordinate2DMake(doubleLat, doubleLng)];
for (id<MKAnnotation> annotation in self.mapView.annotations){
MKAnnotationView* anView = [self.mapView viewForAnnotation: annotation];
if (![annotation isKindOfClass:[PhoneAnnotation class]]){
// Process annotation view
[((HGMovingAnnotation *)annotation) trackToNewPosition:CLLocationCoordinate2DMake(doubleLat, doubleLng)];
}
}
}
The issue is the HG library though works as described doesn't work proper, unless your using the path, if you don't create the annotation with a coordinate attached, which it doesn't.
I'm trying to add the distance from the user's position to a selected annotation's subtitle in a mapview. The mechanics of it are working, but the actual callout gets messed up the first time it's displayed. There appears to be a redraw problem.
Subsequent taps on the pin show the correct layout.
Here's the relevant code:
// called when selecting annotations
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view{
MKPointAnnotation *selectedAnnotation = view.annotation;
//attempt to add distance on annotation
CLLocation *pointALocation = [[CLLocation alloc]
initWithLatitude:selectedAnnotation.coordinate.latitude
longitude:selectedAnnotation.coordinate.longitude];
float distanceMeters = [pointALocation distanceFromLocation:locationManager.location];
//for sending info to detail
myPinTitle = selectedAnnotation.title;
[selectedAnnotation setSubtitle:[NSString stringWithFormat:#"%.2f miles away", (distanceMeters / 1609.344)]];
}
I've tried calling [view setNeedsDisplay], but to no avail.
Thanks in advance for your help.
The Solution that Worked
Here's the solution I finally came up with. It seems to work.
I edited out the duplicate code from the didSelectAnnotationView method, above, and came up with:
//called when user location changes
- (void)updatePinsDistance
{
for (int x=0; x< [[mapView annotations]count]; x++) {
MKPointAnnotation *thisPin =[[mapView annotations] objectAtIndex:x];
//attempt to add distance on annotation
CLLocation *pointALocation = [[CLLocation alloc]
initWithLatitude:thisPin.coordinate.latitude
longitude:thisPin.coordinate.longitude];
float distanceMeters = [pointALocation distanceFromLocation:locationManager.location];
NSString *distanceMiles = [NSString stringWithFormat:#"%.2f miles from you",
(distanceMeters / 1609.344)];
[thisPin setSubtitle:distanceMiles];
}
}
You should set your subtitle in another place than didSelectAnnotationView. Actually all annotationViews should have their title and subtitle set before they are returned by the mapView:viewForAnnotation: method.
The fact that you set a long subtitle certainly explains that the callout is not the right size. The size must be calculated before the didSelectAnnotationView is called.