iOS + MKMapView 用户基于触摸的绘图

30

我已经搜索了很多关于这个问题的资料,但是没有一个能完全满足我的需求。许多教程都展示了如何在代码中添加线条和多边形,但没有自由绘制的方法。

问题如下:

我正在构建一个房地产应用程序。如果用户使用MKMapView,则可以绘制一个矩形/圆形等区域,表示他/她想要购买/租赁房屋的区域。然后,我需要显示与用户所选区域对应的结果。

目前,我在MKMapView上方有一个UIView,其中进行一些自定义绘图,有没有一种方式可以从中将点转换为坐标或其他方法?还是完全不能这样做?我也听说过MKMapOverlayView等内容,但不确定如何使用它们。

是否有人能够指导我朝正确的方向前进,或者有一些示例代码或教程可以帮助我完成我需要的工作吗?

谢谢


我也在尝试做同样的事情,但是到目前为止还没有成功,请让我知道如果你找到了解决方案。我的电子邮件是:ghostsmitm@gmail.com - Ashutosh
大家好,我也在尝试做同样的事情,但是还没有完全成功...如果你们成功了,请也帮助我一下...我的电子邮件地址是rahulmishra449@gmail.com - Rahul
@krswtjns 你实现了这个吗? - user800116
4个回答

17

我有一个应用程序,基本上是这样的。我有一个地图视图,在屏幕顶部有一个工具栏。当您按下该工具栏上的按钮时,您现在处于可以在地图上滑动手指的模式。滑动的起点和终点将表示矩形的角落。应用程序将绘制一个半透明的蓝色矩形覆盖层,以显示您选择的区域。当您放开手指时,矩形选择完成,并且应用程序开始搜索我的数据库中的位置。

我不处理圆形,但我认为您可以做类似的事情,其中您有两种选择模式(矩形或圆形)。在圆形选择模式下,滑动的起点和终点可以表示圆心和边缘(半径)。或者,直径线的两个端点。我会留下那一部分给你。

实现

首先,我定义了一个透明的覆盖层,用于处理选择(OverlaySelectionView.h):

#import <QuartzCore/QuartzCore.h>
#import <MapKit/MapKit.h>

@protocol OverlaySelectionViewDelegate
// callback when user finishes selecting map region
- (void) areaSelected: (CGRect)screenArea;
@end


@interface OverlaySelectionView : UIView {
@private    
    UIView* dragArea;
    CGRect dragAreaBounds;
    id<OverlaySelectionViewDelegate> delegate;
}

@property (nonatomic, assign) id<OverlaySelectionViewDelegate> delegate;

@end

以及OverlaySelectionView.m文件:

#import "OverlaySelectionView.h"

@interface OverlaySelectionView()
@property (nonatomic, retain) UIView* dragArea;
@end

@implementation OverlaySelectionView

@synthesize dragArea;
@synthesize delegate;

- (void) initialize {
    dragAreaBounds = CGRectMake(0, 0, 0, 0);
    self.userInteractionEnabled = YES;
    self.multipleTouchEnabled = NO;
    self.backgroundColor = [UIColor clearColor];
    self.opaque = NO;
    self.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
}

- (id) initWithCoder: (NSCoder*) coder {
    self = [super initWithCoder: coder];
    if (self != nil) {
        [self initialize];
    }
    return self;
}

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

- (void)drawRect:(CGRect)rect {
    // do nothing
}

#pragma mark - Touch handling

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch* touch = [[event allTouches] anyObject];
    dragAreaBounds.origin = [touch locationInView:self];
}

- (void)handleTouch:(UIEvent *)event {
    UITouch* touch = [[event allTouches] anyObject];
    CGPoint location = [touch locationInView:self];

    dragAreaBounds.size.height = location.y - dragAreaBounds.origin.y;
    dragAreaBounds.size.width = location.x - dragAreaBounds.origin.x;

    if (self.dragArea == nil) {
        UIView* area = [[UIView alloc] initWithFrame: dragAreaBounds];
        area.backgroundColor = [UIColor blueColor];
        area.opaque = NO;
        area.alpha = 0.3f;
        area.userInteractionEnabled = NO;
        self.dragArea = area;
        [self addSubview: self.dragArea];
        [dragArea release];
    } else {
        self.dragArea.frame = dragAreaBounds;
    }
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    [self handleTouch: event];
}

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

    if (self.delegate != nil) {
        [delegate areaSelected: dragAreaBounds];
    }
    [self initialize];
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
    [self initialize];
    [self.dragArea removeFromSuperview];
    self.dragArea = nil;
}

