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

我已经为这个问题寻找了很多东西,但是没有一个看起来像我想要的那样。 很多教程向我展示了如何在代码中添加线条和多边形,而不是徒手画。

问题是以下一个:

我正在build造一个房地产应用程序。 如果用户在MKMapView ,则可以在他/她想要购买/租用房屋的某个区域周围绘制矩形/圆形/ …。 然后我需要显示用户select的区域内的结果。

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

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

谢谢

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

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

履行

首先,我定义了一个透明的覆盖层,处理select(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 。 这是我的工具栏上的一个button,通常是Area 。 用户按下后,他们处于区域select模式,在这一点上,button标签变成滑动以提醒他们刷卡(也许不是最好的用户界面,但这就是我所拥有的)。

另外请注意,当用户按区域进入区域select模式时,我会向他们显示一个警告,告诉他们需要滑动。 由于这可能只是一个提醒,他们需要看一次,我用我自己的PoliteAlertView ,这是一个自定义UIAlertView ,用户可以禁止(不要再显示警报)。

我的lookupHistoryByArea只是一个方法,通过保存的searchBounds (在后台)search我的数据库的位置,然后在地图上find新的覆盖图在find的位置。 这显然是不同的你的应用程序。

限制

  • 由于这是让用户select大概的地区,我没有考虑地理精度是至关重要的。 这听起来不像它应该在你的应用程序,也是。 因此,我只画出90度angular的矩形,不考虑地球曲率等。对于几英里的地区,这应该是好的。

  • 我不得不对你的基于触摸的绘图做出一些假设。 我决定实现这个应用程序的最简单的方法,也是触摸屏用户最容易使用的方法,只需简单地用一个滑动来定义区域。 绘制一个带触摸的矩形将需要4次而不是一次,介绍非闭合矩形的复杂性,产生马虎的形状,并可能不会得到他们甚至想要的用户。 所以,我试图保持简单的用户界面。 如果你真的希望用户在地图上绘图 , 请参阅相关答案 。

  • 这个应用程序是在ARC之前编写的,而不是为ARC改变的。

  • 在我的应用程序中,我实际上使用互斥locking来访问主(UI)线程后台(search)线程中的一些variables。 我拿这个代码出来这个例子。 根据数据库search的工作方式以及如何select运行search(GCD等),您应该确保审核自己的线程安全性。

这是我的方式如何将触摸转换为MKMapView上的CLLocation

它也适用于Google MapsApple Maps

 - (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 } 

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 

或者你可以在这里find整个项目: https : //github.com/tazihosniomar/MapKitDrawing

我希望它能帮助你。

试试MKOverlayPathView。 通过在MKMapView上绘制path来表示一个区域的问题是,除非你知道缩放比例,你不知道多less。 所以你必须跟踪。