簡體   English   中英

MKUserLocation點的iOS 10方向箭頭

[英]iOS 10 heading arrow for MKUserLocation dot

現在,iOS 10中的Maps應用在MKUserLocation MKAnnotationView頂部包括一個方向箭頭。 有什么方法可以在自己的應用程序中將其添加到MKMapView

在此處輸入圖片說明

編輯:我很樂意手動執行此操作,但不確定是否可行? 我可以在地圖上添加注釋並使其跟隨用戶的位置,包括動畫移動嗎?

我也遇到了同樣的問題(需要定位指示器而不會旋轉地圖,類似於Apple Maps應用程序)。 不幸的是,Apple尚未提供“藍色標題圖標” API。

我創建了以下源自@ alku83的實現的解決方案。

  1. 確保該類符合MKViewDelegate
  2. 添加委托方法以將藍色箭頭圖標添加到地圖位置點

     func mapView(_ mapView: MKMapView, didAdd views: [MKAnnotationView]) { if views.last?.annotation is MKUserLocation { addHeadingView(toAnnotationView: views.last!) } } 
  3. 添加方法以創建“藍色箭頭圖標”。

     func addHeadingView(toAnnotationView annotationView: MKAnnotationView) { if headingImageView == nil { let image = #YOUR BLUE ARROW ICON# headingImageView = UIImageView(image: image) headingImageView!.frame = CGRect(x: (annotationView.frame.size.width - image.size.width)/2, y: (annotationView.frame.size.height - image.size.height)/2, width: image.size.width, height: image.size.height) annotationView.insertSubview(headingImageView!, at: 0) headingImageView!.isHidden = true } } 
  4. 添加var headingImageView: UIImageView? 上你的課。 主要需要變換/旋轉藍色箭頭圖像。

  5. (根據您的體系結構,在不同的類/對象中)創建一個位置管理器實例,該類符合CLLocationManagerDelegate協議

     lazy var locationManager: CLLocationManager = { let manager = CLLocationManager() // Set up your manager properties here manager.delegate = self return manager }() 
  6. 確保您的位置管理器正在跟蹤用戶標題數據locationManager.startUpdatingHeading()並在適當的locationManager.stopUpdatingHeading()時停止跟蹤

  7. 添加var userHeading: CLLocationDirection? 將保留方向值

  8. 添加要在標題值更改時得到通知的委托方法,並適當更改userHeading值

     func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) { if newHeading.headingAccuracy < 0 { return } let heading = newHeading.trueHeading > 0 ? newHeading.trueHeading : newHeading.magneticHeading userHeading = heading NotificationCenter.default.post(name: Notification.Name(rawValue: #YOUR KEY#), object: self, userInfo: nil) } 
  9. 現在,在符合MKMapViewDelegate的類中,添加方法以“轉換”標題圖像的方向

      func updateHeadingRotation() { if let heading = # YOUR locationManager instance#, let headingImageView = headingImageView { headingImageView.isHidden = false let rotation = CGFloat(heading/180 * Double.pi) headingImageView.transform = CGAffineTransform(rotationAngle: rotation) } } 

是的,您可以手動執行此操作。

基本思想是使用CLLocationManager跟蹤用戶的位置,並使用其數據在地圖上放置和旋轉注釋視圖。

這是代碼。 我省略了某些與問題沒有直接關系的事情(例如,我假設用戶已經授權您的應用進行位置訪問等),因此您可能需要對這段代碼進行一些修改

ViewController.swift

import UIKit
import MapKit

class ViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {
    @IBOutlet var mapView: MKMapView!
    lazy var locationManager: CLLocationManager = {
        let manager = CLLocationManager()
        manager.delegate = self
        return manager
    }()

    var userLocationAnnotation: UserLocationAnnotation!

    override func viewDidLoad() {
        super.viewDidLoad()

        locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation

        locationManager.startUpdatingHeading()
        locationManager.startUpdatingLocation()

        userLocationAnnotation = UserLocationAnnotation(withCoordinate: CLLocationCoordinate2D(), heading: 0.0)

        mapView.addAnnotation(userLocationAnnotation)
    }

    func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
        userLocationAnnotation.heading = newHeading.trueHeading
    }

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        userLocationAnnotation.coordinate = locations.last!.coordinate
    }

    public func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
        if let annotation = annotation as? UserLocationAnnotation {
            let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "UserLocationAnnotationView") ?? UserLocationAnnotationView(annotation: annotation, reuseIdentifier: "UserLocationAnnotationView")
            return annotationView
        } else {
            return MKPinAnnotationView(annotation: annotation, reuseIdentifier: nil)
        }
    }

}

在這里,我們進行地圖視圖的基本設置,並開始使用CLLocationManager跟蹤用戶的位置和前進方向。

UserLocationAnnotation.swift

import UIKit
import MapKit

class UserLocationAnnotation: MKPointAnnotation {
    public init(withCoordinate coordinate: CLLocationCoordinate2D, heading: CLLocationDirection) {
        self.heading = heading

        super.init()
        self.coordinate = coordinate
    }

    dynamic public var heading: CLLocationDirection
}

非常簡單的MKPointAnnotation子類,能夠存儲航向。 dynamic關鍵字是這里的關鍵。 它使我們可以觀察KVO對heading屬性的更改。

UserLocationAnnotationView.swift

import UIKit
import MapKit

class UserLocationAnnotationView: MKAnnotationView {

    var arrowImageView: UIImageView!

    private var kvoContext: UInt8 = 13

    override public init(annotation: MKAnnotation?, reuseIdentifier: String?) {
        super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)

        arrowImageView = UIImageView(image: #imageLiteral(resourceName: "Black_Arrow_Up.svg"))
        addSubview(arrowImageView)
        setupObserver()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        arrowImageView = UIImageView(image: #imageLiteral(resourceName: "Black_Arrow_Up.svg"))
        addSubview(arrowImageView)
        setupObserver()
    }

    func setupObserver() {
        (annotation as? UserLocationAnnotation)?.addObserver(self, forKeyPath: "heading", options: [.initial, .new], context: &kvoContext)
    }

    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if context == &kvoContext {
            let userLocationAnnotation = annotation as! UserLocationAnnotation
            UIView.animate(withDuration: 0.2, animations: { [unowned self] in
                self.arrowImageView.transform = CGAffineTransform(rotationAngle: CGFloat(userLocationAnnotation.heading / 180 * M_PI))
            })
        }
    }

    deinit {
        (annotation as? UserLocationAnnotation)?.removeObserver(self, forKeyPath: "heading")
    }
}

MKAnnotationView子類,用於觀察heading屬性,然后將適當的旋轉變換設置為其子視圖(在我的情況下,它只是帶有箭頭的圖像。您可以創建更復雜的注釋視圖,並只旋轉其中一部分而不是整個視圖。)

UIView.animate是可選的。 添加它可使旋轉更平滑。 CLLocationManager無法每秒觀察60次航向值,因此在快速旋轉時,動畫可能會有點斷斷續續。 UIView.animate調用解決了這個小問題。

MKPointAnnotationMKAnnotationViewMKMapView類已經為我們實現了coordinate值更新的正確處理,因此我們不必自己做。

我通過向MKUserLocation注解視圖添加子視圖來解決此問題,如下所示

func mapView(mapView: MKMapView, didAddAnnotationViews views: [MKAnnotationView]) {
if annotationView.annotation is MKUserLocation {
    addHeadingViewToAnnotationView(annotationView)
    }
}

func addHeadingViewToAnnotationView(annotationView: MKAnnotationView) {
    if headingImageView == nil {
        if let image = UIImage(named: "icon-location-heading-arrow") {
            let headingImageView = UIImageView()
            headingImageView.image = image
            headingImageView.frame = CGRectMake((annotationView.frame.size.width - image.size.width)/2, (annotationView.frame.size.height - image.size.height)/2, image.size.width, image.size.height)
            self.headingImageView = headingImageView
        }
    }

    headingImageView?.removeFromSuperview()
    if let headingImageView = headingImageView {
        annotationView.insertSubview(headingImageView, atIndex: 0)
    }

    //use CoreLocation to monitor heading here, and rotate headingImageView as required
}

我不知道為什么沒人提供delegate解決方案。 它不依賴MKUserLocation ,而是使用@Dim_ov提出的方法,即子類化MKPointAnnotationMKAnnotationView (最MKAnnotationView ,最通用的IMHO方法)。 唯一的區別是觀察者現在已被delegate方法替換。

  1. 創建delegate協議:

     protocol HeadingDelegate : AnyObject { func headingChanged(_ heading: CLLocationDirection) } 
  2. 創建用於通知委托的MKPointAnnotation子類。 headingDelegate屬性將從視圖控制器外部分配,並在每次heading屬性更改時觸發:

     class Annotation : MKPointAnnotation { weak var headingDelegate: HeadingDelegate? var heading: CLLocationDirection { didSet { headingDelegate?.headingChanged(heading) } } init(_ coordinate: CLLocationCoordinate2D, _ heading: CLLocationDirection) { self.heading = heading super.init() self.coordinate = coordinate } } 
  3. 創建實現委托的MKAnnotationView子類:

     class AnnotationView : MKAnnotationView , HeadingDelegate { required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } override init(annotation: MKAnnotation?, reuseIdentifier: String?) { super.init(annotation: annotation, reuseIdentifier: reuseIdentifier) } func headingChanged(_ heading: CLLocationDirection) { // For simplicity the affine transform is done on the view itself UIView.animate(withDuration: 0.1, animations: { [unowned self] in self.transform = CGAffineTransform(rotationAngle: CGFloat(heading / 180 * .pi)) }) } } 
  4. 考慮到您的視圖控制器同時實現了CLLocationManagerDelegateMKMapViewDelegate ,剩下要做的很少(這里不提供完整的視圖控制器代碼):

      // Delegate method of the CLLocationManager func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) { userAnnotation.heading = newHeading.trueHeading } // Delegate method of the MKMapView func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: NSStringFromClass(Annotation.self)) if (annotationView == nil) { annotationView = AnnotationView(annotation: annotation, reuseIdentifier: NSStringFromClass(Annotation.self)) } else { annotationView!.annotation = annotation } if let annotation = annotation as? Annotation { annotation.headingDelegate = annotationView as? HeadingDelegate annotationView!.image = /* arrow image */ } return annotationView } 

最重要的部分是在批注視圖對象中分配了批注的委托屬性( headingDelegate )。 這會將注釋與其視圖綁定在一起,以便每次修改heading屬性時,都會調用該視圖的headingChanged()方法。

注意:這里使用的 didSet{}willSet{}屬性觀察器是在Swift 4中首次引入的。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM