iOS Swift MapKit自定义标注

17

当我尝试放置一个标记时,我的地图视图无法加载自定义注释,遇到了一些麻烦。

import UIKit
import MapKit
import CoreLocation
class MapViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate{
@IBAction func ReportBtn(sender: AnyObject) {
    //MARK: Report Date And Time Details
    let ReportTime = NSDate()
    let TimeStamp = NSDateFormatter()
    TimeStamp.timeStyle = NSDateFormatterStyle.ShortStyle
    TimeStamp.dateStyle = NSDateFormatterStyle.ShortStyle
    TimeStamp.stringFromDate(ReportTime)
    //MARK: Default Point Annotation Begins
    let ReportAnnotation = MKPointAnnotation()
    ReportAnnotation.title = "Annotation Created"
    ReportAnnotation.subtitle = ReportTime.description
    ReportAnnotation.coordinate = locationManager.location!.coordinate
    mapView(MainMap, viewForAnnotation: ReportAnnotation)
    MainMap.addAnnotation(ReportAnnotation)
}

@IBOutlet weak var MainMap: MKMapView!
let locationManager = CLLocationManager()

override func viewDidLoad() {
    super.viewDidLoad()
    self.locationManager.requestWhenInUseAuthorization()
    self.locationManager.delegate = self
    self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
    self.locationManager.startUpdatingLocation()
    self.MainMap.showsUserLocation = true
}


//MARK: - Location Delegate Methods
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
 let location = locations.last
 let center = CLLocationCoordinate2D(latitude: location!.coordinate.latitude, longitude: location!.coordinate.longitude)
 let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 0.02, longitudeDelta: 0.02 ))
    self.MainMap.setRegion(region, animated: true)
    //self.locationManager.stopUpdatingLocation()
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError){
    print(error.localizedDescription)
}
//MARK:Custom Annotation Begins Here
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
    guard !annotation.isKindOfClass(MKUserLocation) else {
        return nil
    }
    /*if annotation.isKindOfClass(MKUserLocation){
        //emty return, guard wasn't cooperating
    }else{
    return nil
    }*/
    let annotationIdentifier = "AnnotationIdentifier"

    var annotationView: MKAnnotationView?
    if let dequeuedAnnotationView = mapView.dequeueReusableAnnotationViewWithIdentifier(annotationIdentifier){
        annotationView = dequeuedAnnotationView
        annotationView?.annotation = annotation
    }
    else{
        let av = MKAnnotationView(annotation: annotation, reuseIdentifier: annotationIdentifier)
        av.rightCalloutAccessoryView = UIButton(type: .DetailDisclosure)
        annotationView = av
    }
    if let annotationView = annotationView {
        annotationView.canShowCallout = true
        annotationView.image = UIImage(named: "image.png")
    }
    return annotationView

}
}

添加信息

我确定按钮功能完美运行。使用上面提供的代码,红色默认图钉注释会出现在正确位置。当我点击图钉时,我指定的描述也可以正常显示。我遇到的唯一问题是无法让我的图片取代无聊的默认红色图钉


4
变量名应以小写字母开头。 - dan
3
所有不以小写字母开头的其他单词。 - dan
1
它不会做任何事情,只是让你的代码更易于阅读。说实话,我甚至不知道你有什么问题,因为你在问题中从未提到过。听起来像是你想要一次代码审查。 - dan
1
@dan...那么你只读了问题的第一句话? - Ethan
1
为什么它被关闭了? - Vyachaslav Gerchicov
显示剩余6条评论
5个回答

59

我建议继承`MKPointAnnotation`。

Pokémon Pin

我只包含了显示自定义地图标记所需的必要代码。将其视为一个模板

概述

  • 我们将创建一个点注释对象,并使用CustomPointAnnotation类分配自定义图像名称。

  • 我们将子类化MKPointAnnotation以设置图像并在委托协议方法viewForAnnotation中进行分配。

  • 在设置点注释的坐标、标题和副标题后,我们将向地图添加注释视图。

  • 我们将实现viewForAnnotation方法,这是一个MKMapViewDelegate协议方法,用于在地图上显示标记。 viewForAnnotation协议方法是自定义标记视图并分配自定义图像的最佳位置。

  • 我们将为给定的标识符取消排队并返回可重用注释,并将注释强制转换为我们的自定义CustomPointAnnotation类,以便访问标记的图像名称。

  • 我们将在Assets.xcassets中创建一个新的图像集,并相应地放置image@3x.png和image@2x.png。

  • 不要忘记plist。

