xác định xem MKMapView có được kéo / di chuyển hay không


84

Có cách nào để xác định xem MKMapView có bị kéo đi không?

Tôi muốn lấy vị trí trung tâm mỗi khi người dùng kéo bản đồ bằng cách sử dụng CLLocationCoordinate2D centre = [locationMap centerCoordinate];nhưng tôi cần một phương pháp ủy quyền hoặc một thứ gì đó kích hoạt ngay khi người dùng điều hướng xung quanh bằng bản đồ.

Cảm ơn trước

Câu trả lời:


22

Nhìn vào tham chiếu MKMapViewDelegate .

Cụ thể, các phương pháp này có thể hữu ích:

- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated

Đảm bảo thuộc tính đại biểu của chế độ xem bản đồ của bạn được đặt để các phương thức đó được gọi.


1
Cảm ơn rất nhiều. - (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animatedđã làm công việc.
hgbnerd

2
Giải pháp tuyệt vời. Hoàn hảo để lại chú thích trên bản đồ khi người dùng thay đổi vị trí
Alejandro Luengo

176
-1 vì giải pháp này không cho bạn biết nếu người dùng đã kéo bản đồ. RegionWillChangeAnimated xảy ra nếu người dùng xoay thiết bị hoặc một phương pháp khác thu phóng bản đồ, không nhất thiết phải phản ứng với thao tác kéo.
CommaToast

Cảm ơn @CommaToast Tôi đã tìm thấy cùng một vấn đề với 'câu trả lời' này
Mingbit

3
Giải pháp của @mobi phát hiện cử chỉ của người dùng (vâng, tất cả đều như vậy) bằng cách kiểm tra trên trình nhận dạng cử chỉ nội bộ của mapviews. Đẹp!
Felix Alcala

236

Mã trong câu trả lời được chấp nhận sẽ kích hoạt khi khu vực được thay đổi vì bất kỳ lý do gì. Để phát hiện một cách kéo bản đồ đúng cách, bạn phải thêm một UIPanGestureRecognizer. Btw, đây là trình nhận dạng cử chỉ kéo (panning = kéo).

Bước 1: Thêm trình nhận dạng cử chỉ trong viewDidLoad:

-(void) viewDidLoad {
    [super viewDidLoad];
    UIPanGestureRecognizer* panRec = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(didDragMap:)];
    [panRec setDelegate:self];
    [self.mapView addGestureRecognizer:panRec];
}

Bước 2: Thêm giao thức UIGestureRecognizerDelegate vào bộ điều khiển chế độ xem để nó hoạt động như một đại biểu.

@interface MapVC : UIViewController <UIGestureRecognizerDelegate, ...>

Bước 3: Và thêm mã sau để UIPanGestureRecognizer hoạt động với các trình nhận dạng cử chỉ đã có trong MKMapView:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
    return YES;
}

Bước 4: Trong trường hợp bạn muốn gọi phương thức của mình một lần thay vì 50 lần mỗi lần kéo, hãy phát hiện trạng thái "kéo đã kết thúc" trong bộ chọn của bạn:

- (void)didDragMap:(UIGestureRecognizer*)gestureRecognizer {
    if (gestureRecognizer.state == UIGestureRecognizerStateEnded){
        NSLog(@"drag ended");
    }
}

Tôi biết đây là một bài đăng khá cũ nhưng tôi thích ý tưởng của bạn ở trên, tôi đã cố gắng tổ chức ứng dụng của mình với phương thức regionDidChange của riêng mình với việc triển khai của mình và khi tôi thấy nó, tất cả đã nhấp vào và bạn đã đúng rằng regionDidChange kích hoạt vì bất kỳ lý do nào không phải là lý tưởng với điều này, tôi có thể nhận được bản đồ có thể làm chính xác những gì tôi muốn vì vậy Kudos cho điều này!
Alex McPherson

3
Nếu bạn muốn nhúm bắt quá, bạn sẽ muốn thêm một UIPinchGestureRecognizercũng
Gregory Cosmo Haun

