iOS + MKMapView 用户基于触摸的绘图 [英] iOS + MKMapView user touch based drawing

查看:23
本文介绍了iOS + MKMapView 用户基于触摸的绘图的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我为这个问题搜索了很多,但似乎没有一个完全符合我的要求.很多教程都向我展示了如何在代码中添加线和多边形,但不是徒手画的.

I have searched a lot for this question, but none of them seem to do exactly what I want. A lot of tutorials show me how to add lines and polygons in code, but not with freehand drawing.

问题如下:

我正在构建一个房地产应用程序.如果用户在 MKMapView 上,它可以在他/她想买/租房子的某个区域周围绘制一个矩形/圆形/....然后我需要在用户选择的区域内显示对应的结果.

I am building a real estate application. If the user is on the MKMapView it has the ability to draw a rectangle/circle/... around a certain area where he/she wants to buy/rent a house. Then I need to display the results that correspond within the area the user has selected.

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

Currently I have a UIView on top of my MKMapView where I do some custom drawing, is there a way to translate points to coordinates from that or ..? Or is this completely not the way this is done ? I have also heard about MKMapOverlayView, etc .. but am not exactly sure how to use this.

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

Can anybody point me in the right direction or does he have some sample code or a tutorial that can help me accomplish what I am in need for?

谢谢

推荐答案

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

I have an app that basically does this. I have a map view, with a toolbar at the top of the screen. When you press a button on that toolbar, you are now in a mode where you can swipe your finger across the map. The start and end of the swipe will represent the corners of a rectangle. The app will draw a translucent blue rectangle overlay to show the area you've selected. When you lift your finger, the rectangular selection is complete, and the app begins a search for locations in my database.

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

I do not handle circles, but I think you could do something similar, where you have two selection modes (rectangular, or circular). In the circular selection mode, the swipe start and end points could represent circle center, and edge (radius). Or, the two ends of a diameter line. I'll leave that part to you.

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

First, I define a transparent overlay layer, that handles selection (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:

and 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):

Then I have a class that implements the protocol defined above (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: 将触摸坐标转换为地理坐标的地方:

And in my MapViewController.m, the areaSelected method is where I perform the conversion of touch coordinates to geographic coordinates with 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.这是我工具栏上的一个按钮,通常显示区域.用户按下后,他们处于区域选择模式,此时按钮标签变为滑动以提醒他们滑动(可能不是最好的 UI,但这就是我所拥有的).

You'll notice that my MapViewController has a property, areaButton. That's a button on my toolbar, which normally says Area. After the user presses it, they are in area selection mode at which point, the button label changes to say Swipe to remind them to swipe (maybe not the best UI, but that's what I have).

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

Also notice that when the user presses Area to enter area selection mode, I show them an alert that tells them that they need to swipe. Since this is probably only a reminder they need to see once, I have used my own PoliteAlertView, which is a custom UIAlertView that users can suppress (don't show the alert again).

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

My lookupHistoryByArea is just a method that searches my database for locations, by the saved searchBounds (in the background), and then plots new overlays on the map at the found locations. This will obviously be different for your app.

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

  • Since this is for letting the user select approximate areas, I did not consider geographic precision to be critical. It doesn't sound like it should be in your app, either. Thus, I just draw rectangles with 90 degree angles, not accounting for earth curvature, etc. For areas of just a few miles, this should be fine.

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

I had to make some assumptions about your phrase touch based drawing. I decided that both the easiest way to implement the app, and the easiest for a touchscreen user to use, was to simply define the area with one single swipe. Drawing a rectangle with touches would require 4 swipes instead of one, introduce the complexity of non-closed rectangles, yield sloppy shapes, and probably not get the user what they even wanted. So, I tried to keep the UI simple. If you really want the user drawing on the map, see this related answer which does that.

此应用程序是在 ARC 之前编写的,并未针对 ARC 进行更改.

This app was written before ARC, and not changed for ARC.

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

In my app, I actually do use mutex locking for some variables accessed on the main (UI) thread, and in the background (search) thread. I took that code out for this example. Depending on how your database search works, and how you choose to run the search (GCD, etc.), you should make sure to audit your own thread-safety.

这篇关于iOS + MKMapView 用户基于触摸的绘图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