15
陀螺儀和方向感應器 iPhoneiPad iPod Touch 最酷的一項功能是它們的內建方向感應器,這個微小 的裝置可以讓 iOS 知道裝置被持握的方式以及是否在移動。iOS 可使用方向感應 器來處理自動旋轉,而許多遊戲也都使用它做為控制機制。此外它也可以用來偵 測搖晃與突然的移動。 17-1 方向感應器的物理原理 方向感應器(accelerometer )是藉由感應某個方向的慣性力大小來衡量其加速度 與重力。iOS 裝置內的方向感應器是一個三軸的方向感應器,表示它可以偵測三 度空間中的移動或重力。因此讀者可以利用方向感應器得知裝置目前被握持的方 式(像是自動旋轉功能),也可以得知它是否是平放在桌上,甚至可以知道是正 面朝上還是正面朝下。 方向感應器的度量單位為 g 力(g 表示重力),所以當方向感應器傳回 1.0 的值時 表示它在某個特定方向感應到了 1g 的力。底下有三個例子: 如果裝置拿在手上沒有移動,由於地心引力的關係,大約會有 1g 的作用力 在它上面。 如果裝置是以直向且完全垂直的方式持握時,它會偵測並報告有 1g 的作用 力在 y 軸上。 如果裝置是以某個角度持握時,則 1g 的力便會根據持握的方式而分散到不 同的軸。當以 45 度角持握時,這 1g 的力會大約均分到兩個軸上。

陀螺儀和方向感應器epaper.gotop.com.tw/pdf/ACL032200.pdf · 陀螺儀和方向感應器 571 17-2 不要忘記旋轉 我們之前提過iPhone 4 還有個陀螺儀感應器,可讓讀者讀取描述裝置繞著軸旋轉

Embed Size (px)

Citation preview

Page 1: 陀螺儀和方向感應器epaper.gotop.com.tw/pdf/ACL032200.pdf · 陀螺儀和方向感應器 571 17-2 不要忘記旋轉 我們之前提過iPhone 4 還有個陀螺儀感應器,可讓讀者讀取描述裝置繞著軸旋轉

陀螺儀和方向感應器 iPhone、iPad和 iPod Touch最酷的一項功能是它們的內建方向感應器,這個微小

的裝置可以讓 iOS 知道裝置被持握的方式以及是否在移動。iOS 可使用方向感應

器來處理自動旋轉,而許多遊戲也都使用它做為控制機制。此外它也可以用來偵

測搖晃與突然的移動。

17-1 方向感應器的物理原理 方向感應器(accelerometer)是藉由感應某個方向的慣性力大小來衡量其加速度

與重力。iOS 裝置內的方向感應器是一個三軸的方向感應器,表示它可以偵測三

度空間中的移動或重力。因此讀者可以利用方向感應器得知裝置目前被握持的方

式(像是自動旋轉功能),也可以得知它是否是平放在桌上,甚至可以知道是正

面朝上還是正面朝下。

方向感應器的度量單位為 g力(g表示重力),所以當方向感應器傳回 1.0的值時

表示它在某個特定方向感應到了 1g的力。底下有三個例子:

如果裝置拿在手上沒有移動,由於地心引力的關係,大約會有 1g的作用力

在它上面。

如果裝置是以直向且完全垂直的方式持握時,它會偵測並報告有 1g的作用

力在 y軸上。

如果裝置是以某個角度持握時,則 1g的力便會根據持握的方式而分散到不

同的軸。當以 45度角持握時,這 1g的力會大約均分到兩個軸上。

Page 2: 陀螺儀和方向感應器epaper.gotop.com.tw/pdf/ACL032200.pdf · 陀螺儀和方向感應器 571 17-2 不要忘記旋轉 我們之前提過iPhone 4 還有個陀螺儀感應器,可讓讀者讀取描述裝置繞著軸旋轉

570

突然的移動則可以藉由檢查方向感應器的值是否比 1g大很多來偵測。在正常的使

用情況下,方向感應器在任何軸上所偵測到的都不會超過 1g太多。但如果讀者搖

晃、掉落或丟擲裝置,則方向感應器會在一個以上的軸上偵測到非常大的受力。

不過請不要掉落或丟擲讀者自己的 iOS裝置來測試這個理論。

