5

In my app, user draws a shape on map and using UIBeizerPath i am drawing that path. Then based on the coordinates of the path i am displaying the results which are only in that area. Everything works great except that now when Annotations drops on the Map view the pins looks like they are behind the path which means path looks in the front.

I am using this code to display the Annotation and path :

 -(void)clearAnnotationAndPath:(id)sender {
    [_mapView removeAnnotations:_mapView.annotations];
    path = [UIBezierPath bezierPath];
    [shapeLayer removeFromSuperlayer];
}

- (void)handleGesture:(UIPanGestureRecognizer *)gesture
{

    CGPoint location = [gesture locationInView:_pathOverlay];

    if (gesture.state == UIGestureRecognizerStateBegan)
    {
        shapeLayer = [[CAShapeLayer alloc] init];
        shapeLayer.fillColor = [[UIColor clearColor] CGColor];
        shapeLayer.strokeColor = [[UIColor greenColor] CGColor];
        shapeLayer.lineWidth = 5.0;
        //[_mapView.layer addSublayer:shapeLayer];
        [pathOverlay.layer addSublayer:shapeLayer];
        path = [UIBezierPath bezierPath];
        [path moveToPoint:location];
    }

    else if (gesture.state == UIGestureRecognizerStateChanged)
    {
        [path addLineToPoint:location];
        shapeLayer.path = [path CGPath];
    }

    else if (gesture.state == UIGestureRecognizerStateEnded)
    {
        // MKMapView *mapView = (MKMapView *)gesture.view;

        [path addLineToPoint:location];
        [path closePath];
        allStations = [RoadmapData sharedInstance].data;
        for (int i=0; i<[allStations count]; i++) {
            NSDictionary * itemNo = [allStations objectAtIndex:i];

            NSString * fullAddress = [NSString stringWithFormat:@"%@,%@,%@,%@",[itemNo objectForKey:@"address"],[itemNo objectForKey:@"city"],[itemNo objectForKey:@"state"],[itemNo objectForKey:@"zip"]];
            CLGeocoder * geoCoder = [[CLGeocoder alloc]init];
            [geoCoder geocodeAddressString:fullAddress completionHandler:^(NSArray *placemarks, NSError *error) {

                if (error) {
                    NSLog(@"Geocode failed with error: %@", error);
                    return;
                }

                if(placemarks && placemarks.count > 0)
                {
                    CLPlacemark *placemark = placemarks[0];
                    CLLocation *location = placemark.location;
                    CLLocationCoordinate2D coords = location.coordinate;
                    CGPoint loc = [_mapView convertCoordinate:coords toPointToView:_pathOverlay];
                    if ([path containsPoint:loc])
                    {
                        NSString * name = [itemNo objectForKey:@"name"];
                        stationAnn = [[LocationAnnotation alloc]initWithCoordinate:coords Title:name subTitle:@"Wells Fargo Offer" annIndex:i];
                        stationAnn.tag = i;
                        [_mapView addAnnotation:stationAnn];
                    }
                    else{
                        NSLog(@"Out of boundary");
                    }
                }
            }];
            [self turnOffGesture:gesture];
        }
    }
}

- (void)mapView:(MKMapView *)aMapView didAddAnnotationViews:(NSArray *)views{
    if (views.count > 0) {
        UIView* firstAnnotation = [views objectAtIndex:0];
        UIView* parentView = [firstAnnotation superview];
        if (_pathOverlay == nil){
            // create a transparent view to add bezier paths to
            pathOverlay = [[UIView alloc] initWithFrame: parentView.frame];
            pathOverlay.opaque = NO;
            pathOverlay.backgroundColor = [UIColor clearColor];
            [parentView addSubview:pathOverlay];
        }

        // make sure annotations stay above pathOverlay
        for (UIView* view in views) {
            [parentView bringSubviewToFront:view];
        }
    }
}

Also once i go back from this and view and come again its not even drawing the Path.

Please help.

Thanks,

Ashutosh
  • 5,286
  • 12
  • 46
  • 82

1 Answers1

3

Apparently, when you add your bezier path to the map via:

        [_mapView.layer addSublayer:shapeLayer];

it is getting added above some internal layer that MKMapView uses to draw the annotations. If you take a look at this somewhat related question, you'll see that you can implement the MKMapViewDelegate protocol, and get callbacks when new station annotations are added. When this happens, you basically inspect the view heirarchy of the newly added annotations, and insert a new, transparent UIView layer underneath them. You take care to bring all the annotations in front of this transparent UIView.

  // always remember to assign the delegate to get callbacks!
  _mapView.delegate = self;

...

#pragma mark - MKMapViewDelegate

- (void)mapView:(MKMapView *)aMapView didAddAnnotationViews:(NSArray *)views{
    if (views.count > 0) {
        UIView* firstAnnotation = [views objectAtIndex:0];
        UIView* parentView = [firstAnnotation superview];
        // NOTE: could perform this initialization in viewDidLoad, too
        if (self.pathOverlay == nil){
            // create a transparent view to add bezier paths to
            pathOverlay = [[UIView alloc] initWithFrame: parentView.frame];
            pathOverlay.opaque = NO;
            pathOverlay.backgroundColor = [UIColor clearColor];
            [parentView addSubview:pathOverlay]; 
        }

        // make sure annotations stay above pathOverlay
        for (UIView* view in views) {
            [parentView bringSubviewToFront:view];
        }
    }
}

