Voglio reagire quando qualcuno scuote l'iPhone. Non mi interessa particolarmente come lo scuotono, solo che è stato agitato vigorosamente per una frazione di secondo. Qualcuno sa come rilevarlo?
Voglio reagire quando qualcuno scuote l'iPhone. Non mi interessa particolarmente come lo scuotono, solo che è stato agitato vigorosamente per una frazione di secondo. Qualcuno sa come rilevarlo?
Risposte:
In 3.0, ora c'è un modo più semplice: agganciare i nuovi eventi di movimento.
Il trucco principale è che devi avere un UIView (non UIViewController) che desideri come firstResponder per ricevere i messaggi dell'evento shake. Ecco il codice che puoi utilizzare in qualsiasi UIView per ottenere eventi di agitazione:
@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
Puoi facilmente trasformare qualsiasi UIView (anche le viste di sistema) in una vista che può ottenere l'evento shake semplicemente eseguendo la sottoclasse della vista solo con questi metodi (e quindi selezionando questo nuovo tipo invece del tipo base in IB, o usandolo quando si alloca un Visualizza).
Nel controller di visualizzazione, si desidera impostare questa visualizzazione per diventare il primo risponditore:
- (void) viewWillAppear:(BOOL)animated
{
[shakeView becomeFirstResponder];
[super viewWillAppear:animated];
}
- (void) viewWillDisappear:(BOOL)animated
{
[shakeView resignFirstResponder];
[super viewWillDisappear:animated];
}
Non dimenticare che se hai altre viste che diventano le prime risposte dalle azioni dell'utente (come una barra di ricerca o un campo di immissione testo) dovrai anche ripristinare lo stato di prima risposta della vista tremolante quando l'altra vista si dimette!
Questo metodo funziona anche se si imposta applicationSupportsShakeToEdit su NO.
motionEnded
prima che la vibrazione si sia effettivamente fermata. Quindi usando questo approccio ottieni una serie sconnessa di brevi scosse invece di una lunga. L'altra risposta funziona molto meglio in questo caso.
[super respondsToSelector:
non farà quello che vuoi, dal momento che equivale a chiamare [self respondsToSelector:
che restituirà YES
. Quello di cui hai bisogno è [[ShakingView superclass] instancesRespondToSelector:
.
Dalla mia applicazione 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
L'isteresi impedisce che l'evento shake si attivi più volte fino a quando l'utente non interrompe il shake.
motionBegan
e gli motionEnded
eventi non sono molto precisi o accurati in termini di rilevamento dell'esatto inizio e fine di una vibrazione. Questo approccio ti consente di essere preciso come desideri.
Alla fine l'ho fatto funzionare usando esempi di codice di questo tutorial di Undo / Redo Manager .
Questo è esattamente ciò che devi fare:
- (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
è YES
.
[self resignFirstResponder];
prima [super viewWillDisappear:animated];
? Questo sembra strano.
Innanzitutto, la risposta del 10 luglio di Kendall è perfetta.
Ora ... volevo fare qualcosa di simile (in iPhone OS 3.0+), solo nel mio caso lo volevo a livello di app in modo da poter avvisare varie parti dell'app quando si verificava una vibrazione. Ecco cosa ho finito per fare.
Innanzitutto, ho eseguito la sottoclasse di UIWindow . Questo è facile. Crea un nuovo file di classe con un'interfaccia come MotionWindow : UIWindow
(sentiti libero di scegliere il tuo, natch). Aggiungi un metodo in questo modo:
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
if (event.type == UIEventTypeMotion && event.subtype == UIEventSubtypeMotionShake) {
[[NSNotificationCenter defaultCenter] postNotificationName:@"DeviceShaken" object:self];
}
}
Passa @"DeviceShaken"
al nome della notifica che preferisci. Salva il file.
Ora, se usi MainWindow.xib (materiale modello Xcode stock), vai lì e cambia la classe del tuo oggetto Window da UIWindow a MotionWindow o come lo hai chiamato. Salva lo xib. Se imposti UIWindow a livello di codice , usa invece la tua nuova classe Window.
Ora la tua app utilizza la classe UIWindow specializzata . Ovunque tu voglia sentirti dire di una scossa, iscriviti per ricevere le notifiche! Come questo:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(deviceShaken) name:@"DeviceShaken" object:nil];
Per rimuovere te stesso come osservatore:
[[NSNotificationCenter defaultCenter] removeObserver:self];
Ho messo il mio in viewWillAppear: e viewWillDisappear: per quanto riguarda i controller di visualizzazione. Assicurati che la tua risposta all'evento shake sappia se è "già in corso" oppure no. Altrimenti, se il dispositivo viene scosso due volte in successione, avrai un ingorgo. In questo modo puoi ignorare altre notifiche fino a quando non hai veramente risposto alla notifica originale.
Inoltre: Si può scegliere di cue off di motionBegan vs. motionEnded . Tocca a voi. Nel mio caso, l'effetto deve sempre verificarsi dopo che il dispositivo è a riposo (rispetto a quando inizia a tremare), quindi utilizzo motionEnded . Prova entrambi e vedi quale ha più senso ... o rileva / notifica per entrambi!
Un'altra osservazione (curiosa?) Qui: Notare che non c'è alcun segno della gestione del primo soccorritore in questo codice. Finora ci ho provato solo con i controller di visualizzazione tabella e tutto sembra funzionare abbastanza bene insieme! Non posso garantire altri scenari però.
Kendall, et. al - qualcuno può parlare del perché questo potrebbe essere così per le sottoclassi di UIWindow ? È perché la finestra è in cima alla catena alimentare?
Mi sono imbattuto in questo post in cerca di un'implementazione "scuotente". La risposta di Millenomi ha funzionato bene per me, anche se stavo cercando qualcosa che richiedesse un po 'più di "azione tremolante" per innescare. Ho sostituito il valore booleano con un shakeCount int. Ho anche reimplementato il metodo L0AccelerationIsShaking () in Objective-C. È possibile modificare la quantità di agitazione richiesta modificando la quantità aggiunta a shakeCount. Non sono sicuro di aver ancora trovato i valori ottimali, ma sembra che stia funzionando bene finora. Spero che questo aiuti qualcuno:
- (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: ho impostato l'intervallo di aggiornamento su 1/15 di secondo.
[[UIAccelerometer sharedAccelerometer] setUpdateInterval:(1.0 / 15)];
È necessario controllare l'accelerometro tramite accelerometro: didAccelerate: metodo che fa parte del protocollo UIAccelerometerDelegate e verificare se i valori superano una soglia per la quantità di movimento necessaria per uno shake.
Nell'accelerometro esiste un codice di esempio decente: didAccelerate: metodo proprio nella parte inferiore di AppController.m nell'esempio GLPaint, disponibile sul sito degli sviluppatori di iPhone.
In iOS 8.3 (forse in precedenza) con Swift, è semplice come sovrascrivere i metodi motionBegan
o motionEnded
nel controller della vista:
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!")
}
}
Questo è il codice delegato di base necessario:
#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];
}
Impostare anche il codice appropriato nell'interfaccia. vale a dire:
@interface MyViewController: UIViewController <UIPickerViewDelegate, UIPickerViewDataSource, UIAccelerometerDelegate>
Guarda l'esempio GLPaint.
http://developer.apple.com/library/ios/#samplecode/GLPaint/Introduction/Intro.html
Aggiungi i seguenti metodi nel file ViewController.m, funziona correttamente
-(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
}
Mi dispiace pubblicare questo come una risposta piuttosto che come un commento, ma come puoi vedere sono nuovo di Stack Overflow e quindi non sono ancora abbastanza affidabile per pubblicare commenti!
Ad ogni modo, secondo @cire, mi assicuro di impostare lo stato del primo risponditore quando la vista fa parte della gerarchia della vista. Pertanto, ad esempio, l'impostazione dello stato del primo risponditore nel viewDidLoad
metodo dei controller della vista non funzionerà. E se non sei sicuro che funzioni, [view becomeFirstResponder]
ti restituisce un valore booleano che puoi testare.
Un altro punto: puoi utilizzare un controller di visualizzazione per acquisire l'evento shake se non si desidera creare inutilmente una sottoclasse UIView. So che non è così tanto fastidio, ma l'opzione è ancora lì. Basta spostare i frammenti di codice che Kendall ha inserito nella sottoclasse UIView nel controller e inviare i messaggi becomeFirstResponder
e invece della sottoclasse UIView.resignFirstResponder
self
Prima di tutto, so che si tratta di un vecchio post, ma è ancora pertinente e ho scoperto che le due risposte più votate non hanno rilevato il tremito il prima possibile . Così è come si fa:
Nel tuo ViewController:
- (BOOL)canBecomeFirstResponder {
return YES;
}
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
if (motion == UIEventSubtypeMotionShake) {
// Shake detected.
}
}
La soluzione più semplice è derivare una nuova finestra di root per la tua applicazione:
@implementation OMGWindow : UIWindow
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
if (event.type == UIEventTypeMotion && motion == UIEventSubtypeMotionShake) {
// via notification or something
}
}
@end
Quindi nel delegato dell'applicazione:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[OMGWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
//…
}
Se stai usando uno Storyboard, questo potrebbe essere più complicato, non conosco esattamente il codice di cui avrai bisogno nel delegato dell'applicazione.
@implementation
dal Xcode 5.
Usa questi tre metodi per farlo
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event{
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event{
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event{
per i dettagli è possibile controllare un codice di esempio completo laggiù
Una versione rapida basata sulla prima risposta!
override func motionEnded(_ motion: UIEventSubtype, with event: UIEvent?) {
if ( event?.subtype == .motionShake )
{
print("stop shaking me!")
}
}
Per abilitare questa app, ho creato una categoria su UIWindow:
@implementation UIWindow (Utils)
- (BOOL)canBecomeFirstResponder
{
return YES;
}
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
if (motion == UIEventSubtypeMotionShake) {
// Do whatever you want here...
}
}
@end
viewDidAppear
invece diviewWillAppear
. Non sono sicuro del perché; forse la vista deve essere visibile prima di poter fare tutto ciò che fa per iniziare a ricevere gli eventi di vibrazione?