Bagaimana cara mendeteksi ketika seseorang mengguncang iPhone?

340

Saya ingin bereaksi ketika seseorang mengguncang iPhone. Saya tidak terlalu peduli bagaimana mereka mengocoknya, hanya saja itu dilambaikan dengan penuh semangat selama sepersekian detik. Adakah yang tahu cara mendeteksi ini?

Josh Gagnon
sumber

Jawaban:

292

Di 3.0, sekarang ada cara yang lebih mudah - hubungkan ke acara gerak baru.

Trik utamanya adalah Anda harus memiliki beberapa UIView (bukan UIViewController) yang Anda inginkan sebagai firstResponder untuk menerima pesan acara shake. Berikut kode yang dapat Anda gunakan di UIView apa pun untuk mendapatkan acara shake:

@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

Anda dapat dengan mudah mengubah UIView (bahkan tampilan sistem) apa pun menjadi tampilan yang bisa mendapatkan peristiwa guncangan hanya dengan mensubklasifikasikan tampilan hanya dengan metode ini (dan kemudian memilih tipe baru ini, bukan tipe dasar dalam IB, atau menggunakannya saat mengalokasikan melihat).

Di pengontrol tampilan, Anda ingin mengatur tampilan ini menjadi responden pertama:

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

Jangan lupa bahwa jika Anda memiliki tampilan lain yang menjadi responden pertama dari tindakan pengguna (seperti bilah pencarian atau bidang entri teks) Anda juga harus mengembalikan status guncangan tampilan responden pertama saat tampilan lain mengundurkan diri!

Metode ini berfungsi bahkan jika Anda mengatur applicationSupportsShakeToEdit ke NO.

