iOS使用位置和方向服务(来自苹果apple官方)

本文章来自苹果官方文档,特此声明

Core Location框架为定位用户当前位置和方向(Heading)提供支持,它负责从相应的设备硬件收集信息,并以异步的方式报告给您的应用程序。数据是否可用取决于设备的类型以及所需的硬件当前是否打开,如果设备处于飞行模式,则某些硬件可能不可用。

在使用Core Location框架的接口之前,必须将CoreLocation.framework加入到您的Xcode工程中,并在相关的目标中进行连接。要访问该框架的类和头文件,还需要在相应的源代码文件的顶部包含#import 语句。更多有关如何在工程中加入框架的信息,请参见Xcode工程管理指南文档中的工程中的文件部分。

有关Core Location框架的类的一般性信息请参见Core Location框架参考

取得用户的当前位置

Core Location框架使您可以定位设备的当前位置,并将这个信息应用到程序中。该框架利用设备内置的硬件,在已有信号的基础上通过三角测量得到固定位置,然后将它报告给您的代码。在接收到新的或更为精确的信号时,该框架还对位置信息进行更新。

如果您确实需要使用Core Location框架,则务必控制在最小程度,且正确地配置位置服务。收集位置数据需要给主板上的接收装置上电,并向基站、Wi-Fi热点、或者GPS卫星查询,这个过程可能要花几秒钟的时间。此外,请求更高精度的位置数据可能需要让接收装置更长时间地处于打开状态,而长时间地打开这个硬件会耗尽设备的电池。如果位置信息不是频繁变化,通常可以先取得初始位置,然后每隔一段时间请求一次更新就可以了。如果您确实需要定期更新位置信息,也可以为位置服务设置一个最小的距离阈值,从而最小化代码必须处理的位置更新。

取得用户当前位置首先要创建CLLocationManager类的实例,并用期望的精度和阈值参数进行配置。开始接收通告则需要为该对象分配一个委托,然后调用startUpdatingLocation方法来确定用户当前位置。当新的位置数据到来时,位置管理器会通知它的委托对象。如果位置更新通告已经发送完成,您也可以直接从CLLocationManager对象获取最新的位置数据,而不需要等待新的事件。

