Birisi bir iPhone'u salladığında nasıl tespit edebilirim?


340

Birisi iPhone'u salladığında tepki vermek istiyorum. Özellikle nasıl salladıkları umurumda değil, sadece bir saniye boyunca şiddetle sallandı. Bunu nasıl tespit edeceğini bilen var mı?

Yanıtlar:


292

3.0'da artık daha kolay bir yol var - yeni hareket olaylarına bağlanın.

Ana hile, sallamak olay mesajlarını almak için firstResponder olarak istediğiniz bazı UIView (UIViewController değil) olması gerektiğidir. Sarsıntı olayları almak için herhangi bir UIView'de kullanabileceğiniz kod şunlardır:

@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

Herhangi bir UIView'i (sistem görünümlerini bile) yalnızca görünümü yalnızca bu yöntemlerle alt sınıflandırarak (ve ardından IB'deki temel tür yerine bu yeni türü seçerek veya bir görünüm).

Görünüm denetleyicisinde, bu görünümü ilk yanıtlayıcı olacak şekilde ayarlamak istiyorsunuz:

- (void) viewWillAppear:(BOOL)animated
{
    [shakeView becomeFirstResponder];
    [super viewWillAppear:animated];
}
- (void) viewWillDisappear:(BOOL)animated
{
    [shakeView resignFirstResponder];
    [super viewWillDisappear:animated];
}

Kullanıcı işlemlerinden (yanıt çubuğu veya metin giriş alanı gibi) ilk yanıtlayan diğer görünümleriniz varsa, diğer görünümden istifa ettiğinde sallayarak görünüm ilk yanıtlayıcı durumunu da geri yüklemeniz gerektiğini unutmayın!

Bu yöntem applicationSupportsShakeToEdit öğesini NO olarak ayarlasanız bile çalışır.


5
Bu benim için pek işe yaramadı: Denetleyicimin viewDidAppearyerine geçersiz kılmam gerekiyordu viewWillAppear. Neden olduğundan emin değilim; belki de sarsıntı olaylarını almaya başlamak için ne gerekiyorsa yapabilmesi için görünümün görünür olması gerekir?
Kristopher Johnson

1
Bu daha kolay ama ille de daha iyi değil. Uzun, sürekli bir sarsıntı tespit etmek istiyorsanız motionEnded, sarsıntı gerçekten durmadan iPhone'un ateş etmeyi sevdiği için bu yaklaşım yararlı değildir . Yani bu yaklaşımı kullanarak uzun bir tane yerine ayrık bir dizi kısa sallama elde edersiniz. Diğer cevap bu durumda çok daha iyi çalışır.
aroth

3
@Kendall - UIViewControllers UIResponder uygular ve cevaplayıcı zincirindedir. En üstteki UIWindow da. developer.apple.com/library/ios/DOCUMENTATION/EventHandling/…
DougW

