誰かがiPhoneを振ったときに反応したい。私は彼らがそれをどのように振るのかは特に気にしません。ほんの一瞬だけ激しく振られたというだけです。誰もこれを検出する方法を知っていますか?
3.0では、より簡単な方法があります-新しいモーションイベントにフックします。
主なトリックは、シェイクイベントメッセージを受信するためにfirstResponderとして使用するUIView(UIViewControllerではない)が必要なことです。シェイクイベントを取得するために任意のUIViewで使用できるコードは次のとおりです。
@implementation ShakingView
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
if ( event.subtype == UIEventSubtypeMotionShake )
{
// Put in code here to handle shake
}
if ( [super respondsToSelector:@selector(motionEnded:withEvent:)] )
[super motionEnded:motion withEvent:event];
}
- (BOOL)canBecomeFirstResponder
{ return YES; }
@end
これらのメソッドのみでビューをサブクラス化するだけで、UIView(システムビューも)をシェイクイベントを取得できるビューに簡単に変換できます(そして、IBのベースタイプの代わりにこの新しいタイプを選択するか、表示)。
View Controllerで、このビューをファーストレスポンダーになるように設定します。
- (void) viewWillAppear:(BOOL)animated
{
[shakeView becomeFirstResponder];
[super viewWillAppear:animated];
}
- (void) viewWillDisappear:(BOOL)animated
{
[shakeView resignFirstResponder];
[super viewWillDisappear:animated];
}
ユーザーアクションからファーストレスポンダーになる他のビュー(検索バーやテキスト入力フィールドなど)がある場合、他のビューが辞任したときに、揺れているビューファーストレスポンダーステータスを復元する必要があることも忘れないでください!
ApplicationSupportsShakeToEditをNOに設定しても、このメソッドは機能します。
私の Diceshaker アプリケーションから:
// Ensures the shake is strong enough on at least two axes before declaring it a shake.
// "Strong enough" means "greater than a client-supplied threshold" in G's.
static BOOL L0AccelerationIsShaking(UIAcceleration* last, UIAcceleration* current, double threshold) {
double
deltaX = fabs(last.x - current.x),
deltaY = fabs(last.y - current.y),
deltaZ = fabs(last.z - current.z);
return
(deltaX > threshold && deltaY > threshold) ||
(deltaX > threshold && deltaZ > threshold) ||
(deltaY > threshold && deltaZ > threshold);
}
@interface L0AppDelegate : NSObject <UIApplicationDelegate> {
BOOL histeresisExcited;
UIAcceleration* lastAcceleration;
}
@property(retain) UIAcceleration* lastAcceleration;
@end
@implementation L0AppDelegate
- (void)applicationDidFinishLaunching:(UIApplication *)application {
[UIAccelerometer sharedAccelerometer].delegate = self;
}
- (void) accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration {
if (self.lastAcceleration) {
if (!histeresisExcited && L0AccelerationIsShaking(self.lastAcceleration, acceleration, 0.7)) {
histeresisExcited = YES;
/* SHAKE DETECTED. DO HERE WHAT YOU WANT. */
} else if (histeresisExcited && !L0AccelerationIsShaking(self.lastAcceleration, acceleration, 0.2)) {
histeresisExcited = NO;
}
}
self.lastAcceleration = acceleration;
}
// and proper @synthesize and -dealloc boilerplate code
@end
ヒステリシスは、ユーザーがシェイクを停止するまで、シェイクイベントが複数回トリガーされるのを防ぎます。
最後に、この ndo/Redo Manager Tutorial のコード例を使用して動作するようにしました。
これはまさにあなたがする必要があることです:
- (void)applicationDidFinishLaunching:(UIApplication *)application {
application.applicationSupportsShakeToEdit = YES;
[window addSubview:viewController.view];
[window makeKeyAndVisible];
}
-(BOOL)canBecomeFirstResponder {
return YES;
}
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self becomeFirstResponder];
}
- (void)viewWillDisappear:(BOOL)animated {
[self resignFirstResponder];
[super viewWillDisappear:animated];
}
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
if (motion == UIEventSubtypeMotionShake)
{
// your code
}
}
まず、ケンダルの7月10日の回答はスポットオンです。
今...私は似たようなことをしたかった(iPhone OS 3.0以降)、私の場合にのみ、アプリ全体でそれを望んでいたので、さまざまな揺れが発生したときのアプリの一部。これが私がやったことです。
最初に、サブクラス化UIWindow。これは簡単です。 MotionWindow : UIWindow
などのインターフェイスを使用して新しいクラスファイルを作成します(独自のナッチを自由に選択してください)。次のようなメソッドを追加します。
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
if (event.type == UIEventTypeMotion && event.subtype == UIEventSubtypeMotionShake) {
[[NSNotificationCenter defaultCenter] postNotificationName:@"DeviceShaken" object:self];
}
}
@"DeviceShaken"
を選択した通知名に変更します。ファイルを保存します。
ここで、MainWindow.xib(ストックXcodeテンプレートスタッフ)を使用する場合は、そこに移動して、WindowオブジェクトのクラスをUIWindowからMotionWindowに変更しますまたはあなたがそれを呼んだもの。 xibを保存します。プログラムでUIWindowを設定する場合は、代わりにそこで新しいWindowクラスを使用してください。
これで、アプリは特殊なUIWindowクラスを使用しています。シェイクについての通知を受け取りたい場合はいつでも、通知にサインアップしてください!このような:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(deviceShaken) name:@"DeviceShaken" object:nil];
観察者としての自分を削除するには
[[NSNotificationCenter defaultCenter] removeObserver:self];
View Controllerが関係するviewWillAppear:およびviewWillDisappear:に鉱山を置きます。シェイクイベントへの応答が、「進行中」かどうかを確認してください。それ以外の場合、デバイスが連続して2回振られると、交通渋滞が発生します。この方法では、元の通知への応答が本当に完了するまで、他の通知を無視できます。
また、motionBeganvs.motionEndedからキューオフすることもできます。それはあなた次第です。私の場合、効果は常に発生する必要がありますafterデバイスが静止している(vs.揺れ始めるとき)ので、motionEnded。両方を試して、どちらがより理にかなっているかを確認してください...
ここでもう1つ(好奇心?)観察:このコードには、ファーストレスポンダー管理の兆候がないことに注意してください。私はこれまでTable View Controllerでこれを試しましたが、すべてがうまく連携しているようです!しかし、他のシナリオを保証することはできません。
ケンドール他al-UIWindowサブクラスの場合、これがなぜそうなるのか誰にも話すことができますか?窓が食物連鎖の一番上にあるからでしょうか?
この投稿に出会って、「揺れ動く」実装を探しました。ミレノミの答えは私にとってはうまくいきましたが、トリガーするためにもう少し「揺さぶるアクション」が必要なものを探していました。ブール値をint shakeCountに置き換えました。また、Objective-CでL0AccelerationIsShaking()メソッドを再実装しました。 shakeCountに追加された量を微調整することで、必要な振動量を微調整できます。最適な値をまだ見つけたかどうかはわかりませんが、今のところうまく機能しているようです。これが誰かを助けることを願っています:
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration {
if (self.lastAcceleration) {
if ([self AccelerationIsShakingLast:self.lastAcceleration current:acceleration threshold:0.7] && shakeCount >= 9) {
//Shaking here, DO stuff.
shakeCount = 0;
} else if ([self AccelerationIsShakingLast:self.lastAcceleration current:acceleration threshold:0.7]) {
shakeCount = shakeCount + 5;
}else if (![self AccelerationIsShakingLast:self.lastAcceleration current:acceleration threshold:0.2]) {
if (shakeCount > 0) {
shakeCount--;
}
}
}
self.lastAcceleration = acceleration;
}
- (BOOL) AccelerationIsShakingLast:(UIAcceleration *)last current:(UIAcceleration *)current threshold:(double)threshold {
double
deltaX = fabs(last.x - current.x),
deltaY = fabs(last.y - current.y),
deltaZ = fabs(last.z - current.z);
return
(deltaX > threshold && deltaY > threshold) ||
(deltaX > threshold && deltaZ > threshold) ||
(deltaY > threshold && deltaZ > threshold);
}
PS:更新間隔を1/15秒に設定しました。
[[UIAccelerometer sharedAccelerometer] setUpdateInterval:(1.0 / 15)];
UIAccelerometerDelegateプロトコルの一部であるaccelerometer:didAccelerate:メソッドで加速度計を確認し、値がシェイクに必要な移動量のしきい値を超えているかどうかを確認する必要があります。
IPhone開発者サイトで入手できるGLPaintサンプルのAppController.mの一番下にあるaccelerometer:didAccelerate:メソッドには、適切なサンプルコードがあります。
Swiftを使用したiOS 8.3(おそらくそれ以前)では、View ControllerでmotionBegan
またはmotionEnded
メソッドをオーバーライドするのと同じくらい簡単です。
class ViewController: UIViewController {
override func motionBegan(motion: UIEventSubtype, withEvent event: UIEvent) {
println("started shaking!")
}
override func motionEnded(motion: UIEventSubtype, withEvent event: UIEvent) {
println("ended shaking!")
}
}
これは、必要な基本的なデリゲートコードです。
#define kAccelerationThreshold 2.2
#pragma mark -
#pragma mark UIAccelerometerDelegate Methods
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration
{
if (fabsf(acceleration.x) > kAccelerationThreshold || fabsf(acceleration.y) > kAccelerationThreshold || fabsf(acceleration.z) > kAccelerationThreshold)
[self myShakeMethodGoesHere];
}
また、インターフェースの適切なコードで設定します。すなわち:
@interface MyViewController:UIViewController <UIPickerViewDelegate、UIPickerViewDataSource、UIAccelerometerDelegate>
ViewController.mファイルに以下のメソッドを追加し、適切に機能する
-(BOOL) canBecomeFirstResponder
{
/* Here, We want our view (not viewcontroller) as first responder
to receive shake event message */
return YES;
}
-(void) motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
if(event.subtype==UIEventSubtypeMotionShake)
{
// Code at shake event
UIAlertView *alert=[[UIAlertView alloc] initWithTitle:@"Motion" message:@"Phone Vibrate"delegate:self cancelButtonTitle:@"OK" otherButtonTitles: nil];
[alert show];
[alert release];
[self.view setBackgroundColor:[UIColor redColor]];
}
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self becomeFirstResponder]; // View as first responder
}
コメントではなく回答としてこれを投稿して申し訳ありませんが、ご覧のとおり、私はStack Overflowが初めてなので、コメントを投稿するにはまだ評判が良くありません!
とにかく、ビューがビュー階層の一部になったら、必ず最初のレスポンダーのステータスを設定することについて@cireを2回目にします。したがって、View ControllerのviewDidLoad
メソッドでファーストレスポンダーのステータスを設定しても機能しません。そして、[view becomeFirstResponder]
が動作しているかどうかわからない場合は、テストできるブール値を返します。
別のポイント:あなたはcan UIViewサブクラスを不必要に作成したくない場合、View Controllerを使用してシェイクイベントをキャプチャします。それほど面倒ではないことは知っていますが、それでも選択肢はあります。 KendallがUIViewサブクラスに入れたコードスニペットをコントローラーに移動し、becomeFirstResponder
およびresignFirstResponder
メッセージをUIViewサブクラスの代わりにself
に送信するだけです。
最初に、これは古い投稿であることを知っていますが、それでも関連性があり、2つの上位の回答ができるだけ早く揺れを検出するではないことがわかりました。これはそれを行う方法です:
ViewControllerで:
- (BOOL)canBecomeFirstResponder {
return YES;
}
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
if (motion == UIEventSubtypeMotionShake) {
// Shake detected.
}
}
最も簡単な解決策は、アプリケーションの新しいルートウィンドウを派生させることです。
@implementation OMGWindow : UIWindow
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
if (event.type == UIEventTypeMotion && motion == UIEventSubtypeMotionShake) {
// via notification or something
}
}
@end
次に、アプリケーションのデリゲートで:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[OMGWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
//…
}
ストーリーボードを使用している場合、これはより複雑になる可能性があります。アプリケーションデリゲートで必要なコードは正確にはわかりません。
最初の答えに基づいた迅速なバージョン!
override func motionEnded(_ motion: UIEventSubtype, with event: UIEvent?) {
if ( event?.subtype == .motionShake )
{
print("stop shaking me!")
}
}
これらの3つの方法を使用するだけです
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event{
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event{
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event{
詳細については、 there で完全なサンプルコードを確認できます。