取得用户的当前位置
Core Location框架使您可以定位设备的当前位置,并将这个信息应用到程序中。该框架利用设备内置的硬件,在已有信号的基础上通过三角测量得到固定位置,然后将它报告给您的代码。在接收到新的或更为精确的信号时,该框架还对位置信息进行更新。
如果您确实需要使用Core Location框架,则务必控制在最小程度,且正确地配置位置服务。收集位置数据需要给主板上的接收装置上电,并向基站、Wi-Fi热点、或者GPS卫星查询,这个过程可能要花几秒钟的时间。此外,请求更高精度的位置数据可能需要让接收装置更长时间地处于打开状态,而长时间地打开这个硬件会耗尽设备的电池。如果位置信息不是频繁变化,通常可以先取得初始位置,然后每隔一段时间请求一次更新就可以了。如果您确实需要定期更新位置信息,也可以为位置服务设置一个最小的距离阈值,从而最小化代码必须处理的位置更新。
取得用户当前位置首先要创建CLLocationManager
类的实例,并用期望的精度和阈值参数进行配置。开始接收通告则需要为该对象分配一个委托,然后调用startUpdatingLocation
方法来确定用户当前位置。当新的位置数据到来时,位置管理器会通知它的委托对象。如果位置更新通告已经发送完成,您也可以直接从CLLocationManager
对象获取最新的位置数据,而不需要等待新的事件。
程序清单8-7展示了定制的startUpdates
方法和locationManager:didUpdateToLocation:fromLocation:
委托方法的的一个实现。startUpdates
方法创建一个新的位置管理器对象(如果尚未存在的话),并用它启动位置更新事件的递送(在这个实例中,locationManager
变量是MyLocationGetter
类中声明的成员变量,该类遵循CLLocationManagerDelegate
协议。事件处理方法通过事件的时间戳来确定其延迟的程度,对于太过时的事件,该方法会直接忽略,并等待更为实时的事件。在得到足够实时的数据后,即关闭位置服务。
对时间戳进行检查是推荐的做法,因为位置服务通常会立即返回最后缓存的位置事件。得到一个大致的固定位置可能要花几秒钟的时间,更新之前的数据只是反映最后一次得到的数据。您也可以通过精度来确定是否希望接收位置事件。位置服务在收到精度更高的数据时,可能返回额外的事件,事件中的精度值也会反映相应的精度变化。
获取与方向有关的事件
Core Location框架支持两种获取方向信息的方法。包含GPS硬件的设备可以提供当前移动方向的大致信息,该信息和经纬度数据通过同一个位置事件进行传递。包含磁力计的设备可以通过方向对象提供更为精确的方向信息,方向对象是CLHeading
类的实例。
通过GPS硬件取得大致方向的过程和“取得用户的当前位置”部分的描述是一样的,框架会向您的应用程序委托传递一个CLLocation
对象,对象中的course
和speed
属性声明包含相关的信息。这个接口适用于需要跟踪用户移动的大多数应用程序,比如实现汽车导航系统的导航程序。对于基于指南针或者可能需要了解用户静止时朝向的应用程序,可以请求位置管理器提供方向对象。
您的程序必须运行在包含磁力计的设备上才能接收方向对象。磁力计可以测量地球散发的磁场,进而确定设备的准确方向。虽然磁力计可能受到局部磁场(比如扬声器的永磁铁、马达、以及其它类型电子设备发出的磁场)的影响,但是Core Location框架具有足够的智能,可以过滤很多局部磁场的影响,确保方向对象包含有用的数据。
为了接收方向事件,您需要创建一个CLLocationManager
对象,为其分配一个委托对象,并调用其startUpdatingHeading
方法,如程序清单8-8所示。然而,在请求方向事件之前,应该检查一下位置管理器的headingAvailable
属性,确保相应的硬件是存在的。如果该硬件不存在,应用程序应该回退到通过位置事件获取路线信息的代码路径。
您赋值给delegate属性的对象必须遵循CLLocationManagerDelegate
协议。当一个新的方向事件到来时,位置管理器会调用locationManager:didUpdateHeading:
方法,将事件传递给您的应用程序。一旦收到新的事件,应用程序应该检查headingAccuracy
属性,确保刚收到的数据是有效的
CLHeading
对象的magneticHeading
属性包含主方向数据,且该数据一直存在。这个属性给出了相对于磁北极的方向数据,磁北极和北极不在同一个位置上。如果您希望得到相对于北极(也称为地理北极)的方向数据,则必须在startUpdatingHeading
之前调用startUpdatingLocation
方法来启动位置更新,然通过CLHeading
对象的trueHeading
属性取得相对于地理北极的方向。
显示地图和注解
iPhone OS 3.0引入了Map Kit框架。通过这个框架可以在应用程序的窗口中嵌入一个全功能的地图界面。Maps程序中的很多常见功能都包含在这个框架提供的地图支持中,您可以通过它来显示标准的街道地图、卫星图像,或两者的组合;还可以通过代码来缩放和移动地图。该框架还自动支持触摸事件,用户可以用手指缩放或移动地图。您还可以在地图中加入自己定制的注释信息,以及用框架提供的反向地理编码功能寻找和地图坐标关联的地址。
在使用Map Kit框架的功能之前,必须将MapKit.framework
加入到Xcode工程中,并且在相关的目标中加以连接;在访问框架的类和头文件之前,需要在相应的源代码文件的顶部加入#import
语句。有关如何将框架加入工程的更多信息,请参见Xcode工程管理指南中的工程中的文件部分;有关Map
<MapKit/MapKit.h>
Kit框架类的一般性信息,则请参见MapKit框架参考。
在用户界面中加入地图视图
为应用程序加入地图之前,需要在应用程序的视图层次中嵌入一个MKMapView
类的实例,该类为地图信息的显示和用户交互提供支持。您可以通过代码来为该类创建实例,并通过initWithFrame:
方法来对其进行初始化,或者用Interface
Builder将它加入到nib文件中。
地图视图也是个视图,因此您可以通过它的frame
属性声明随意调整它的位置和尺寸。虽然地图视图本身没有提供任何控件,但是您可以在它的上面放置工具条或其它视图,使用户可以和地图内容进行交互。您在地图视图中加入的所有子视图的位置是不变的,不会随着地图内容的滚动而滚动。如果您希望在地图上加入定制的内容,并使它们跟着地图滚动,则必须创建注解,具体描述请参见“显示注解”部分。
MKMapView
类有很多属性,可以在显示之前进行配置,其中最重要的是region
属性,负责定义最初显示的地图部分及如何缩放和移动地图内容。
缩放和移动地图内容
MKMapView
类的region
属性控制着当前显示的地图部分。当您希望缩放和移动地图时,需要做的只是正确改变这个属性的值。这个属性包含一个MKCoordinateRegion
类型的结构,其定义如下:
typedef struct { |
CLLocationCoordinate2D center; |
MKCoordinateSpan span; |
} MKCoordinateRegion; |
改变center域可以将地图移动到新的位置;而改变span域的值则可以实现缩放。这些域的值需要用地图坐标来指定,地图坐标用度、分、和秒来度量。对于span域,您需要通过经纬度距离来指定它的值。虽然纬度距离相对固定,每度大约111公里,但是经度距离却是随着纬度的变化而变化的。在赤道上,经度距离大约每度111公里;而在地球的极点上,这个值则接近于零。当然,您总是可以通过MKCoordinateRegionMakeWithDistance
函数来创建基于公里值(而不是度数)的区域。
如果您希望在更新地图时不显示过程动画,可以直接修改region
或centerCoordinate
属性的值;如果需要动画过程,则必须使用setRegion:animated:
或setCenterCoordinate:animated:
方法。setCenterCoordinate:animated:
方法可以移动地图,且避免在无意中触发缩放,而setRegion:animated:
方法则可以同时缩放和移动地图。举例来说,如果您要使地图向左移动,移动距离为当前宽度的一半,则可以通过下面的代码找到地图左边界的坐标,然后将它用于中心点的设置
显示用户的当前位置
Map Kit框架内置支持将用户的当前位置显示在地图上,具体做法是将地图视图对象的showsUserLocation
属性值设置为YES
就可以了。进行这个设置会使地图视图通过Core
Location框架找到用户位置,并在地图上加入类型为MKUserLocation
的注解。
在地图上加入MKUserLocation
注解对象的事件会通过委托对象进行报告,这和定制注解的报告方式是一样的。如果您希望在用户位置上关联一个定制的注解视图,应该在委托对象的mapView:viewForAnnotation:
方法中返回该视图。如果您希望使用缺省的注解视图,则应该在该方法中返回nil
。
坐标和像素之间的转换
您通常通过经纬度值来指定地图上的点,但有些时候也需要在经纬度值和地图视图对象中的像素之间进行转换。举例来说,如果您允许用户在地图表面拖动注解,定制注解视图的事件处理器代码就需要将边框坐标转换为地图坐标,以便更新关联的注解对象。MKMapView
类中几个例程,用于在地图坐标和地图视图对象的本地坐标系统之间进行转换,这些例包括:
有关如何处理定制注解事件的更多信息,请参见“处理注解视图中的事件”部分。
显示注解
注解是您定义并放置在地图上面的信息片段。Map Kit框架将注解实现为两个部分,即注解对象和用于显示注解的视图。大多数情况下,您需要负责提供这些定制对象,但框架也提供一些标准的注解和视图供您使用。
在地图视图上显示注解需要两个步骤:
创建注解对象并将它加入到地图视图中。
- 在自己的委托对象中实现
mapView:viewForAnnotation:
方法,并在该方法中创建相应的注解视图。
注解对象是指遵循MKAnnotation
协议的任何对象。通常情况下,注解对象是相对小的数据对象,存储注解的坐标及相关信息,比如注解的名称。注解是通过协议来定义的,因此应用程序中的任何对象都可以成为注解对象。然而,在实践上,注解对象应该是轻量级的,因为在显式删除注解对象之前,地图视图会一直保存它们的引用。注意,同样的结论并不一定适用于注解视图。
在将注解显示在屏幕上时,地图视图负责确保注解对象具有相关联的注解视图,具体的方法是在注解坐标即将变为可见时调用其委托对象的mapView:viewForAnnotation:
方法。但是,由于注解视图的量级通常总是比其对应的注解对象更重,所以地图对象尽可能不在内存中同时保存很多注解视图。为此,它实现了注解视图的回收机制。这个机制和表视图在滚动时回收表单元使用的机制相类似,即当一个注解视图移出屏幕时,地图视图就解除其与注解对象之间关联,将它放入重用队列。而在创建新的注解视图之前,委托的mapView:viewForAnnotation:
方法应该总是调用地图对象的dequeueReusableAnnotationViewWithIdentifier:
方法来检查重用队列中是否还有可用的视图对象。如果该方法返回一个正当的视图对象,您就可以对其进行再次初始化,并将它返回;否则,您再创建和返回一个新的视图对象。
添加和移除注解对象
您不应直接在地图上添加注解视图,而是应该添加注解对象,注解对象通常不是视图。注解对象可以是应用程序中遵循MKAnnotation
协议的任何对象。注解对象中最重要的部分是它的coordinate
属性声明,它是MKAnnotation
协议必需实现的属性,用于为地图上的注解提供锚点。
往地图视图加入注解所需要的全部工作就是调用地图视图对象的addAnnotation:
或addAnnotations:
方法。何时往地图视图加入注解以及何时为加入的注解提供用户界面由您自己来决定。您可以提供一个工具条,由用户通过工具条上的命令来创建注解,或者也可以自行编码创建注解,注解信息可能来自本地或远程的数据库信息。
如果您的应用程序需要删除某个老的注解,则在删除之前,应该调用removeAnnotation:
或removeAnnotations:
方法将它从地图中移除。地图视图会显示它知道的所有注解,如果您不希望某些注解被显示在地图上,就需要显式地将它们删除。例如,如果您的应用程序允许用户对餐厅或本地风景点进行过滤,就需要删除与过滤条件不相匹配的所有注解。
定义注解视图
Map Kit框架提供了两个注解视图类:MKAnnotationView
和MKPinAnnotationView
。MKAnnotationView
类是一个具体的视图,定义了所有注解视图的基本行为。MKPinAnnotationView
类则是MKAnnotationView
的子类,用于在关联的注解坐标点上显示一个标准的系统大头针图像。
您可以将MKAnnotationView
类用于显示简单的注解,也可以从该类派生出子类,提供更多的交互行为。在直接使用该类时,您需要提供一个定制的图像,用于在地图上表示您希望显示的内容,并将它赋值给注解视图的image
属性。如果您显示的内容不需要动态改变,而且不需要支持用户交互,则这种用法是非常合适的。但是,如果您需要支持动态内容和用户交互,则必须定义定制子类。
在一个定制的子类中,有两种方式可以描画动态内容:可以继续使用image属性来显示注解图像,这样或许需要设置一个定时器,负责定时改变当前的图像;也可以重载视图的drawRect:
方法来显示描画您的内容,这种方法也需要设置一个定时器,以定时调用视图的setNeedsDisplay
方法。
如果您通过drawRect:
方法来描画内容,则必须记住:要在注解视图初始化后不久为其指定尺寸。注解视图的缺省初始化方法并不包含边框矩形参数,而是在初始化后通过您分配给image
属性的图像来设置边框尺寸。如果您没有设置图像,就必须显式设置边框尺寸,您渲染的内容才会被显示。
有关如何在注解视图中支持用户交互的信息,请参见“处理注解视图的事件”部分;有关如何设置定时器的信息,则请参见Cocoa定时器编程主题。
创建注解视图
您应该总是在委托对象的mapView:viewForAnnotation:
创建注解视图。在创建新视图之前,您应该总是调用dequeueReusableAnnotationViewWithIdentifier:
方法来检查是否有可重用的视图,如果该方法返回非nil
值,就应该将地图视图提供的注解分配给重用视图的annotation
属性,并执行其它必要的配置,使视图处于期望的状态,然后将它返回;如果该方法返回nil
,则应该创建并返回一个新的注解视图对象。
处理注解视图中的事件
虽然注解视图位于地图内容上面的特殊层中,但它们也是功能完全的视图,能够接收触摸事件。您可以通过这些事件来实现用户和注解之间的交互。比如,您可以通过视图中的触摸事件来实现注解在地图表面的拖拽行为。
通过反向地理编码器获取地标信息
Map Kit框架主要处理地图坐标值。地图坐标值由经度和纬度组成的,比较易于在代码中使用,但却不是用户最容易理解的描述方式。为使用户更加易于理解,您可以通过MKReverseGeocoder
类来取得与地图坐标相关联的地标信息,比如街道地址、城市、州、和国家。
MKReverseGeocoder
类负责向潜在的地图服务查询指定地图坐标的信息。由于需要访问网络,反向地理编码器对象总是以异步的方式执行查询,并将结果返回给相关联的委托对象。委托对象必须遵循MKReverseGeocoderDelegate
协议。
启动反向地理编码器的具体做法是首先创建一个MKReverseGeocoder
类的实例,并将恰当的对象赋值给该实例的delegate
属性,然后调用start
方法。如果查询成功完成,您的委托就会收到带有一个MKPlacemark
对象的查询结果。MKPlacemark对象本身也是注解对象—也就是说,它们采纳了MKAnnotation
协议—因此如果您愿意的话,可以将它们添加到地图视图的注解列表中。