NSLocationAlwaysUsageDescriptionNSLocationWhenInUseUsageDescription

输入图片描述

像往常一样在真机上测试。

交换方法

//1

CustomPointAnnotation.swift

import UIKit
import MapKit

class CustomPointAnnotation: MKPointAnnotation {
var pinCustomImageName:String!
}

//2

ViewController.swift

import UIKit
import MapKit

class ViewController: UIViewController, MKMapViewDelegate,  CLLocationManagerDelegate {


@IBOutlet weak var pokemonMap: MKMapView!
let locationManager = CLLocationManager()
var pointAnnotation:CustomPointAnnotation!
var pinAnnotationView:MKPinAnnotationView!

override func viewDidLoad() {
    super.viewDidLoad()

    //Mark: - Authorization
    locationManager.delegate = self
    locationManager.desiredAccuracy = kCLLocationAccuracyBest
    locationManager.requestAlwaysAuthorization()
    locationManager.startUpdatingLocation()

    pokemonMap.delegate = self
    pokemonMap.mapType = MKMapType.Standard
    pokemonMap.showsUserLocation = true

}

func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    let location = CLLocationCoordinate2D(latitude: 35.689949, longitude: 139.697576)
    let center = location
    let region = MKCoordinateRegionMake(center, MKCoordinateSpan(latitudeDelta: 0.025, longitudeDelta: 0.025))
    pokemonMap.setRegion(region, animated: true)

    pointAnnotation = CustomPointAnnotation()
    pointAnnotation.pinCustomImageName = "Pokemon Pin"
    pointAnnotation.coordinate = location
    pointAnnotation.title = "POKéSTOP"
    pointAnnotation.subtitle = "Pick up some Poké Balls"

    pinAnnotationView = MKPinAnnotationView(annotation: pointAnnotation, reuseIdentifier: "pin")
    pokemonMap.addAnnotation(pinAnnotationView.annotation!)
}

func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
    print(error.localizedDescription)
}

//MARK: - Custom Annotation
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
    let reuseIdentifier = "pin"
    var annotationView = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseIdentifier)

    if annotationView == nil {
        annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseIdentifier)
        annotationView?.canShowCallout = true
    } else {
        annotationView?.annotation = annotation
    }

    let customPointAnnotation = annotation as! CustomPointAnnotation
    annotationView?.image = UIImage(named: customPointAnnotation.pinCustomImageName)

    return annotationView
}
}

1
那么我应该把图片的名称放在哪里呢?比如“image.png”。 - Ethan
3
png 文件的名称并不重要,因为“Pokemon Pin”是位于蓝色 Assets.xcassets 文件夹内的图像集的名称。当您选择“Pokemon Pin”图像集时,您会看到通常用于 x1、x2 和 x3 图像的占位符。这些 png 文件可以命名为任何名称。重要的是图像集的名称,因为它必须与 pointAnnotation.pinCustomImageName = "Pokemon Pin" 相匹配。 - Edison
5
以上的代码在Swift 3中无法运行...会在“let customPointAnnotation = annotation as! CustomPointAnnotation”这一行崩溃。 - the_legend_27
1
更改为 if let customPointAnnotation = annotation as? CustomPointAnnotation 以避免崩溃。 - skornos
没有起作用,地图上也没有显示针。 - Arshad Shaik
你不需要移除旧的 pointAnnotation 吗? - Vyachaslav Gerchicov

15

你需要处理一些问题。

MKMapView 和标注

首先,有必要了解 MKMapView 如何从标注显示注释视图。有以下几个方面:

  1. MKMapView:显示地图并管理标注。
  2. MKMapViewDelegate:从特定函数返回数据给 MKMapView。
  3. MKAnnotation:包含有关地图上位置的数据。
  4. MKAnnotationView:显示注释。

MKAnnotation 包含地图上位置的数据。你创建这些数据并将其提供给 MKMapView。在将来的某个时间点,当地图视图准备好显示注释时,它会回调委托对象并要求其为 MKAnnotation 创建一个 MKAnnotationView。委托对象创建和返回视图,然后地图视图将其显示出来。你可以在 storyboard 或代码中指定委托对象,例如:mapView.delegate = self