#pragma mark -

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

@end

然后我有一个类来实现上述定义的协议(MapViewController.h):

#import "OverlaySelectionView.h"

typedef struct {
    CLLocationDegrees minLatitude;
    CLLocationDegrees maxLatitude;
    CLLocationDegrees minLongitude;
    CLLocationDegrees maxLongitude;
} LocationBounds;

@interface MapViewController : UIViewController<MKMapViewDelegate, OverlaySelectionViewDelegate> {
    LocationBounds searchBounds;
    UIBarButtonItem* areaButton;

在我的 MapViewController.m 中,areaSelected 方法是我使用 convertPoint:toCoordinateFromView: 进行触摸坐标到地理坐标转换的地方:

#pragma mark - OverlaySelectionViewDelegate

- (void) areaSelected: (CGRect)screenArea
{       
    self.areaButton.style = UIBarButtonItemStyleBordered;
    self.areaButton.title = @"Area";

    CGPoint point = screenArea.origin;
    // we must account for upper nav bar height!
    point.y -= 44;
    CLLocationCoordinate2D upperLeft = [mapView convertPoint: point toCoordinateFromView: mapView];
    point.x += screenArea.size.width;
    CLLocationCoordinate2D upperRight = [mapView convertPoint: point toCoordinateFromView: mapView];
    point.x -= screenArea.size.width;
    point.y += screenArea.size.height;
    CLLocationCoordinate2D lowerLeft = [mapView convertPoint: point toCoordinateFromView: mapView];
    point.x += screenArea.size.width;
    CLLocationCoordinate2D lowerRight = [mapView convertPoint: point toCoordinateFromView: mapView];

    searchBounds.minLatitude = MIN(lowerLeft.latitude, lowerRight.latitude);
    searchBounds.minLongitude = MIN(upperLeft.longitude, lowerLeft.longitude);
    searchBounds.maxLatitude = MAX(upperLeft.latitude, upperRight.latitude);
    searchBounds.maxLongitude = MAX(upperRight.longitude, lowerRight.longitude);

    // TODO: comment out to keep search rectangle on screen
    [[self.view.subviews lastObject] removeFromSuperview];

    [self performSelectorInBackground: @selector(lookupHistoryByArea) withObject: nil];
}

// this action is triggered when user selects the Area button to start selecting area
// TODO: connect this to areaButton yourself (I did it in Interface Builder)
- (IBAction) selectArea: (id) sender
{
    PoliteAlertView* message = [[PoliteAlertView alloc] initWithTitle: @"Information"
                                                              message: @"Select an area to search by dragging your finger across the map"
                                                             delegate: self
                                                              keyName: @"swipe_msg_read"
                                                    cancelButtonTitle: @"Ok"
                                                    otherButtonTitles: nil];
    [message show];
    [message release];

    OverlaySelectionView* overlay = [[OverlaySelectionView alloc] initWithFrame: self.view.frame];
    overlay.delegate = self;
    [self.view addSubview: overlay];
    [overlay release];

    self.areaButton.style = UIBarButtonItemStyleDone;
    self.areaButton.title = @"Swipe";
}

你会发现我的 MapViewController 有一个属性,areaButton。这是工具栏上的一个按钮,通常显示为 Area。当用户按下它时,他们进入了区域选择模式,此时按钮标签更改为 Swipe 来提醒他们向右滑动(可能不是最好的UI,但这就是我所拥有的)。

另请注意,当用户按下 Area 进入区域选择模式时,我会向他们显示一个警告,告诉他们需要向右滑动。由于这可能只是提醒他们需要看到的一次,因此我使用了自己的PoliteAlertView,这是一个用户可以抑制(不再显示警告)的自定义 UIAlertView

我的 lookupHistoryByArea 只是一个方法,后台搜索我的数据库中的位置,并通过保存的 searchBounds 在地图上绘制新的覆盖物。对于你的应用程序,这显然会有所不同。

限制

  • 由于这是让用户选择大概区域,我认为地理精度不是关键因素。在你的应用程序中似乎也不需要考虑这一点。因此,我只绘制带有90度角的矩形,不考虑地球曲率等因素。对于几英里范围内的区域,这应该可以胜任。