1
[super respondsToSelector:İstediğinizi yapmayacaktır, çünkü [self respondsToSelector:geri dönecek olan çağrıya eşdeğerdir YES. İhtiyacınız olan şey [[ShakingView superclass] instancesRespondToSelector:.
Vadim Yelagin

2
Kullanmak yerine: if (event.subtype == UIEventSubtypeMotionShake) şunu kullanabilirsiniz: if (motion == UIEventSubtypeMotionShake)
Hashim Akhtar

179

Benim itibaren Diceshaker uygulaması:

// 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

Histeresis, kullanıcı sallamayı durdurana kadar sallama olayının birçok kez tetiklenmesini önler.


7
Not 3.0 için daha kolay bir yöntem sunan aşağıya bir cevap ekledim.
Kendall Helmstetter Gelner

2
Belirli bir eksende tam olarak sallıyorlarsa ne olur?
jprete

5
En iyi cevap, çünkü iOS 3.0 motionBeganve motionEndedolaylar bir sarsıntıyı tam olarak başlatarak ve bitirmeyi tespit etmek açısından çok hassas veya doğru değil. Bu yaklaşım, istediğiniz kadar hassas olmanızı sağlar.
aroth

2
Hızlanma : iOS5'te kullanımdan kaldırıldı.
Yantao Xie


154

Sonunda bu Undo / Redo Manager Eğitimi kod örnekleri kullanarak çalışması yaptı .
Tam olarak yapmanız gerekenler:

  • Uygulama Temsilcisindeki applicationSupportsShakeToEdit özelliğini ayarlayın :
  • 
        - (void)applicationDidFinishLaunching:(UIApplication *)application {
    
            application.applicationSupportsShakeToEdit = YES;
    
            [window addSubview:viewController.view];
            [window makeKeyAndVisible];
    }

  • Görünüm Denetleyicinizde canBecomeFirstResponder , viewDidAppear: ve viewWillDisappear: yöntemlerini Ekle / Geçersiz Kıl :
  • 
    -(BOOL)canBecomeFirstResponder {
        return YES;
    }
    
    -(void)viewDidAppear:(BOOL)animated {
        [super viewDidAppear:animated];
        [self becomeFirstResponder];
    }
    
    - (void)viewWillDisappear:(BOOL)animated {
        [self resignFirstResponder];
        [super viewWillDisappear:animated];
    }

  • Ekle motionEnded sizin View Controller için metot:
  • 
    - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
    {
        if (motion == UIEventSubtypeMotionShake)
        {
            // your code
        }
    }

    Sadece Eran Talmor çözümüne eklemek için: yeni proje seçicide böyle bir uygulama seçmeniz durumunda bir görünüm denetleyicisi yerine bir navigasyon denetleyicisi kullanmalısınız.
    Rob

    Bunu kullanmayı denedim ama bazen heavyshake veya hafif sallama sadece durdu
    vinothp 18

    Adım 1 varsayılan değer olarak, artık gereksiz applicationSupportsShakeToEditDİR YES.
    DonVaughn

    1
    Neden [self resignFirstResponder];öndesin [super viewWillDisappear:animated];? Bu tuhaf görünüyor.
    JaredH

    94

    İlk olarak, Kendall'ın 10 Temmuz cevabı yerinde.

    Şimdi ... Benzer bir şey yapmak istedim (iPhone OS 3.0+), sadece benim durumumda, uygulama genelinde istedim, böylece bir sarsıntı meydana geldiğinde uygulamanın çeşitli bölümlerini uyarabilirim . İşte sonunda bunu yaptım.

    İlk olarak, UIWindow alt sınıfını . Bu çok kolay. Gibi bir arayüz ile yeni bir sınıf dosyası oluşturun MotionWindow : UIWindow(kendiniz seçin, natch). Bunun gibi bir yöntem ekleyin:

    - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
        if (event.type == UIEventTypeMotion && event.subtype == UIEventSubtypeMotionShake) {
            [[NSNotificationCenter defaultCenter] postNotificationName:@"DeviceShaken" object:self];
        }
    }

    Değiştir @"DeviceShaken"Seçtiğiniz bildirim adının. Dosya 'yı kaydet.

    Bir MainWindow.xib (stok Xcode şablon şeyler) kullanmak Şimdi, eğer oraya gidip gelen Window nesnesinin sınıfını değiştirmek UIWindow için MotionWindow veya bunu denilen neyse. Xib'i kaydedin. Eğer kurarsanız UIWindow programlı yerine orada yeni Pencere sınıfını kullanın.

    Şimdi uygulamanız özel UIWindow sınıfını kullanıyor. Bir sarsıntı hakkında bilgi almak istediğiniz her yerde, onlara bildirimler için kaydolun! Bunun gibi:

    [[NSNotificationCenter defaultCenter] addObserver:self
    selector:@selector(deviceShaken) name:@"DeviceShaken" object:nil];

    Kendinizi bir gözlemci olarak çıkarmak için:

    [[NSNotificationCenter defaultCenter] removeObserver:self];

    Benimkini viewWillAppear'a koydum : ve viewWillDisappear: Burada Görünüm Denetleyicileri söz konusu olduğunda . Sallama olayına verdiğiniz yanıtın "zaten sürüyor" olup olmadığını bildiğinden emin olun. Aksi takdirde, cihaz art arda iki kez çalkalanırsa, bir trafik sıkışıklığınız olur. Bu şekilde, orijinal bildirime gerçekten yanıt vermeyi bitirinceye kadar diğer bildirimleri yok sayabilirsiniz.

    Ayrıca: motionBegan'a karşı motionEnded işaretini iptal etmeyi seçebilirsiniz . Sana kalmış. Benim durumumda, efekt her zaman cihaz dinlendikten sonra gerçekleşmelidir (titremeye başladığında vs.), bu yüzden motionEnded kullanıyorum . Her ikisini de deneyin ve hangisinin daha anlamlı olduğunu görün ... ya da her ikisini de tespit edin / bilgilendirin!

    Burada bir (meraklı?) Gözlem daha var: Bu kodda ilk yanıtlayıcı yönetimi belirtisi olmadığına dikkat edin. Şimdiye kadar sadece Tablo Görünümü Kontrolörleri ile denedim ve her şey birlikte oldukça güzel çalışıyor gibi görünüyor! Yine de diğer senaryolar için kefil olamam.

    Kendall, vd. al - UIWindow alt sınıfları için bunun neden böyle olabileceğini herkes söyleyebilir mi? Pencere besin zincirinin en üstünde olduğu için mi?


    1
    Bu çok garip olan şey. Olduğu gibi çalışır. Umarım "kendine rağmen çalışma" bir durum değil! Lütfen kendi testinizde neler bulduğunuzu bildirin.
    Joe D'Andrea

    Bunu düzenli bir UIView alt sınıfına almayı tercih ediyorum, özellikle de bunun tek bir yerde var olması gerektiğinden, bir pencere alt sınıfına koymak doğal bir uyum gibi görünüyor.
    Shaggy Frog

    Jdandrea teşekkür ederim, ben yönteminizi kullanın ama birden çok kez sallayarak sallayarak !!! i sadece kullanıcıların bir kez sallamak istiyorum (sallayarak orada yaptığı bir görünümde) ...! nasıl halledebilirim? Ben böyle sth uygulamak. i gibi benim kod smht uygulamak: i-phone.ir/forums/uploadedpictures/… ---- ama sorun uygulama uygulama ömrü için sadece 1 kez sallamak olduğunu! sadece görünüm
    Momi

    1
    Momeks: Cihaz dinlendikten (sallanma sonrası) sonra bildirimin gerçekleşmesi gerekiyorsa, motionBegan yerine motionEnded kullanın. Bunu yapmalı!
    Joe D'Andrea

    1
    @Joe D'Andrea - UIWindow yanıtlayıcı zincirinde olduğu için olduğu gibi çalışır. Daha yüksek zincirin yukarı şey yakalanan ve bu olayları tüketilen, bu olur değil onları alırsınız. developer.apple.com/library/ios/DOCUMENTATION/EventHandling/…
    DougW

    34

    Bu yayına "titreyen" bir uygulama aradım. Ben tetiklemek için biraz daha "sallama eylemi" gerektiren bir şey arıyordum millenomi'nin yanıtı benim için iyi çalıştı. Boolean değeri int shakeCount ile değiştirdim. Ayrıca Objective-C L0AccelerationIsShaking () yöntemini yeniden uyguladı. ShakeCount'a eklenen miktarı ayarlayarak gerekli sarsıntı miktarını değiştirebilirsiniz. Henüz optimal değerleri bulduğumdan emin değilim, ancak şu ana kadar iyi çalışıyor gibi görünüyor. Umarım bu birine yardımcı olur:

    - (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);
    }

    Not: Güncelleme aralığını saniyenin 1 / 15'ine ayarladım.

    [[UIAccelerometer sharedAccelerometer] setUpdateInterval:(1.0 / 15)];

    Cihaz hareketini panorama gibi nasıl algılayabilirim?
    user2526811

    İphone sadece X yönünde hareket ettirilirse sarsıntı nasıl belirlenir? Y veya Z yönünde sallanmayı tespit etmek istemiyorum.
    Manthan

    12

    İvmeölçeri ivmeölçer aracılığıyla kontrol etmeniz gerekir: didAccelerate: UIAccelerometerDelegate protokolünün bir parçası olan yöntem ve değerlerin bir sarsıntı için gereken hareket miktarı için bir eşiğin üzerinden geçip geçmediğini kontrol edin.

    İvmeölçerde iyi bir örnek kod var: didAccelerate: yöntem, iPhone geliştirici sitesinde bulunan GLPaint örneğinde AppController.m'nin hemen altında.


    12

    Swift ile iOS 8.3'te (belki de daha önce) , görünüm denetleyicinizdeki motionBeganveya motionEndedyöntemlerini geçersiz kılmak kadar basit :

    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!")
        }
    }

    Onu denedin mi? Ben uygulama arka planda olduğunda bu işe almak için biraz ekstra legwork alacağını varsayalım.
    nhgrif

    Hayır .. yakında .. ama iPhone 6s fark ettiyseniz, bu "uyandırma ekranına almak" özelliği vardır, burada ekran bir süre aydınlatır, cihazı aldığınızda. Bu davranışı yakalamam gerekiyor
    nr5

    9

    Bu, ihtiyacınız olan temel delege kodudur:

    #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];   
        }

    Ayrıca Arabirim'de uygun kodu girin. yani:

    @interface MyViewController: UIViewController <UIPickerViewDelegate, UIPickerViewDataSource, UIAccelerometerDelegate>


    Cihaz hareketini panorama gibi nasıl algılayabilirim?
    user2526811

    İphone sadece X yönünde hareket ettirilirse sarsıntı nasıl belirlenir? Y veya Z yönünde sallanmayı tespit etmek istemiyorum.
    Manthan


    7

    ViewController.m dosyasına aşağıdaki yöntemleri ekleyin, düzgün çalışıyor

        -(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 
         }

    5

    Bunu bir yorum yerine bir cevap olarak göndermek için üzgünüm ama gördüğünüz gibi Stack Overflow'da yeniyim ve bu yüzden henüz yorum gönderecek kadar saygın değilim!

    Her neyse, görünüm görünüm hiyerarşisinin bir parçası olduğunda ilk yanıtlayıcı durumunu ayarladığınızdan emin olmak için ikinci saniye. Dolayısıyla, görünüm denetleyicileri viewDidLoadyönteminizde ilk yanıtlayıcı durumunu ayarlamak örneğin çalışmaz. Ve çalışıp çalışmadığından [view becomeFirstResponder]emin değilseniz, test edebileceğiniz bir boolean döndürür.

    Başka bir nokta: Eğer yapabilirsiniz gereksiz yere bir UIView alt sınıfı oluşturmak istemiyorsanız sarsıntı olayı yakalamak için bir görünüm denetleyicisi kullanın. Çok fazla güçlük olmadığını biliyorum ama yine de seçenek var. Kendall'ın UIView alt sınıfına koyduğu kod snippet'lerini denetleyicinize taşıyın ve UIView alt sınıfı yerine becomeFirstResponderve resignFirstResponderiletilerini gönderin self.


    Bu soruya bir cevap sağlamaz. Bir yazardan eleştiri veya açıklama istemek için gönderilerinin altına bir yorum bırakın.
    Alex Salauyou

    @SashaSalauyou Burada ilk cümlede açıkça belirtmiştim O zaman bir yorum göndermek için yeterli itibarım yoktu
    Newtz

    afedersiniz. Burada 5-yo cevabının inceleme kuyruğuna girmesi mümkündür))
    Alex Salauyou

    5

    Öncelikle, bunun eski bir yazı olduğunu biliyorum, ama yine de alakalı ve en yüksek oy alan iki cevabın sallamayı mümkün olduğunca erken tespit etmediğini buldum . Böyle yapılır:

    1. CoreMotion'ı hedefin oluşturma aşamalarında projenize bağlayın: CoreMotion
    2. ViewController'ınızda:

      - (BOOL)canBecomeFirstResponder {
          return YES;
      }
      
      - (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
      {
          if (motion == UIEventSubtypeMotionShake) {
              // Shake detected.
          }
      }

    4

    En kolay çözüm, uygulamanız için yeni bir kök pencere türetmektir:

    @implementation OMGWindow : UIWindow
    
    - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
        if (event.type == UIEventTypeMotion && motion == UIEventSubtypeMotionShake) {
            // via notification or something   
        }
    }
    @end

    Sonra başvuru temsilcinizde:

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        self.window = [[OMGWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
        //…
    }

    Bir Storyboard kullanıyorsanız, bu daha zor olabilir, uygulama temsilcisinde ihtiyacınız olan kodu tam olarak bilmiyorum.


    Ve evet, olabilir daki temel sınıfını türetmek @implementationXcode 5. yana
    mxcl

    Bu işe yarıyor, birkaç uygulamada kullanıyorum. Neden seçildiğinden emin değilim.
    mxcl

    bir cazibe gibi çalıştı. Merak ediyorsanız, pencere sınıfını şu şekilde geçersiz kılabilirsiniz: stackoverflow.com/a/10580083/709975
    Edu

    Uygulama arka planda olduğunda bu işe yarar mı? Başka bir fikir yoksa arka planda ne zaman tespit etmek?
    nr5

    Bir arka plan modu ayarınız varsa, bu muhtemelen işe yarayacaktır.
    mxcl

    1

    Bunu yapmak için sadece bu üç yöntemi kullanın

    - (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event{
    - (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event{
    - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event{

    Ayrıntılar için baştan tam bir örnek kodunu kontrol edebilir orada


    1

    İlk cevaba dayanan hızlı bir versiyon!

    override func motionEnded(_ motion: UIEventSubtype, with event: UIEvent?) {
        if ( event?.subtype == .motionShake )
        {
            print("stop shaking me!")
        }
    }

    -1

    Bu uygulamayı etkinleştirmek için UIWindow'da bir kategori oluşturdum:

    @implementation UIWindow (Utils)
    
    - (BOOL)canBecomeFirstResponder
    {
        return YES;
    }
    
    - (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
    {
        if (motion == UIEventSubtypeMotionShake) {
            // Do whatever you want here...
        }
    }
    
    @end
    Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
    Licensed under cc by-sa 3.0 with attribution required.