我有一个基本上可以做到这一点的应用程序。我有一个地图视图,屏幕顶部有一个工具栏。当您按下该工具栏上的按钮时,您现在处于可以在地图上滑动手指的模式。滑动的开始和结束将代表一个矩形的角。该应用程序将绘制一个半透明的蓝色矩形叠加层以显示您选择的区域。当您抬起手指时,矩形选择完成,应用程序开始在我的数据库中搜索位置。
我不处理圆圈,但我认为你可以做类似的事情,你有两种选择模式(矩形或圆形)。在圆形选择模式下,滑动的起点和终点可以代表圆心和边缘(半径)。或者,直径线的两端。我会把那部分留给你。
执行
首先,我定义了一个透明的覆盖层,它处理选择(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";
}
你会注意到 myMapViewController
有一个属性,areaButton
. 那是我工具栏上的一个按钮,通常显示为Area。用户按下它后,他们处于区域选择模式,此时按钮标签变为“滑动”以提醒他们滑动(可能不是最好的 UI,但这就是我所拥有的)。
另请注意,当用户按下区域进入区域选择模式时,我会向他们显示一个警报,告诉他们需要滑动。由于这可能只是他们需要查看一次的提醒,因此我使用了自己的PoliteAlertView,这是UIAlertView
用户可以禁止的自定义(不要再次显示警报)。
MylookupHistoryByArea
只是一种方法,它通过保存的searchBounds
(在后台)搜索我的数据库中的位置,然后在找到的位置在地图上绘制新的叠加层。对于您的应用,这显然会有所不同。
限制
由于这是为了让用户选择近似区域,因此我认为地理精度并不重要。听起来也不应该在您的应用程序中。因此,我只绘制 90 度角的矩形,不考虑地球曲率等。对于只有几英里的区域,这应该没问题。
我不得不对你基于短语触摸的绘图做出一些假设。我决定实现该应用程序的最简单方法,也是触摸屏用户最容易使用的方法,就是通过一次滑动来简单地定义区域。 绘制一个带有触摸的矩形需要 4 次滑动而不是 1 次,这会引入非闭合矩形的复杂性,产生草率的形状,并且可能无法获得用户想要的东西。所以,我试图保持 UI 简单。如果您真的希望用户在地图上绘图,请参阅执行该操作的相关答案。
这个应用程序是在 ARC 之前编写的,并且没有针对 ARC 进行更改。
在我的应用程序中,我确实对在主(UI)线程和后台(搜索)线程中访问的一些变量使用互斥锁。我为这个例子取出了那个代码。根据您的数据库搜索的工作方式以及您选择运行搜索的方式(GCD 等),您应该确保审核您自己的线程安全性。