32
Lưu ý rằng cuộn chế độ xem bản đồ mang theo động lượng và ví dụ trên sẽ được kích hoạt ngay sau khi cử chỉ kết thúc nhưng trước khi chế độ xem bản đồ ngừng di chuyển. Có thể có một cách tốt hơn, nhưng những gì tôi đã làm là đặt một cờ khi cử chỉ dừng, readyForUpdaterồi kiểm tra cờ đó - (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated.
tvon

14
Lưu ý rằng người dùng có thể nhấn đúp vào một hoặc hai ngón tay để thu phóng, thao tác này sẽ thay đổi khu vực nhưng sẽ không gọi trình nhận dạng pan này.
SomeGuy

2
Tại sao giải pháp này ở dưới cùng? Đó là một trong những tốt nhất! Đúng vậy , giải pháp của @mobi đơn giản hơn nhưng giải pháp này an toàn hơn.
Leslie Godwin

77

Đây là cách duy nhất phù hợp với tôi để phát hiện các thay đổi xoay cũng như thu phóng do người dùng thực hiện:

- (BOOL)mapViewRegionDidChangeFromUserInteraction
{
    UIView *view = self.mapView.subviews.firstObject;
    //  Look through gesture recognizers to determine whether this region change is from user interaction
    for(UIGestureRecognizer *recognizer in view.gestureRecognizers) {
        if(recognizer.state == UIGestureRecognizerStateBegan || recognizer.state == UIGestureRecognizerStateEnded) {
            return YES;
        }
    }

    return NO;
}

static BOOL mapChangedFromUserInteraction = NO;

- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated
{
    mapChangedFromUserInteraction = [self mapViewRegionDidChangeFromUserInteraction];

    if (mapChangedFromUserInteraction) {
        // user changed map region
    }
}

- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
{
    if (mapChangedFromUserInteraction) {
        // user changed map region
    }
}

2
Điều này phù hợp với tôi, nhưng cần lưu ý rằng điều này phụ thuộc vào việc triển khai nội bộ MKMapViewtrong iOS. Việc triển khai đó có thể thay đổi trong bất kỳ bản cập nhật iOS nào vì nó không phải là một phần của API.
progrmr

Điều này hoạt động và tôi thích nó hơn câu trả lời hàng đầu vì nó không làm thay đổi những gì ở đó.
QED

Cảm ơn giải pháp thanh lịch của mã so với thao tác trên bản đồ người dùng.
djneely

32

(Chỉ là) Phiên bản Swift của giải pháp tuyệt vời của @ mobi :

private var mapChangedFromUserInteraction = false

private func mapViewRegionDidChangeFromUserInteraction() -> Bool {
    let view = self.mapView.subviews[0]
    //  Look through gesture recognizers to determine whether this region change is from user interaction
    if let gestureRecognizers = view.gestureRecognizers {
        for recognizer in gestureRecognizers {
            if( recognizer.state == UIGestureRecognizerState.Began || recognizer.state == UIGestureRecognizerState.Ended ) {
                return true
            }
        }
    }
    return false
}

func mapView(mapView: MKMapView, regionWillChangeAnimated animated: Bool) {
    mapChangedFromUserInteraction = mapViewRegionDidChangeFromUserInteraction()
    if (mapChangedFromUserInteraction) {
        // user changed map region
    }
}

func mapView(mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
    if (mapChangedFromUserInteraction) {
        // user changed map region
    }
}

2
Ngoại hình đẹp, nhưng tôi đã phải thay đổi self.mapView.subviews[0]đểself.mapView.subviews[0] as! UIView
kmurph79

3
Giải pháp này (và của moby) không quá xuất sắc. Không có gì đảm bảo rằng Apple sẽ duy trì lượt xem phụ đầu tiên của mapViews. Có thể ở một số phiên bản trong tương lai, subView đầu tiên của mapView sẽ không phải là UIView. Vì vậy, mã của bạn không phải là lỗi chống lại sự cố. Cố gắng thêm GestureRecognizers của riêng bạn vào MapView.
Samet DEDE

1
Đáng chú ý, để làm việc này tôi đã có thêm self.map.delegate = selftới viewDidLoad
tylerSF

17

Giải pháp Swift 3 cho câu trả lời của Jano ở trên:

Thêm giao thức UIGestureRecognizerDelegate vào ViewController của bạn

class MyViewController: UIViewController, UIGestureRecognizerDelegate

Tạo UIPanGestureRecognizer trong viewDidLoadvà đặt delegatethành tự

viewDidLoad() {
    // add pan gesture to detect when the map moves
    let panGesture = UIPanGestureRecognizer(target: self, action: #selector(self.didDragMap(_:)))

    // make your class the delegate of the pan gesture
    panGesture.delegate = self

    // add the gesture to the mapView
    mapView.addGestureRecognizer(panGesture)
}

Thêm phương thức Giao thức để trình nhận dạng cử chỉ của bạn sẽ hoạt động với các cử chỉ MKMapView hiện có

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    return true
}

Thêm phương thức sẽ được gọi bởi bộ chọn trong cử chỉ xoay của bạn

func didDragMap(_ sender: UIGestureRecognizer) {
    if sender.state == .ended {

        // do something here

    }
}

Thiss là giải pháp! Cảm ơn!
Hilalkah,

8

Theo kinh nghiệm của tôi, tương tự như "tìm kiếm trong khi nhập", tôi thấy bộ đếm thời gian là giải pháp đáng tin cậy nhất. Nó loại bỏ nhu cầu thêm các bộ nhận dạng cử chỉ bổ sung để quét, chụm, xoay, chạm, chạm hai lần, v.v.

Giải pháp rất đơn giản:

  1. Khi khu vực bản đồ thay đổi, hãy đặt / đặt lại bộ hẹn giờ
  2. Khi bộ hẹn giờ kích hoạt, hãy tải các điểm đánh dấu cho vùng mới

    import MapKit
    
    class MyViewController: MKMapViewDelegate {
    
        @IBOutlet var mapView: MKMapView!
        var mapRegionTimer: NSTimer?
    
        // MARK: MapView delegate
    
        func mapView(mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
            setMapRegionTimer()
        }
    
        func setMapRegionTimer() {
            mapRegionTimer?.invalidate()
            // Configure delay as bet fits your application
            mapRegionTimer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "mapRegionTimerFired:", userInfo: nil, repeats: false)
        }
    
        func mapRegionTimerFired(sender: AnyObject) {
            // Load markers for current region:
            //   mapView.centerCoordinate or mapView.region
        }
    
    }
    

