第八章练习 Adding Maps to the Passport App - 给护照App增加地图功能

在本章的练习中,我们要给前几章中的Passport App增加地图功能,添加一个Map View,在每个去过的国家上方都出现一个大头针(见图8-1)。当用户点击Countries Visited界面右上角的Map按钮时,出现地图界面。

Page 220 | Chapter 8: Maps and Location

首先我们打开Passport.xcodeproj文件,点击Project Navigator中的工程名字Passport,点击Capabilities,往下滑,找到Maps,打开开关(见图8-2)。

Exercise: Adding Maps to the Passport App | Page 221

在Project Navigation中会出现MapKit.framework,接着选择顶部菜单栏File -> New -> File,选择iOS下的Cocoa Touch Class,点击Next。

Subclass of选择UIViewController,Class名字为MapViewController(见图8-3),语言Swift,点击Next,保存在Passport文件夹中,点击Create。新文件MapViewController.swift出现在屏幕上。

Page 222 | Chapter 8: Maps and Location

选中文件中的绿色注释代码,删掉。注意不要误删最后的右大括号,删掉didReceiveMemoryWarning方法。

打开Main.storyboard(见图8-4),首先确认当前处于“Any,Any”这个size class下,如果不是,点击Size class,选择中间的那个格子(见图8-5)。

Exercise: Adding Maps to the Passport App | Page 223

隐藏Project Navigator和Document Outline,打开Inspector,界面调整到Table View Controller Scene右侧(见图8-6)。

Page 224 | Chapter 8: Maps and Location

在Object Library中拖拽一个View Controller,放到Table View Controller Scene右侧,然后在Object Library搜索框中输入map,拖拽一个Map View到View Controller中(见图8-7)。把Map View四周拖拽变大直到覆盖整个View。

选中Map View,从顶部菜单栏选择Editor -> Pin -> Leading Space to Superview;再次选中Map View,从顶部菜单栏选择Editor -> Pin -> Trailing Space to Superview;再次选中Map View,从顶部菜单栏选择Editor -> Pin -> Top Space to Superview;再次选中Map View,从顶部菜单栏选择Editor -> Pin -> Bottom Space to Superview。这样Map View就能够适配任何尺寸的设备了。

清空Object Library的搜索框,然后输入BarButton,选择Bar Button Item拖拽到Table View Controller Scene中,双击Bar Button Item,输入Map。按住Control键,点击Map按钮,拖拽到刚刚设置的有Map的View Controller上,弹出一个选项框,选择show选项(见图8-8)。

在Map scene中,出现了一个navigation bar。

Exercise: Adding Maps to the Passport App | Page 225

点击其中的MKMapView,Control拖拽法拖到上方的黄色圆圈(见图8-9)。

弹出一个小的菜单,选择delegate选项(见图8-10)。这样,MapViewController.swift文件能够收到MKMapView的委托通知(delegate update)了。

Page 226 | Chapter 8: Maps and Location

storyboard的设置几乎快要完成了。必须把Map scene和MapViewController.swift文件关联起来。点击Map scene上的黄色圆圈,接着打开Identity Inspector,工具栏中点击第三个图标,在Class这输入MapViewController(见图8-11)。这样,Map scene和MapViewController.swift文件就关联起来了。

隐藏Inspector打开Assistant Editor,点击Assistant Editor上方的Preview,选择Automatic,Control拖拽法把MKMapView拖到MapViewController.swift文件中(见图8-12),弹出菜单(见图8-13)。

Exercise: Adding Maps to the Passport App | Page 227

Name一栏输入myMapView,点击Connect,创建了一个IBOutlet连接。不过这里出现了红色报错,出现错误是因为Map Kit framework还没有引入到MapViewController类中,输入import MapKit即可(见图8-14)。

Page 228 | Chapter 8: Maps and Location

这时报错提示消失了。不管什么时候map出发事件,MapViewController都会收到delegate update。当然我们还需要让MapViewController使用MKMapView Delegate协议,修改增加下列代码:

class MapViewController: UIViewController, MKMapViewDelegate {

这样声明MapViewController会遵守MKMapView Delegate协议,把鼠标放到viewDidLoad方法中的super.viewDidLoad()下方。

在viewDidLoad方法中,你需要创建四个MKPointAnnotation对象,这四个对象能存储大头针需要出现的位置和显示文案。在viewDidLoad方法中添加下列代码:

let italy = MKPointAnnotation()
italy.coordinate = CLLocationCoordinate2DMake(41.8947400,12.4839000)
italy.title = "Rome, Italy"

let england = MKPointAnnotation()
england.coordinate = CLLocationCoordinate2DMake(51.5085300, -0.1257400)
england.title = "London, England"

let norway = MKPointAnnotation()
norway.coordinate = CLLocationCoordinate2DMake(59.914225, 10.75256)
norway.title = "Oslo, Norway"

let spain = MKPointAnnotation()
spain.coordinate = CLLocationCoordinate2DMake(40.41694, -3.70081) 
spain.title = "Madrid, Spain"

Exercise: Adding Maps to the Passport App | Page 229

这样就创建了MKPointAnnotation对象。第一个行代码把常量italy设置为一个MKPointAnnotation对象,coordinate属性可以设置具体的位置,CLLocationCoordinate2DMake方法获取位置的经度和纬度,然后转换成一组coordinate。最后title属性是郭靖和城市的名字,当用户点击地图上的大头针时,出现title。

四个MKPointAnnotation对象要添加到Map中,不过首先需要添加到数组中,创建一个数组,存储这四个位置,把下列代码添加到span MKPointAnnotation后面:

let locations = [italy, england, norway, spain]

这样创建了名为locations的数组,数组中有四个MKPointAnnotation对象。 添加到map中方法使用addAnnotations()方法,在之前的代码下方添加下列代码:

myMapView.addAnnotations(locations)

这行代码能够发送消息给myMapView,让其把italy,england,span和norway的地点添加到地图中。然而,我们还需要做一件事,所有的MKPointAnnotation都必须与MKPinAnnotationView配对,我们用mapView(_: viewForAnnotation:)方法来配对。把鼠标光标放到viewDidLoad方法外面的下方,输入下列代码:

func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!)-> MKAnnotationView! {

}

当一个MKPointAnnotation添加到地图中后,这个方法回应MKMapView Delegate触发事件。这样MKPointAnnotations和MKPinAnnotationViews之间完成匹配工作。在mapView(_: viewForAnnotation:)方法里面,添加下列代码(见图8-15):

func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!)-> MKAnnotationView! {
    var pin = MKPinAnnotationView(annotation: annotation,reuseIdentifier: "pinIdentifier")
    pin.canShowCallout = true
    return pin
}

Page 230 | Chapter 8: Maps and Location

每次annotation添加到地图中时,这个方法都会被调用。第一行创建变量pin,赋值 MKPinAnnotationView。 MKPinAnnotationView创建成功,annotation属性设置为annotation参数。

当前的MKPinAnnotation有参数值annotation,这样可能会造成困惑,然而,我们经常见过统一的名字使用两次代表不同的变量。关键是认识到annotation属性和annotation参数值的去边,知道两者的区别才是关键。annotation属性,在MKPinAnnotationView中可用,用来定位地图大头针的位置。而annotation参数值,是MKPinAnnotation匹配MKPinAnnotationView。

MKPinAnnotationView中的reuseIdentifier设置为pinIdentifier,这样可以回收利用MKPinAnnotationView,尤其是地图中的大头针特别多时,回收利用会有明显的优势。最后,MKPinAnnotationView的canShowCallout属性值设置为true。这样当大头针被用户点击时,能够出现泡泡效果。

把鼠标光标放到myMapView.addAnnotations(locations)下方,添加下列代码:

var myRegion = MKCoordinateRegionMakeWithDistance(italy.coordinate,5500000,5500000)
myMapView.setRegion(myRegion, animated: true)

Exercise: Adding Maps to the Passport App | Page 231

第一行是创建名为myRegion的变量,设置只为MKCoordinateRegion。可以用MKCoordinateRegionMakeWithDistance()方法创建MKCoordinateRegion。这个方法首先找到一个点,然后围绕这个点画出一块区域,这个区域就是地图呈现的区域。

这第二行代码是给map发送消息,告诉Map把区域设置成新创建的myRegion,true表示可以放大缩小,运行程序,这次模拟器选择iPhone 6 Plus(见图8-16)。

现在当App启动后,大头针就会自动出现在对应的国家上了。结束运行,选择iPad Air模拟器,运行看一下效果(见图8-17)。

Page 232 | Chapter 8: Maps and Location

如果App没有按照你想要的结果运行,或者程序有了错误或警告,不要太担心,学习的最佳方式就是试错,熟能生巧,到我们的网站上下载示例代码,对比一下代码,多试几次,直到搞定这个程序为止。

Exercise: Adding Maps to the Passport App | Page 233