圖 17-1以圖形表示方向感應器所使用的三個軸。請注意方向感應器使用的是較標

準的 y 軸座標習慣用法,也就是 y 值增加表示向上的力,這剛好和第十四章中

Quartz 2D的座標系統相反。當讀者使用方向感應器搭配 Quartz 2D做為控制機制

時,需要轉換 y軸座標。但當搭配 OpenGL ES時(比較可能發生在讀者使用方向

感應器來控制動畫時),就不需要轉換。

圖 17-1:iPhone方向感應器的三維座標軸左邊的 iPhone 4前視圖顯示了 x和 y軸。右

邊的 iPhone 4側視圖則顯示了 z軸。

Page 3: 陀螺儀和方向感應器epaper.gotop.com.tw/pdf/ACL032200.pdf · 陀螺儀和方向感應器 571 17-2 不要忘記旋轉 我們之前提過iPhone 4 還有個陀螺儀感應器,可讓讀者讀取描述裝置繞著軸旋轉

陀螺儀和方向感應器

571

17-2 不要忘記旋轉 我們之前提過 iPhone 4還有個陀螺儀感應器,可讓讀者讀取描述裝置繞著軸旋轉

的值。

如果此感應器和方向感應器之間的差異看起來不清楚的話,請想想平放在桌上的

iPhone。假如讀者在該手機平放時開始旋轉它,方向感應器的值不會改變。這是

因為移動手機的力量(在這裡只有沿著 z 軸向下拉的重力)並沒有改變。(實際

上的情形則有一點點的不同,因為讀者手碰到手機的動作一定會觸發少量的方向

感應器動作)。不過在相同動作的期間,裝置的旋轉值則會改變,特別是 z 軸的

旋轉值。順時針旋轉裝置會產生負的值,逆時針旋轉則會提供正的值。停止轉動

則 z軸的旋轉值會回到零。

陀螺儀會在裝置發生旋轉時通知讀者該項變更,而不是記錄一個絕對的旋轉值。

讀者馬上就會在本章第一個範例中看到這是如何運作。

17-3 Core Motion和動作管理程式(motion manager) 從 iOS 4起,方向感應器和陀螺儀的值是使用 Core Motion架構來存取。此架構

提供的東西中包括了 CMMotionManager 此一類別,它可當成所有用來描述使用

者如何移動裝置之值的方法。讀者的應用程式可建立 CMMotionManager的實例,

然後以下面的模式來利用它

它可以在一有動作發生時就為讀者執行某些程式碼。

它可以保持永久性的更新結構而讓讀者可以在任何時間存取最新的值。

後面一種方法很適合遊戲及其他高度互動的應用程式,因為它們需要在每次通過

遊戲迴圈時查詢裝置的目前狀態。我們將告訴讀者如何實作這二種方式。

請注意 CMMotionManager類別實際上不是單一實例,但讀者的應用程式應該把它當

成是來對待。讀者應該使用正常的 alloc和 init方法在應用程式中建立唯一的一

個 CMMotionManager類別。所以如果讀者需要從應用程式中的好幾個地方來存取動

作管理程式時,應該在應用程式委派中建立它並提供從委派存取它的權利。

除了 CMMotionManager 類別外,Core Motion 還提供了幾個其他的類別,像是CMAccelermeterData 和 CMGryoData,它們只是容器,讀者的應用程式可透過它們

來存取動作資料。我們在提到這些類別時會加以說明。

Page 4: 陀螺儀和方向感應器epaper.gotop.com.tw/pdf/ACL032200.pdf · 陀螺儀和方向感應器 571 17-2 不要忘記旋轉 我們之前提過iPhone 4 還有個陀螺儀感應器,可讓讀者讀取描述裝置繞著軸旋轉

572

事件型動作(event-based motion) 我們之前提過動作管理程式的一種運作模式是每次動作資料改變時就為讀者執行

某些程式碼。大多數其他的 Cocoa Touch類別,都是藉由讓讀者連接到這時候會

取得訊息的委派,來提供這種功能,但 Core Motion的方式則有一點不同。

因為它是個新的架構,只有在 iOS 4之後才有,所以 Apple決定讓 CMMotionManager

使用 iOS 4 SDK的另一項新功能,也就是區塊(block)。我們在本書中已經使用

過數次區塊,現在讀者將看到此技巧的另一種應用。

請使用 Xcode來建立一個名為 MotionMonitor的檢視型應用程式新專案。這會是

個可讀取方向感應器資料和陀螺儀資料(有的話)並在螢幕上加以顯示的簡單應

用程式。

本章中的應用程式無法在模擬器上運作,因為模擬器沒有方向感應器也沒

有陀螺儀。噢,真遜。

首先我們需要將 Core Motion 連結到我們的應用程式。因為這是個選用的系統架

構,所以我們必須將它加入。請按照第七章增加音訊工具箱架構(在「音訊工具

箱架構中的連結」一節)的說明,但不要選取 AudioToolbox.framework而是選取

CoreMotion.frmaework。(總言之,請按住 control鍵再點按 Frameworks資料夾,

並從出現的內容選單中選取 Add>Existing Frameworks...。)

現在請選取 MotionMonitorViewController.h檔並做下列變更:

#import <UIKit/UIKit.h> #import <CoreMotion/CoreMotion.h> @interface MotionMonitorViewController : UIViewController { CMMotionManager *motionManager; UILabel *accelerometerLabel; UILabel *gyroscopeLabel; } @property (nonatomic, retain) CMMotionManager *motionManager; @property (nonatomic, retain) IBOutlet UILabel *accelerometerLabel; @property (nonatomic, retain) IBOutlet UILabel *gyroscopeLabel; @end

這會提供我們一個存取動作管理程式本身的指標,以及一對到顯示資訊之標籤的

接口。這裡不太需要解釋,所以請繼續並儲存讀者的變更。

接著在 Interface Builder中開啟 MotionMonitorViewController.xib。

Page 5: 陀螺儀和方向感應器epaper.gotop.com.tw/pdf/ACL032200.pdf · 陀螺儀和方向感應器 571 17-2 不要忘記旋轉 我們之前提過iPhone 4 還有個陀螺儀感應器,可讓讀者讀取描述裝置繞著軸旋轉

陀螺儀和方向感應器

573

在此 nib 視窗中連按二下其圖示來開啟檢視,然後從程式庫中拖拉出一個標籤到此檢視中。調整標籤的大小以讓它從左邊的藍色指引線跑到右邊的藍色指引線,

高度則為整個檢視的一半,然後將標籤的頂端對齊上面的藍色指引線。

現在開啟屬性項檢閱器並將 #Lines欄位從 1改成 0。#Lines屬性項是用來指定標籤上會出現幾行文字,並可提供嚴格的上限。但如果讀者將它設為 0,則沒有限制可用,所以標籤有多少行都可以。

接著請按住 option鍵再拖拉該標籤已建立一個複本,並將此複本對齊檢視下半部中的藍色指引線。

最 後 請 按 住 control 鍵 再 從 File’s Owner 圖 示 拖 拉 到 各 個 標 籤 , 將

accelerometerLabel和 gyroscopeLabel分別連接到上下二個標籤。

這個簡單的 GUI已經完成了,請儲存讀者的工作並準備好進行編碼。

接著請選取 MotionMonitorViewController.m並將屬性合成器(synthesizer)增加至實作區塊的頂端,將記憶體管理呼叫增加到 viewDidUnload和 dealloc方法的底部:

#import "MotionMonitorViewController.h" @implementation MotionMonitorViewController @synthesize motionManager; @synthesize accelerometerLabel; @synthesize gyroscopeLabel; ... - (void)viewDidUnload { // Release any retained subviews of the main view. // e.g. self.myOutlet = nil; self.motionManager = nil; self.accelerometerLabel = nil; self.gyroscopeLabel = nil; [super viewDidUnload]; } - (void)dealloc { [motionManager release]; [accelerometerLabel release]; [gyroscopeLabel release]; [super dealloc]; } @end

現在比較有趣的部分來了。請移除 viewDidLoad 方法周圍的註解標記並給它下列

的內容:

- (void)viewDidLoad { [super viewDidLoad];

Page 6: 陀螺儀和方向感應器epaper.gotop.com.tw/pdf/ACL032200.pdf · 陀螺儀和方向感應器 571 17-2 不要忘記旋轉 我們之前提過iPhone 4 還有個陀螺儀感應器,可讓讀者讀取描述裝置繞著軸旋轉

574

self.motionManager = [[[CMMotionManager alloc] init] autorelease]; NSOperationQueue *queue = [[[NSOperationQueue alloc] init] autorelease]; if (motionManager.accelerometerAvailable) { motionManager.accelerometerUpdateInterval = 1.0/10.0; [motionManager startAccelerometerUpdatesToQueue:queue withHandler: ^(CMAccelerometerData *accelerometerData, NSError *error){ NSString *labelText; if (error) { [motionManager stopAccelerometerUpdates]; labelText = [NSString stringWithFormat: @"Accelerometer encountered error: %@", error]; } else { labelText = [NSString stringWithFormat: @"Accelerometer\n-----------\nx: %+.2f\ny: %+.2f\nz: %+.2f", accelerometerData.acceleration.x, accelerometerData.acceleration.y, accelerometerData.acceleration.z]; } [accelerometerLabel performSelectorOnMainThread:@selector(setText:) withObject:labelText waitUntilDone:YES]; }]; } else { accelerometerLabel.text = @"This device has no accelerometer."; } if (motionManager.gyroAvailable) { motionManager.gyroUpdateInterval = 1.0/10.0; [motionManager startGyroUpdatesToQueue:queue withHandler: ^(CMGyroData *gyroData, NSError *error) { NSString *labelText; if (error) { [motionManager stopGyroUpdates]; labelText = [NSString stringWithFormat: @"Gyroscope encountered error: %@", error]; } else { labelText = [NSString stringWithFormat: @"Gyroscope\n--------\nx: %+.2f\ny: %+.2f\nz: %+.2f", gyroData.rotationRate.x, gyroData.rotationRate.y, gyroData.rotationRate.z]; } [gyroscopeLabel performSelectorOnMainThread:@selector(setText:) withObject:labelText waitUntilDone:YES]; }]; } else { gyroscopeLabel.text = @"This device has no gyroscope"; } }

此方法包含了啟動感應器所需的全部程式碼,並告訴它們每 1/10秒就向我們報告

一次並同時更新螢幕畫面。

Page 7: 陀螺儀和方向感應器epaper.gotop.com.tw/pdf/ACL032200.pdf · 陀螺儀和方向感應器 571 17-2 不要忘記旋轉 我們之前提過iPhone 4 還有個陀螺儀感應器,可讓讀者讀取描述裝置繞著軸旋轉

陀螺儀和方向感應器

575

感謝區塊的強大功能,讓一切都變得簡單又緊密。現在不用將功能部分放入委派

方法中,區塊中定義的行為就可讓我們在設定行為的同一個方法中看見該行為!

讓我們一次處理一小部分。就從底下的先開始:

self.motionManager = [[[CMMotionManager alloc] init] autorelease]; NSOperationQueue *queue = [[[NSOperationQueue alloc] init] autorelease];

此程式碼首先建立了一個 CMMotionManager的實例,我們將會使用它來觀察動作事

件。然後它又建立了一個作業佇列,它只是一堆需要完成之工作的容器,讀者可

以回想第十三章一下。

動作管理程式想要有一個佇列來放置要完成的工作,並依讀者給它的區塊

所指定,每次發生一個事件。使用系統的預設佇列來做這件事是很誘人的,但

CMMotionManager 的說明文件明白地警告不要這麼做!因為擔心預設佇列最後塞滿

了這些事件,而導致在處理其他重要的系統事件時會有困難。

接著進行到設定方向感應器。我們先檢查以確認裝置真得有方向感應器。目前上

市的所有手持 iOS裝置都有,但為防未來可能有的會沒有,所以還是值得檢查一下。接著我們設定更新之間的時間間隔,並以秒為單位來指定。這裡我們要求的

是 1/10秒。請注意這麼設定並不一定保證我們會接收到速度如此精準的更新。事實上,該項設定其實是個上限,只是指定動作管理程式可以提供給我們的最佳速

率。實際上的更新可能沒這麼頻繁。

if (motionManager.accelerometerAvailable) { motionManager.accelerometerUpdateInterval = 1.0/10.0;

接著我們告訴動作管理程式開始報告方向感應器的更新。我們傳入了一個讓動作

管理程式放置工作的佇列,以及一個定義工作會在每次更新發生時完成的區塊。

請記住,區塊都是從插入符號(^)開始,後面再接著以括號包起來的一串引數,

這些引數是區塊執行時要去填入的(在此例中,是方向感應器的資料和警告我們

有問題的錯誤),最後再以內有要執行之程式碼的大括號區段作結尾。

[motionManager startAccelerometerUpdatesToQueue:queue withHandler: ^(CMAccelerometerData *accelerometerData, NSError *error) {

底下是區塊的內容。它會根據目前方向感應器的值建立一個字串,或是如果有問

題的話則產生一個錯誤訊息。然後它會將該字串值推入 accelermeterLabel中。這

裡我們不能直接這麼做,因為像 UILable等的 UIKit類別通常只有在從主執行緒中存取時才會運作順利。由於這段程式碼是從 NSOperationQueue內執行,所以我們

不知道將會在哪個特定佇列中執行。因此我們使用 performSelectorOnMainThread:

withObject:waitUntilDone: 方法以讓主執行緒處理此工作。

Page 8: 陀螺儀和方向感應器epaper.gotop.com.tw/pdf/ACL032200.pdf · 陀螺儀和方向感應器 571 17-2 不要忘記旋轉 我們之前提過iPhone 4 還有個陀螺儀感應器,可讓讀者讀取描述裝置繞著軸旋轉

576

請注意方向感應器的值是透過傳入之 accelerometerData的 acceleration屬性來存

取。Acceleration 屬性是 CMAcceleration 類型,該類型只是個包含了三個 float

值的簡單 struct。

accelerometerData 本身是個 CMAccelerometer 類別的實例,而該類別其實只是

CMAcceleration的包裝函式(wrapper)而已!如果讀者認為只為了傳送三個 float

而使用了這麼多類別好像是沒必要的話,那麼不是只有讀者會這麼想。不管如何,

底下是它的使用方式:

NSString *labelText; if (error) { [motionManager stopAccelerometerUpdates]; labelText = [NSString stringWithFormat: @"Accelerometer encountered error: %@", error]; } else { labelText = [NSString stringWithFormat: @"Accelerometer\n-----------\nx: %+.2f\ny: %+.2f\nz: %+.2f", accelerometerData.acceleration.x, accelerometerData.acceleration.y, accelerometerData.acceleration.z]; } [accelerometerLabel performSelectorOnMainThread:@selector(setText:) withObject:labelText waitUntilDone:YES];

接著我們結束此區塊,並結束一開始傳入此區塊所在之方括號方法呼叫。最後我

們提供完全不同的程式碼路徑,以防裝置沒有方向感應器。如前所述,目前所有

的 iOS裝置都有方向感應器,但誰知道未來會是怎樣呢?

}]; } else { accelerometerLabel.text = @"This device has no accelerometer."; }

讀者可能注意到陀螺儀的程式碼在結構上完全相同,不同的只是哪些方法會被呼

叫以及如何存取所報告的值。由於太類似了所以這裡不需要逐一為讀者解釋。

現在請在讀者現有的 iOS裝置上建立和執行此應用程式,並試試它(見圖 17-2)。

當讀者將裝置傾斜不同的方向時,會看到方向感應器的值會隨著方向不同而調

整,而只要讀者穩穩地拿住裝置,該值也會保持穩定。

Page 9: 陀螺儀和方向感應器epaper.gotop.com.tw/pdf/ACL032200.pdf · 陀螺儀和方向感應器 571 17-2 不要忘記旋轉 我們之前提過iPhone 4 還有個陀螺儀感應器,可讓讀者讀取描述裝置繞著軸旋轉

陀螺儀和方向感應器

577

圖 17-2:在 iPhone 4上執行的 MotionMonitor。不幸地,如果讀者在模擬器上執行此

應用程式時將會看到一對錯誤訊息。

如果讀者有 iPhone 4(若將來任何有陀螺儀的裝置),會看到這些值如何改變。

每當裝置定住不動時,不管是在哪個方向,陀螺儀的值都會在零附近徘徊。不過

當讀者旋轉它時,會看到陀螺儀的值如何依據讀者的旋轉而改變。當讀者停止移

動裝置時,這個值都會回到零。

主動式動作存取 讀者已經看過在動作發生時如何藉由傳送要呼叫的 CMMotionManager區塊來存取動

作資料。這種由事件驅動的動作處理足夠應付一般的 Cocoa應用程式,但有時候

它不太符合應用程式的特定需求。舉例來說,互動式遊戲通常有個一直在執行的

迴圈來處理使用者輸入、更新遊戲狀態和重繪螢幕畫面。在這種情況下,由事件驅

動的方式就不怎麼適合,因為讀者需要去實作一個等待動作事件的物件、在收到

感應器最近的位置時加以記錄並準備在需要時將該資料回報給主要的遊戲迴圈。

好在 CMMotionManager 有個內建的解決方案。現在不用將區塊傳入,只要使用

startAccelerometerUpdates和 startGytoUpdates二方法去告訴區塊啟用感應器,

之後任何時間想要的話,只要直接從動作管理程式讀取數值就好了。

Page 10: 陀螺儀和方向感應器epaper.gotop.com.tw/pdf/ACL032200.pdf · 陀螺儀和方向感應器 571 17-2 不要忘記旋轉 我們之前提過iPhone 4 還有個陀螺儀感應器,可讓讀者讀取描述裝置繞著軸旋轉

578

讓我們使用此方式來改變我們的 MotionMonitor 應用程式,以讓讀者可以看看它

是如何運作。一開始請先製作讀者 MotionMonitor 專案資料夾的複本。接著增加

一個新的實例變數和比對(matching)屬性到 MotionMonitorViewController.h,

和一個指向 NSTimer的指標,此 NSTimer將會觸發我們所有顯示的更新:

#import <UIKit/UIKit.h> #import <CoreMotion/CoreMotion.h> @interface MotionMonitorViewController : UIViewController { CMMotionManager *motionManager; UILabel *accelerometerLabel; UILabel *gyroscopeLabel; NSTimer *updateTimer;

} @property (retain) CMMotionManager *motionManager; @property (retain) IBOutlet UILabel *accelerometerLabel; @property (retain) IBOutlet UILabel *gyroscopeLabel; @property (retain) NSTimer *updateTimer;

@end

現在請切換到 MotionMonitorViewController.m,讀者需要在這裡合成此新屬性,

並在 dealloc中釋放它:

@implementation MotionMonitorViewController @synthesize motionManager; @synthesize accelerometerLabel; @synthesize gyroscopeLabel; @synthesize updateTimer;

... - (void)dealloc { [motionManager release]; [accelerometerLabel release]; [gyroscopeLabel release]; [updateTimer release];

[super dealloc]; }

刪除我們之前有的整個 viewDidLoad 方法,並以底下較簡單的版本來取代,此版

本只是設置此動作管理程式並提供資訊性的標籤給欠缺感應器的裝置:

- (void)viewDidLoad { [super viewDidLoad]; self.motionManager = [[[CMMotionManager alloc] init] autorelease];

Page 11: 陀螺儀和方向感應器epaper.gotop.com.tw/pdf/ACL032200.pdf · 陀螺儀和方向感應器 571 17-2 不要忘記旋轉 我們之前提過iPhone 4 還有個陀螺儀感應器,可讓讀者讀取描述裝置繞著軸旋轉

陀螺儀和方向感應器

579

if (motionManager.accelerometerAvailable) { motionManager.accelerometerUpdateInterval = 1.0/10.0; [motionManager startAccelerometerUpdates]; } else { accelerometerLabel.text = @"This device has no accelerometer."; } if (motionManager.gyroAvailable) { motionManager.gyroUpdateInterval = 1.0/10.0; [motionManager startGyroUpdates]; } else { gyroscopeLabel.text = @"This device has no gyroscope."; } }

一般而言,我們會使用 viewDidLoad和 viewDidUnload將有關 GUI顯示之屬性的建

立與摧毀加上括號。不過在我們新計時器的情形中,我們想要它只在更小的時間

視窗內啟用,也就是實際顯示該檢視時。以這種方式我們可以將主要「遊戲迴圈」

的使用量維持在最小。我們可藉由像底下一樣地實作 viewWillAppear: 和viewDidDisappear: 來完成此事。請將這二個方法增加到 viewDidLoad後:

- (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; self.updateTimer = [NSTimer scheduledTimerWithTimeInterval:1.0/10.0 target:self selector:@selector(updateDisplay) userInfo:nil repeats:YES]; } - (void)viewDidDisappear:(BOOL)animated { [super viewDidDisappear:animated]; self.updateTimer = nil; }

viewWillAppear: 中的程式碼會建立一個新的計時器,並排定它每 1/10 秒就啟動

一次去呼叫 updateDisplay 方法,但這個方法我們還沒建立。請將此方法增加到

viewDidDisappear底下:

- (void)updateDisplay { if (motionManager.accelerometerAvailable) { CMAccelerometerData *accelerometerData = motionManager.accelerometerData; accelerometerLabel.text = [NSString stringWithFormat: @"Accelerometer\n-----------\nx: %+.2f\ny: %+.2f\nz: %+.2f", accelerometerData.acceleration.x, accelerometerData.acceleration.y, accelerometerData.acceleration.z]; }

Page 12: 陀螺儀和方向感應器epaper.gotop.com.tw/pdf/ACL032200.pdf · 陀螺儀和方向感應器 571 17-2 不要忘記旋轉 我們之前提過iPhone 4 還有個陀螺儀感應器,可讓讀者讀取描述裝置繞著軸旋轉

580

if (motionManager.gyroAvailable) { CMGyroData *gyroData = motionManager.gyroData; gyroscopeLabel.text = [NSString stringWithFormat: @"Gyroscope\n--------\nx: %+.2f\ny: %+.2f\nz: %+.2f", gyroData.rotationRate.x, gyroData.rotationRate.y, gyroData.rotationRate.z]; } }

請在讀者的裝置上建立並執行此應用程式,讀者應該會看到它的行為和第一版的

一模一樣。現在讀者已經看過二種存取動作資料的方式。請使用最適合的給讀者

的應用程式。

方向感應器的結果 我們之前提過 iPhone 的方向感應器可偵測三個軸上的加速度,而且它可使用CMAcceleration struct來提供此資訊。每個 CMAcceleration都有 x、y和 z欄位,

而每個欄位都存放一個浮點值。0 的值表示方向感應器在該軸上沒有偵測到移

動。正或負的值則表示在某方向上有受力。舉例來說,y 值為負表示感應到向下

的拉力,因此可能代表是以直向模式垂直拿著手機。y 值為正則表在相反的方向

上有作用力,這可能表示手機是顛倒拿或是向下移動。

請記住圖 17-3中的圖表,並讓我們看一下部分的方向感應器結果。請注意,在真

實生活中,讀者不太可能會得到這麼精確的值,因為方向感應器很靈敏,即使一

點點動作都讀得到,所以讀者通常在三個軸上都至少讀得到一丁點的力。這是真

實生活中的物理現象而不是課堂中的物理現象。

在第三方應用程式中最常見的方向感應器使用方式或許是做為遊戲的控制器。本

章稍後我們將建立一個使用方向感應器做為輸入的程式,但首先讓我們看另一個

常見的方向感應器使用:偵測搖晃。

Page 13: 陀螺儀和方向感應器epaper.gotop.com.tw/pdf/ACL032200.pdf · 陀螺儀和方向感應器 571 17-2 不要忘記旋轉 我們之前提過iPhone 4 還有個陀螺儀感應器,可讓讀者讀取描述裝置繞著軸旋轉

陀螺儀和方向感應器

581

圖 17-3:不同裝置方向的理想化加速度值

17-4 偵測搖晃 搖晃和手勢動作一樣,也可以當成應用程式的一種輸入形式。舉例來說,iOS 範

例程式碼專案中的繪圖程式 GLPaint,它讓使用者可以藉由搖晃 iOS 裝置的方式

來擦掉繪製的圖形。

偵測搖晃的情形非常簡單,只需要檢查某一個軸上的絕對值是否大於設定的門

檻。在正常的使用情況下,三個軸之中有一個軸紀錄高達 1.3 g的值並非少見,但

要得到比這還要高出許多的值則通常需要故意施力。方向感應器似乎無法記錄超

過 2.3 g以上的值(至少我們的經驗是如此),所以不要將門檻設得比這還要高。