位置

跟踪用户位置的复杂之处在于:

  1. 需要得到用户授权才能启用位置跟踪。
  2. 在用户允许跟踪之后,需要等待一段时间,直到用户的位置可用。
  3. 可能尚未启用位置服务。

你的代码需要通过检查 CLLocationManager.authorizationStatus 并实现 CLLocationManagerDelegate 方法来处理授权。

请注意,要使用 requestWhenInUseAuthorization 需要在 Info.plist 中加入 NSLocationWhenInUseUsageDescription 条目。

示例

GitHub 上的示例项目

import UIKit
import MapKit
import CoreLocation

class ViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {

    // Instance of location manager. 
    // This is is created in viewDidLoad() if location services are available.
    var locationManager: CLLocationManager?

    // Last location made available CoreLocation.
    var currentLocation: MKUserLocation? {
        didSet {
            // Hide the button if no location is available.
            button.hidden = (currentLocation == nil)
        }
    }

    // Date formatter for formatting dates in annotations.
    // We use a lazy instance so that it is only created when needed.
    lazy var formatter: NSDateFormatter = {

        let formatter = NSDateFormatter()
        formatter.timeStyle = NSDateFormatterStyle.ShortStyle
        formatter.dateStyle = NSDateFormatterStyle.ShortStyle
        return formatter
    }()

    @IBOutlet var button: UIButton!
    @IBOutlet var mapView: MKMapView!

    override func viewDidLoad() {

        super.viewDidLoad()

        mapView.delegate = self

        // Track the user's location if location services are enabled.
        if CLLocationManager.locationServicesEnabled() {

            locationManager = CLLocationManager()
            locationManager?.delegate = self
            locationManager?.desiredAccuracy = kCLLocationAccuracyBest

            switch CLLocationManager.authorizationStatus() {

            case .AuthorizedAlways, .AuthorizedWhenInUse:
                // Location services authorised.
                // Start tracking the user.
                locationManager?.startUpdatingLocation()
                mapView.showsUserLocation = true

            default:
                // Request access for location services.
                // This will call didChangeAuthorizationStatus on completion. 
                locationManager?.requestWhenInUseAuthorization()
            }
        }
    }

    //
    // CLLocationManagerDelegate method
    // Called by CLLocationManager when access to authorisation changes.
    //
    func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {

        switch status {

        case .AuthorizedAlways, .AuthorizedWhenInUse:
            // Location services are authorised, track the user.
            locationManager?.startUpdatingLocation()
            mapView.showsUserLocation = true

        case .Denied, .Restricted:
            // Location services not authorised, stop tracking the user.
            locationManager?.stopUpdatingLocation()
            mapView.showsUserLocation = false
            currentLocation = nil

        default:
            // Location services pending authorisation.
            // Alert requesting access is visible at this point.
            currentLocation = nil
        }
    }

    //
    // MKMapViewDelegate method
    // Called when MKMapView updates the user's location.
    //
    func mapView(mapView: MKMapView, didUpdateUserLocation userLocation: MKUserLocation) {

        currentLocation = userLocation
    }

    @IBAction func addButtonTapped(sender: AnyObject) {

        guard let coordinate = currentLocation?.coordinate else {
            return
        }

        let reportTime = NSDate()
        let formattedTime = formatter.stringFromDate(reportTime)

        let annotation = MKPointAnnotation()
        annotation.title = "Annotation Created"
        annotation.subtitle = formattedTime
        annotation.coordinate = coordinate

        mapView.addAnnotation(annotation)
    }

    //
    // From Bhoomi's answer. 
    //
    // MKMapViewDelegate method
    // Called when the map view needs to display the annotation.
    // E.g. If you drag the map so that the annotation goes offscreen, the annotation view will be recycled. When you drag the annotation back on screen this method will be called again to recreate the view for the annotation.
    //
    func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {

        guard !annotation.isKindOfClass(MKUserLocation) else {

            return nil
        }

        let annotationIdentifier = "AnnotationIdentifier"

        var annotationView = mapView.dequeueReusableAnnotationViewWithIdentifier(annotationIdentifier)

        if annotationView == nil {
            annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: annotationIdentifier)
            annotationView!.rightCalloutAccessoryView = UIButton(type: .DetailDisclosure)
            annotationView!.canShowCallout = true
        }
        else {
            annotationView!.annotation = annotation
        }

