How to enable touchEvents (scroll and pan) in MKMa

2019-04-02 23:34发布

alt text http://www.gisnotes.com/wordpress/wp-content/uploads/2009/09/poly.png

In a nutshell, I am trying to figure out how to scale the geometry (point, line, and polygon) implemented in a custom view (geometryView) on top of MKMapView (mapView).

What I did was..

  1. Create DrawMapViewController. Add the UIBarButtonItems (MAP, PT, LN, PG) on the bottom toolbar.

  2. When you click on map button, you are able to pan/zoom on the Map. This enables the mapView by setting the geometryView.hidden = YES;

  3. When any of the three geometry buttons is clicked, the geometryView is displayed by geometryView.hidden = NO, thus, enabling touchEvents and drawing the geometry from GeometryView.drawRect's method.

Layer ordering is as follows: mapView is at the bottom of geometryView.

-geometryView

-mapView

What is my problem? Ideally, during "map" mode and when the user is panning and zooming, I am hoping if its possible to display the drawing from geometryView. But when the user hits "map", then geometryView.hidden = YES, thus the drawing disappears. If I make geometryView visible, then the user interacts with geometryView not mapView, so there's no zooming and panning.

Is it possible to handle touchEvents (pan/zoom) of MKMapView below a custom view while the custom View is displayed? Any other ideas/approaches is very much appreciated.

Thanks,

Rupert

GeometryView Listing:

@synthesize mapview, pinFactory;

- (id)initWithFrame:(CGRect)frame{
    if (self = [super initWithFrame:frame]) {
    }
    return self;
}

- (void)drawRect:(CGRect)rect {
    // Drawing code
    NSLog(@"DrawRect called");

    CGContextRef context = UIGraphicsGetCurrentContext();

    // Drawing lines with a white stroke color
    CGContextSetRGBStrokeColor(context, 1.0, 1.0, 1.0, 1.0);
    // Draw them with a 2.0 stroke width so they are a bit more visible.
    CGContextSetLineWidth(context, 2.0);

    if(pinFactory.geometryState == 2){  //Draw Line

        if( [pinFactory actualPinCount] > 1){

            Pin *pin1 = (Pin *)[[pinFactory pinArray] objectAtIndex:0];
            CGPoint pt1 = pin1.touchLocation;
            CGContextMoveToPoint(context, pt1.x, pt1.y);

            for (int i = 1; i < ([pinFactory actualPinCount]); i++)
            {
                Pin *pin2 = (Pin *)[[pinFactory pinArray] objectAtIndex:i];
                CGPoint pt2 = pin2.touchLocation;
                CGContextAddLineToPoint(context, pt2.x, pt2.y);
            }

            CGContextStrokePath(context);
        }
    }
    else if(pinFactory.geometryState == 3){ //Draw Polygon
        //if there are two points, draw a line first.
        //if there are three points, fill the polygon
        if( [pinFactory actualPinCount] == 2){

            Pin *pin1 = (Pin *)[[pinFactory pinArray] objectAtIndex:0];
            CGPoint pt1 = pin1.touchLocation;
            CGContextMoveToPoint(context, pt1.x, pt1.y);

            Pin *pin2 = (Pin *)[[pinFactory pinArray] objectAtIndex:1];
            CGPoint pt2 = pin2.touchLocation;
            CGContextAddLineToPoint(context, pt2.x, pt2.y);

            CGContextStrokePath(context);
        }
        else if([pinFactory actualPinCount] > 2){

            //fill with a blue color
            CGContextSetRGBFillColor(context, 0.0, 0.0, 1.0, 1.0);

            Pin *pin1 = (Pin *)[[pinFactory pinArray] objectAtIndex:0];
            CGPoint pt1 = pin1.touchLocation;
            CGContextMoveToPoint(context, pt1.x, pt1.y);

            for (int i = 1; i < ([pinFactory actualPinCount]); i++)
            {

                Pin *pin2 = (Pin *)[[pinFactory pinArray] objectAtIndex:i];
                CGPoint pt2 = pin2.touchLocation;
                CGContextAddLineToPoint(context, pt2.x, pt2.y);
            }

            CGContextClosePath(context);

            CGContextDrawPath(context, kCGPathFillStroke);
        }
    }
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    [self setNeedsDisplay];

    UITouch* aTouch = [touches anyObject];
    location = [aTouch locationInView:self];
    NSLog(@"touchesBegan: x:%f, y:%f", location.x, location.y );

    CLLocationCoordinate2D coordinate = [mapview convertPoint:location toCoordinateFromView:self];

    switch (pinFactory.geometryState) {
        case 1:{
            if( [pinFactory actualPinCount] == 1){
                //[UIView beginAnimations:@"stalk" context:nil];
                //[UIView setAnimationDuration:1];
                //[UIView setAnimationBeginsFromCurrentState:YES];

                Pin *pin = (Pin *)[pinFactory getObjectAtIndex:0];
                [mapview removeAnnotation:pin];
                [pinFactory removeObject:pin];

                Pin *newPin = [[Pin alloc] initWithCoordinate:coordinate initLocation:location withTitle:@"My Pin"];
                [pinFactory addObject:newPin];
                [mapview addAnnotation:newPin];

                [newPin release];

                //[UIView commitAnimations];
            }
            else{
                //Lets add a new pin to the geometry
                Pin *pin = [[Pin alloc] initWithCoordinate:coordinate initLocation:location withTitle:@"My Pin"];
                [pinFactory addObject:pin];
                [mapview addAnnotation:pin];

                [pin release];
            }
            break;
        }
        case 2:{
            //Lets add a new pin
            Pin *pin = [[Pin alloc] initWithCoordinate:coordinate initLocation:location withTitle:@"My Pin"];
            [pinFactory addObject:pin];
            [mapview addAnnotation:pin];

            [pin release];
            [self setNeedsDisplay];

            break;
        }
        case 3:{
            //Lets add a new pin
            Pin *pin = [[Pin alloc] initWithCoordinate:coordinate initLocation:location withTitle:@"My Pin"];
            [pinFactory addObject:pin];
            [mapview addAnnotation:pin];

            [pin release];
            [self setNeedsDisplay];

            break;
        }
        default:
            break;
    }
}


