30

私はこの質問をたくさん検索しましたが、どれも私が望んでいることを正確に行っているようには見えません。多くのチュートリアルでは、コードで線とポリゴンを追加する方法を教えてくれますが、フリーハンドでの描画ではありません。

質問は次のとおりです。

私は不動産アプリケーションを構築しています。ユーザーがオンにMKMapViewなっている場合、家を購入/賃貸したい特定のエリアの周りに長方形/円/...を描くことができます。次に、ユーザーが選択した領域内に対応する結果を表示する必要があります。

現在、私はカスタム描画を行っている場所のUIView上にありMKMapViewますが、ポイントをそこから座標に変換する方法はありますか..? それとも、これは完全にこれが行われる方法ではありませんか? などについても聞いたことがありMKMapOverlayViewますが、これの使い方が正確にはわかりません。

誰かが私を正しい方向に向けることができますか、それとも私が必要としているものを達成するのに役立つサンプルコードまたはチュートリアルを持っていますか?

ありがとう

4

4 に答える 4

17

基本的にこれを行うアプリがあります。画面の上部にツールバーがあるマップビューがあります。そのツールバーのボタンを押すと、マップ上で指をスワイプできるモードになります。スワイプの開始と終了は、長方形の角を表します。アプリは半透明の青い四角形のオーバーレイを描画して、選択した領域を表示します。指を離すと、長方形の選択が完了し、アプリがデータベース内の場所の検索を開始します。

私は円を扱いませんが、2 つの選択モード (長方形または円形) がある場合、同様のことができると思います。円形選択モードでは、スワイプの開始点と終了点は円の中心とエッジ (半径) を表すことができます。または、直径線の両端。その部分はお任せします。

実装

まず、選択を処理する透過オーバーレイ レイヤーを定義します (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";
}

MapViewControllermyにはプロパティ があることに気付くでしょうareaButton。これはツールバーのボタンで、通常はAreaと表示されます。ユーザーがそれを押すと、その時点で領域選択モードになり、ボタンのラベルが Swipe に変わり、スワイプすることを思い出させます (おそらく最高の UI ではありませんが、それが私が持っているものです)。

また、ユーザーがAreaを押して領域選択モードに入ると、スワイプする必要があることを知らせるアラートが表示されることにも注意してください。これはおそらく一度だけ表示する必要があるリマインダーであるため、ユーザーが抑制できるカスタムである独自のPoliteAlertViewUIAlertViewを使用しました (アラートを再度表示しないでください)。

MylookupHistoryByAreaは、(バックグラウンドで) 保存された場所によってデータベースで場所を検索searchBoundsし、見つかった場所で地図上に新しいオーバーレイをプロットする方法です。これは明らかにアプリによって異なります。

制限事項

  • これはユーザーがおおよそのエリアを選択できるようにするためのものであるため、地理的な精度が重要であるとは考えていませんでした。あなたのアプリにもあるべきだとは思えません。したがって、地球の曲率などを考慮せずに、90 度の角度で長方形を描くだけです。わずか数マイルの領域の場合、これで問題ありません。

  • 私はあなたのフレーズtouch based drawingについていくつかの仮定をしなければなりませんでした。アプリを実装する最も簡単な方法と、タッチスクリーン ユーザーにとって最も使いやすい方法の両方が、1 回のスワイプで領域を定義することであると判断しました。 タッチで四角形を描くには、1回ではなく4回のスワイプが必要になり、閉じていない四角形の複雑さが生じ、ずさんな形になり、おそらくユーザーが望んでいたものを得ることができません. というわけで、UIはシンプルにしようと思いました。ユーザーがマップ上に描画することが本当に必要な場合は、それを行うこの関連する回答を参照してください

  • このアプリは ARC の前に作成され、ARC 用に変更されていません。

  • 私のアプリでは、実際には、メイン (UI) スレッドバックグラウンド (検索) スレッドでアクセスされるいくつかの変数に対してミューテックス ロックを使用しています。この例では、そのコードを取り出しました。データベース検索の仕組みと、検索の実行方法 (GCD など) に応じて、独自のスレッド セーフを必ず監査する必要があります。

于 2013-02-03T23:33:38.573 に答える
2

CLLocationこれは、タッチをに変換する私の方法MKMapViewです。

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
}
于 2013-02-04T09:24:02.287 に答える
0

MKOverlayPathView を試してください。MKMapView にパスを描画して領域を示す際の問題は、ズーム スケールを知らない限り、よくわからないことです。したがって、それを追跡する必要があります。

于 2013-01-19T22:00:11.750 に答える