程序清单8-7展示了定制的startUpdates方法和locationManager:didUpdateToLocation:fromLocation:委托方法的的一个实现。startUpdates方法创建一个新的位置管理器对象(如果尚未存在的话),并用它启动位置更新事件的递送(在这个实例中,locationManager变量是MyLocationGetter类中声明的成员变量,该类遵循CLLocationManagerDelegate协议。事件处理方法通过事件的时间戳来确定其延迟的程度,对于太过时的事件,该方法会直接忽略,并等待更为实时的事件。在得到足够实时的数据后,即关闭位置服务。

程序清单8-7发起和处理位置更新事件

#import 
 
@implementation MyLocationGetter
- (void)startUpdates
{
    // Create the location manager if this object does not
    // already have one.
    if (nil == locationManager)
        locationManager = [[CLLocationManager alloc] init];
 
    locationManager.delegate = self;
    locationManager.desiredAccuracy = kCLLocationAccuracyKilometer;
 
    // Set a movement threshold for new events
    locationManager.distanceFilter = 500;
 
    [locationManager startUpdatingLocation];
}
 
 
// Delegate method from the CLLocationManagerDelegate protocol.
- (void)locationManager:(CLLocationManager *)manager
    didUpdateToLocation:(CLLocation *)newLocation
    fromLocation:(CLLocation *)oldLocation
{
    // If it's a relatively recent event, turn off updates to save power
    NSDate* eventDate = newLocation.timestamp;
    NSTimeInterval howRecent = [eventDate timeIntervalSinceNow];
    if (abs(howRecent) < 5.0)
    {
        [manager stopUpdatingLocation];
 
        printf("latitude %+.6f, longitude %+.6f\n",
                newLocation.coordinate.latitude,
                newLocation.coordinate.longitude);
    }
    // else skip the event and process the next one.
}
@end

对时间戳进行检查是推荐的做法,因为位置服务通常会立即返回最后缓存的位置事件。得到一个大致的固定位置可能要花几秒钟的时间,更新之前的数据只是反映最后一次得到的数据。您也可以通过精度来确定是否希望接收位置事件。位置服务在收到精度更高的数据时,可能返回额外的事件,事件中的精度值也会反映相应的精度变化。

请注意:Core Location框架在位置请求的一开始(而不是请求返回的时候)记录时间戳。由于Core Location使用几个不同的技术来取得固定位置,位置请求返回的顺序有时可能和时间戳指示的顺序不同。这样,新事件的时间戳有时会比之前的事件还要老一点,这是正常的。Core Location框架致力于提高每个新事件的位置精度,而不考虑时间戳的值。

获取与方向有关的事件

Core Location框架支持两种获取方向信息的方法。包含GPS硬件的设备可以提供当前移动方向的大致信息,该信息和经纬度数据通过同一个位置事件进行传递。包含磁力计的设备可以通过方向对象提供更为精确的方向信息,方向对象是CLHeading类的实例。

通过GPS硬件取得大致方向的过程和“取得用户的当前位置”部分的描述是一样的,框架会向您的应用程序委托传递一个CLLocation对象,对象中的coursespeed属性声明包含相关的信息。这个接口适用于需要跟踪用户移动的大多数应用程序,比如实现汽车导航系统的导航程序。对于基于指南针或者可能需要了解用户静止时朝向的应用程序,可以请求位置管理器提供方向对象。

您的程序必须运行在包含磁力计的设备上才能接收方向对象。磁力计可以测量地球散发的磁场,进而确定设备的准确方向。虽然磁力计可能受到局部磁场(比如扬声器的永磁铁、马达、以及其它类型电子设备发出的磁场)的影响,但是Core Location框架具有足够的智能,可以过滤很多局部磁场的影响,确保方向对象包含有用的数据。

请注意:如果路线或方向信息对于您的应用程序的必须的,则应该在程序的Info.plist文件中正确地包含UIRequiredDeviceCapabilities键。这个键用于指定应用程序正常工作需要具备的设备特性,您可以用它来指定设备必须具有GPS和磁力计硬件。更多有关这个键值设置的信息请参见“信息属性列表”部分。

为了接收方向事件,您需要创建一个CLLocationManager对象,为其分配一个委托对象,并调用其startUpdatingHeading方法,如程序清单8-8所示。然而,在请求方向事件之前,应该检查一下位置管理器的headingAvailable属性,确保相应的硬件是存在的。如果该硬件不存在,应用程序应该回退到通过位置事件获取路线信息的代码路径。

程序清单8-8发起方向事件的传送

CLLocationManager* locManager = [[CLLocationManager alloc] init];
if (locManager.headingAvailable)
{
   locManager.delegate = myDelegateObject; // Assign your custom delegate object
   locManager.headingFilter = 5;
   [locManager startUpdatingHeading];
}
else
   // Use location events instead

您赋值给delegate属性的对象必须遵循CLLocationManagerDelegate协议。当一个新的方向事件到来时,位置管理器会调用locationManager:didUpdateHeading:方法,将事件传递给您的应用程序。一旦收到新的事件,应用程序应该检查headingAccuracy属性,确保刚收到的数据是有效的,具体做法如清单8-9

程序清单8-9处理方向事件

- (void)locationManager:(CLLocationManager*)manager didUpdateHeading:(CLHeading*)newHeading
{
   // If the accuracy is valid, go ahead and process the event.
   if (newHeading.headingAccuracy > 0)
   {
      CLLocationDirection theHeading = newHeading.magneticHeading;
 
      // Do something with the event data.
   }
}

CLHeading对象的magneticHeading属性包含主方向数据,且该数据一直存在。这个属性给出了相对于磁北极的方向数据,磁北极和北极不在同一个位置上。如果您希望得到相对于北极(也称为地理北极)的方向数据,则必须在startUpdatingHeading之前调用startUpdatingLocation方法来启动位置更新,然通过CLHeading对象的trueHeading属性取得相对于地理北极的方向。

显示地图和注解

iPhone OS 3.0引入了Map Kit框架。通过这个框架可以在应用程序的窗口中嵌入一个全功能的地图界面。Maps程序中的很多常见功能都包含在这个框架提供的地图支持中,您可以通过它来显示标准的街道地图、卫星图像,或两者的组合;还可以通过代码来缩放和移动地图。该框架还自动支持触摸事件,用户可以用手指缩放或移动地图。您还可以在地图中加入自己定制的注释信息,以及用框架提供的反向地理编码功能寻找和地图坐标关联的地址。

在使用Map Kit框架的功能之前,必须将MapKit.framework加入到Xcode工程中,并且在相关的目标中加以连接;在访问框架的类和头文件之前,需要在相应的源代码文件的顶部加入#import 语句。有关如何将框架加入工程的更多信息,请参见Xcode工程管理指南中的工程中的文件部分;有关Map Kit框架类的一般性信息,则请参见MapKit框架参考

重要提示:Map Kit框架使用Google的服务来提供地图数据。框架及其相关接口的使用必须遵守Google Maps/Google Earth API的服务条款,具体条款信息位于http://code.google.com/apis/maps/iphone/terms.html

在用户界面中加入地图视图

为应用程序加入地图之前,需要在应用程序的视图层次中嵌入一个MKMapView类的实例,该类为地图信息的显示和用户交互提供支持。您可以通过代码来为该类创建实例,并通过initWithFrame:方法来对其进行初始化,或者用Interface Builder将它加入到nib文件中。

地图视图也是个视图,因此您可以通过它的frame属性声明随意调整它的位置和尺寸。虽然地图视图本身没有提供任何控件,但是您可以在它的上面放置工具条或其它视图,使用户可以和地图内容进行交互。您在地图视图中加入的所有子视图的位置是不变的,不会随着地图内容的滚动而滚动。如果您希望在地图上加入定制的内容,并使它们跟着地图滚动,则必须创建注解,具体描述请参见“显示注解”部分。

MKMapView类有很多属性,可以在显示之前进行配置,其中最重要的是region属性,负责定义最初显示的地图部分及如何缩放和移动地图内容。

缩放和移动地图内容

MKMapView类的region属性控制着当前显示的地图部分。当您希望缩放和移动地图时,需要做的只是正确改变这个属性的值。这个属性包含一个MKCoordinateRegion类型的结构,其定义如下:

typedef struct {
   CLLocationCoordinate2D center;
   MKCoordinateSpan span;
} MKCoordinateRegion;

改变center域可以将地图移动到新的位置;而改变span域的值则可以实现缩放。这些域的值需要用地图坐标来指定,地图坐标用度、分、和秒来度量。对于span域,您需要通过经纬度距离来指定它的值。虽然纬度距离相对固定,每度大约111公里,但是经度距离却是随着纬度的变化而变化的。在赤道上,经度距离大约每度111公里;而在地球的极点上,这个值则接近于零。当然,您总是可以通过MKCoordinateRegionMakeWithDistance函数来创建基于公里值(而不是度数)的区域。

如果您希望在更新地图时不显示过程动画,可以直接修改regioncenterCoordinate属性的值;如果需要动画过程,则必须使用setRegion:animated:setCenterCoordinate:animated:方法。setCenterCoordinate:animated:方法可以移动地图,且避免在无意中触发缩放,而setRegion:animated:方法则可以同时缩放和移动地图。举例来说,如果您要使地图向左移动,移动距离为当前宽度的一半,则可以通过下面的代码找到地图左边界的坐标,然后将它用于中心点的设置,如下所示:

CLLocationCoordinate2D mapCenter = myMapView.centerCoordinate;
mapCenter = [myMapView convertPoint:
               CGPointMake(1, (myMapView.frame.size.height/2.0))
               toCoordinateFromView:myMapView];
[myMapView setCenterCoordinate:mapCenter animated:YES];

缩放地图则应该修改span属性的值,而不是中心坐标。减少span属性值可以使视图缩小;相反,增加该属性值可以使视图放大。换句话说,如果当前的span值是一度,将它指定为两度会使地图跨度放大两倍:

MKCoordinateRegion theRegion = myMapView.region;
 
// Zoom out
theRegion.span.longitudeDelta *= 2.0;
theRegion.span.latitudeDelta *= 2.0;
[myMapView setRegion:theRegion animated:YES];

显示用户的当前位置

Map Kit框架内置支持将用户的当前位置显示在地图上,具体做法是将地图视图对象的showsUserLocation属性值设置为YES就可以了。进行这个设置会使地图视图通过Core Location框架找到用户位置,并在地图上加入类型为MKUserLocation的注解。

在地图上加入MKUserLocation注解对象的事件会通过委托对象进行报告,这和定制注解的报告方式是一样的。如果您希望在用户位置上关联一个定制的注解视图,应该在委托对象的mapView:viewForAnnotation:方法中返回该视图。如果您希望使用缺省的注解视图,则应该在该方法中返回nil

坐标和像素之间的转换

您通常通过经纬度值来指定地图上的点,但有些时候也需要在经纬度值和地图视图对象中的像素之间进行转换。举例来说,如果您允许用户在地图表面拖动注解,定制注解视图的事件处理器代码就需要将边框坐标转换为地图坐标,以便更新关联的注解对象。MKMapView类中几个例程,用于在地图坐标和地图视图对象的本地坐标系统之间进行转换,这些例包括:

  • convertCoordinate:toPointToView:

  • convertPoint:toCoordinateFromView:

  • convertRegion:toRectToView:

  • convertRect:toRegionFromView:

有关如何处理定制注解事件的更多信息,请参见“处理注解视图中的事件”部分。

显示注解

注解是您定义并放置在地图上面的信息片段。Map Kit框架将注解实现为两个部分,即注解对象和用于显示注解的视图。大多数情况下,您需要负责提供这些定制对象,但框架也提供一些标准的注解和视图供您使用。

在地图视图上显示注解需要两个步骤:

  1. 创建注解对象并将它加入到地图视图中。

  2. 在自己的委托对象中实现mapView:viewForAnnotation:方法,并在该方法中创建相应的注解视图。

注解对象是指遵循MKAnnotation协议的任何对象。通常情况下,注解对象是相对小的数据对象,存储注解的坐标及相关信息,比如注解的名称。注解是通过协议来定义的,因此应用程序中的任何对象都可以成为注解对象。然而,在实践上,注解对象应该是轻量级的,因为在显式删除注解对象之前,地图视图会一直保存它们的引用。注意,同样的结论并不一定适用于注解视图。

在将注解显示在屏幕上时,地图视图负责确保注解对象具有相关联的注解视图,具体的方法是在注解坐标即将变为可见时调用其委托对象的mapView:viewForAnnotation:方法。但是,由于注解视图的量级通常总是比其对应的注解对象更重,所以地图对象尽可能不在内存中同时保存很多注解视图。为此,它实现了注解视图的回收机制。这个机制和表视图在滚动时回收表单元使用的机制相类似,即当一个注解视图移出屏幕时,地图视图就解除其与注解对象之间关联,将它放入重用队列。而在创建新的注解视图之前,委托的mapView:viewForAnnotation:方法应该总是调用地图对象的dequeueReusableAnnotationViewWithIdentifier:方法来检查重用队列中是否还有可用的视图对象。如果该方法返回一个正当的视图对象,您就可以对其进行再次初始化,并将它返回;否则,您再创建和返回一个新的视图对象。

添加和移除注解对象

您不应直接在地图上添加注解视图,而是应该添加注解对象,注解对象通常不是视图。注解对象可以是应用程序中遵循MKAnnotation协议的任何对象。注解对象中最重要的部分是它的coordinate属性声明,它是MKAnnotation协议必需实现的属性,用于为地图上的注解提供锚点。

往地图视图加入注解所需要的全部工作就是调用地图视图对象的addAnnotation:addAnnotations:方法。何时往地图视图加入注解以及何时为加入的注解提供用户界面由您自己来决定。您可以提供一个工具条,由用户通过工具条上的命令来创建注解,或者也可以自行编码创建注解,注解信息可能来自本地或远程的数据库信息。

如果您的应用程序需要删除某个老的注解,则在删除之前,应该调用removeAnnotation:removeAnnotations:方法将它从地图中移除。地图视图会显示它知道的所有注解,如果您不希望某些注解被显示在地图上,就需要显式地将它们删除。例如,如果您的应用程序允许用户对餐厅或本地风景点进行过滤,就需要删除与过滤条件不相匹配的所有注解。

定义注解视图

Map Kit框架提供了两个注解视图类:MKAnnotationViewMKPinAnnotationViewMKAnnotationView类是一个具体的视图,定义了所有注解视图的基本行为。MKPinAnnotationView类则是MKAnnotationView的子类,用于在关联的注解坐标点上显示一个标准的系统大头针图像。

您可以将MKAnnotationView类用于显示简单的注解,也可以从该类派生出子类,提供更多的交互行为。在直接使用该类时,您需要提供一个定制的图像,用于在地图上表示您希望显示的内容,并将它赋值给注解视图的image属性。如果您显示的内容不需要动态改变,而且不需要支持用户交互,则这种用法是非常合适的。但是,如果您需要支持动态内容和用户交互,则必须定义定制子类。

在一个定制的子类中,有两种方式可以描画动态内容:可以继续使用image属性来显示注解图像,这样或许需要设置一个定时器,负责定时改变当前的图像;也可以重载视图的drawRect:方法来显示描画您的内容,这种方法也需要设置一个定时器,以定时调用视图的setNeedsDisplay方法。

如果您通过drawRect:方法来描画内容,则必须记住:要在注解视图初始化后不久为其指定尺寸。注解视图的缺省初始化方法并不包含边框矩形参数,而是在初始化后通过您分配给image属性的图像来设置边框尺寸。如果您没有设置图像,就必须显式设置边框尺寸,您渲染的内容才会被显示。

有关如何在注解视图中支持用户交互的信息,请参见“处理注解视图的事件”部分;有关如何设置定时器的信息,则请参见Cocoa定时器编程主题

创建注解视图

您应该总是在委托对象mapView:viewForAnnotation:创建注解视图。在创建新视图之前,您应该总是调用dequeueReusableAnnotationViewWithIdentifier:方法来检查是否有可重用的视图,如果该方法返回非nil值,就应该将地图视图提供的注解分配给重用视图的annotation属性,并执行其它必要的配置,使视图处于期望的状态,然后将它返回;如果该方法返回nil,则应该创建并返回一个新的注解视图对象。

程序清单8-10mapView:viewForAnnotation:方法的一个例子实现,展示了如何为定制注解对象提供大头针注解视图。如果队列中已经存在一个大头针注解视图,该方法就将它和相应的注解对象相关联;如果重用队列中没有视图,该方法则创建一个新的视图,对其基本属性进行配置,并为插图符号配置一个附加视图。

程序清单8-10创建注解视图

- (MKAnnotationView *)mapView:(MKMapView *)mapView
                      viewForAnnotation:(id )annotation
{
    // If it's the user location, just return nil.
    if ([annotation isKindOfClass:[MKUserLocation class]])
        return nil;
 
    // Handle any custom annotations.
    if ([annotation isKindOfClass:[CustomPinAnnotation class]])
    {
        // Try to dequeue an existing pin view first.
        MKPinAnnotationView*    pinView = (MKPinAnnotationView*)[mapView
        dequeueReusableAnnotationViewWithIdentifier:@"CustomPinAnnotation"];
 
        if (!pinView)
        {
            // If an existing pin view was not available, create one
           pinView = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation
                       reuseIdentifier:@"CustomPinAnnotation"]
                             autorelease];
            pinView.pinColor = MKPinAnnotationColorRed;
            pinView.animatesDrop = YES;
            pinView.canShowCallout = YES;
 
            // Add a detail disclosure button to the callout.
            UIButton* rightButton = [UIButton buttonWithType:
                               UIButtonTypeDetailDisclosure];
            [rightButton addTarget:self action:@selector(myShowDetailsMethod:)
                               forControlEvents:UIControlEventTouchUpInside];
            pinView.rightCalloutAccessoryView = rightButton;
        }
        else
            pinView.annotation = annotation;
 
        return pinView;
    }
 
    return nil;
}

处理注解视图中的事件

虽然注解视图位于地图内容上面的特殊层中,但它们也是功能完全的视图,能够接收触摸事件。您可以通过这些事件来实现用户和注解之间的交互。比如,您可以通过视图中的触摸事件来实现注解在地图表面的拖拽行为。

请注意:由于地图被显示在一个滚动界面上,所以,在用户触击定制视图和事件最终被派发之间往往有一个小的延迟。滚动视图可以利用这个延迟来确定触摸事件是否为某种滚动手势的一部分。

随后的一系列示例代码将向您展示如何实现一个支持用户拖动的注解视图。例子中的注解视图直接在注解坐标点上显示一个公牛眼图像,并包含一个定制的附加视图,用以显示目的地的详细信息。图8-1显示注解视图的一个实例以及其包含的气泡符号。

图8-1公牛眼注解视图

程序清单8-11显示了BullseyeAnnotationView类的定义。类中包含一些正确跟踪视图移动需要的其它成员变量,以及一个指向地图视图本身的指针,指针的值是在mapView:viewForAnnotation:方法中设置的,该方法是创建或再次初始化注解视图的地方。在事件跟踪完成后,代码需要调整注解对象的地图坐标,这时需要用到地图视图对象。

程序清单8-11BullseyeAnnotationView类