  • 我必须对你的短语 touch based drawing 进行一些假设。我认为实现应用程序最简单、触摸屏用户使用最简单的方法是用一次单独的滑动定义区域。用触摸绘制矩形需要4次滑动,引入非闭合矩形的复杂性,产生松散的形状,可能无法获得用户想要的内容。所以,我试图保持UI的简洁。如果你确实想让用户在地图上绘图,请参见这个相关的答案

  • 此应用程序是在ARC之前编写的,并且没有针对ARC进行更改。

  • 在我的应用程序中,我实际上为一些在主(UI)线程和后台(搜索)线程上访问的变量使用了互斥锁,我将这些代码从此示例中删除了。根据你的数据库搜索工作方式以及你选择运行搜索的方式(GCD等),你应该确保自己线程安全性的审计。


嗨,Nate,我能够在地图上绘制区域,但是我无法获取所选区域内的坐标,请帮忙。 - Rahul
@Rahul,如果你看一下上面的代码,areaSelected:是我将屏幕区域转换为地理坐标(纬度和经度)的方法。如果你有其他需求,请发布一个新问题。你上面的评论并不足以让我知道你具体遇到了什么问题。谢谢。 - Nate
感谢您的回复。我的要求与Krswtjns之前提出的问题完全相同,目前我能够在地图视图上画线。但是,在我的情况下,按照您上面的指示和示例,我能够画出线条,但无法获取坐标。我不知道在哪里调用您的areaSelected方法,因为我没有使用任何单独的覆盖层类。 - Rahul
@Rahul,在我的示例中,使用OverlaySelectionView类是必需的。这个类是我检测用户触摸的地方,然后调用areaSelected:方法的地方。如果你真的不想向用户显示任何指示他们手指“绘制”的区域的叠加层,那没关系。你可以更改OverlaySelectionView中的代码,以便删除所有带有dragArea实例变量的代码。但是,你必须保留使用dragAreaBounds的代码,因为它将传递给areaSelected: - Nate

3

ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@end

ViewController.m

#import "ViewController.h"
#import <MapKit/MapKit.h>

@interface ViewController () <MKMapViewDelegate>
@property (weak, nonatomic) IBOutlet MKMapView *mapView;
@property (nonatomic, weak) MKPolyline *polyLine;
@property (nonatomic, strong) NSMutableArray *coordinates;
@property (weak, nonatomic) IBOutlet UIButton *drawPolygonButton;
@property (nonatomic) BOOL isDrawingPolygon;
@end

@implementation ViewController
@synthesize coordinates = _coordinates;

- (NSMutableArray*)coordinates
{
    if(_coordinates == nil) _coordinates = [[NSMutableArray alloc] init];
    return _coordinates;
}

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

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

- (IBAction)didTouchUpInsideDrawButton:(UIButton*)sender
{
    if(self.isDrawingPolygon == NO) {

        self.isDrawingPolygon = YES;
        [self.drawPolygonButton setTitle:@"done" forState:UIControlStateNormal];
        [self.coordinates removeAllObjects];
        self.mapView.userInteractionEnabled = NO;

    } else {

        NSInteger numberOfPoints = [self.coordinates count];

        if (numberOfPoints > 2)
        {
            CLLocationCoordinate2D points[numberOfPoints];
            for (NSInteger i = 0; i < numberOfPoints; i++)
                points[i] = [self.coordinates[i] MKCoordinateValue];
            [self.mapView addOverlay:[MKPolygon polygonWithCoordinates:points count:numberOfPoints]];
        }

        if (self.polyLine)
            [self.mapView removeOverlay:self.polyLine];

        self.isDrawingPolygon = NO;
        [self.drawPolygonButton setTitle:@"draw" forState:UIControlStateNormal];
        self.mapView.userInteractionEnabled = YES;

    }
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    if (self.isDrawingPolygon == NO)
        return;

    UITouch *touch = [touches anyObject];
    CGPoint location = [touch locationInView:self.mapView];
    CLLocationCoordinate2D coordinate = [self.mapView convertPoint:location toCoordinateFromView:self.mapView];

    [self addCoordinate:coordinate];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    if (self.isDrawingPolygon == NO)
        return;

    UITouch *touch = [touches anyObject];
    CGPoint location = [touch locationInView:self.mapView];
    CLLocationCoordinate2D coordinate = [self.mapView convertPoint:location toCoordinateFromView:self.mapView];

    [self addCoordinate:coordinate];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    if (self.isDrawingPolygon == NO)
        return;

    UITouch *touch = [touches anyObject];
    CGPoint location = [touch locationInView:self.mapView];
    CLLocationCoordinate2D coordinate = [self.mapView convertPoint:location toCoordinateFromView:self.mapView];

    [self addCoordinate:coordinate];
    [self didTouchUpInsideDrawButton:nil];

}

- (void)addCoordinate:(CLLocationCoordinate2D)coordinate
{
    [self.coordinates addObject:[NSValue valueWithMKCoordinate:coordinate]];

    NSInteger numberOfPoints = [self.coordinates count];
    if (numberOfPoints > 2) {
        MKPolyline *oldPolyLine = self.polyLine;
        CLLocationCoordinate2D points[numberOfPoints];
        for (NSInteger i = 0; i < numberOfPoints; i++) {
            points[i] = [self.coordinates[i] MKCoordinateValue];
        }
        MKPolyline *newPolyLine = [MKPolyline polylineWithCoordinates:points count:numberOfPoints];
        [self.mapView addOverlay:newPolyLine];

        self.polyLine = newPolyLine;
        if (oldPolyLine) {
            [self.mapView removeOverlay:oldPolyLine];
        }
    }
}

#pragma mark - MKMapViewDelegate

- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay
{
    MKOverlayPathView *overlayPathView;

    if ([overlay isKindOfClass:[MKPolygon class]])
    {
        overlayPathView = [[MKPolygonView alloc] initWithPolygon:(MKPolygon*)overlay];

        overlayPathView.fillColor = [[UIColor cyanColor] colorWithAlphaComponent:0.2];
        overlayPathView.strokeColor = [[UIColor blueColor] colorWithAlphaComponent:0.7];
        overlayPathView.lineWidth = 3;

        return overlayPathView;
    }

    else if ([overlay isKindOfClass:[MKPolyline class]])
    {
        overlayPathView = [[MKPolylineView alloc] initWithPolyline:(MKPolyline *)overlay];

        overlayPathView.strokeColor = [[UIColor blueColor] colorWithAlphaComponent:0.7];
        overlayPathView.lineWidth = 3;

        return overlayPathView;
    }

    return nil;
}

- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
    if ([annotation isKindOfClass:[MKUserLocation class]])
        return nil;