7

Một giải pháp khả thi khác là triển khai touchMved: (hoặc touchEnded:, v.v.) trong bộ điều khiển chế độ xem giữ chế độ xem bản đồ của bạn, như sau:

-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesMoved:touches withEvent:event];

    for (UITouch * touch in touches) {
        CGPoint loc = [touch locationInView:self.mapView];
        if ([self.mapView pointInside:loc withEvent:event]) {
            #do whatever you need to do
            break;
        }
    }
}

Điều này có thể đơn giản hơn so với việc sử dụng công cụ nhận dạng cử chỉ, trong một số trường hợp.


6

Bạn cũng có thể thêm trình nhận dạng cử chỉ vào bản đồ của mình trong Trình tạo giao diện. Liên kết nó với một ổ cắm cho hành động của nó trong viewController của bạn, tôi đã gọi là "mapDrag" của tôi ...

Sau đó, bạn sẽ làm một cái gì đó như thế này trong viewController's .m:

- (IBAction)mapDrag:(UIPanGestureRecognizer *)sender {
    if(sender.state == UIGestureRecognizerStateBegan){
        NSLog(@"drag started");
    }
}

Hãy chắc chắn rằng bạn cũng có cái này ở đó:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
    return YES;
}

Tất nhiên, bạn sẽ phải đặt viewController của mình thành UIGestureRecognizerDelegate trong tệp .h của bạn để điều đó hoạt động.

Nếu không, người phản hồi của bản đồ là người duy nhất nghe thấy sự kiện cử chỉ.


hoàn hảo cho giải pháp bảng phân cảnh. Làm tốt lắm vớiUIGestureRecognizerStateBegan
Jakub

5

Để nhận biết thời điểm kết thúc bất kỳ cử chỉ nào trên mapview:

[ https://web.archive.org/web/20150215221143/http://b2cloud.com.au/tutorial/mkmapview-deterfining-whether-region-change-is-from-user-interaction/ )

Điều này rất hữu ích để chỉ thực hiện một truy vấn cơ sở dữ liệu sau khi người dùng đã thực hiện xong việc thu phóng / xoay / kéo bản đồ xung quanh.

Đối với tôi, phương thức regionDidChangeAnimated chỉ được gọi sau khi thực hiện xong cử chỉ và không bị gọi nhiều lần khi kéo / thu phóng / xoay, nhưng sẽ rất hữu ích nếu biết đó có phải là do cử chỉ hay không.



Phương pháp này đã không làm việc cho tôi. Ngay sau khi khu vực mapView thay đổi từ mã, nó sẽ kích hoạt rằng đó là từ người dùng ...
Maksim Kniazev

5

Rất nhiều giải pháp này nằm trong mục tiêu hack / không phải những gì Swift dự định, vì vậy tôi đã chọn một giải pháp sạch hơn.

Tôi chỉ đơn giản là phân lớp MKMapView và ghi đè lên các lần chạm vào Đã xóa. Mặc dù đoạn mã này không bao gồm nó, nhưng tôi khuyên bạn nên tạo một đại diện hoặc thông báo để chuyển bất kỳ thông tin nào bạn muốn về phong trào.

import MapKit

class MapView: MKMapView {
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesMoved(touches, with: event)

        print("Something moved")
    }
}

Bạn sẽ cần cập nhật lớp trên các tệp bảng phân cảnh của mình để trỏ đến lớp con này, cũng như sửa đổi bất kỳ bản đồ nào bạn tạo thông qua các phương tiện khác.

Như đã lưu ý trong các bình luận, Apple không khuyến khích việc sử dụng phân lớp MKMapView. Mặc dù điều này tùy thuộc vào quyết định của nhà phát triển, nhưng cách sử dụng cụ thể này không làm thay đổi hành vi của bản đồ và đã hoạt động với tôi mà không xảy ra sự cố trong hơn ba năm. Tuy nhiên, hiệu suất trong quá khứ không cho thấy khả năng tương thích trong tương lai, vì vậy hãy báo trước .


1
Đây có vẻ là giải pháp tốt nhất. Tôi đã thử nghiệm nó và có vẻ như hoạt động tốt. Tôi nghĩ tốt cho những người khác nên biết rằng Apple khuyên không nên phân lớp MKMapView: "Mặc dù bạn không nên phân lớp con của chính lớp MKMapView, nhưng bạn có thể nhận thông tin về hành vi của chế độ xem bản đồ bằng cách cung cấp một đối tượng ủy quyền." Liên kết: developer.apple.com/documentation/mapkit/mkmapview . Tuy nhiên, tôi không có quan điểm mạnh mẽ về việc bỏ qua lời khuyên của họ để không phân cấp MKMapView và tôi sẵn sàng tìm hiểu thêm từ những người khác về điều này.
Andrej

1
"Đây có vẻ là giải pháp tốt nhất mặc dù Apple nói rằng đừng làm điều đó" có vẻ như nó không phải là giải pháp tốt nhất.
dst3p

3

Câu trả lời của Jano phù hợp với tôi, vì vậy tôi nghĩ rằng tôi sẽ để lại một phiên bản cập nhật cho Swift 4 / XCode 9 vì tôi không đặc biệt thành thạo về Objective C và tôi chắc chắn rằng có một số người khác cũng vậy.

Bước 1: Thêm mã này vào viewDidLoad:

let panGesture = UIPanGestureRecognizer(target: self, action: #selector(didDragMap(_:)))
panGesture.delegate = self

Bước 2: Đảm bảo rằng lớp của bạn tuân thủ UIGestureRecognizerDelegate:

class MapViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate, UIGestureRecognizerDelegate {

Bước 3: Thêm chức năng sau để đảm bảo panGesture của bạn sẽ hoạt động đồng thời với các cử chỉ khác:

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    return true
}

Bước 4: Và đảm bảo rằng phương pháp của bạn không được gọi là "50 lần mỗi lần kéo" như Jano đã chỉ ra đúng:

@objc func didDragMap(_ gestureRecognizer: UIPanGestureRecognizer) {
    if (gestureRecognizer.state == UIGestureRecognizerState.ended) {
        redoSearchButton.isHidden = false
        resetLocationButton.isHidden = false
    }
}

* Lưu ý thêm @objc ở bước cuối cùng. XCode sẽ buộc tiền tố này trên hàm của bạn để nó biên dịch.


3

Bạn có thể kiểm tra thuộc tính động nếu sai thì bản đồ do người dùng kéo

 func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
    if animated == false {
        //user dragged map
    }
}

Người dùng có thể đã phóng to bản đồ.
Victor Engel

