版本記錄
版本號 | 時間 |
---|---|
V1.0 | 2018.10.14 星期日 |
前言
MapKit框架直接從您的應用界面顯示地圖或衛星圖像,調出興趣點,并確定地圖坐標的地標信息。接下來幾篇我們就一起看一下這個框架。感興趣的看下面幾篇文章。
1. MapKit框架詳細解析(一) —— 基本概覽(一)
2. MapKit框架詳細解析(二) —— 基本使用簡單示例(一)
Parsing JSON Data into Artwork Objects - 將JSON數據解析為Artwork對象
現在您已了解如何在地圖上顯示一件artwork
,以及如何從pin
的標注信息按鈕啟動Maps
應用程序,現在是時候將數據集解析為一個Artwork
對象數組。 然后,您將它們作為annotations
添加到地圖視圖中,以顯示位于當前地圖區域中的所有藝術品。
將這個可用的初始化程序添加到初始化程序下面的Artwork.swift
:
init?(json: [Any]) {
// 1
self.title = json[16] as? String ?? "No Title"
self.locationName = json[12] as! String
self.discipline = json[15] as! String
// 2
if let latitude = Double(json[18] as! String),
let longitude = Double(json[19] as! String) {
self.coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
} else {
self.coordinate = CLLocationCoordinate2D()
}
}
這是你正在做的事情:
- 1)
json
參數是表示藝術作品的數組之一 -Any
對象的數組。 如果計算數組的元素,您將看到title
,locationName
等位于此方法中指定的索引處。 某些artworks
的title
字段為null
,因此您為title
值提供默認值。 - 2)
json
數組中的緯度和經度值是字符串:如果可以從它們創建Double
對象,則創建CLLocationCoordinate2D
。
換句話說,這個初始化器轉換一個這樣的數組:
[ 55, "8492E480-43E9-4683-927F-0E82F3E1A024", 55, 1340413921, "436621", 1340413921, "436621", "{\n}", "Sean Browne", "Gift of the Oahu Kanyaku Imin Centennial Committee", "1989", "Large than life-size bronze figure of King David Kalakaua mounted on a granite pedestal. Located at Waikiki Gateway Park.", "Waikiki Gateway Park", "http://hiculturearts.pastperfect-online.com/34250images/002/199103-3.JPG", "1991.03", "Sculpture", "King David Kalakaua", "Full", "21.283921", "-157.831661", [ null, "21.283921", "-157.831661", null, false ], null ]
進入您之前創建的Artwork
對象:
locationName: “Waikiki Gateway Park”
discipline: “Sculpture”
title: “King David Kalakaua”
coordinate with latitude: 21.283921 longitude: -157.831661
要使用此初始化程序,請打開ViewController.swift
,并將以下屬性添加到類中 - 一個用于保存JSON
文件中的Artwork
對象的數組:
var artworks: [Artwork] = []
接下來,將以下輔助方法添加到類中:
func loadInitialData() {
// 1
guard let fileName = Bundle.main.path(forResource: "PublicArt", ofType: "json")
else { return }
let optionalData = try? Data(contentsOf: URL(fileURLWithPath: fileName))
guard
let data = optionalData,
// 2
let json = try? JSONSerialization.jsonObject(with: data),
// 3
let dictionary = json as? [String: Any],
// 4
let works = dictionary["data"] as? [[Any]]
else { return }
// 5
let validWorks = works.flatMap { Artwork(json: $0) }
artworks.append(contentsOf: validWorks)
}
以下是您在此代碼中所做的事情:
- 1) 您將
PublicArt.json
文件讀入Data
對象。 - 2) 您使用
JSONSerialization
來獲取JSON對象。 - 3) 您檢查JSON對象是具有
String
鍵和Any
值的字典。 - 4) 您只對其鍵為
data
的JSON對象感興趣。 - 5) 您使用剛剛添加到
Artwork
類的可用初始化程序對這個數組進行flatmap
,并將生成的validWorks
附加到artwork
數組中。
Plotting the Artworks - 繪制Artworks
您現在擁有數據集中所有公共藝術作品的數組,您將添加到地圖中。
仍然在ViewController.swift
中,在viewDidLoad()
的末尾添加以下代碼:
loadInitialData()
mapView.addAnnotations(artworks)
注意:請務必使用復數
addAnnotations
,而不是單數addAnnotation
!
注釋或刪除創建單個“King David Kalakaua”
地圖annotation
的行 - 您現在不需要它們,現在loadInitialData
創建artworks
數組:
// let artwork = Artwork(title: "King David Kalakaua",
// locationName: "Waikiki Gateway Park",
// discipline: "Sculpture",
// coordinate: CLLocationCoordinate2D(latitude: 21.283921, longitude: -157.831661))
// mapView.addAnnotation(artwork)
Build并運行您的應用程序并檢查所有標記!
移動地圖以查看其他標記。 例如,在您的初始位置的北面,在1號高速公路上方,是Honolulu’s Pioneer Artesian Well
:
注意:標記的西北部是
Punahou
學校,它聲稱一位前美國總統作為校友! 西部的標記是他出生的醫院。
點擊一個標記打開其callout
氣泡,然后點擊其信息按鈕啟動Maps
應用程序 - 是的,您使用King Kalakaua
雕像所做的一切都適用于所有這些新作品!
注意:感謝Dave Mark指出 Apple recommends adding all the annotations right away,無論它們是否在地圖區域中可見 - 當您移動地圖時,它會自動顯示可見
annotations
。
就是這樣! 您已經構建了一個應用程序,將JSON
文件解析為一artworks
數組,然后將其顯示為注釋標記(annotation markers)
,并帶有一個用于啟動Maps
應用程序的callout info
按鈕!
Customizing Annotations - 自定義注釋
1. Markers with Color-Coding & Text - 帶有顏色編碼和文本的標記
還記得Artwork
類中的discipline
屬性嗎? 它的價值觀就像“Sculpture”
和“Mural”
- 實際上,最多的disciplines
是雕塑,牌匾,壁畫和紀念碑。 對標記進行顏色編碼很容易,因此這些disciplines
具有不同顏色的標記,所有其他disciplines
都有綠色標記。
在Artwork.swift
中,添加以下屬性:
// markerTintColor for disciplines: Sculpture, Plaque, Mural, Monument, other
var markerTintColor: UIColor {
switch discipline {
case "Monument":
return .red
case "Mural":
return .cyan
case "Plaque":
return .blue
case "Sculpture":
return .purple
default:
return .green
}
}
現在,您可以繼續向mapView(_:viewFor :)
添加代碼,但這會使視圖控制器變得混亂。 有一種更優雅的方式,類似于您可以為表視圖單元格執行的操作。 創建一個名為ArtworkViews.swift
的新Swift文件,并在import
語句下面添加此代碼:
import MapKit
class ArtworkMarkerView: MKMarkerAnnotationView {
override var annotation: MKAnnotation? {
willSet {
// 1
guard let artwork = newValue as? Artwork else { return }
canShowCallout = true
calloutOffset = CGPoint(x: -5, y: 5)
rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
// 2
markerTintColor = artwork.markerTintColor
glyphText = String(artwork.discipline.first!)
}
}
}
很快,您將此類注冊為Artwork annotations
的可重用annotation
視圖。 系統會將注釋作為newValue
傳遞給你,所以這就是你正在做的事情:
- 1) 這些行與
mapView(_:viewFor :)
完成相同的操作,配置callout
。 - 2) 然后設置標記的色調顏色,并將其
pin icon(glyph)
替換為annotation’s discipline
的第一個字母。
現在切換到ViewController.swift
,并在調用loadInitialData()
之前將此行添加到viewDidLoad()
:
mapView.register(ArtworkMarkerView.self,
forAnnotationViewWithReuseIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier)
在這里,您使用地圖視圖的默認重用標識符注冊新類。 對于具有更多annotation
類型的應用程序,您將使用自定義標識符注冊類。
向下滾動到擴展,并注釋掉mapView(_:viewFor :)
方法。
Build并運行您的應用程序,然后移動地圖,以查看不同顏色和標記的標記:
在地圖的這一部分中,實際上有比地圖視圖更多的artworks
:它通過聚類太靠近的標記來減少混亂。 在下一節中,您將看到所有annotations
。
但首先,設置字形的圖像而不是文本。 將以下屬性添加到Artwork.swift
:
var imageName: String? {
if discipline == "Sculpture" { return "Statue" }
return "Flag"
}
來自icons8.com的這些圖片已經在Images.xcassets
中。
Build并運行您的應用程序以查看帶有圖像的不同顏色標記:
這是另一個自定義選項,你的下一個任務:用圖像替換標記!
2. Images, Not Markers - 圖像,而不是標記
在ArtworkViews.swift
中,添加以下類:
class ArtworkView: MKAnnotationView {
override var annotation: MKAnnotation? {
willSet {
guard let artwork = newValue as? Artwork else {return}
canShowCallout = true
calloutOffset = CGPoint(x: -5, y: 5)
rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
if let imageName = artwork.imageName {
image = UIImage(named: imageName)
} else {
image = nil
}
}
}
}
現在,您使用的是普通的舊MKAnnotationView
而不是MKMarkerAnnotationView
,并且視圖具有image
屬性。
回到ViewController.swift
,在viewDidLoad()
中,注冊這個新類,而不是ArtworkMarkerView
:
mapView.register(ArtworkView.self,
forAnnotationViewWithReuseIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier)
Build并運行您的應用程序以查看雕塑和標志:
現在,您沒有看到標題,但地圖視圖顯示了所有annotations
。
3. Custom Callout Accessory Views - 自定義標注附件視圖
正確的標注附件是一個信息按鈕,但點擊它會打開Maps
應用,所以現在您將更改按鈕以顯示地圖圖標。
在ArtworkView
中找到這一行:
rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
用下面代碼替換上面那一行
let mapsButton = UIButton(frame: CGRect(origin: CGPoint.zero,
size: CGSize(width: 30, height: 30)))
mapsButton.setBackgroundImage(UIImage(named: "Maps-icon"), for: UIControlState())
rightCalloutAccessoryView = mapsButton
在這里,您創建一個UIButton
,將其背景圖像設置為Images.xcassets
中iconarchive.com的Maps圖標,然后將視圖右側標注附件設置為此按鈕。
Build并運行您的應用,然后點按視圖以查看新的地圖按鈕:
最后的定制是細節標注附件(detail callout accessory)
:它是一行,足以用于短位置文本,但是如果你想要顯示大量文本呢?
在Artwork.swift
中,在init(json :)
中找到這一行:
self.locationName = json[12] as! String
用下面這行進行替換
self.locationName = json[11] as! String
在這里,您選擇了artwork
的長描述,這不適用于默認的單行細節標注附件。 現在您需要一個多行標簽:將以下代碼添加到ArtworkView
:
let detailLabel = UILabel()
detailLabel.numberOfLines = 0
detailLabel.font = detailLabel.font.withSize(12)
detailLabel.text = artwork.subtitle
detailCalloutAccessoryView = detailLabel
Build并運行您的應用,然后點按視圖以查看詳細說明:
Bonus Topic: User Location Authorization - 獎金主題:用戶位置授權
此應用程序無需要求用戶授權訪問其位置,但您可能希望將其包含在其他基于MapKit
的應用程序中。
在ViewController.swift
中,添加以下行:
let locationManager = CLLocationManager()
func checkLocationAuthorizationStatus() {
if CLLocationManager.authorizationStatus() == .authorizedWhenInUse {
mapView.showsUserLocation = true
} else {
locationManager.requestWhenInUseAuthorization()
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
checkLocationAuthorizationStatus()
}
在這里,您可以創建一個CLLocationManager
來跟蹤應用程序的授權狀態,以便訪問用戶的位置。 在checkLocationAuthorizationStatus()
中,如果您的應用已獲得授權,則tick
地圖視圖的Shows-User-Location
復選框;否則,您告訴locationManager
請求用戶授權。
注意:
locationManager
可以發出兩種授權請求:requestWhenInUseAuthorization
或requestAlwaysAuthorization
。第一個讓你的應用程序在前臺使用位置服務;第二個授權您的應用程序運行時。 Apple’s documentation不鼓勵使用“Always”
:
由于對用戶隱私的潛在負面影響,不鼓勵請求“Always”
授權。只有這樣做才能為用戶提供真正的好處,您才應該請求此級別的授權。
1. Info.plist item: important but easy to overlook! - Info.plist項目:重要但容易被忽視!
您還需要執行一項與授權相關的任務 - 如果不這樣做,您的應用程序不會崩潰,但不會出現locationManager
的請求。要使請求生效,您必須提供一條消息,向用戶解釋您的應用想要訪問其位置的原因。
在Info.plist
中,打開Information Property List
。將光標懸停在上下箭頭上,或單擊列表中的任何項目,以顯示+和 - 符號,然后單擊+符號以創建新項目。向下滾動以選擇Privacy – Location When In Use Usage Description
,然后將其值設置為類似To show you cool things nearby:
:
Build并運行。 您會在啟動時看到權限請求:
有這樣的用法說明,誰不允許訪問?
注意:從iOS 11開始,如果不提供
“When in Use”
,則無法請求“Always”
:如果您僅設置Privacy – Location Always Usage Description
,則不會顯示,并且您將收到錯誤消息“Info.plist must contain both NSLocationAlwaysAndWhenInUseUsageDescription and NSLocationWhenInUseUsageDescription keys…”
。
下面,location manager
要求“Always”
:
現在您已經了解了使用MapKit
的基礎知識,但您可以添加更多內容:地理編碼,地理圍欄,自定義地圖疊加等。 Apple的Location and Maps Programming Guide是查找其他信息的好地方。
另請參閱WWDC 2017 Session 237 MapKit中的新功能,以查找他們在iOS 11中添加的更多酷炫功能。
源碼
首先我們看一下工程文件
接著看一下sb中的內容
接著我們就看一下源碼
1. Swift源碼
1. ViewController.swift
import UIKit
import MapKit
class ViewController: UIViewController {
// MARK: - Properties
var artworks: [Artwork] = []
@IBOutlet weak var mapView: MKMapView!
let regionRadius: CLLocationDistance = 1000
// MARK: - View life cycle
override func viewDidLoad() {
super.viewDidLoad()
// set initial location in Honolulu
let initialLocation = CLLocation(latitude: 21.282778, longitude: -157.829444)
centerMapOnLocation(location: initialLocation)
mapView.delegate = self
// mapView.register(ArtworkMarkerView.self, forAnnotationViewWithReuseIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier)
mapView.register(ArtworkView.self, forAnnotationViewWithReuseIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier)
loadInitialData()
mapView.addAnnotations(artworks)
// show artwork on map
// let artwork = Artwork(title: "King David Kalakaua",
// locationName: "Waikiki Gateway Park",
// discipline: "Sculpture",
// coordinate: CLLocationCoordinate2D(latitude: 21.283921, longitude: -157.831661))
// mapView.addAnnotation(artwork)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
checkLocationAuthorizationStatus()
}
// MARK: - CLLocationManager
let locationManager = CLLocationManager()
func checkLocationAuthorizationStatus() {
if CLLocationManager.authorizationStatus() == .authorizedAlways {
mapView.showsUserLocation = true
} else {
locationManager.requestAlwaysAuthorization()
}
// if CLLocationManager.authorizationStatus() == .authorizedWhenInUse {
// mapView.showsUserLocation = true
// } else {
// locationManager.requestWhenInUseAuthorization()
// }
}
// MARK: - Helper methods
func centerMapOnLocation(location: CLLocation) {
let coordinateRegion = MKCoordinateRegionMakeWithDistance(location.coordinate,
regionRadius, regionRadius)
mapView.setRegion(coordinateRegion, animated: true)
}
func loadInitialData() {
// 1
guard let fileName = Bundle.main.path(forResource: "PublicArt", ofType: "json")
else { return }
let optionalData = try? Data(contentsOf: URL(fileURLWithPath: fileName))
guard
let data = optionalData,
// 2
let json = try? JSONSerialization.jsonObject(with: data),
// 3
let dictionary = json as? [String: Any],
// 4
let works = dictionary["data"] as? [[Any]]
else { return }
// 5
let validWorks = works.flatMap { Artwork(json: $0) }
artworks.append(contentsOf: validWorks)
}
}
// MARK: - MKMapViewDelegate
extension ViewController: MKMapViewDelegate {
// 1
// func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
// guard let annotation = annotation as? Artwork else { return nil }
// // 2
// let identifier = "marker"
// var view: MKMarkerAnnotationView
// if let dequeuedView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier)
// as? MKMarkerAnnotationView { // 3
// dequeuedView.annotation = annotation
// view = dequeuedView
// } else {
// // 4
// view = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: identifier)
// view.canShowCallout = true
// view.calloutOffset = CGPoint(x: -5, y: 5)
// view.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
// }
// return view
// }
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView,
calloutAccessoryControlTapped control: UIControl) {
let location = view.annotation as! Artwork
let launchOptions = [MKLaunchOptionsDirectionsModeKey:
MKLaunchOptionsDirectionsModeDriving]
location.mapItem().openInMaps(launchOptions: launchOptions)
}
}
2. Artwork.swift
import Foundation
import MapKit
import Contacts
class Artwork: NSObject, MKAnnotation {
let title: String?
let locationName: String
let discipline: String
let coordinate: CLLocationCoordinate2D
init(title: String, locationName: String, discipline: String, coordinate: CLLocationCoordinate2D) {
self.title = title
self.locationName = locationName
self.discipline = discipline
self.coordinate = coordinate
super.init()
}
init?(json: [Any]) {
// 1
if let title = json[16] as? String {
self.title = title
} else {
self.title = "No Title"
}
// json[11] is the long description
self.locationName = json[11] as! String
// json[12] is the short location string
// self.locationName = json[12] as! String
self.discipline = json[15] as! String
// 2
if let latitude = Double(json[18] as! String),
let longitude = Double(json[19] as! String) {
self.coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
} else {
self.coordinate = CLLocationCoordinate2D()
}
}
var subtitle: String? {
return locationName
}
// pinTintColor for disciplines: Sculpture, Plaque, Mural, Monument, other
var markerTintColor: UIColor {
switch discipline {
case "Monument":
return .red
case "Mural":
return .cyan
case "Plaque":
return .blue
case "Sculpture":
return .purple
default:
return .green
}
}
var imageName: String? {
if discipline == "Sculpture" { return "Statue" }
return "Flag"
}
// Annotation right callout accessory opens this mapItem in Maps app
func mapItem() -> MKMapItem {
let addressDict = [CNPostalAddressStreetKey: subtitle!]
let placemark = MKPlacemark(coordinate: coordinate, addressDictionary: addressDict)
let mapItem = MKMapItem(placemark: placemark)
mapItem.name = title
return mapItem
}
}
3. ArtworkViews.swift
import Foundation
import MapKit
class ArtworkMarkerView: MKMarkerAnnotationView {
override var annotation: MKAnnotation? {
willSet {
guard let artwork = newValue as? Artwork else { return }
canShowCallout = true
calloutOffset = CGPoint(x: -5, y: 5)
rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
markerTintColor = artwork.markerTintColor
// glyphText = String(artwork.discipline.first!)
if let imageName = artwork.imageName {
glyphImage = UIImage(named: imageName)
} else {
glyphImage = nil
}
}
}
}
class ArtworkView: MKAnnotationView {
override var annotation: MKAnnotation? {
willSet {
guard let artwork = newValue as? Artwork else {return}
canShowCallout = true
calloutOffset = CGPoint(x: -5, y: 5)
let mapsButton = UIButton(frame: CGRect(origin: CGPoint.zero,
size: CGSize(width: 30, height: 30)))
mapsButton.setBackgroundImage(UIImage(named: "Maps-icon"), for: UIControlState())
rightCalloutAccessoryView = mapsButton
// rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
if let imageName = artwork.imageName {
image = UIImage(named: imageName)
} else {
image = nil
}
let detailLabel = UILabel()
detailLabel.numberOfLines = 0
detailLabel.font = detailLabel.font.withSize(12)
detailLabel.text = artwork.subtitle
detailCalloutAccessoryView = detailLabel
}
}
}
下面看一下點擊info按鈕打開Maps應用的效果。
后記
本篇主要講述了基本使用簡單示例,感興趣的給個贊或者關注~~~