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

krs*_*jns 29 touch uiview mapkit ios

我已经搜索了很多这个问题,但它们似乎都没有完全符合我的要求.很多教程向我展示了如何在代码中添加线条和多边形,而不是使用徒手绘图.

问题是以下问题:

我正在建立房地产申请.如果用户在MKMapView其上,则能够在他/她想要购买/租赁房屋的某个区域周围绘制矩形/圆形/ ... 然后我需要显示用户所选区域内对应的结果.

目前我有一个UIView在我的MKMapView地方,我做一些自定义绘图,有没有办法将点转换为坐标或从...?或者这完全不是这样做的?我也听说过MKMapOverlayView等等.但我不确定如何使用它.

任何人都可以指出我正确的方向,或者他是否有一些示例代码或教程可以帮助我完成我需要的东西?

谢谢

Nat*_*ate 16

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

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

履行

首先,我定义了一个处理选择的透明覆盖层(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
Run Code Online (Sandbox Code Playgroud)

和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
Run Code Online (Sandbox Code Playgroud)

然后我有一个实现上面定义的协议的类(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;
Run Code Online (Sandbox Code Playgroud)

在我的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";
}
Run Code Online (Sandbox Code Playgroud)

你会注意到我MapViewController有一处房产areaButton.这是我工具栏上的一个按钮,通常是区域.在用户按下它之后,它们处于区域选择模式,此时,按钮标签改为说明滑动以提醒它们滑动(可能不是最好的UI,但这就是我所拥有的).

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

lookupHistoryByArea只是一种方法,通过保存searchBounds(在后台)搜索我的数据库中的位置,然后在找到的位置在地图上绘制新的叠加层.对于您的应用,这显然会有所不同.

限制

  • 由于这是为了让用户选择近似区域,我不认为地理精度至关重要.它听起来不应该在你的应用程序中.因此,我只绘制90度角的矩形,不考虑地球曲率等.对于只有几英里的区域,这应该没问题.

  • 我不得不对你的短语触摸绘图做出一些假设.我认为实现应用程序的最简单方法,也是触摸屏用户最容易使用的方法,只需用一次滑动即可定义区域. 绘制一个带触摸的矩形需要4次滑动而不是一次滑动,引入非闭合矩形的复杂性,产生草率的形状,并且可能无法让用户得到他们想要的东西.所以,我试图保持UI简单.如果您真的希望用户在地图上绘图,请参阅此相关答案.

  • 这个应用程序是在ARC之前编写的,并没有针对ARC进行更改.

  • 在我的应用程序中,我实际上对主(UI)线程后台(搜索)线程中访问的一些变量使用互斥锁定.我把这个代码用于这个例子.根据数据库搜索的工作方式以及选择运行搜索的方式(GCD等),您应该确保审核自己的线程安全性.


小智 0

尝试 MKOverlayPathView。通过在 MKMapView 上绘制路径来表示区域的问题是,除非您知道缩放比例,否则您了解不多。所以你必须跟踪它。