        annotationView!.image = UIImage(named: "smile")

        return annotationView

    }
}

7

请检查您的项目包或Assets.xcassets中的image.png文件

 func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {

    guard !annotation.isKind(of: MKUserLocation.self) else {
        return nil
    }

    let annotationIdentifier = "AnnotationIdentifier"

    var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: annotationIdentifier)
    if annotationView == nil {
        annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: annotationIdentifier)
        annotationView?.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
        annotationView!.canShowCallout = true
    }
    else {
        annotationView!.annotation = annotation
    }

    annotationView!.image = UIImage(named: "image.png")

    return annotationView
}

你好!这对你有用吗? - Bhoomi Jagani
当在Storyboard上按下按钮时,我需要调用这个函数吗? - Ethan
这是地图视图的委托方法。这是用于注释(大头针)视图的方法。如果您想要在按钮点击时添加大头针,则将addAnnotation代码放入您的按钮点击中。 - Bhoomi Jagani
嗨,这对您没有用吗?如果有用的话,您可以接受我的答案。 - Bhoomi Jagani
我的Mac电脑坏了,我会在接下来的几天内收到一台新的iMac,届时我可以尝试这些解决方案。 - Ethan
显示剩余3条评论

5

以下操作可能对您有所帮助:

1)创建自定义类用于注释图钉。

class CustomPointAnnotation: MKPointAnnotation {
    var imageName: UIImage!
}

2)将变量定义如下。

var locationManager = CLLocationManager()

3)在 viewDidLoad() 中调用以下方法。

  func checkLocationAuthorizationStatus() {
        if CLLocationManager.authorizationStatus() == .AuthorizedAlways {
            map.showsUserLocation = false
        } else {
            locationManager.requestWhenInUseAuthorization()
        }
    }

4)在viewWillAppear()中加入以下代码

    self.map.delegate = self
    locationManager.desiredAccuracy = kCLLocationAccuracyBest
    locationManager.requestAlwaysAuthorization()
    locationManager.delegate = self
    dispatch_async(dispatch_get_main_queue(),{
        self.locationManager.startUpdatingLocation()
    })

5) 最重要的是实现以下方法。

 func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
        if !(annotation is CustomPointAnnotation) {
            return nil
        }

        let reuseId = "Location"

        var anView = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseId)
        if anView == nil {
            anView = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
            anView!.canShowCallout = true
        }
        else {
            anView!.annotation = annotation
        }
        let cpa = annotation as! CustomPointAnnotation
        anView!.image = cpa.imageName

        return anView
    } 

6)在您收到自定义 PIN 图像的位置执行以下代码:

   let strLat = "YOUR LATITUDE"
   let strLon = "YOUR LONGITUDE"
   let info = CustomPointAnnotation()
   info.coordinate = CLLocationCoordinate2DMake(strLat.toDouble()!,strLon.toDouble()!)
   info.imageName = resizedImage
   info.title = dict!["locationName"]! as? String
   self.map.addAnnotation(info)

“let cpa = annotation as! CustomPointAnnotation” 这就是魔法般的一行代码! - Paranoid Android

2

根据MapKit指南和代码,您的代码看起来正确。我认为可能是这一行引起了问题:annotationView.image = UIImage(named: "image.png")

image.png的名称可能错误或在编译时未添加到项目中。另外,如果您使用的是.xcassets,则不必添加.png

由于annotationView.image是可选的,因此当图像UIImage(named: "image.png")为空时,它不会崩溃,而只会呈现默认的图钉图像。

如果这不是问题,请提供您所采取的调试步骤的更多信息,以便我们其余人可以更好地理解并帮助您。干杯 =)


我所做的就是将图片复制粘贴到项目中。我需要做些其他的事情才能让它加载进来吗?虽然我知道图片名称与代码中的引用名称相同。 - Ethan
建议使用 Assets.xcassets。您可以将图像拖放到 Assets.xcassets 中,并从那里管理所有未来的资源。如果您真的不想使用它,至少要检查图像是否被添加到项目 Target > Build Phrase > Copy Bundle Resources 下的 Copy Bundle Resources 中。 - Zac Kwan
好的,我很快会检查。谢谢,希望这能起作用。 - Ethan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接