如何在自定义 UIView 下方的 MKMapview 中启用 touchEvents(滚动和平移)?

Posted

技术标签:

【中文标题】如何在自定义 UIView 下方的 MKMapview 中启用 touchEvents(滚动和平移)?【英文标题】:How to enable touchEvents (scroll and pan) in MKMapview below a custom UIView? 【发布时间】:2009-09-28 06:19:38 【问题描述】:

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

简而言之,我试图弄清楚如何缩放在 MKMapView (mapView) 之上的自定义视图 (geometryView) 中实现的几何图形(点、线和多边形)。

我所做的是..

    创建 DrawMapViewController。在底部工具栏上添加 UIBarButtonItems(MAP、PT、LN、PG)。

    当您点击地图按钮时,您可以在地图上平移/缩放。这通过设置geometryView.hidden = YES来启用mapView;

    当点击三个几何按钮中的任何一个时,geometryView 会通过geometryView.hidden = NO 显示,从而启用touchEvents 并从GeometryView.drawRect 的方法中绘制几何图形。

层序如下:mapView在geometryView的底部。

-几何视图

-地图视图

我的问题是什么? 理想情况下,在“地图”模式期间以及当用户平移和缩放时,我希望它是否可以显示来自 geometryView 的绘图。但是当用户点击“地图”时,geometryView.hidden = YES,因此绘图消失了。如果我让geometryView 可见,那么用户与geometryView 交互而不是mapView,因此没有缩放和平移。

在显示自定义视图时,是否可以在自定义视图下方处理 MKMapView 的触摸事件(平移/缩放)?非常感谢任何其他想法/方法。

谢谢,

鲁珀特

几何视图列表:

@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 清单:

#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

【问题讨论】:

【参考方案1】:

嘿,我看到没有人回答你,我只是想出了如何做到这一点。不幸的是,您无法拦截事件并将它们转发到 mapView 之类的

@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

这是您想要做的,但它不会工作。出于某种原因,您不能拦截地图的事件,也不能执行反向和子类 MKMapView 并重载它的触摸方法。我发现这样做的唯一方法如下。

创建您的 MapOverlay 视图,将其设置为具有透明背景并禁用对其的触摸。将其覆盖在您的 MKMapView 之上。然后执行以下操作

子类 UIWindow 具有一个自定义类,它将所有触摸转发到您的叠加层,或者将逻辑处理为“如果叠加层没有隐藏,则转发”,或者在叠加层本身保持状态。反正看起来是这样的

@implementation CustomWindow

- (void)sendEvent:(UIEvent*)event

   [super sendEvent:event];
   // Forward the event to the overlay

当您将事件转发到覆盖层时,您首先检查触摸是否在您的 mapView 区域内,然后确定它们是什么类型的触摸,然后在覆盖层上调用正确的触摸方法。

祝你好运!

【讨论】:

【参考方案2】:

我知道这个答案可能来得太晚了,但是为了那些也遇到这个问题的人(比如我自己)的利益,我还是会采取一些措施。

MKMapView 类的触摸事件都由 UIScrollView 内部处理。您可以通过使 MKMapView 成为自定义 UIView 的子视图并在自定义视图中提供自定义触摸方法来捕获此滚动视图的事件。

诀窍是跟踪 MKMapView 使用的 UIScrollView。为此,我重写了“hitTest”方法,该方法返回“self”,我相信这意味着这个自定义视图应该处理触摸事件。

另外,hitTest 方法获取 MKMapView 的 UIScrollView。在我的自定义 UIView 中,我将其称为“echo_to”,因为事件随后会回显到 UIScrollView 以使地图正常工作。

- (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];

祝你好运。

【讨论】:

【参考方案3】:

根据您的几何视图的复杂程度,您可以使用注释视图来绘制它。

这种方法的缺点是视图不会随地图缩放(尽管您可以使用mapView:regionDidChangeanimated: 委托方法在缩放后重绘),但它会随地图平移。

【讨论】:

【参考方案4】:

只要您不需要用户点击注释或捏合和缩放地图,hitTest 技巧就非常有效。这些都无法转发。

【讨论】:

以上是关于如何在自定义 UIView 下方的 MKMapview 中启用 touchEvents(滚动和平移)?的主要内容,如果未能解决你的问题,请参考以下文章

如何在自定义 UIView 中添加 UITableView?

如何在自定义 UIView 中从 ViewController 传递数据

如何借助 AutoLayout 在自定义 UITableviewCell 中添加自定义 UIView(xib)?

如何让 UITableViewCell 在自定义 UIView 子类上调用 setHighlighted 或 setSelected?

当我无法使用 UIScrollView 时,如何在自定义 UIView 中模拟惯性滚动?

在自定义 UIView 类中从 UIView 快速创建 IBoutlet [重复]