@interface BullseyeAnnotationView : MKAnnotationView
{
    BOOL isMoving;
    CGPoint startLocation;
    CGPoint originalCenter;
 
    MKMapView* map;
}
 
@property (assign,nonatomic) MKMapView* map;
 
- (id)initWithAnnotation:(id )annotation;
 
@end
 
@implementation BullseyeAnnotationView
@synthesize map;
- (id)initWithAnnotation:(id )annotation
{
    self = [super initWithAnnotation:annotation
               reuseIdentifier:@"BullseyeAnnotation"];
    if (self)
    {
        UIImage*    theImage = [UIImage imageNamed:@"bullseye32.png"];
        if (!theImage)
            return nil;
 
        self.image = theImage;
        self.canShowCallout = YES;
        self.multipleTouchEnabled = NO;
        map = nil;
 
        UIButton*    rightButton = [UIButton buttonWithType:
                       UIButtonTypeDetailDisclosure];
        [rightButton addTarget:self action:@selector(myShowAnnotationAddress:)
                       forControlEvents:UIControlEventTouchUpInside];
        self.rightCalloutAccessoryView = rightButton;
    }
    return self;
}
@end

当触击事件首次到达公牛眼视图时,该类的touchesBegan:withEvent:方法会记录事件的信息,作为初始信息,如清单8-12所示。touchesMoved:withEvent:方法会利用这些信息来调整视图位置。所有的位置信息都存储在父视图的坐标空间中。

