誰かがiPhoneを振ったことを検出するにはどうすればよいですか?
-
02-07-2019 - |
質問
誰かが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
履歴は、ユーザーがシェイクを停止するまでシェイクイベントが複数回トリガーされるのを防ぎます。
ついに、この Undo / Redo Managerチュートリアル。
これはまさにあなたがする必要があることです:
- (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
}
}
まず、Kendallの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:に配置します。 shakeイベントへの応答が<!> quot; already in progress <!> quot;かどうかを確認してください。か否か。それ以外の場合、デバイスが連続して2回振られると、交通渋滞が発生します。このようにして、元の通知への応答が本当に完了するまで、他の通知を無視できます。
また: motionBegan 対 motionEnded のキューを選択することもできます。それはあなた次第です。私の場合、エフェクトは常にデバイスが静止している( 後)発生する必要があるため、 motionEnded を使用します。両方を試して、どちらがより理にかなっているかを確認するか、両方を検出/通知してください!
ここでもう1つ(好奇心??)私はこれまでTable View Controllerでこれを試しましたが、すべてがうまく連携しているようです!ただし、他のシナリオを保証することはできません。
ケンドール他al- UIWindow サブクラスの場合、これがなぜそうなるのか誰にも話せますか?窓が食物連鎖の一番上にあるからでしょうか?
<!> quot; shaking <!> quot;を探してこの投稿に出会いました。実装。ミレノミの答えは私にとってはうまくいきましたが、もう少し<!> quot;シェイクアクション<!> quot;が必要なものを探していました。トリガーします。ブール値を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:メソッドで加速度計を確認し、値がシェイクに必要な移動量のしきい値を超えているかどうかを確認する必要があります。
accelerometer:didAccelerate:メソッドには、iPhone開発者サイトで入手可能なGLPaintサンプルのAppController.mのすぐ下に適切なサンプルコードがあります。
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 <!> lt; UIPickerViewDelegate、UIPickerViewDataSource、UIAccelerometerDelegate <!> gt;
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]
はテスト可能なブール値を返します。
別のポイント:UIViewサブクラスを不必要に作成したくない場合は、View Controllerを使用してシェイクイベントをキャプチャすることができます 。それほど面倒ではないことは知っていますが、それでも選択肢はあります。 KendallがUIViewサブクラスに入れたコードスニペットをコントローラーに移動し、UIViewサブクラスの代わりにbecomeFirstResponder
およびresignFirstResponder
メッセージをself
に送信するだけです。
まず、これは古い投稿であることは知っていますが、それでも関連性があり、2つの最も高い投票数の回答ができるだけ早く揺れを検出しなかったことがわかりました。これがその方法です:
- ターゲットのビルドフェーズでCoreMotionをプロジェクトにリンクします。
-
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];
//…
}
ストーリーボードを使用している場合、これはより複雑になる可能性があります。アプリケーションデリゲートで必要なコードが正確にわからない<!>#8217;
これらの3つの方法を使用してそれを実行します
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event{
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event{
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event{
詳細については、そこ
最初の回答に基づいたswifteaseバージョン!
override func motionEnded(_ motion: UIEventSubtype, with event: UIEvent?) {
if ( event?.subtype == .motionShake )
{
print("stop shaking me!")
}
}
このアプリ全体で有効にするには、UIWindowにカテゴリを作成しました:
@implementation UIWindow (Utils)
- (BOOL)canBecomeFirstResponder
{
return YES;
}
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
if (motion == UIEventSubtypeMotionShake) {
// Do whatever you want here...
}
}
@end