I'm trying to render MKPolygon using the following code:
NSMutableArray *overlays = [NSMutableArray array];
for (NSDictionary *state in states) {
NSArray *points = [state valueForKeyPath:#"point"];
NSInteger numberOfCoordinates = [points count];
CLLocationCoordinate2D *polygonPoints = malloc(numberOfCoordinates * sizeof(CLLocationCoordinate2D));
NSInteger index = 0;
for (NSDictionary *pointDict in points) {
polygonPoints[index] = CLLocationCoordinate2DMake([[pointDict valueForKeyPath:#"latitude"] floatValue], [[pointDict valueForKeyPath:#"longitude"] floatValue]);
index++;
}
MKPolygon *overlayPolygon = [MKPolygon polygonWithCoordinates:polygonPoints count:numberOfCoordinates];
overlayPolygon.title = [state valueForKey:#"name"];
[overlays addObject:overlayPolygon];
free(polygonPoints);
}
[self.stateMapView addOverlays:overlays];
I used the following code to provide stroke and fill colors:
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id <MKOverlay>)overlay NS_AVAILABLE(10_9, 7_0);
{
if ([overlay isKindOfClass:[MKPolygon class]])
{
MKPolygonRenderer *pv = [[MKPolygonRenderer alloc] initWithPolygon:overlay];
pv.fillColor = [UIColor redColor];
pv.strokeColor = [UIColor blackColor];
return pv;
}
return nil;
}
Do I need to do something to render the Title? I think I should enable a configuration or something but I'm new to MapView. Or I need to create a UILabel?
Overlays don't automatically show their titles like annotations can (in their callout actually) so there's nothing you "need to do" or any configuration that you can enable.
A simple workaround to show titles on overlays is, as you suggest, to create a UILabel.
However, this UILabel should be added to an annotation view that is positioned at each overlay's center.
A minor drawback (or maybe not) to this method is that the titles will not scale with the zoom of the map -- they'll stay the same size and can eventually collide and overlay with other titles (but you may be ok with this).
To implement this approach:
For each overlay, add an annotation (using addAnnotation: or addAnnotations:) and set the coordinate to the approximate center of the overlay and the title to the overlay's title.
Note that since MKPolygon implements both the MKOverlay and the MKAnnotation protocols, you don't necessarily need to create a separate annotation class or separate objects for each overlay. MKPolygon automatically sets its coordinate property to the approximate center of the polygon so you don't need to calculate anything. You can just add the overlay objects themselves as the annotations. That's how the example below does it.
Implement the mapView:viewForAnnotation: delegate method and create an MKAnnotationView with a UILabel in it that displays the title.
Example:
[self.stateMapView addOverlays:overlays];
//After adding the overlays as "overlays",
//also add them as "annotations"...
[self.stateMapView addAnnotations:overlays];
//Implement the viewForAnnotation delegate method...
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
if ([annotation isKindOfClass:[MKUserLocation class]])
{
//show default blue dot for user location...
return nil;
}
static NSString *reuseId = #"ann";
MKAnnotationView *av = [mapView dequeueReusableAnnotationViewWithIdentifier:reuseId];
if (av == nil)
{
av = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:reuseId];
av.canShowCallout = NO;
//add a UILabel in the view itself to show the title...
UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 50, 30)];
titleLabel.tag = 42;
titleLabel.backgroundColor = [UIColor clearColor];
titleLabel.textColor = [UIColor blackColor];
titleLabel.font = [UIFont boldSystemFontOfSize:16];
titleLabel.textAlignment = NSTextAlignmentCenter;
titleLabel.minimumScaleFactor = 0.5;
[av addSubview:titleLabel];
av.frame = titleLabel.frame;
}
else
{
av.annotation = annotation;
}
//find the UILabel and set the title HERE
//so that it gets set whether we're re-using a view or not...
UILabel *titleLabel = (UILabel *)[av viewWithTag:42];
titleLabel.text = annotation.title;
return av;
}
The alternative approach is to create a custom overlay renderer and do all the drawing yourself (the polygon line, the stroke color, the fill color, and the text). See Draw text in circle overlay and Is there a way to add text using Paths Drawing for some ideas on how to implement that.
Related
The purpose of the following code is to achieve a gradient background color
In newer versions, it looks fine, but in macOS 10.12.4, the title of the button doesn't appear
- (void)setGradient:(NSArray<__kindof NSColor *> *)colorArr StartPoint:(CGPoint)startPoint EndPoint:(CGPoint)endPoint {
self.wantsLayer = YES;
CAGradientLayer *gradinentlayer = [CAGradientLayer layer];
NSMutableArray *array = [NSMutableArray array];
for (NSColor *color in colorArr) {
[array addObject:(id)color.CGColor];
}
gradinentlayer.colors = array;
gradinentlayer.startPoint = startPoint;
gradinentlayer.endPoint = endPoint;
gradinentlayer.frame = self.bounds;
[self.layer insertSublayer:gradinentlayer atIndex:0];
}
How do I make the title text appear, other than adding a subview?
Any help would be appreciated!
I've tried :
self.layer = gradinentlayer;
But this invalidates the layer's other Settings,This is not in line with expectations
I am currently struggling to use a custom map pin image instead of the default coloured pins. I have tried some guides online but can’t seem to integrate it into my current set up.
Currently I have a certain coloured (tinted) pin set during certain times (online) and when the time is outside these parameters the pin turns a different colour.
I want to change these pins to two different custom pin images that I have created; one for online and one for offline.
I have read something about the MKPinAnnotationView not liking the pinView.image property (I have tried this and can confirm it doesn’t work).
If someone could edit this code so I can have a “online.png“ image for the annotation tag 0 and an “offline.png” image for the annotation tag 1, that would be perfect.
MapViewController.m
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
MKPinAnnotationView *pinView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"CustomPinAnnotationView"];
UIButton *advertButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
pinView.rightCalloutAccessoryView = advertButton;
pinView.canShowCallout = YES;
pinView.draggable = NO;
pinView.highlighted = YES;
if (annotation == mapView.userLocation)
return nil;
if (([(Annotation*)annotation tag] == 0) && [[(Annotation*)annotation area] isEqualToString:#"Online"])
{
pinView.pinTintColor = [UIColor colorWithRed:0.92 green:0.21 blue:0.21 alpha:1];
pinView.alpha = 1;
[advertButton addTarget:self action:#selector(Online:) forControlEvents:UIControlEventTouchUpInside];
}
else if (([(Annotation*)annotation tag] == 1) && [[(Annotation*)annotation area] isEqualToString:#"Offline"])
{
pinView.pinTintColor = [UIColor colorWithRed:0.8 green:0.8 blue:0.8 alpha:0.5];
pinView.alpha = 1;
[advertButton addTarget:self action:#selector(Offline:) forControlEvents:UIControlEventTouchUpInside];
}
return pinView;
}
Any help would be greatly appreciated; I am not a coder by nature so go easy.
Cheers
Lewis
If you want to display a pin as annotation, you use MKPinAnnotationView. If you want to use a custom annotation image, you had to use MKAnnotationView, and set the image property.
So I suggest to replace
MKPinAnnotationView *pinView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"CustomPinAnnotationView"];
by
MKAnnotationView *pinView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"CustomPinAnnotationView"];
and set the image property, e.g. using:
pinView.image = [UIImage imageNamed:#„xxx“];
Of course, you should rename pinView, CustomPinAnnotationView and xxx appropriately.
I have an MKMapview with a custom annotation image, when i click on the annotation i change the image for a bigger sized one and this causes the point to shift.
Is there a way to make sure the image gets aligned on the latitude and longitude again ?
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
{
MKPointAnnotation * pin = view.annotation;
if ([pin isKindOfClass:[MKUserLocation class]])
{
return;
}
NSMutableString * imageName = [[NSMutableString alloc] initWithString:[((SpotView *)self.baseView) generatePin:self.category]];
[imageName appendString:#"-selected"];
view.image = [UIImage imageNamed:imageName];
}
The results :
I have fixed my issue using the following property in the didSelectAnnotationView method
customPinView.centerOffset = CGPointMake(xOffset,yOffset);
and setting it back to zero in the didDeselectAnnotationView method
customPinView.centerOffset = CGPointMake(0, 0);
i am developing a ER diagram editor, i have a bunch of draggable UILabels but all of them have the same name. i want to be able to create a line between two UIlabels when both are pressed together using the long press gesture recognizer. any help will be most appreciated
You can create your long press gesture on the superview shared by these two labels, e.g.:
UILongPressGestureRecognizer *twoTouchLongPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self
action:#selector(handleLongPress:)];
twoTouchLongPress.numberOfTouchesRequired = 2;
[self.view addGestureRecognizer:twoTouchLongPress];
You can then write a gesture handler:
- (void)handleLongPress:(UILongPressGestureRecognizer *)gesture
{
if (gesture.state == UIGestureRecognizerStateBegan)
{
CGPoint location0 = [gesture locationOfTouch:0 inView:gesture.view];
CGPoint location1 = [gesture locationOfTouch:1 inView:gesture.view];
if ((CGRectContainsPoint(self.label0.frame, location0) && CGRectContainsPoint(self.label1.frame, location1)) ||
(CGRectContainsPoint(self.label1.frame, location0) && CGRectContainsPoint(self.label0.frame, location1)))
{
NSLog(#"success; draw your line");
}
else
{
NSLog(#"failure; don't draw your line");
}
}
}
In the updated comments, you suggest that you're creating a local UILabel variable, and then adding the resulting label to the view. That's fine, but you really want to maintain a backing model, that captures what you're doing in the view. For simplicity's sake, let me assume that you'll have array of these labels, e.g.:
#property (nonatomic, strong) NSMutableArray *labels;
Which you then initialize at some point (e.g. viewDidLoad):
self.labels = [[NSMutableArray alloc] init];
Then as you add labels to your view, add a reference to them in your array:
UILabel *label = [[UILabel alloc]initWithFrame:CGRectMake(xVal, yVal, 200.0f, 60.0f)];
label.text = sentence;
label.layer.borderColor = [UIColor blueColor].CGColor;
label.layer.borderWidth = 0.0;
label.backgroundColor = [UIColor clearColor];
label.font = [UIFont systemFontOfSize:19.0f];
[self.view addSubview:label];
[self.labels addObject:label];
Then, your gesture can do something like:
- (UILabel *)labelForLocation:(CGPoint)location
{
for (UILabel *label in self.labels)
{
if (CGRectContainsPoint(label.frame, location))
return label; // if found one, return that `UILabel`
}
return nil; // if not, return nil
}
- (void)handleLongPress:(UILongPressGestureRecognizer *)gesture
{
if (gesture.state == UIGestureRecognizerStateBegan)
{
CGPoint location0 = [gesture locationOfTouch:0 inView:gesture.view];
CGPoint location1 = [gesture locationOfTouch:1 inView:gesture.view];
UILabel *label0 = [self labelForLocation:location0];
UILabel *label1 = [self labelForLocation:location1];
if (label0 != nil && label1 != nil && label0 != label1)
{
NSLog(#"success; draw your line");
}
else
{
NSLog(#"failure; don't draw your line");
}
}
}
Frankly, I'd rather see this backed by a proper model, but that's a more complicated conversation beyond the scope of a simple Stack Overflow answer. But hopefully the above gives you an idea of what it might look like. (BTW, I just typed in the above without assistance of Xcode, so I'll apologize in advance for typos.)
My app gets the points for a route from google. The response also contains the bounding rect. My app creates the rect and shows the map correctly and I add the overlay. The
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)
overlay is called and I construct a MKOverlayPathView from the points. I have verified that the points are within the bounding rect. However, the route overlay does not draw on the displayed map.
I have checked everything I can think of with no joy, any suggestions would be greatly appreciated.
I don't know yet why the overlay isn't showing but try the following code in a fresh, new project.
In my test, I added the map view control to the xib and then connected the IBOutlet and the map view's delegate outlet to File's Owner. Creating the map view in code will also work (just don't also add it to the xib and be sure to set the delegate property).
This is my test TRTrip class:
//TRTrip.h...
#interface TRTrip : NSObject<MKOverlay>
#property (nonatomic, readonly) MKMapRect boundingMapRect;
#property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
#end
//TRTrip.m...
#implementation TRTrip
#synthesize boundingMapRect;
#synthesize coordinate;
-(MKMapRect)boundingMapRect {
return MKMapRectWorld;
}
-(CLLocationCoordinate2D)coordinate {
return CLLocationCoordinate2DMake(42.3507,-71.0608);
}
#end
In the view controller's viewDidLoad, I add an MKPointAnnotation, MKCircle, TRTrip, and an MKPolyline to the map:
- (void)viewDidLoad
{
[super viewDidLoad];
CLLocationCoordinate2D bostonCoord = CLLocationCoordinate2DMake(42.3507,-71.0608);
//center map on Boston...
mMapView.region = MKCoordinateRegionMakeWithDistance(bostonCoord, 30000, 30000);
//add boston annotation...
MKPointAnnotation *pa = [[MKPointAnnotation alloc] init];
pa.coordinate = bostonCoord;
pa.title = #"Boston";
[mMapView addAnnotation:pa];
[pa release];
//add MKCircle overlay...
MKCircle *circle = [MKCircle circleWithCenterCoordinate:bostonCoord radius:10000];
[mMapView addOverlay:circle];
//add TRTrip overlay...
TRTrip *trt = [[TRTrip alloc] init];
[mMapView addOverlay:trt];
[trt release];
//NOTE:
//Using an MKPolyline and MKPolylineView is probably easier than
//manually drawing lines using MKOverlayPathView.
//add MKPolyline overlay...
int numberOfRouteCoords = 3;
CLLocationCoordinate2D *routeCoords = malloc(numberOfRouteCoords * sizeof(CLLocationCoordinate2D));
routeCoords[0] = CLLocationCoordinate2DMake(42.34, -71.1);
routeCoords[1] = CLLocationCoordinate2DMake(42.25, -71.05);
routeCoords[2] = CLLocationCoordinate2DMake(42.3, -71.02);
MKPolyline *pl = [MKPolyline polylineWithCoordinates:routeCoords count:numberOfRouteCoords];
[mMapView addOverlay:pl];
free(routeCoords);
}
and this is the viewForOverlay method:
-(MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id<MKOverlay>)overlay
{
if ([overlay isKindOfClass:[MKCircle class]])
{
MKCircleView *cv = [[[MKCircleView alloc] initWithCircle:overlay] autorelease];
cv.fillColor = [UIColor greenColor];
cv.strokeColor = [UIColor blueColor];
cv.alpha = 0.5;
return cv;
}
if ([overlay isKindOfClass:[TRTrip class]])
{
MKOverlayPathView *opv = [[[MKOverlayPathView alloc] initWithOverlay:overlay] autorelease];
opv.strokeColor = [UIColor redColor];
opv.lineWidth = 3;
CGMutablePathRef myPath = CGPathCreateMutable();
MKMapPoint mp1 = MKMapPointForCoordinate(CLLocationCoordinate2DMake(42.3507,-71.1));
CGPoint cgp1 = [opv pointForMapPoint:mp1];
CGPathMoveToPoint(myPath, nil, cgp1.x, cgp1.y);
MKMapPoint mp2 = MKMapPointForCoordinate(CLLocationCoordinate2DMake(42.45,-71.05));
CGPoint cgp2 = [opv pointForMapPoint:mp2];
CGPathAddLineToPoint(myPath, nil, cgp2.x, cgp2.y);
MKMapPoint mp3 = MKMapPointForCoordinate(CLLocationCoordinate2DMake(42.3,-71.0));
CGPoint cgp3 = [opv pointForMapPoint:mp3];
CGPathAddLineToPoint(myPath, nil, cgp3.x, cgp3.y);
opv.path = myPath;
CGPathRelease(myPath);
return opv;
}
if ([overlay isKindOfClass:[MKPolyline class]])
{
MKPolylineView *plv = [[[MKPolylineView alloc] initWithPolyline:overlay] autorelease];
plv.strokeColor = [UIColor purpleColor];
plv.lineWidth = 5;
return plv;
}
return nil;
}
Here is the result:
The red line on top is the TRTrip and the purple one on the bottom is the MKPolyline.
If your example is a bit more complex then this, check if you have any calls like this:
[mMapView removeOverlay:overlay]
in the code.