程序清单8-12跟踪视图的位置

@implementation BullseyeAnnotationView (TouchBeginMethods)
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    // The view is configured for single touches only.
    UITouch* aTouch = [touches anyObject];
    startLocation = [aTouch locationInView:[self superview]];
    originalCenter = self.center;
 
    [super touchesBegan:touches withEvent:event];
}
 
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch* aTouch = [touches anyObject];
    CGPoint newLocation = [aTouch locationInView:[self superview]];
    CGPoint newCenter;
 
    // If the user's finger moved more than 5 pixels, begin the drag.
    if ( (abs(newLocation.x - startLocation.x) > 5.0) ||
         (abs(newLocation.y - startLocation.y) > 5.0) )
         isMoving = YES;
 
    // If dragging has begun, adjust the position of the view.
    if (isMoving)
    {
        newCenter.x = originalCenter.x + (newLocation.x - startLocation.x);
        newCenter.y = originalCenter.y + (newLocation.y - startLocation.y);
        self.center = newCenter;
    }
    else    // Let the parent class handle it.
        [super touchesMoved:touches withEvent:event];
}
@end

当用户停止拖动注解视图时,您需要调整原有注解的坐标,确保视图位于新的位置。清单8-13显示了BullseyeAnnotationView类的touchesEnded:withEvent:方法,该方法通过地图成员变量将基于像素的点转化为地图坐标值。由于注解的coordinate属性通常是只读的,所以例子中的注解对象实现了一个名为changeCoordinate的定制方法,负责更新它在本地存储的值,而这个值可以通过coordinate属性取得。如果触摸事件由于某种原因被取消,touchesCancelled:withEvent:方法会使注解视图回到原来的位置。

