Je veux réagir quand quelqu'un secoue l'iPhone. Je ne me soucie pas particulièrement de la façon dont ils le secouent, juste qu'il a été agité vigoureusement pendant une fraction de seconde. Quelqu'un sait-il comment détecter cela?
Je veux réagir quand quelqu'un secoue l'iPhone. Je ne me soucie pas particulièrement de la façon dont ils le secouent, juste qu'il a été agité vigoureusement pendant une fraction de seconde. Quelqu'un sait-il comment détecter cela?
Réponses:
Dans 3.0, il existe désormais un moyen plus simple: connectez-vous aux nouveaux événements de mouvement.
L'astuce principale est que vous devez avoir certains UIView (pas UIViewController) que vous souhaitez que firstResponder reçoive les messages d'événement shake. Voici le code que vous pouvez utiliser dans n'importe quelle UIView pour obtenir des événements de secousse:
@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
Vous pouvez facilement transformer n'importe quelle UIView (même les vues système) en une vue qui peut obtenir l'événement shake simplement en sous-classant la vue avec uniquement ces méthodes (puis en sélectionnant ce nouveau type au lieu du type de base dans IB, ou en l'utilisant lors de l'allocation d'un vue).
Dans le contrôleur de vue, vous souhaitez définir cette vue pour devenir le premier répondant:
- (void) viewWillAppear:(BOOL)animated
{
[shakeView becomeFirstResponder];
[super viewWillAppear:animated];
}
- (void) viewWillDisappear:(BOOL)animated
{
[shakeView resignFirstResponder];
[super viewWillDisappear:animated];
}
N'oubliez pas que si vous avez d'autres vues qui deviennent le premier répondant des actions de l'utilisateur (comme une barre de recherche ou un champ de saisie de texte), vous devrez également restaurer l'état de premier répondant de la vue tremblante lorsque l'autre vue démissionne!
Cette méthode fonctionne même si vous définissez applicationSupportsShakeToEdit sur NO.
motionEnded
avant que le tremblement ne se soit réellement arrêté. Donc, en utilisant cette approche, vous obtenez une série décousue de secousses courtes au lieu d'une longue. L'autre réponse fonctionne beaucoup mieux dans ce cas.
[super respondsToSelector:
ne fera pas ce que vous voulez, car c'est équivalent à appeler [self respondsToSelector:
qui reviendra YES
. Ce dont vous avez besoin, c'est [[ShakingView superclass] instancesRespondToSelector:
.
Depuis mon application 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'histérèse empêche l'événement de tremblement de se déclencher plusieurs fois jusqu'à ce que l'utilisateur arrête le tremblement.
motionBegan
et les motionEnded
événements ne sont pas très précis ni précis en termes de détection du début et de la fin exacts d'un tremblement. Cette approche vous permet d'être aussi précis que vous le souhaitez.
Je l'ai finalement fait fonctionner en utilisant des exemples de code de ce didacticiel Undo / Redo Manager .
C'est exactement ce que vous devez faire:
- (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
est YES
.
[self resignFirstResponder];
avance [super viewWillDisappear:animated];
? Cela semble particulier.
Tout d'abord, la réponse de Kendall du 10 juillet est parfaite.
Maintenant ... Je voulais faire quelque chose de similaire (dans l'iPhone OS 3.0+), seulement dans mon cas, je le voulais à l'échelle de l'application afin de pouvoir alerter différentes parties de l'application en cas de tremblement. Voici ce que j'ai fini par faire.
Tout d'abord, j'ai sous- classé UIWindow . C'est facile. Créez un nouveau fichier de classe avec une interface telle que MotionWindow : UIWindow
(n'hésitez pas à choisir le vôtre, natch). Ajoutez une méthode comme ceci:
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
if (event.type == UIEventTypeMotion && event.subtype == UIEventSubtypeMotionShake) {
[[NSNotificationCenter defaultCenter] postNotificationName:@"DeviceShaken" object:self];
}
}
Modifiez @"DeviceShaken"
le nom de notification de votre choix. Enregistrez le fichier.
Maintenant, si vous utilisez un MainWindow.xib (élément de modèle Xcode stock), allez-y et changez la classe de votre objet Window de UIWindow en MotionWindow ou tout ce que vous avez appelé. Enregistrez le xib. Si vous configurez UIWindow par programme, utilisez plutôt votre nouvelle classe Window.
Maintenant, votre application utilise la classe spécialisée UIWindow . Partout où vous voulez être informé d'une secousse, inscrivez-vous pour les notifications! Comme ça:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(deviceShaken) name:@"DeviceShaken" object:nil];
Pour vous retirer en tant qu'observateur:
[[NSNotificationCenter defaultCenter] removeObserver:self];
J'ai mis le mien dans viewWillAppear: et viewWillDisappear: en ce qui concerne les contrôleurs de vue. Assurez-vous que votre réponse à l'événement shake sait s'il est "déjà en cours" ou non. Sinon, si l'appareil est secoué deux fois de suite, vous aurez un embouteillage limité. De cette façon, vous pouvez ignorer les autres notifications jusqu'à ce que vous ayez vraiment fini de répondre à la notification d'origine.
Aussi: Vous pouvez choisir de repérer motionBegan vs motionEnded . C'est à vous. Dans mon cas, l'effet doit toujours avoir lieu après que l'appareil est au repos (vs quand il commence à trembler), donc j'utilise motionEnded . Essayez les deux et voyez lequel a le plus de sens ... ou détectez / notifiez les deux!
Une autre observation (curieuse?) Ici: Remarquez qu'il n'y a aucun signe de gestion des premiers intervenants dans ce code. Je n'ai essayé cela qu'avec les contrôleurs de vue de table jusqu'à présent et tout semble fonctionner très bien ensemble! Je ne peux cependant pas garantir d'autres scénarios.
Kendall, et. al - quelqu'un peut-il expliquer pourquoi cela pourrait être le cas pour les sous-classes UIWindow ? Est-ce parce que la fenêtre est au sommet de la chaîne alimentaire?
Je suis tombé sur ce post à la recherche d'une implémentation "tremblante". La réponse de millenomi a bien fonctionné pour moi, même si je cherchais quelque chose qui nécessitait un peu plus d '"action tremblante" pour se déclencher. J'ai remplacé la valeur booléenne par un entier shakeCount. J'ai également réimplémenté la méthode L0AccelerationIsShaking () dans Objective-C. Vous pouvez modifier la quantité de secousses requise en modifiant la quantité ajoutée à shakeCount. Je ne suis pas sûr d'avoir encore trouvé les valeurs optimales, mais cela semble bien fonctionner jusqu'à présent. J'espère que cela aide quelqu'un:
- (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: J'ai défini l'intervalle de mise à jour au 1 / 15e de seconde.
[[UIAccelerometer sharedAccelerometer] setUpdateInterval:(1.0 / 15)];
Vous devez vérifier l'accéléromètre via accelerometer: didAccelerate: méthode qui fait partie du protocole UIAccelerometerDelegate et vérifier si les valeurs dépassent un seuil pour la quantité de mouvement nécessaire pour une secousse.
Il y a un exemple de code décent dans l'accéléromètre: didAccelerate: méthode tout en bas d'AppController.m dans l'exemple GLPaint qui est disponible sur le site des développeurs iPhone.
Dans iOS 8.3 (peut-être plus tôt) avec Swift, c'est aussi simple que de remplacer les méthodes motionBegan
ou motionEnded
dans votre contrôleur de vue:
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!")
}
}
Voici le code délégué de base dont vous avez besoin:
#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];
}
Définissez également le dans le code approprié dans l'interface. c'est à dire:
@interface MyViewController: UIViewController <UIPickerViewDelegate, UIPickerViewDataSource, UIAccelerometerDelegate>
Consultez l'exemple GLPaint.
http://developer.apple.com/library/ios/#samplecode/GLPaint/Introduction/Intro.html
Ajouter les méthodes suivantes dans le fichier ViewController.m, son fonctionnement correctement
-(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
}
Désolé de poster ceci comme réponse plutôt que comme commentaire mais comme vous pouvez le voir, je suis nouveau sur Stack Overflow et je ne suis donc pas encore assez réputé pour poster des commentaires!
Quoi qu'il en soit, j'appuie @cire à m'assurer de définir le statut du premier répondant une fois que la vue fait partie de la hiérarchie des vues. Ainsi, la définition du statut de premier répondant dans votre viewDidLoad
méthode de contrôleur de vue ne fonctionnera pas par exemple. Et si vous ne savez pas si cela fonctionne, [view becomeFirstResponder]
vous obtenez un booléen que vous pouvez tester.
Autre point: vous pouvez utiliser un contrôleur de vue pour capturer l'événement shake si vous ne voulez pas créer inutilement une sous-classe UIView. Je sais que ce n'est pas très compliqué, mais l'option est toujours là. Déplacez simplement les extraits de code que Kendall a mis dans la sous-classe UIView dans votre contrôleur et envoyez les messages becomeFirstResponder
et resignFirstResponder
à la self
place de la sous-classe UIView.
Tout d'abord, je sais que c'est un ancien poste, mais il est toujours pertinent, et j'ai trouvé que les deux réponses les plus votées n'ont pas détecté la secousse le plus tôt possible . Voici comment faire:
Dans votre ViewController:
- (BOOL)canBecomeFirstResponder {
return YES;
}
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
if (motion == UIEventSubtypeMotionShake) {
// Shake detected.
}
}
La solution la plus simple consiste à dériver une nouvelle fenêtre racine pour votre application:
@implementation OMGWindow : UIWindow
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
if (event.type == UIEventTypeMotion && motion == UIEventSubtypeMotionShake) {
// via notification or something
}
}
@end
Ensuite, dans votre délégué d'application:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[OMGWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
//…
}
Si vous utilisez un Storyboard, cela peut être plus délicat, je ne connais pas précisément le code dont vous aurez besoin dans le délégué d'application.
@implementation
depuis le Xcode 5.
Utilisez simplement ces trois méthodes pour le faire
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event{
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event{
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event{
pour plus de détails, vous pouvez vérifier un exemple de code complet là-bas
Une version swiftease basée sur la toute première réponse!
override func motionEnded(_ motion: UIEventSubtype, with event: UIEvent?) {
if ( event?.subtype == .motionShake )
{
print("stop shaking me!")
}
}
Pour activer cette application à l'échelle, j'ai créé une catégorie sur 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
lieu deviewWillAppear
. Je ne sais pas pourquoi; peut-être que la vue doit être visible avant de pouvoir faire ce qu'elle fait pour commencer à recevoir les événements de tremblement?