Then, instead of adding your shape layer to _mapView.layer, you add it to your transparent view layer, also using this new layer in the coordinate conversion:

- (void)handleGesture:(UIPanGestureRecognizer*)gesture
{
    CGPoint location = [gesture locationInView: self.pathOverlay];

    if (gesture.state == UIGestureRecognizerStateBegan)
    {
        if (!shapeLayer)
        {
            shapeLayer = [[CAShapeLayer alloc] init];
            shapeLayer.fillColor = [[UIColor clearColor] CGColor];
            shapeLayer.strokeColor = [[UIColor greenColor] CGColor];
            shapeLayer.lineWidth = 5.0;
            [pathOverlay.layer addSublayer:shapeLayer];   // <- change here !!!
        }
        self.path = [[UIBezierPath alloc] init];
        [path moveToPoint:location];
    }
    else if (gesture.state == UIGestureRecognizerStateChanged)
    {
        [path addLineToPoint:location];
        shapeLayer.path = [path CGPath];
    }
    else if (gesture.state == UIGestureRecognizerStateEnded)
    {
        /*
         * This code is the same as what you already have ...
         */

             // But replace this next line with the following line ...
             //CGPoint loc = [_mapView convertCoordinate:coords toPointToView:self];
             CGPoint loc = [_mapView convertCoordinate:coords toPointToView: self.pathOverlay];

        /*
         * And again use the rest of your original code
         */            
    }
}

where I also added an ivar (and property) for the new transparent layer:

UIView* pathOverlay;

I tested this with a bogus grid of stations and got the following results:

enter image description here

P.S. I'd also recommend getting rid of your static variables. Just make them ivars/properties of your class.

Community
  • 1
  • 1
Nate
  • 30,589
  • 12
  • 76
  • 201
  • I did this and now i cannot even see the layer drawn on the Map. Although it does check for the correct annotation and displays them. – Ashutosh Feb 06 '13 at 21:58
  • @Ashutosh, That's really not enough information to help you. There's a lot of code above, and there's a few things I can imagine you changing. Did you remember to make `pathOverlay` a property? In `handleGesture:`, did you read the code, taking note that I cut out portions of your original code, for clarity? Or did you just paste in the whole method? Did you change those statics as I suggested? At this line: `[parentView addSubview:pathOverlay];`, can you stop in the debugger and make sure `parentView` is not nil? – Nate Feb 06 '13 at 22:08
  • @Ashutosh, also, it may just be necessary for you to update the question above, pasting in the code you're using **now**, so I can help debug. Or start a new question if you think it's getting too cluttered. Your call. – Nate Feb 06 '13 at 22:09
  • One more thing i noticed is it works when i clear and start again and the next time it works totally fine. – Ashutosh Feb 07 '13 at 00:36
  • @Ashutosh, I don't have time to look at your new code in detail right now, but I can tell you that in my sample app, every time the swipe gesture ended, to create a new bezier path to search inside, I would remove all the previous pins with `[_mapView removeAnnotations: _mapView.annotations];` – Nate Feb 07 '13 at 00:49
  • I am noticing the pathOverlay is coming as nil in handleGesture. It gets allocated when we get a match between the set of Coordinates and drawn area. – Ashutosh Feb 07 '13 at 20:25
  • If you want to be safe, just add the 4-line initialization of `pathOverlay` into your view controller's `viewDidLoad:` method. I did it the way I did above to implement *lazy loading*, and to reduce the amount of code I had to show. But, you can certainly just initialize `pathOverlay` when the whole view heirarchy loads. Also, as I said before, make sure `pathOverlay` is a *property*, not just an *ivar*. If you want to be doubly safe, make sure it's retained (`@property (nonatomic, strong) UIView* pathOverlay;`, or `@property (nonatomic, retain) UIView* pathOverlay;` for non-ARC code). – Nate Feb 07 '13 at 21:19
  • And in that case just add the pathOverlay to self or something else ? – Ashutosh Feb 07 '13 at 22:02
  • Sorry man still no luck. Its something else happening. I have configured my code in a way that pathOverlay will be allocated for sure. But still i think there is some other problem with property and iVar. I am using both as you showed in the code. – Ashutosh Feb 07 '13 at 22:11
  • @Ashutosh, it sounds like you're not completely clear on the concept of *properties*. A property is associated with a class. A read/write property is basically a *set* and a *get* method that wraps an instance variable (*ivar*). I did not include all the code required to setup the property, because it's assumed that you would know how to do that. You have to declare it in the header, usually as an ivar, the `@property` declaration, and then synthesize it in the .m file with `@synthesize`. As I said, it should probably be *retained* or `strong`, too. – Nate Feb 07 '13 at 22:13
  • This isn't really a good forum to learn about Objective-C properties. I would recommend just googling for an Objective-C primer. There's tons online. – Nate Feb 07 '13 at 22:14
  • @Ashutosh, if you're still having trouble with this, I would recommend posting a new question. I don't think the problem now has anything to do with maps or bezier paths. I think it's a simple issue of implementing/assigning/retaining your property. Just add a new question showing how you declare your ivar and property, how you assign it, and then say that it's still `nil` at some later point. Any number of iOS developers should answer it quickly. Good luck. – Nate Feb 08 '13 at 09:52