程序清单8-13处理最后的触摸事件

@implementation BullseyeAnnotationView (TouchEndMethods)
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    if (isMoving)
    {
        // Update the map coordinate to reflect the new position.
        CGPoint newCenter = self.center;
        BullseyeAnnotation* theAnnotation = self.annotation;
        CLLocationCoordinate2D newCoordinate = [map convertPoint:newCenter
                           toCoordinateFromView:self.superview];
 
        [theAnnotation changeCoordinate:newCoordinate];
 
        // Clean up the state information.
        startLocation = CGPointZero;
        originalCenter = CGPointZero;
        isMoving = NO;
    }
    else
        [super touchesEnded:touches withEvent:event];
}
 
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
    if (isMoving)
    {
        // Move the view back to its starting point.
        self.center = originalCenter;
 
        // Clean up the state information.
        startLocation = CGPointZero;
        originalCenter = CGPointZero;
        isMoving = NO;
    }
    else
        [super touchesCancelled:touches withEvent:event];
}
@end

通过反向地理编码器获取地标信息

Map Kit框架主要处理地图坐标值。地图坐标值由经度和纬度组成的,比较易于在代码中使用,但却不是用户最容易理解的描述方式。为使用户更加易于理解,您可以通过MKReverseGeocoder类来取得与地图坐标相关联的地标信息,比如街道地址、城市、州、和国家。

MKReverseGeocoder类负责向潜在的地图服务查询指定地图坐标的信息。由于需要访问网络,反向地理编码器对象总是以异步的方式执行查询,并将结果返回给相关联的委托对象。委托对象必须遵循MKReverseGeocoderDelegate协议。

启动反向地理编码器的具体做法是首先创建一个MKReverseGeocoder类的实例,并将恰当的对象赋值给该实例的delegate属性,然后调用start方法。如果查询成功完成,您的委托就会收到带有一个MKPlacemark对象的查询结果。MKPlacemark对象本身也是注解对象—也就是说,它们采纳了MKAnnotation协议—因此如果您愿意的话,可以将它们添加到地图视图的注解列表中。

你可能感兴趣的:(iOS使用位置和方向服务(来自苹果apple官方))