Kendall Helmstetter Gelner
sumber
5
Ini tidak berhasil bagi saya: Saya harus mengganti pengontrol saya, viewDidAppearbukan viewWillAppear. Saya tidak yakin mengapa; mungkin tampilan harus terlihat sebelum dapat melakukan apa pun untuk mulai menerima acara goyang?
Kristopher Johnson
1
Ini lebih mudah tetapi belum tentu lebih baik. Jika Anda ingin mendeteksi goyangan yang panjang dan terus-menerus, pendekatan ini tidak berguna karena iPhone suka memecat motionEndedsebelum goyang benar-benar berhenti. Jadi, dengan menggunakan pendekatan ini Anda mendapatkan serangkaian getar pendek yang terputus-putus, bukan satu yang panjang. Jawaban lain bekerja lebih baik dalam hal ini.
Agustus
3
@Kendall - UIViewControllers mengimplementasikan UIResponder dan berada dalam rantai responden. UIWindow paling atas juga. developer.apple.com/library/ios/DOCUMENTATION/EventHandling/…
DougW
1
[super respondsToSelector:tidak akan melakukan apa yang Anda inginkan, karena itu sama dengan menelepon [self respondsToSelector:yang akan kembali YES. Yang Anda butuhkan adalah [[ShakingView superclass] instancesRespondToSelector:.
Vadim Yelagin
2
Alih-alih menggunakan: if (event.subtype == UIEventSubtypeMotionShake) Anda dapat menggunakan: if (motion == UIEventSubtypeMotionShake)
Hashim Akhtar
179

Dari aplikasi Diceshaker saya :

// 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 mencegah peristiwa guncangan memicu beberapa kali hingga pengguna menghentikan guncangan.

millenomi
sumber
7
Catatan Saya telah menambahkan jawaban di bawah ini yang menyajikan metode yang lebih mudah untuk 3.0.
Kendall Helmstetter Gelner
2
Apa yang terjadi jika mereka mengguncangnya dengan tepat pada poros tertentu?
jprete
5
Jawaban terbaik, karena iOS 3.0 motionBegandan motionEndedacara tidak terlalu tepat atau akurat dalam hal mendeteksi awal dan akhir goyang. Pendekatan ini memungkinkan Anda untuk menjadi setepat yang Anda inginkan.
Agustus
2
Akselerometer UIAccelerometerDelegate : didAccelerate: telah usang di iOS5.
Yantao Xie
Jawaban lain ini lebih baik dan lebih cepat untuk mengimplementasikan stackoverflow.com/a/2405692/396133
Abramodj
154

Saya akhirnya membuatnya bekerja menggunakan contoh kode dari Undo / Redo Manager Tutorial ini .
Inilah yang perlu Anda lakukan:

  • Setel properti applicationSupportsShakeToEdit di Delegasi Aplikasi:
  • 
        - (void)applicationDidFinishLaunching:(UIApplication *)application {
    
            application.applicationSupportsShakeToEdit = YES;
    
            [window addSubview:viewController.view];
            [window makeKeyAndVisible];
    }
    

  • Tambah / Override canBecomeFirstResponder , viewDidAppear: dan viewWillDisappear: metode di View Controller Anda:
  • 
    -(BOOL)canBecomeFirstResponder {
        return YES;
    }
    
    -(void)viewDidAppear:(BOOL)animated {
        [super viewDidAppear:animated];
        [self becomeFirstResponder];
    }
    
    - (void)viewWillDisappear:(BOOL)animated {
        [self resignFirstResponder];
        [super viewWillDisappear:animated];
    }
    

  • Tambahkan metode motionEnded ke View Controller Anda:
  • 
    - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
    {
        if (motion == UIEventSubtypeMotionShake)
        {
            // your code
        }
    }
    Eran Talmor
    sumber
    Hanya untuk menambah solusi Eran Talmor: Anda harus menggunakan navigasicontroller daripada viewcontroller jika Anda memilih aplikasi seperti itu di pemilih proyek baru.
    Rob
    Saya mencoba menggunakan ini tetapi kadang-kadang goyah atau goyangan ringan itu baru saja berhenti
    vinothp
    Langkah 1 sekarang berlebihan, karena nilai defaultnya applicationSupportsShakeToEditadalah YES.
    DonVaughn
    1
    Kenapa menelepon [self resignFirstResponder];dulu [super viewWillDisappear:animated];? Ini sepertinya aneh.
    JaredH
    94

    Pertama, jawaban Kendall tanggal 10 Juli tepat.

    Sekarang ... Saya ingin melakukan sesuatu yang serupa (di iPhone OS 3.0+), hanya dalam kasus saya saya menginginkannya di seluruh aplikasi sehingga saya bisa mengingatkan berbagai bagian aplikasi ketika terjadi guncangan. Inilah yang akhirnya saya lakukan.

    Pertama, saya subklasifikasi UIWindow . Ini peasy mudah. Buat file kelas baru dengan antarmuka seperti MotionWindow : UIWindow(jangan ragu untuk memilih sendiri, natch). Tambahkan metode seperti ini:

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

    Ubah @"DeviceShaken"ke nama notifikasi pilihan Anda. Simpan file.

    Sekarang, jika Anda menggunakan MainWindow.xib (stok template kode Xcode), masuk ke sana dan ubah kelas objek Window Anda dari UIWindow ke MotionWindow atau apa pun namanya. Simpan xib. Jika Anda mengatur UIWindow terprogram, gunakan kelas Window baru Anda di sana.

    Sekarang aplikasi Anda menggunakan kelas UIWindow khusus . Di mana pun Anda ingin diberi tahu tentang goyangan, daftar untuk pemberitahuan mereka! Seperti ini:

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

    Untuk menghapus diri Anda sebagai pengamat:

    [[NSNotificationCenter defaultCenter] removeObserver:self];

    Saya menempatkan milik saya di viewWillAppear: dan viewWillDisappear: di mana View Controllers bersangkutan. Pastikan respons Anda terhadap acara goyang tahu apakah itu "sudah dalam proses" atau tidak. Jika tidak, jika perangkat diguncang dua kali berturut-turut, Anda akan mengalami kemacetan lalu lintas. Dengan cara ini Anda dapat mengabaikan notifikasi lain hingga Anda benar-benar selesai merespons notifikasi asli.

    Juga: Anda dapat memilih untuk isyarat off dari motionBegan vs motionEnded . Terserah kamu. Dalam kasus saya, efeknya selalu perlu terjadi setelah perangkat dalam keadaan diam (vs. ketika mulai bergetar), jadi saya menggunakan motionEnded . Coba keduanya dan lihat mana yang lebih masuk akal ... atau deteksi / beri tahu untuk keduanya!

    Satu lagi pengamatan (penasaran?) Di sini: Perhatikan tidak ada tanda-tanda manajemen responden pertama dalam kode ini. Saya hanya mencoba ini dengan Table View Controllers sejauh ini dan semuanya tampaknya bekerja dengan sangat baik bersama-sama! Saya tidak bisa menjamin skenario lain.

    Kendall, et. al - adakah yang bisa berbicara mengapa ini mungkin terjadi untuk subkelas UIWindow ? Apakah karena jendelanya ada di puncak rantai makanan?

    Joe D'Andrea
    sumber
    1
    Itu yang aneh sekali. Ini bekerja apa adanya. Semoga ini bukan kasus "bekerja meskipun dengan sendirinya"! Tolong beri tahu saya apa yang Anda temukan dalam pengujian Anda sendiri.
    Joe D'Andrea
    Saya lebih suka ini daripada subkelas UIView biasa, terutama karena Anda hanya perlu ini ada di satu tempat, jadi menempatkan subkelas jendela tampaknya cocok secara alami.
    Shaggy Frog
    Terima kasih jdandrea, saya menggunakan metode Anda tetapi gemetaran semakin banyak !!! Saya hanya ingin pengguna dapat mengguncang satu kali (pada pandangan yang bergetar tidak ada) ...! bagaimana saya bisa mengatasinya? Saya menerapkan sth seperti ini. saya menerapkan kode smht saya seperti ini: i-phone.ir/forums/uploadedpictures/... ---- tetapi masalahnya adalah aplikasi hanya mengocok 1 kali seumur hidup aplikasi! tidak hanya melihat
    Momi
    1
    Momeks: Jika Anda memerlukan pemberitahuan terjadi setelah perangkat dalam keadaan diam (pasca-goyang), gunakan motionEnded alih-alih motionBegan. Yang seharusnya melakukannya!
    Joe D'Andrea
    1
    @ Jo D'Andrea - Ini berfungsi apa adanya karena UIWindow ada di rantai responden. Jika sesuatu yang lebih tinggi dari rantai dicegat dan dikonsumsi peristiwa ini, itu tidak akan menerimanya. developer.apple.com/library/ios/DOCUMENTATION/EventHandling/…
    DougW
    34

    Saya menemukan posting ini mencari implementasi "gemetar". jawaban millenomi bekerja dengan baik untuk saya, walaupun saya sedang mencari sesuatu yang membutuhkan "aksi gemetar" untuk memicu. Saya telah mengganti ke nilai Boolean dengan shakeCount int. Saya juga mengimplementasikan kembali metode L0AccelerationIsShaking () di Objective-C. Anda dapat mengubah jumlah pengocokan yang diperlukan dengan mengubah jumlah yang ditambahkan ke shakeCount. Saya tidak yakin saya telah menemukan nilai optimal, tetapi tampaknya masih berfungsi dengan baik sejauh ini. Semoga ini bisa membantu seseorang:

    - (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: Saya sudah mengatur interval pembaruan ke 1/15 detik.

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

    sumber
    Bagaimana saya bisa mendeteksi pergerakan perangkat seperti panorama?
    user2526811
    Bagaimana cara mengidentifikasi shake jika iphone dipindahkan hanya dalam arah X? Saya tidak ingin mendeteksi shake jika ke arah Y atau Z.
    Manthan
    12

    Anda perlu memeriksa accelerometer melalui accelerometer: didAccelerate: metode yang merupakan bagian dari protokol UIAccelerometerDelegate dan periksa apakah nilainya melampaui ambang batas untuk jumlah gerakan yang diperlukan untuk shake.

    Ada contoh kode yang layak di accelerometer: didAccelerate: metode tepat di bagian bawah AppController.m dalam contoh GLPaint yang tersedia di situs pengembang iPhone.

    Dave Verwer
    sumber
    12

    Di iOS 8.3 (mungkin sebelumnya) dengan Swift, sesederhana mengganti metode motionBeganatau motionEndeddi pengontrol tampilan Anda:

    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!")
        }
    }
    nhgrif
    sumber
    Apakah kamu sudah mencobanya? Saya berasumsi bahwa untuk mengaktifkan ini ketika aplikasi di latar belakang akan memerlukan sedikit kerja keras tambahan.
    nhgrif
    Tidak .. akan segera .. tetapi Jika Anda perhatikan iPhone 6s memiliki fitur "angkat layar" ini, di mana layar menjadi cerah untuk beberapa waktu, saat Anda mengambil perangkat. Saya perlu menangkap perilaku ini
    nr5
    9

    Ini adalah kode delegasi dasar yang Anda butuhkan:

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

    Juga atur kode yang sesuai di Interface. yaitu:

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

    Benjamin Ortuzar
    sumber
    Bagaimana saya bisa mendeteksi pergerakan perangkat seperti panorama?
    user2526811
    Bagaimana cara mengidentifikasi shake jika iphone dipindahkan hanya dalam arah X? Saya tidak ingin mendeteksi shake jika ke arah Y atau Z.
    Manthan
    7

    Tambahkan metode berikut dalam file ViewController.m, itu berfungsi dengan baik

        -(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 
         }
    Himanshu Mahajan
    sumber
    5

    Maaf memposting ini sebagai jawaban daripada komentar tetapi karena Anda dapat melihat saya baru di Stack Overflow dan jadi saya belum cukup terkemuka untuk mengirim komentar!

    Pokoknya saya kedua @cire tentang memastikan untuk mengatur status responden pertama setelah tampilan adalah bagian dari hierarki tampilan. Jadi pengaturan status responden pertama dalam viewDidLoadmetode pengontrol tampilan Anda tidak akan berfungsi misalnya. Dan jika Anda tidak yakin apakah itu berfungsi [view becomeFirstResponder]mengembalikan boolean yang dapat Anda uji.

    Poin lain: Anda bisa menggunakan pengontrol tampilan untuk menangkap acara goyang jika Anda tidak ingin membuat subkelas UIView secara tidak perlu. Saya tahu ini tidak terlalu merepotkan tetapi masih ada opsi. Cukup pindahkan cuplikan kode yang dimasukkan Kendall ke dalam subkelas UIView ke dalam pengontrol Anda dan kirim pesan becomeFirstResponderdan resignFirstResponderke selfalih-alih subkelas UIView.

    Newtz
    sumber
    Ini tidak memberikan jawaban untuk pertanyaan itu. Untuk mengkritik atau meminta klarifikasi dari penulis, tinggalkan komentar di bawah posting mereka.
    Alex Salauyou
    @SashaSalauyou dengan jelas saya nyatakan dalam kalimat pertama di sini saya tidak punya cukup reputasi untuk mengirim komentar pada saat itu
    Newtz
    Maaf. Dimungkinkan di sini untuk jawaban 5-yo untuk masuk ke antrean ulasan))
    Alex Salauyou
    5

    Pertama, saya tahu ini adalah posting lama, tetapi masih relevan, dan saya menemukan bahwa dua jawaban pilihan tertinggi tidak mendeteksi guncangan sedini mungkin . Ini adalah cara untuk melakukannya:

    1. Tautkan CoreMotion ke proyek Anda di fase build target: CoreMotion
    2. Di ViewController Anda:

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

    Solusi termudah adalah mendapatkan jendela root baru untuk aplikasi Anda:

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

    Kemudian dalam delegasi aplikasi Anda:

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

    Jika Anda menggunakan Storyboard, ini mungkin lebih sulit, saya tidak tahu kode yang akan Anda butuhkan dalam delegasi aplikasi secara tepat.

    mxcl
    sumber
    Dan ya, Anda dapat menurunkan kelas dasar Anda @implementationsejak Xcode 5.
    mxcl
    Ini berfungsi, saya menggunakannya di beberapa aplikasi. Jadi tidak yakin mengapa itu ditolak.
    mxcl
    bekerja seperti pesona. Jika Anda bertanya-tanya, Anda dapat mengganti kelas jendela seperti ini: stackoverflow.com/a/10580083/709975
    Edu
    Apakah ini akan berfungsi ketika aplikasi di latar belakang? Jika tidak ada ide lain bagaimana mendeteksi ketika di latar belakang?
    nr5
    Ini mungkin akan berfungsi jika Anda memiliki set mode latar belakang.
    mxcl
    1

    Cukup gunakan tiga metode ini untuk melakukannya

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

    untuk perincian Anda dapat memeriksa kode contoh lengkap di sana

    Mashhadi
    sumber
    1

    Versi cepat berdasarkan pada jawaban pertama!

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

    Untuk mengaktifkan aplikasi ini, saya membuat kategori di UIWindow:

    @implementation UIWindow (Utils)
    
    - (BOOL)canBecomeFirstResponder
    {
        return YES;
    }
    
    - (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
    {
        if (motion == UIEventSubtypeMotionShake) {
            // Do whatever you want here...
        }
    }
    
    @end
    Mike
    sumber