    static NSString * const annotationIdentifier = @"CustomAnnotation";

    MKAnnotationView *annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:annotationIdentifier];

    if (annotationView)
    {
        annotationView.annotation = annotation;
    }
    else
    {
        annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:annotationIdentifier];
        annotationView.image = [UIImage imageNamed:@"annotation.png"];
        annotationView.alpha = 0.5;
    }

    annotationView.canShowCallout = NO;

    return annotationView;
}

@end

您可以在这里找到整个项目: https://github.com/tazihosniomar/MapKitDrawing 希望它会对您有所帮助。

2

这是我将触摸转换为 CLLocationMKMapView 上的方法。

它适用于 Google 地图Apple 地图

- (void)viewDidLoad {
    // ...

    // ... where the _customMapView is a MKMapView object;

    // find the gesture recogniser of the map
    UIGestureRecognizer *_factoryDoubleTapGesture = nil;
    NSArray *_gestureRecognizersArray = [_customMapView gestureRecognizers];
    for (UIGestureRecognizer *_tempRecogniser in _gestureRecognizersArray) {
        if ([_tempRecogniser isKindOfClass:[UITapGestureRecognizer class]]) {
            if ([(UITapGestureRecognizer *)_tempRecogniser numberOfTapsRequired] == 2) {
                _factoryDoubleTapGesture = _tempRecogniser;
                break;
            }
        }
    }

    // my tap gesture recogniser
    UITapGestureRecognizer *_locationTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(mapLocationTouchedUpInside:)];
    if (_factoryDoubleTapGesture) [_locationTapGesture requireGestureRecognizerToFail:_factoryDoubleTapGesture];
    [_customMapView addGestureRecognizer:_locationTapGesture];

    // ...
}

并且...

- (void)mapLocationTouchedUpInside:(UITapGestureRecognizer *)sender {
    CGPoint _tapPoint = [sender locationInView:_customMapView];
    CLLocationCoordinate2D _coordinates = [_customMapView convertPoint:_tapPoint toCoordinateFromView:_customMapView];

    // ... do whatever you'd like with the coordinates
}

0
尝试使用MKOverlayPathView。在MKMapView上通过绘制路径来表示区域的问题是,除非您知道缩放比例,否则您不会知道太多信息。因此,您需要跟踪它。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接