2

Tôi biết đây là một bài viết cũ nhưng đây là mã Swift 4/5 câu trả lời của Jano với cử chỉ Pan và Pinch.

class MapViewController: UIViewController, MapViewDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()

        let panGesture = UIPanGestureRecognizer(target: self, action: #selector(self.didDragMap(_:)))
        let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(self.didPinchMap(_:)))
        panGesture.delegate = self
        pinchGesture.delegate = self
        mapView.addGestureRecognizer(panGesture)
        mapView.addGestureRecognizer(pinchGesture)
    }

}

extension MapViewController: UIGestureRecognizerDelegate {

    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }

    @objc func didDragMap(_ sender: UIGestureRecognizer) {
        if sender.state == .ended {
            //code here
        }
    }

    @objc func didPinchMap(_ sender: UIGestureRecognizer) {
        if sender.state == .ended {
            //code here
        }
    }
}

Thưởng thức!


Như đã nói trước đó, tính năng này không nhận dạng thu phóng nhưng nó phải là Good Enough ™ cho hầu hết.
Skoua

1
Nó hoạt động ngay cả khi phóng to. Tôi vừa thử nghiệm nó;)
Baran Emre

1

Tôi đã cố gắng có một chú thích ở trung tâm của bản đồ luôn ở trung tâm của bản đồ bất kể mục đích sử dụng là gì. Tôi đã thử một số cách tiếp cận được đề cập ở trên, và không có cách nào trong số chúng đủ tốt. Cuối cùng tôi đã tìm ra một cách rất đơn giản để giải quyết vấn đề này, mượn câu trả lời của Anna và kết hợp với câu trả lời của Eneko. Về cơ bản, nó coi regionWillChangeAnimated là nơi bắt đầu kéo và regionDidChangeAnimated là cuối của một và sử dụng bộ đếm thời gian để cập nhật ghim trong thời gian thực:

var mapRegionTimer: Timer?
public func mapView(_ mapView: MKMapView, regionWillChangeAnimated animated: Bool) {
    mapRegionTimer?.invalidate()
    mapRegionTimer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true, block: { (t) in
        self.myAnnotation.coordinate = CLLocationCoordinate2DMake(mapView.centerCoordinate.latitude, mapView.centerCoordinate.longitude);
        self.myAnnotation.title = "Current location"
        self.mapView.addAnnotation(self.myAnnotation)
    })
}
public func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
    mapRegionTimer?.invalidate()
}

0

nhập mã vào đây Tôi đã quản lý để thực hiện điều này theo cách dễ dàng nhất, xử lý tất cả các tương tác với bản đồ (nhấn / nhấn đúp / nhấn N bằng 1/2 / N ngón tay, xoay bằng 1/2 / N ngón tay, chụm và xoay

  1. Tạo gesture recognizervà thêm vào vùng chứa của chế độ xem bản đồ
  2. Đặt gesture recognizer's delegatethành một số triển khai đối tượngUIGestureRecognizerDelegate
  3. gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch)Phương pháp triển khai
private func setupGestureRecognizers()
{
    let gestureRecognizer = UITapGestureRecognizer(target: nil, action: nil)
    gestureRecognizer.delegate = self
    self.addGestureRecognizer(gestureRecognizer)
}   

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool
{
    self.delegate?.mapCollectionViewBackgroundTouched(self)
    return false
}

-1

Trước tiên , hãy đảm bảo rằng bộ điều khiển chế độ xem hiện tại của bạn là đại biểu của bản đồ. Vì vậy, hãy đặt đại biểu Chế độ xem bản đồ của bạn thành tự và thêm MKMapViewDelegatevào bộ điều khiển chế độ xem của bạn. Ví dụ bên dưới.

class Location_Popup_ViewController: UIViewController, MKMapViewDelegate {
   // Your view controller stuff
}

Và thêm cái này vào chế độ xem bản đồ của bạn

var myMapView: MKMapView = MKMapView()
myMapView.delegate = self

Thứ hai , thêm chức năng này được kích hoạt khi bản đồ được di chuyển. Nó sẽ lọc ra bất kỳ hoạt ảnh nào và chỉ kích hoạt nếu được tương tác với.

func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
   if !animated {
       // User must have dragged this, filters out all animations
       // PUT YOUR CODE HERE
   }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.