要偵測搖晃情況,讀者可以使用像底下的程式碼來檢查絕對值,大於 1.5 的為輕

微搖晃,大於 2.0的則為強力搖晃:

Page 14: 陀螺儀和方向感應器epaper.gotop.com.tw/pdf/ACL032200.pdf · 陀螺儀和方向感應器 571 17-2 不要忘記旋轉 我們之前提過iPhone 4 還有個陀螺儀感應器,可讓讀者讀取描述裝置繞著軸旋轉

582

- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration {

if (fabsf(acceleration.x) > 2.0 || fabsf(acceleration.y) > 2.0 || fabsf(acceleration.z) > 2.0) { // Do something here... } }

上述方法可以偵測到任何軸上任何超過二倍重力的動作。讀者也可以藉由要求使

用者來回搖晃一定次數後才將此動作紀錄為搖晃,從而實作更精密的搖晃偵測,

其程式碼如下:

- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration {

static NSInteger shakeCount = 0; static NSDate *shakeStart;

NSDate *now = [[NSDate alloc] init]; NSDate *checkDate = [[NSDate alloc] initWithTimeInterval:1.5f sinceDate:shakeStart]; if ([now compare:checkDate] == NSOrderedDescending || shakeStart == nil) { shakeCount = 0; [shakeStart release]; shakeStart = [[NSDate alloc] init]; } [now release]; [checkDate release];

if (fabsf(acceleration.x) > 2.0 || fabsf(acceleration.y) > 2.0 || fabsf(2.0.z) > 2.0) { shakeCount++; if (shakeCount > 4) { // Do something shakeCount = 0; [shakeStart release]; shakeStart = [[NSDate alloc] init]; } } }

這個方法會記錄方向感應器回報值超過 2.0的次數,如果在 1.5秒中有四次以上,它就將其記錄為搖晃。

內建的搖晃 偵測搖晃實際上還有另外一種方式,也就是潛藏在回應程式鏈中的方式。還記得

我們在第十五章中如何實作像 touchesBegan:withEvent: 等方法來偵測觸碰嗎?

iOS也提供三個類似的回應程式方法來偵測動作:

Page 15: 陀螺儀和方向感應器epaper.gotop.com.tw/pdf/ACL032200.pdf · 陀螺儀和方向感應器 571 17-2 不要忘記旋轉 我們之前提過iPhone 4 還有個陀螺儀感應器,可讓讀者讀取描述裝置繞著軸旋轉

陀螺儀和方向感應器

583

當動作開始進行時,motionBegan:withEvent: 方法會如第十五章中所討論過

的先被傳送到第一回應程式然後在回應程式鏈上一路傳下去。

當動作結束時,motionEnded:withEvent: 方法會被傳送到第一回應程式。

如 果 在 搖 晃 期 間 , 手 機 鈴 響 或 發 生 其 他 動 作 而 導 致 中 斷 時 ,

motionCancelled:withEvent: 訊息會傳送到第一回應程式。

這表示不用直接使用 CMMotionManager也可以偵測搖晃。讀者只要覆寫檢視或檢視

控制器中適當的動作感應方法即可,然後當使用者搖晃手機時便會自動呼叫這些

方法。除非讀者真得有需要對搖晃的手勢有更多種的控制方式,不然應該使用這

些內建好的動作偵測方法,而不要使用本章介紹的手動方法,不過我們還是會將

這種手動方法介紹一下,以防讀者萬一需要進行更多的控制。

現在讀者已經對如何偵測搖晃有了基本的概念,我們接著將打壞讀者的手機。

搖到壞 我們不是真的要搖壞讀者的手機,只是要寫一個能夠偵測搖晃的應用程式,然後

讓讀者的手機看起來和聽起來像是被搖晃搖到壞了。

當讀者啟動這個應用程式時,螢幕上應該會顯示如圖 17-4所示的 iPhone主畫面。

如果將手機用力搖晃,讀者可憐的手機便會發出像是被搖壞的聲音,而且螢幕會

出現如圖 17-5所示的樣子。為什麼我們要做這麼邪惡的事?

不用擔心,讀者可藉由碰觸螢幕來將 iPhone重設回原來清純的樣貌。