- ios 定位新功能----在程序中实现定位功能
- 转:http://www.2cto.com/kf/201501/369336.html
-
Core Location是iOS SDK中一个提供设备位置的框架。可以使用三种技术来获取位置:GPS、蜂窝或WiFi。在这些技术中,GPS最为精准,如果有GPS硬件,Core Location将优先使用它。如果设备没有GPS硬件(如WiFi iPad)或使用GPS获取当前位置时失败,Core Location将退而求其次,选择使用蜂窝或WiFi。Core Location的大多数功能是由位置管理器(CLLocationManager)提供的,可以使用位置管理器来指定位置更新的频率和精度,以及开始和停止接收这些更新。
要使用位置管理器,必须首先将框架Core Location加入到项目中,再导入其接口文件:
1#
import
<corelocation corelocation.h=
""
></corelocation>
接下来,需要分配并初始化一个位置管理器实例、指定将接收位置更新的委托并启动更新:
12
3
4
CLLocationManager *locManager = [[CLLocationManager alloc] init];
locManager.delegate = self;
[locManager startUpdatingLocation];
//[locManager stopUpdatingLocation];
位置管理器委托(CLLocationManagerDelegate)有两个与位置相关的方法:
12
3
4
5
6
7
8
9
- (
void
)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
}
- (
void
)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
}
第一个方法处理定位成功,manager参数表示位置管理器实例;locations为一个数组,是位置变化的集合,它按照时间变化的顺序存放。如果想获得设备的当前位置,只需要访问数组的最后一个元素即可。集合中每个对象类型是CLLocation,它包含以下属性:
coordinate — 坐标。一个封装了经度和纬度的结构体。
altitude — 海拔高度。正数表示在海平面之上,而负数表示在海平面之下。
horizontalAccuracy — 位置的精度(半径)。位置精度通过一个圆表示,实际位置可能位于这个圆内的任何地方。这个圆是由coordinate(坐标)和horizontalAccuracy(半径)共同决定的,horizontalAccuracy的值越大,那么定义的圆就越大,因此位置精度就越低。如果horizontalAccuracy的值为负,则表明coordinate的值无效。
verticalAccuracy — 海拔高度的精度。为正值表示海拔高度的误差为对应的米数;为负表示altitude(海拔高度)的值无效。
speed — 速度。该属性是通过比较当前位置和前一个位置,并比较它们之间的时间差异和距离计算得到的。鉴于Core Location更新的频率,speed属性的值不是非常精确,除非移动速度变化很小
12
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (
void
)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
CLLocation *curLocation = [locations lastObject];
if
(curLocation.horizontalAccuracy >
0
)
{
NSLog(@
"当前位置:%.0f,%.0f +/- %.0f meters"
,curLocation.coordinate.longitude,
curLocation.coordinate.latitude,
curLocation.horizontalAccuracy);
}
if
(curLocation.verticalAccuracy >
0
)
{
NSLog(@
"当前海拔高度:%.0f +/- %.0f meters"
,curLocation.altitude,curLocation.verticalAccuracy);
}
}
应用程序开始跟踪用户的位置时,将在屏幕上显示一个是否允许定位的提示框。如果用户禁用定位服务,iOS不会禁止应用程序运行,但位置管理器将生成错误。
第二个方法处理这种定位失败,该方法的参数指出了失败的原因。如果用户禁止应用程序定位,error参数将为kCLErrorDenied;如果Core Location经过努力后无法确认位置,error参数将为kCLErrorLocationUnknown;如果没有可供获取位置的源,error参数将为kCLErrorNetwork。
通常,Core Location将在发生错误后继续尝试确定位置,但如果是用户禁止定位,它就不会这样做;在这种情况下,应使用方法stopUpdatingLocation停止位置管理器。
12
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (
void
)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
if
(error.code == kCLErrorLocationUnknown)
{
NSLog(@
"Currently unable to retrieve location."
);
}
else
if
(error.code == kCLErrorNetwork)
{
NSLog(@
"Network used to retrieve location is unavailable."
);
}
else
if
(error.code == kCLErrorDenied)
{
NSLog(@
"Permission to retrieve location is denied."
);
[manager stopUpdatingLocation];
}
}
可根据实际情况来指定位置精度。例如,对于只需确定用户在哪个国家的应用程序,没有必要要求Core Location的精度为10米。要指定精度,可在启动位置更新前设置位置管理器的desiredAccuracy。有6个表示不同精度的枚举值
12
3
4
5
6
extern
const
CLLocationAccuracy kCLLocationAccuracyBestForNavigation;
extern
const
CLLocationAccuracy kCLLocationAccuracyBest;
extern
const
CLLocationAccuracy kCLLocationAccuracyNearestTenMeters;
extern
const
CLLocationAccuracy kCLLocationAccuracyHundredMeters;
extern
const
CLLocationAccuracy kCLLocationAccuracyKilometer;
extern
const
CLLocationAccuracy kCLLocationAccuracyThreeKilometers;
对位置管理器启动更新后,更新将不断传递给位置管理器委托,直到停止更新。您无法直接控制这些更新的频率,但可使用位置管理器的属性distanceFilter进行间接控制。在启动更新前设置属性distanceFilter,它指定设备(水平或垂直)移动多少米后才将另一个更新发送给委托。下面的代码使用适合跟踪长途跋涉者的设置启动位置管理器:
12
3
4
5
CLLocationManager *locManager = [[CLLocationManager alloc] init];
locManager.delegate = self;
locManager.desiredAccuracy = kCLLocationAccuracyHundredMeters;
locManager.distanceFilter =
200
;
[locManager startUpdatingLocation];
P.s. 定位要求的精度越高、属性distanceFilter的值越小,应用程序的耗电量就越大。
位置管理器有一个headingAvailable属性,它指出设备是否装备了磁性指南针。如果该属性为YES,就可以使用Core Location来获取航向(heading)信息。接收航向更新与接收位置更新极其相似,要开始接收航向更新,可指定位置管理器委托,设置属性headingFilter以指定要以什么样的频率(以航向变化的度数度量)接收更新,并对位置管理器调用方法startUpdatingHeading:
位置管理器委托协议定义了用于接收航向更新的方法。该协议有两个与航向相关的方法:
12
3
4
5
6
7
8
9
- (BOOL)locationManagerShouldDisplayHeadingCalibration:(CLLocationManager *)manager
{
return
YES;
}
- (
void
)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading
{
}
第一个方法指定位置管理器是否向用户显示校准提示。该提示将自动旋转设备360°。由于指南针总是自我校准,因此这种提示仅在指南针读数剧烈波动时才有帮助。当设置为YES后,提示可能会分散用户的注意力,或影响用户的当前操作。
第二个方法的参数newHeading是一个CLHeading对象。CLHeading通过一组属性来提供航向读数:magneticHeading和trueHeading。这些值的单位为度,类型为CLLocationDirection,即双精度浮点数。这意味着:
如果航向为0.0,则前进方向为北;
如果航向为90.0,则前进方向为东;
如果航向为180.0,则前进方向为南;
如果航向为270.0,则前进方向为西。
CLHeading对象还包含属性headingAccuracy(精度)、timestamp(读数的测量时间)和description(这种描述更适合写入日志而不是显示给用户)。下面演示了利用这个方法处理航向更新:
12
3
4
5
6
7
8
9
- (
void
)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading
{
if
(newHeading.headingAccuracy >=
0
)
{
NSString *headingDesc = [NSString stringWithFormat:@
"%.0f degrees (true), %.0f degrees (magnetic)"
,newHeading.trueHeading,newHeading.magneticHeading];
NSLog(@
"%@"
,headingDesc);
}
}
trueHeading和magneticHeading分别表示真实航向和磁性航向。如果位置服务被关闭了,GPS和wifi就只能获取magneticHeading(磁场航向)。只有打开位置服务,才能获取trueHeading(真实航向)。
下面的代码演示了,当存在一个确定了经纬度的地点,当前位置离这个地点的距离及正确航向:
12
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#
import
"ViewController.h"
#define kDestLongitude
113.12
//精度
#define kDestLatitude
22.23
//纬度
#define kRad2Deg
57.2957795
// 180/π
#define kDeg2Rad
0.0174532925
// π/180
@interface
ViewController ()
@property
(strong, nonatomic) IBOutlet UILabel *lblMessage;
@property
(strong, nonatomic) IBOutlet UIImageView *imgView;
@property
(strong, nonatomic) CLLocationManager *locationManager;
@property
(strong, nonatomic) CLLocation *recentLocation;
-(
double
)headingToLocation:(CLLocationCoordinate2D)desired current:(CLLocationCoordinate2D)current;
@end
@implementation
ViewController
- (
void
)viewDidLoad
{
[
super
viewDidLoad];
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
self.locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers;
self.locationManager.distanceFilter =
1609
;
//1英里≈1609米
[self.locationManager startUpdatingLocation];
if
([CLLocationManager headingAvailable])
{
self.locationManager.headingFilter =
10
;
//10°
[self.locationManager startUpdatingHeading];
}
}
/*
* According to Movable Type Scripts
* http://mathforum.org/library/drmath/view/55417.html
*
* Javascript:
*
* var y = Math.sin(dLon) * Math.cos(lat2);
* var x = Math.cos(lat1)*Math.sin(lat2) -
* Math.sin(lat1)*Math.cos(lat2)*Math.cos(dLon);
* var brng = Math.atan2(y, x).toDeg();
*/
-(
double
)headingToLocation:(CLLocationCoordinate2D)desired current:(CLLocationCoordinate2D)current
{
double
lat1 = current.latitude*kDeg2Rad;
double
lat2 = desired.latitude*kDeg2Rad;
double
lon1 = current.longitude;
double
lon2 = desired.longitude;
double
dlon = (lon2-lon1)*kDeg2Rad;
double
y = sin(dlon)*cos(lat2);
double
x = cos(lat1)*sin(lat2) - sin(lat1)*cos(lat2)*cos(dlon);
double
heading=atan2(y,x);
heading=heading*kRad2Deg;
heading=heading+
360.0
;
heading=fmod(heading,
360.0
);
return
heading;
}
//处理航向
- (
void
)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading
{
if
(self.recentLocation!=nil && newHeading.headingAccuracy>=
0
)
{
CLLocation *destLocation = [[CLLocation alloc] initWithLatitude:kDestLatitude longitude:kDestLongitude];
double
course = [self headingToLocation:destLocation.coordinate current:self.recentLocation.coordinate];
double
delta = newHeading.trueHeading - course;
if
(abs(delta) <=
10
)
{
self.imgView.image = [UIImage imageNamed:@
"up_arrow.png"
];
}
else
{
if
(delta >
180
)
{
self.imgView.image = [UIImage imageNamed:@
"right_arrow.png"
];
}
else
if
(delta >
0
)
{
self.imgView.image = [UIImage imageNamed:@
"left_arrow.png"
];
}
else
if
(delta > -
180
)
{
self.imgView.image = [UIImage imageNamed:@
"right_arrow.png"
];
}
else
{
self.imgView.image = [UIImage imageNamed:@
"left_arrow.png"
];
}
}
self.imgView.hidden = NO;
}
else
{
self.imgView.hidden = YES;
}
}
//处理定位成功
- (
void
)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
CLLocation *curLocation = [locations lastObject];
if
(curLocation.horizontalAccuracy >=
0
)
{
self.recentLocation = curLocation;
CLLocation *destLocation = [[CLLocation alloc] initWithLatitude:kDestLatitude longitude:kDestLongitude];
CLLocationDistance distance = [destLocation distanceFromLocation:curLocation];
if
(distance<
500
)
{
[self.locationManager stopUpdatingLocation];
[self.locationManager stopUpdatingHeading];
self.lblMessage.text = @
"您已经到达目的地!"
;
}
else
{
self.lblMessage.text = [NSString stringWithFormat:@
"距离目的地还有%f米"
,distance];
}
}
}
//处理定位失败
- (
void
)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
if
(error.code == kCLErrorLocationUnknown)
{
NSLog(@
"Currently unable to retrieve location."
);
}
else
if
(error.code == kCLErrorNetwork)
{
NSLog(@
"Network used to retrieve location is unavailable."
);
}
else
if
(error.code == kCLErrorDenied)
{
NSLog(@
"Permission to retrieve location is denied."
);
[self.locationManager stopUpdatingLocation];
self.locationManager = nil;
}
}
- (
void
)didReceiveMemoryWarning
{
[
super
didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end