i`m using MapKit framework and i want to ask you about something :
+ (NSUInteger)zoomLevelForMapRect:(MKMapRect)mRect withMapViewSizeInPixels:(CGSize)viewSizeInPixels
{
NSUInteger zoomLevel = MAXIMUM_ZOOM; // MAXIMUM_ZOOM is 20 with MapKit
MKZoomScale zoomScale = mRect.size.width / viewSizeInPixels.width; //MKZoomScale is just a CGFloat typedef
double zoomExponent = log2(zoomScale);
zoomLevel = (NSUInteger)(MAXIMUM_ZOOM - ceil(zoomExponent));
return zoomLevel;
}
this method..how can i know the value of mRect and viewSizeInPixels parameters to be able to call it?? thx in advance :)
The map view's current MKMapRect is the visibleMapRect property and the view size would be in frame.size (since MKMapView is a subclass of UIView) so the method would be called using something like:
NSUInteger zoomLevel = [UtilityClass
zoomLevelForMapRect:mapView.visibleMapRect
withMapViewSizeInPixels:mapView.frame.size];
UtilityClass is whatever class that method is in and replace mapView with whatever you map view is actually named.
By the way, the MapKit Framework Reference and the Location Awareness Programming Guide are worth looking at.
Related
I'm very new to iOS programming, and apologize fi this question sounds rather trivial. But after searching the web for hours, I've given up.
I need to get current zoom level of the map in my iOS app, so that when I change current view, I can retain zoom level. Using this code, I can get and set zoom levels. I implemented the mapView:regionDidChangeAnimated: method of MKMapViewDelegate protocol. But this method is called multiple times during initial "zoom in" animation of the map, and if during this period, I need to update the map, I might have wrong zoom level. I certainly don't want to turn off map animations. So, I was looking for a way to determine if map is currently being animated or stationary, before reading and storing zoom level.
I'm using MKMapView.
You can try by below code
#define MERCATOR_RADIUS 85445659.44705395
#define MAX_GOOGLE_LEVELS 20
#interface MKMapView (ZoomLevel)
- (double)getZoomLevel;
#end
#implementation MKMapView (ZoomLevel)
- (double)getZoomLevel
{
CLLocationDegrees longitudeDelta = self.region.span.longitudeDelta;
CGFloat mapWidthInPixels = self.bounds.size.width;
double zoomScale = longitudeDelta * MERCATOR_RADIUS * M_PI / (180.0 * mapWidthInPixels);
double zoomer = MAX_GOOGLE_LEVELS - log2( zoomScale );
if ( zoomer < 0 ) zoomer = 0;
// zoomer = round(zoomer);
return zoomer;
}
#end
I am trying to Geo-fence using Google Map for iPhone app.
A lot of tutorials can be found for MKMapView. But can't find for the GMSMapView.
The basic thing is how to convert the screen coordinate (x,y) to the MapCoordinate lat/lng.
Is there any API available for Google Map in iOS for that conversion?
Thanks
You can use something like this:
GMSMapView* mapView = ...;
CGPoint point = ...;
...
CLLocationCoordinate2D coordinate =
[mapView.projection coordinateForPoint: point];
UPDATE:
The comments on the projection property in GMSMapView.h are:
/**
* The GMSProjection currently used by this GMSMapView. This is a snapshot of
* the current projection, and will not automatically update when the camera
* moves. The projection may be nil while the render is not running (if the map
* is not yet part of your UI, or is part of a hidden UIViewController, or you
* have called stopRendering).
*/
#property (nonatomic, readonly) GMSProjection *projection;
Therefore you can only access the .projection property after the map has rendered. It will be nil if you try to access it during loadView or viewDidLoad.
I don't know if there is a better way to tell if the map has been rendered, but I noticed that the mapView:didChangeCameraPosition: method is called once after the map view is first displayed, and that the map's projection property is valid there.
So, in your view controller's header, add GMSMapViewDelegate:
#interface ViewController : UIViewController <GMSMapViewDelegate>
When you allocate the map view, assign the delegate:
_map = [GMSMapView mapWithFrame: CGRectMake(0, 0, width, height) camera: camera];
_map.delegate = self;
[self.view addSubview: _map];
Then add the delegate method:
- (void)mapView: (GMSMapView*)mapView
didChangeCameraPosition: (GMSCameraPosition*)position
{
CGPoint point = CGPointMake(x, y);
CLLocationCoordinate2D coordinate =
[_map.projection coordinateForPoint: point];
}
Note that mapView:didChangeCameraPosition: is called every time the user changes the camera, so you'd probably need to use a flag, so that you only do your calculations the first time mapView:didChangeCameraPosition: is called.
No need to convert x,y to lat,long.
GMSCircle *fence = [GMSCircle circleWithPosition:locationCord radius:fenceRadius];
[fence setFillColor:[UIColor colorWithRed:102.0/255 green:178.0/255 blue:255.0/255 alpha:0.3]];
[fence setZIndex:100];
[fence setMap: _map];
Add this code when you are making GMSMapView and geo fence will be shown with your location marker.
I'm trying to map a specific point in the mapview to my view. I am using the following code, but it returns some really high and wrong numbers.
CGPoint annPoint = [self.mapView convertCoordinate:coord toPointToView:self.view];
Does anyone can help me?
I found the error. The mapview should be loaded (completly?) before i can call the convertion method.
I use my custom subclass of MKAnnotationView. In mapView:didSelectAnnotationView: method of my Map's delegate I call the method of this class, which adds UIImageView with an image as a subview - it serves as my custom annotation callout.
When using default MKPinAnnotationView map does automatically adjust map region to display the annotation callout that have just appeared. How can I implement this behavior using custom MKAnnotationView subclass?
Current solution
I've crafted demo project having the stuff discussed below implemented: see there
AdjustRegionToFitAnnotationCallout project.
The latest iOS7 changes in how Map Kit's MKMapView renders map annotations made me to revisit this problem. I've made more accurate thinking about it and come up with much, very much better solution. I will leave the previous solution at the bottom of this answer, but remember - I was so wrong when I did it that way.
First of all we will need a helper CGRectTransformToContainRect() that expands a given CGRect to contain another CGRect.
Note: it's behavior is different from what CGRectUnion() does - CGRectUnion() returns just the smallest CGRect containing both CGRects, whereas the following helper allows parallel movement i.e. CGRectTransformToContainRect(CGRectMake(0, 0, 100, 100), CGRectMake(50, 50, 100, 100)) equals (CGRect){50, 50, 100, 100} and not (CGRect){0, 0, 150, 150} like CGRectUnion() does it. This behavior is exactly what we need when we want to have only adjusts using parallel movements and want to avoid map's zooming.
static inline CGRect CGRectTransformToContainRect(CGRect rectToTransform, CGRect rectToContain) {
CGFloat diff;
CGRect transformedRect = rectToTransform;
// Transformed rect dimensions should encompass the dimensions of both rects
transformedRect.size.width = MAX(CGRectGetWidth(rectToTransform), CGRectGetWidth(rectToContain));
transformedRect.size.height = MAX(CGRectGetHeight(rectToTransform), CGRectGetHeight(rectToContain));
// Comparing max X borders of both rects, adjust if
if ((diff = CGRectGetMaxX(rectToContain) - CGRectGetMaxX(transformedRect)) > 0) {
transformedRect.origin.x += diff;
}
// Comparing min X borders of both rects, adjust if
else if ((diff = CGRectGetMinX(transformedRect) - CGRectGetMinX(rectToContain)) > 0) {
transformedRect.origin.x -= diff;
}
// Comparing max Y borders of both rects, adjust if
if ((diff = CGRectGetMaxY(rectToContain) - CGRectGetMaxY(transformedRect)) > 0) {
transformedRect.origin.y += diff;
}
// Comparing min Y borders of both rects, adjust if
else if ((diff = CGRectGetMinY(transformedRect) - CGRectGetMinY(rectToContain)) > 0) {
transformedRect.origin.y -= diff;
}
return transformedRect;
}
Adjust method wrapped into an Objective-C category MKMapView(Extensions):
#implementation MKMapView (Extensions)
- (void)adjustToContainRect:(CGRect)rect usingReferenceView:(UIView *)referenceView animated:(BOOL)animated {
// I just like this assert here
NSParameterAssert(referenceView);
CGRect visibleRect = [self convertRegion:self.region toRectToView:self];
// We convert our annotation from its own coordinate system to a coodinate system of a map's top view, so we can compare it with the bounds of the map itself
CGRect annotationRect = [self convertRect:rect fromView:referenceView.superview];
// Fatten the area occupied by your annotation if you want to have a margin after adjustment
CGFloat additionalMargin = 2;
adjustedRect.origin.x -= additionalMargin;
adjustedRect.origin.y -= additionalMargin;
adjustedRect.size.width += additionalMargin * 2;
adjustedRect.size.height += additionalMargin * 2;
// This is the magic: if the map must expand its bounds to contain annotation, it will do this
CGRect adjustedRect = CGRectTransformToContainRect(visibleRect, annotationRect);
// Now we just convert adjusted rect to a coordinate region
MKCoordinateRegion adjustedRegion = [self convertRect:adjustedRect toRegionFromView:self];
// Trivial regionThatFits: sugar and final setRegion:animated: call
[self setRegion:[self regionThatFits:adjustedRegion] animated:animated];
}
#end
Now the controller and views:
#interface AnnotationView : MKAnnotationView
#property AnnotationCalloutView *calloutView;
#property (readonly) CGRect annotationViewWithCalloutViewFrame;
#end
#implementation AnnotationView
- (void)showCalloutBubble {
// This is a code where you create your custom annotation callout view
// add add it using -[self addSubview:]
// At the end of this method a callout view should be displayed.
}
- (CGRect)annotationViewWithCalloutViewFrame {
// Here you should adjust your annotation frame so it match itself in the moment when annotation callout is displayed and ...
return CGRectOfAdjustedAnnotation; // ...
}
#end
When AnnotationView-classed annotation is selected on map, it adds its calloutView as a subview, so custom annotation callout view is displayed. It is done using MKMapViewDelegate's method:
- (void)mapView:(MapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view {
// AnnotationPresenter is just a class that contains information to be displayed on callout annotation view
if ([view.annotation isKindOfClass:[AnnotationPresenter class]]) {
// Hide another annotation if it is shown
if (mapView.selectedAnnotationView != nil && [mapView.selectedAnnotationView isKindOfClass:[AnnotationView class]] && mapView.selectedAnnotationView != view) {
[mapView.selectedAnnotationView hideCalloutBubble];
}
mapView.selectedAnnotationView = view;
annotationView *annotationView = (annotationView *)view;
// This just adds *calloutView* as a subview
[annotationView showCalloutBubble];
[mapView adjustToContainRect:annotationView.annotationViewWithCalloutViewFrame usingReferenceView:annotationView animated:NO];
}
}
Of course your implementation may be different from what I've described here (mine is!). The most important part of above code is of course the [MKMapView adjustToContainRect:usingReferenceView:animated: method. Now I am really satisfied with the current solution and my understanding of this (and some related) problem. If you need any comments about the solution above, feel free to contact me (see profile).
The following Apple docs are very useful to understand what is going on in methods like -[MKMapView convertRect:fromView:]:
http://developer.apple.com/library/ios/#documentation/MapKit/Reference/MKMapView_Class/MKMapView/MKMapView.html
http://developer.apple.com/library/ios/#documentation/MapKit/Reference/MapKitDataTypesReference/Reference/reference.html
http://developer.apple.com/library/ios/#documentation/MapKit/Reference/MapKitFunctionsReference/Reference/reference.html
Also the first 10-15 minutes of WWDC 2013 session "What’s New in Map Kit" (#304) are very good to watch to have an excellent quick demo of the whole "Map with annotations" setup done by Apple engineer.
Initial solution (Does not work in iOS7, do not use it, use the solution above instead)
Somehow I forgot to answer my question at a time. Here is the complete solution I use nowadays (edited slightly for readability):
First of all a bit of map logic to be encapsulated somewhere in helpers file like MapKit+Helpers.h
typedef struct {
CLLocationDegrees top;
CLLocationDegrees bottom;
} MKLatitudeEdgedSpan;
typedef struct {
CLLocationDegrees left;
CLLocationDegrees right;
} MKLongitudeEdgedSpan;
typedef struct {
MKLatitudeEdgedSpan latitude;
MKLongitudeEdgedSpan longitude;
} MKEdgedRegion;
MKEdgedRegion MKEdgedRegionFromCoordinateRegion(MKCoordinateRegion region) {
MKEdgedRegion edgedRegion;
float latitude = region.center.latitude;
float longitude = region.center.longitude;
float latitudeDelta = region.span.latitudeDelta;
float longitudeDelta = region.span.longitudeDelta;
edgedRegion.longitude.left = longitude - longitudeDelta / 2;
edgedRegion.longitude.right = longitude + longitudeDelta / 2;
edgedRegion.latitude.top = latitude + latitudeDelta / 2;
edgedRegion.latitude.bottom = latitude - latitudeDelta / 2;
return edgedRegion;
}
Like MKCoordinateRegion (center coordinate + spans), MKEdgedRegion is just a way to define a region but using coordinates of its edges instead.
MKEdgedRegionFromCoordinateRegion() is a self-explanatory converter-method.
Suppose we have the following class for our annotations, containing its callout as a subview.
#interface AnnotationView : MKAnnotationView
#property AnnotationCalloutView *calloutView;
#end
When AnnotationView-classed annotation is selected on map, it adds its calloutView as a subview, so custom annotation callout view is displayed. It is done using MKMapViewDelegate's method:
- (void)mapView:(MapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view {
// AnnotationPresenter is just a class that contains information to be displayed on callout annotation view
if ([view.annotation isKindOfClass:[AnnotationPresenter class]]) {
// Hide another annotation if it is shown
if (mapView.selectedAnnotationView != nil && [mapView.selectedAnnotationView isKindOfClass:[AnnotationView class]] && mapView.selectedAnnotationView != view) {
[mapView.selectedAnnotationView hideCalloutBubble];
}
mapView.selectedAnnotationView = view;
annotationView *annotationView = (annotationView *)view;
// This just adds *calloutView* as a subview
[annotationView showCalloutBubble];
/* Here the trickiest piece of code goes */
/* 1. We capture _annotation's (not callout's)_ frame in its superview's (map's!) coordinate system resulting in something like (CGRect){4910547.000000, 2967852.000000, 23.000000, 28.000000} The .origin.x and .origin.y are especially important! */
CGRect annotationFrame = annotationView.frame;
/* 2. Now we need to perform an adjustment, so our frame would correspond to the annotation view's _callout view subview_ that it holds. */
annotationFrame.origin.x = annotationFrame.origin.x + ANNOTATION_CALLOUT_TRIANLE_HALF; // Mine callout view has small x offset - you should choose yours!
annotationFrame.origin.y = annotationFrame.origin.y - ANNOTATION_CALLOUT_HEIGHT / 2; // Again my custom offset.
annotationFrame.size = placeAnnotationView.calloutView.frame.size; // We can grab calloutView size directly because in its case we don't care about the coordinate system.
MKCoordinateRegion mapRegion = mapView.region;
/* 3. This was a long run before I did stop to try to pass mapView.view as an argument to _toRegionFromView_. */
/* annotationView.superView is very important - it gives us the same coordinate system that annotationFrame.origin is based. */
MKCoordinateRegion annotationRegion = [mapView convertRect:annotationFrame toRegionFromView:annotationView.superview];
/* I hope that the following MKEdgedRegion magic is self-explanatory */
MKEdgedRegion mapEdgedRegion = MKEdgedRegionFromCoordinateRegion(mapRegion);
MKEdgedRegion annotationEdgedRegion = MKEdgedRegionFromCoordinateRegion(annotationRegion);
float diff;
if ((diff = (annotationEdgedRegion.longitude.left - mapEdgedRegion.longitude.left)) < 0 ||
(diff = (annotationEdgedRegion.longitude.right - mapEdgedRegion.longitude.right)) > 0)
mapRegion.center.longitude += diff;
if ((diff = (annotationEdgedRegion.latitude.bottom - mapEdgedRegion.latitude.bottom)) < 0 ||
(diff = (annotationEdgedRegion.latitude.top - mapEdgedRegion.latitude.top)) > 0)
mapRegion.center.latitude += diff;
mapView.region = mapRegion;
}
}
I was looking for a similar solution, to fit a route and a callout in the visible rectangle of the screen. I tried some solutions but finally ended up just with setting enough padding on setVisibleMapRect:edgePadding:animated:. May not be as sophisticated, but basically does what I needed.
MKMapRect routeMapRect = myRoute.polyline.boundingMapRect;
CGFloat padding = myCallout.bounds.width / 2.0;
[myMapView setVisibleMapRect: routeMapRect edgePadding:UIEdgeInsetsMake(padding, padding, padding, padding) animated:YES];
Of course this can be way more optimized, e.g. with detecting on which side you actually need the padding and setting a smaller one on the other sides. But you get the idea.
I am fairly new to programming in general, and have been following the CS193p videos on iTunesU. I am currently doing assignment 3, and am having trouble getting a bit of information from the View sent to the View Controller.
I believe I have set up the whole delegation thing correctly, so the question really is to how to get my View Controller to see a bit of information (such as self.bounds.size.width), which is a property that only the View has. Would this involve using self.dataSource? And if so, through what means could I pass this bit of information? My end goal is to have the View Controller perform some transformation to the View's properties, and send it back to the View's drawRect so that it could be drawn.
Thanks!!
** Edit, as requested, I have posted parts of my drawRect code below
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGFloat scale = 32;
CGPoint midPoint;
midPoint.x = self.bounds.origin.x + self.bounds.size.width/2;
midPoint.y = self.bounds.origin.y + self.bounds.size.height/2;
// ---- finding the y starting point
float totalXsInWidth;
totalXsInWidth = self.bounds.size.width / scale;
float leftMostX = totalXsInWidth / -2;
float graphResultY = sin(leftMostX); // ** in theory, I want "leftMostX" to be modifed by the equation entered (in the CONTROLLER)
NSLog(#"The leftMostX is %f", leftMostX);
[self.dataSource passingVariable:leftMostX]; //** Here I pass the variable from drawRect to get modifyed in the CONTROLLER
float graphResultY1;
graphResultY1 = 5; //this is a test, I want to see if the controller actually effect a change
graphResultY1 = [self.dataSource calcResult:self]; //this should now be a different number than 5 or 0 (the init value)
NSLog(#"From graphingview, the result is %f", graphResultY1); //** unfortunately = 0... :(
Having had a read of CS193p Assignment 3 (for anyone interested, it is available here in PDF format), it looks like you are being asked to create a protocol for your UIView subclass, and have the delegate of that protocol (the view's managing view controller) provide the data used for drawing.
If you have set up your protocol correctly, the view's drawRect method should be asking the view controller for data through the protocol's method, something like:
DataObject *data = [self.delegate getData];
// It might be [self.dataSource getData]; in your case
That should call the getData delegate method that you should have written into the view controller (I have made up a method signature for this, adapt to the one used by your protocol. Also, this code doesn't consider relevant memory management, if required):
- (DataObject *)getData
{
// Get data from the model, and return
DataObject *dataObjectToReturn = [Model getRelevantData];
return dataObjectToReturn;
}
The view's drawRect should now have the relevant instance of DataObject and can go about using that data to draw what it needs to draw.
Below is my original answer, which is not relevant to the specific problem above, but does show another method of view/view controller interaction. (This method isn't applicable to the above problem because the data needed for drawing shouldn't be owned by the view itself.)
To access the view's properties from that view's controller you would use self.view. Thus, to get the view's width, like in your example, you would use:
CGFloat viewWidth = self.view.bounds.size.width;
You can also set the controller's view, though note that for your example you have to supply an entire CGRect for the frame, as manipulating the width directly is not allowed:
// This gets a reference to the view's frame, then uses values from it to create a new
// CGRect that is 10.0 points wider, and sets the frame of the view to the new frame
CGRect viewFrame = self.view.frame;
self.view.frame = CGRectMake(viewFrame.origin.x, viewFrame.origin.y, viewFrame.size.width + 10.0f, viewFrame.size.height);
Im not famaliar with the assignment but if you are using a view within a view controller you can access that views property like so:
SomeView *someView = [SomeView new];
CGFloat width = someView.bounds.size.width;