9.3 方向監測
擁有GPS硬體的裝置可以生成裝置的目前方向(course屬性)和速度資訊。iPhone裝置攜帶的定位管理器可以傳回一個已經計算好的course值,通過這個值我們可以獲得目前前進的方向,course值是0~360之間的浮點數,0°值表示正北方向,90°值表示正東方向,180°值表示正南方向,270°值表示正西方向,程式可以通過course值來檢測使用者位置的移動方向。除此之外,還可以通過磁力計來擷取裝置的真實方向。
提示:
地球是一個大磁場,磁力計的北極将會永遠真實地指向北方。
iOS系統通過heading屬性來擷取裝置的真實方向。需要指出的是,并不是所有的iOS裝置都支援heading屬性,從iPhone 3gs開始引入了磁力計,是以程式在擷取方向之前需要先測試該裝置是否支援heading。如果定位管理器支援heading屬性,那麼CLLocationManager的headingAvailable屬性将會傳回“YES”。
使用CLLocationManager擷取裝置方向與擷取移動距離的步驟基本相似,隻是此時不是檢測位置移動,而是檢測方向改變。
使用CLLocationManager擷取裝置方向的步驟如下。
建立CLLocationManager對象,該對象負責擷取定位相關資訊。并為該對象設定一些必要的屬性。
為CLLocationManager指定delegate屬性,該屬性值必須是一個實作CLLocationManagerDelegate協定的對象。實作CLLocationManagerDelegate協定時可根據需要實作協定中特定的方法。
調用CLLocationManager的startUpdatingHeading方法擷取方向資訊。擷取方向結束時,可調用stopUpdatingHeading方法結束擷取方向資訊。
當裝置的方向改變時,iOS系統将會自動激發CLLocationManager的delegate對象的locationManager:didUpdateHeading:方法,而程式可通過重寫該方法來擷取裝置方向。
iOS允許為檢測方向改變設定如下屬性。
CLLocationDegrees headingFilter:設定隻有當裝置方向的改變值超過該屬性值時才激發delegate的方法。
CLDeviceOrientation headingOrientation:設定裝置目前方向。
監聽方向時傳回的是一個CLHeading對象,該對象包含如下屬性。
magneticHeading:該屬性傳回裝置與磁北的相對方向。
trueHeading:該屬性傳回裝置與真北的相對方向。 提示:真北始終指向地理北極點;磁北則對應于随時間變化的地球磁場北極。iOS系統使用一個計算後的偏移量(稱為偏差)來确定這兩者之間的差異。
headingAccuracy:該屬性傳回方向值的誤差範圍。
timestamp:該屬性傳回方向值的生成時間。
x:擷取該裝置在X方向上監聽得到的原始磁力值,該磁力值的強度機關是微特斯拉。
y:擷取該裝置在Y方向上監聽得到的原始磁力值,該磁力值的強度機關是微特斯拉。
z:擷取該裝置在Z方向上監聽得到的原始磁力值,該磁力值的強度機關是微特斯拉。
在啟用該功能的iOS裝置上,即使使用者在Settings應用中關閉了定位更新,磁向更新仍然可以使用。此外,使用heading服務的應用不會提示使用者授權問題,是以磁向資訊不會洩露使用者的隐私,應用程式可以随便使用它。
需要說明的是,trueHeading屬性需要與位置探測功能一起使用,iOS系統需要裝置的位置來計算确定真北所需要的偏差。偏差随地理位置的變化而變化,比如北京的偏差不同于東京的偏差,也不同于新加坡和馬來西亞的偏差等。有一些地方根本不能使用磁力計進行讀數。
除此之外,在某些特殊位置例如有強磁、強電幹擾的地方,磁力計可能無法使用。
執行個體:指南針此執行個體将會示範如何使用磁力計來擷取裝置方向,然後根據裝置方向來建立一個指南針應用。建立一個Single View Application,無須修改界面設計檔案,直接在應用的視圖控制器類的實作部分建立界面,并讓應用中顯示方向的圖檔随着裝置方向自動旋轉即可。
下面是該應用的視圖控制器類的實作部分代碼。
程式清單:codes/09/9.3/Compass/Compass/FKViewController.m
1
2
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
<code>@interface FKViewController () <CLLocationManagerDelegate></code>
<code>{</code>
<code> </code><code>CALayer* znzLayer;</code>
<code>}</code>
<code>@property (nonatomic , strong)CLLocationManager *locationManager;</code>
<code>@end</code>
<code>@implementation FKViewController</code>
<code>- (</code><code>void</code><code>)viewDidLoad</code>
<code> </code><code>[super viewDidLoad];</code>
<code> </code><code>// 如果磁力計可用,則開始監聽方向改變</code>
<code> </code><code>if</code><code>([CLLocationManager headingAvailable])</code>
<code> </code><code>{</code>
<code> </code><code>// 建立顯示方向的指南針圖檔Layer</code>
<code> </code><code>znzLayer = [[CALayer alloc] init];</code>
<code> </code><code>NSInteger screenHeight = [UIScreen mainScreen].bounds.size.height;</code>
<code> </code><code>NSInteger y = (screenHeight - 320) / 2;</code>
<code> </code><code>znzLayer.frame = CGRectMake(0 , y , 320, 320);</code>
<code> </code><code>// 設定znzLayer顯示的圖檔</code>
<code> </code><code>znzLayer.contents = (id)[[UIImage imageNamed:@</code><code>"znz.png"</code><code>] CGImage];</code>
<code> </code><code>// 将znzLayer添加到系統的UIView中</code>
<code> </code><code>[self.view.layer addSublayer:znzLayer];</code>
<code> </code><code>// 建立CLLocationManager對象</code>
<code> </code><code>self.locationManager = [[CLLocationManager alloc] init];</code>
<code> </code><code>self.locationManager.delegate = self;</code>
<code> </code><code>[self.locationManager startUpdatingHeading];</code>
<code> </code><code>}</code>
<code> </code><code>// 如果磁力計不可用,則使用UIAlertView顯示提示資訊</code>
<code> </code><code>else</code>
<code> </code><code>// 使用警告框提醒使用者</code>
<code> </code><code>[[[UIAlertView alloc] initWithTitle:@</code><code>"提醒"</code>
<code> </code><code>message:@</code><code>"您的裝置不支援磁力計"</code> <code>delegate:self</code>
<code> </code><code>cancelButtonTitle:@</code><code>"确定"</code> <code>otherButtonTitles: nil]</code>
<code> </code><code>show];</code>
<code>// 當成功擷取裝置的方向值後激發該方法</code>
<code>-(</code><code>void</code><code>)locationManager:(CLLocationManager *)manager</code>
<code> </code><code>didUpdateHeading:(CLHeading *)newHeading</code>
<code> </code><code>// 将裝置的方向角度換算成弧度</code>
<code> </code><code>CGFloat headings = -1.0f * M_PI * newHeading.magneticHeading / 180.0f;</code>
<code> </code><code>// 建立不斷改變CALayer的transform屬性的屬性動畫</code>
<code> </code><code>CABasicAnimation* anim = [CABasicAnimation</code>
<code> </code><code>animationWithKeyPath:@</code><code>"transform"</code><code>];</code>
<code> </code><code>CATransform3D fromValue = znzLayer.transform;</code>
<code> </code><code>// 設定動畫開始的屬性值</code>
<code> </code><code>anim.fromValue = [NSValue valueWithCATransform3D: fromValue];</code>
<code> </code><code>// 繞Z軸旋轉heading弧度的變換矩陣</code>
<code> </code><code>CATransform3D toValue = CATransform3DMakeRotation(headings , 0 , 0 , 1);</code>
<code> </code><code>// 設定動畫結束的屬性</code>
<code> </code><code>anim.toValue = [NSValue valueWithCATransform3D: toValue];</code>
<code> </code><code>anim.duration = 0.5;</code>
<code> </code><code>anim.removedOnCompletion = YES;</code>
<code> </code><code>// 設定動畫結束後znzLayer的變換矩陣</code>
<code> </code><code>znzLayer.transform = toValue;</code>
<code> </code><code>// 為znzLayer添加動畫</code>
<code> </code><code>[znzLayer addAnimation:anim forKey:nil];</code>
<code>-(</code><code>BOOL</code><code>)locationManagerShouldDisplayHeadingCalibration:(CLLocationManager *)manager</code>
<code> </code><code>return</code> <code>YES;</code>
上面程式中的前兩行粗體字代碼用于為CLLocationManager設定delegate屬性,接下來程式調用該對象的startUpdatingHeading方法開始監聽裝置的方向改變——當裝置的方向改變時,系統會自動激發CLLocationManager設定delegate的locationManager:didUpdateHeading:方法,程式的視圖控制器重寫了該方法,并在該方法中擷取裝置方向,然後将圖檔“反轉”相應的角度,進而讓圖檔的北極總是指向真實的北極。
編譯、運作該應用(要在真機上測試該應用,因為iOS模拟器不支援磁力計),将可以看到如圖9.5所示的效果。

————本文節選自《瘋狂ios講義(下)》