回答:
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
これらのメソッドのみでビューをサブクラス化する(そして、IBでベースタイプの代わりにこの新しいタイプを選択する、またはIBを割り当てるときにそれを使用する)だけで、任意のUIView(システムビューでさえ)をシェイクイベントを取得できるビューに簡単に変換できます。見る)。
ビューコントローラーで、このビューをファーストレスポンダーになるように設定します。
- (void) viewWillAppear:(BOOL)animated
{
[shakeView becomeFirstResponder];
[super viewWillAppear:animated];
}
- (void) viewWillDisappear:(BOOL)animated
{
[shakeView resignFirstResponder];
[super viewWillDisappear:animated];
}
ユーザーアクションからファーストレスポンダーになる他のビュー(検索バーやテキスト入力フィールドなど)がある場合は、他のビューが辞任したときに、シェイクビューのファーストレスポンダーステータスも復元する必要があることを忘れないでください。
このメソッドは、applicationSupportsShakeToEditをNOに設定した場合でも機能します。
motionEnded
、シェイクが実際に停止する前にiPhoneが発砲するのを好むため、このアプローチは役に立ちません。したがって、このアプローチを使用すると、1つの長い揺れではなく、バラバラな一連の短い揺れが得られます。この場合、他の答えがはるかにうまく機能します。
[super respondsToSelector:
は、[self respondsToSelector:
を返すと同等であるため、意図したとおりに動作しませんYES
。必要なのは[[ShakingView superclass] instancesRespondToSelector:
。
私の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
ヒステリシスは、ユーザーがシェイクを停止するまでシェイクイベントが複数回トリガーされるのを防ぎます。
motionBegan
とmotionEnded
イベントはあまり正確ではないため、最良の答えです。このアプローチにより、必要なだけ正確にすることができます。
最後に、この「元に戻す/やり直しマネージャーのチュートリアル」のコード例を使用して動作させました。
これはまさにあなたがする必要があることです:
- (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
}
}
applicationSupportsShakeToEdit
あるため、ステップ1は冗長になりましたYES
。
[self resignFirstResponder];
先に電話するの[super viewWillDisappear:animated];
ですか?これは奇妙に思えます。
まず、ケンドールの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];
私はビューコントローラーが関係するviewWillAppear:とviewWillDisappear:に配置しました。シェイクイベントへの応答で、「すでに進行中」かどうかを確認してください。そうしないと、デバイスが2回続けて振られると、交通渋滞が発生します。この方法では、元の通知への応答が本当に完了するまで、他の通知を無視できます。
また:あなたはのオフ頭出しすることもできますmotionBegan対motionEnded。それはあなた次第です。私の場合、効果は常に場所を取る必要がある後、私が使用してデバイスは、残りの部分(それは揺れを開始対)であるmotionEndedを。両方を試して、どちらがより意味があるかを確認してください...または、両方を検出/通知してください!
ここでもう1つ(おもしろい?)観察:このコードには、ファーストレスポンダ管理の兆候がないことに注意してください。私はこれまでTable View Controllersでこれを試しただけで、すべてが非常にうまく連携しているようです!他のシナリオについては保証できません。
ケンドール他 al-なぜこれがUIWindowサブクラスにそうなるのか、誰かが話すことができますか?窓が食物連鎖の最上部にあるからですか?
私は「揺さぶる」実装を探しているこの投稿に出くわしました。ミレノミの答えは私にはうまくいきましたが、トリガーするのにもう少し「振るアクション」が必要なものを探していました。ブール値に置き換えて、intのshakeCountを使用します。Objective-CのL0AccelerationIsShaking()メソッドも再実装しました。shakeCountに追加されたammountを微調整することにより、必要なシェイクの量を微調整できます。まだ最適な値が見つかったかどうかはわかりませんが、これまでのところうまくいっているようです。これが誰かを助けることを願っています:
- (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)];
iOS 8.3(おそらく以前のバージョン)でSwiftを使用すると、ビューコントローラーの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番目に設定します。したがってviewDidLoad
、たとえば、ビューコントローラーメソッドでファーストレスポンダーステータスを設定しても機能しません。そして、それが機能しているかどうかわからない[view becomeFirstResponder]
場合は、テストできるブール値を返します。
別のポイント:不必要にUIViewサブクラスを作成したくない場合は、ビューコントローラーを使用してシェイクイベントをキャプチャできます。それほど面倒ではありませんが、それでも選択肢はあります。KendallがUIViewサブクラスに配置したコードスニペットをコントローラーに移動し、UIViewサブクラスの代わりにbecomeFirstResponder
およびresignFirstResponder
メッセージを送信するだけ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];
//…
}
Storyboardを使用している場合、これはよりトリッキーになる可能性があります。アプリケーションデリゲートで必要となるコードを正確には知りません。
@implementation
Xcodeの5以降
最初の回答に基づいた迅速なバージョン!
override func motionEnded(_ motion: UIEventSubtype, with event: UIEvent?) {
if ( event?.subtype == .motionShake )
{
print("stop shaking me!")
}
}
viewDidAppear
代わりにコントローラーをオーバーライドする必要がありましたviewWillAppear
。理由はわかりません。おそらく、シェイクイベントの受信を開始するためにビューが実行する前に、ビューを表示する必要がありますか?