- (void)dealloc {
    [super dealloc];
}


@end

DrawMapViewController Listing:

#import "DrawMapViewController.h"
#import "Pin.h"

@implementation DrawMapViewController

@synthesize mapview, mapBarButton, pointBarButton, lineBarButton, polygonBarButton, geometryView;

/*  State represents state of the map
 *  0 = map
 *  1 = point
 *  2 = line
 *  3 = polygon
 */

// The designated initializer. Override to perform setup that is required before the view is loaded.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
        // Custom initialization
        self.title = @"Map";
    }
    return self;
}


// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
    [super viewDidLoad];
    mapview.mapType = MKMapTypeSatellite;


    NSMutableArray *pinArray = [[NSMutableArray alloc] initWithObjects:nil];
    pinFactory = [[PinFactory alloc] initWithArray:pinArray]; 
    pinFactory.map = mapview;
    [pinArray release];

    geometryView = [[GeometryView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, 372.0f)];
    geometryView.pinFactory = pinFactory;
    geometryView.mapview = mapview;
    geometryView.backgroundColor = [UIColor clearColor];
    [self.view addSubview:geometryView];

    [self changeButtonAndViewState:0];
}


/*
// Override to allow orientations other than the default portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    // Return YES for supported orientations
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
*/

- (void)didReceiveMemoryWarning {
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];

    // Release any cached data, images, etc that aren't in use.
}

- (void)viewDidUnload {
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}


- (void)dealloc {
    [mapBarButton release];
    [pointBarButton release];
    [lineBarButton release];
    [polygonBarButton release];

    [super dealloc];
}

- (IBAction)mapBarButtonPressed{
    NSLog(@"mapBarButtonPressed");
    [self changeButtonAndViewState:0];
}

- (IBAction)pointBarButtonPressed{
    NSLog(@"pointBarButtonPressed");
    [self changeButtonAndViewState:1];

    if( [pinFactory actualPinCount] > 0){
        [self resetGeometry];
    }   
}

- (IBAction)lineBarButtonPressed{
    NSLog(@"lineBarButtonPressed");

    if( [pinFactory actualPinCount] > 0){
        [self resetGeometry];
    }

    [self changeButtonAndViewState:2];
}

- (IBAction)polygonBarButtonPressed{
    NSLog(@"polygonBarButtonPressed");

    if( [pinFactory actualPinCount] > 0){
        [self resetGeometry];
    }

    [self changeButtonAndViewState:3];
}

- (void)resetGeometry{
    NSLog(@"resetting geometry.. deleting all pins");
    [mapview removeAnnotations:[pinFactory pinArray]];

    NSMutableArray *array = [pinFactory pinArray];
    [array removeAllObjects];

    [geometryView setNeedsDisplay];
}

- (void)changeButtonAndViewState:(int)s{
    [pinFactory setGeometryState:s];

    mapBarButton.style = UIBarButtonItemStyleBordered;
    pointBarButton.style = UIBarButtonItemStyleBordered;
    lineBarButton.style = UIBarButtonItemStyleBordered;
    polygonBarButton.style = UIBarButtonItemStyleBordered;

    pointBarButton.enabled = YES;
    lineBarButton.enabled = YES;
    polygonBarButton.enabled = YES;

    switch ([pinFactory geometryState]) {
        case 0:{
            mapBarButton.style = UIBarButtonItemStyleDone;
            geometryView.hidden = YES;
            break;
        }
        case 1:{
            pointBarButton.enabled = NO;

            pointBarButton.style = UIBarButtonItemStyleDone;

            geometryView.hidden = NO;
            break;
        }
        case 2:{
            lineBarButton.enabled = NO;

            lineBarButton.style = UIBarButtonItemStyleDone;

            geometryView.hidden = NO;
            break;
        }
        case 3:{
            polygonBarButton.enabled = NO;

            polygonBarButton.style = UIBarButtonItemStyleDone;

            geometryView.hidden = NO;

            break;
        }
        default:
            break;
    }
}

-(void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animate{
    NSLog(@"regionDidChangeAnimated");
}

@end

4条回答
放我归山
2楼-- · 2019-04-03 00:00

Depending on the sophistication of your geometryView, you may be able to use an annotation view to draw it.

The downside to this approach is that the view won't scale with the map (although you can use the mapView:regionDidChangeanimated: delegate method to redraw after a zoom), but it will pan with the map.

查看更多
放荡不羁爱自由
3楼-- · 2019-04-03 00:02

Hey I see nobody has answered you, and I just figured out how to do this. Unfortunately you cannot intercept events and forward them to the mapView so something like

@interface MapOverlay
   MKMapView* map;
@end

@implementation
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
   // Do my stuff
   foo();
   bar();
   // Forward to the map
   [map touchesBegan....];
}
@end

This is what you want to do but it WILL NOT WORK. For some reason you cannot intercept the map's events, nor can you do the reverse and subclass MKMapView and overload it's touch methods. The only way I have found to do this is as follows.

Create your MapOverlay view, set it to have a transparent background and disable touches to it. Overlay this on top of your MKMapView. Then do the following

Subclass UIWindow with a custom class that will forward all touches to your overlay, either handling the logic as "if the overlay is not hidden, then forward", or in the overlay itself keep state. Anyway it looks like this

@implementation CustomWindow

- (void)sendEvent:(UIEvent*)event
{
   [super sendEvent:event];
   // Forward the event to the overlay
}

When you are forwarding the event to the overlay, you first check if the touches are inside of your mapView region, then figure out what type of touches they are, then call the correct touch method on your overlay.

Good luck!

查看更多
ゆ 、 Hurt°
4楼-- · 2019-04-03 00:09

The hitTest trick works great as long as you don't need users to be able to tap on annotations, or pinch-and-zoom the map. Neither of these are able to be forwarded.

查看更多
女痞
5楼-- · 2019-04-03 00:18

I know this answer probably comes too late, but I'll take a stab anyway for the benefit of those (such as myself) who've also come across this problem as well.

The MKMapView class's touch events are all handled by a UIScrollView internally. You can capture the events of this scroll view by making the MKMapView a subview of a custom UIView, and providing your custom touches methods in the custom view.

The trick is to keep track of the UIScrollView used by the MKMapView. To do this, I overrode the "hitTest" method, which returns "self", which I believe means this custom view should handle the touch events.

Also, the hitTest method gets the UIScrollView of the MKMapView. In my custom UIView I called it "echo_to" because the events are then echoed to the UIScrollView to make the map work as it does normally.


- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {

    //  Get the UIView (in this case, a UIScrollView) that
    //  would ordinarily handle the events
    echo_to = [super hitTest:point withEvent:event];

    //  But let it be known we'll handle them instead.
    return self;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

    NSLog(@"Touches Began");
    //  add your custom annotation behavior here
    [echo_to touchesBegan:touches withEvent:event];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {

    NSLog(@"Touches moved");
    //  add your custom annotation behavior here
    [echo_to touchesMoved:touches withEvent:event];
}

Best of luck.

查看更多
登录 后发表回答