Ich möchte reagieren, wenn jemand das iPhone schüttelt. Es ist mir egal, wie sie es schütteln, nur dass es für den Bruchteil einer Sekunde heftig geschwenkt wurde. Weiß jemand, wie man das erkennt?
Ich möchte reagieren, wenn jemand das iPhone schüttelt. Es ist mir egal, wie sie es schütteln, nur dass es für den Bruchteil einer Sekunde heftig geschwenkt wurde. Weiß jemand, wie man das erkennt?
Antworten:
In 3.0 gibt es jetzt einen einfacheren Weg - haken Sie sich in die neuen Bewegungsereignisse ein.
Der Haupttrick besteht darin, dass Sie über UIView (nicht UIViewController) verfügen müssen, das als firstResponder die Shake-Ereignismeldungen empfangen soll. Hier ist der Code, den Sie in jedem UIView verwenden können, um Shake-Ereignisse abzurufen:
@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
Sie können jede UIView (auch Systemansichten) einfach in eine Ansicht umwandeln, die das Shake-Ereignis abrufen kann, indem Sie die Ansicht nur mit diesen Methoden in Unterklassen unterteilen (und dann diesen neuen Typ anstelle des Basistyps in IB auswählen oder ihn beim Zuweisen von a verwenden Aussicht).
Im Ansichts-Controller möchten Sie diese Ansicht als Ersthelfer festlegen:
- (void) viewWillAppear:(BOOL)animated
{
[shakeView becomeFirstResponder];
[super viewWillAppear:animated];
}
- (void) viewWillDisappear:(BOOL)animated
{
[shakeView resignFirstResponder];
[super viewWillDisappear:animated];
}
Vergessen Sie nicht, dass Sie, wenn Sie andere Ansichten haben, die aufgrund von Benutzeraktionen zum Ersthelfer werden (z. B. eine Suchleiste oder ein Texteingabefeld), auch den Ersthelferstatus der erschütternden Ansicht wiederherstellen müssen, wenn die andere Ansicht zurücktritt!
Diese Methode funktioniert auch, wenn Sie applicationSupportsShakeToEdit auf NO setzen.
motionEnded
bevor das Verwackeln tatsächlich aufgehört hat. Wenn Sie diesen Ansatz verwenden, erhalten Sie eine unzusammenhängende Reihe von kurzen Shakes anstelle eines langen. Die andere Antwort funktioniert in diesem Fall viel besser.
[super respondsToSelector:
wird nicht tun, was Sie wollen, da es gleichbedeutend ist mit einem Anruf, [self respondsToSelector:
der zurückkehren wird YES
. Was Sie brauchen ist [[ShakingView superclass] instancesRespondToSelector:
.
Aus meiner Diceshaker- Anwendung:
// 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
Die Histerese verhindert, dass das Shake-Ereignis mehrmals ausgelöst wird, bis der Benutzer das Shake stoppt.
motionBegan
und motionEnded
Ereignisse nicht sehr genau oder genau sind, um den genauen Beginn und das genaue Ende eines Shakes zu ermitteln. Mit diesem Ansatz können Sie so präzise sein, wie Sie möchten.
Ich habe es endlich mit Codebeispielen aus diesem Undo / Redo Manager-Tutorial zum Laufen gebracht .
Genau das müssen Sie tun:
- (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
ist YES
.
[self resignFirstResponder];
vorher anrufen [super viewWillDisappear:animated];
? Das scheint eigenartig.
Erstens ist Kendalls Antwort vom 10. Juli genau richtig.
Jetzt ... wollte ich etwas Ähnliches tun (in iPhone OS 3.0+), nur in meinem Fall wollte ich es App-weit, damit ich verschiedene Teile der App benachrichtigen konnte, wenn ein Verwackeln auftrat. Folgendes habe ich getan.
Zuerst habe ich UIWindow unterklassifiziert . Das ist einfach peasy. Erstellen Sie eine neue Klassendatei mit einer Schnittstelle wie MotionWindow : UIWindow
(Sie können auch Ihre eigene auswählen, natch). Fügen Sie eine Methode wie folgt hinzu:
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
if (event.type == UIEventTypeMotion && event.subtype == UIEventSubtypeMotionShake) {
[[NSNotificationCenter defaultCenter] postNotificationName:@"DeviceShaken" object:self];
}
}
Ändern Sie @"DeviceShaken"
den Benachrichtigungsnamen Ihrer Wahl. Speicher die Datei.
Wenn Sie jetzt eine MainWindow.xib (Standard-Xcode-Vorlage) verwenden, gehen Sie dort hinein und ändern Sie die Klasse Ihres Window-Objekts von UIWindow in MotionWindow oder wie auch immer Sie es genannt haben. Speichern Sie die xib. Wenn Sie UIWindow einrichten programmgesteuert , verwenden Sie stattdessen dort Ihre neue Window-Klasse.
Jetzt verwendet Ihre App die spezialisierte UIWindow- Klasse. Wo immer Sie über einen Shake informiert werden möchten, melden Sie sich für diese Benachrichtigungen an! So was:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(deviceShaken) name:@"DeviceShaken" object:nil];
Um sich als Beobachter zu entfernen:
[[NSNotificationCenter defaultCenter] removeObserver:self];
Ich habe meine in viewWillAppear: und viewWillDisappear: für View Controller eingefügt . Stellen Sie sicher, dass Ihre Antwort auf das Shake-Ereignis weiß, ob es "bereits ausgeführt" wird oder nicht. Andernfalls tritt ein Stau auf, wenn das Gerät zweimal hintereinander geschüttelt wird. Auf diese Weise können Sie andere Benachrichtigungen ignorieren, bis Sie wirklich fertig sind, auf die ursprüngliche Benachrichtigung zu antworten.
Außerdem: Sie können wählen, ob Sie abrufen möchten motionBegan vs. motionEnded abzurufen . Es liegt an dir. In meinem Fall muss der Effekt immer stattfinden , nachdem das Gerät im Ruhezustand ist (gegenüber, wenn es beginnt zu schütteln), so dass ich motionEnded . Probieren Sie beide aus und finden Sie heraus, welche sinnvoller ist ... oder erkennen / benachrichtigen Sie beide!
Noch eine (merkwürdige?) Beobachtung hier: Beachten Sie, dass dieser Code keine Anzeichen für eine Ersthelferverwaltung enthält. Ich habe dies bisher nur mit Table View Controllern versucht und alles scheint ganz gut zusammenzuarbeiten! Ich kann jedoch nicht für andere Szenarien bürgen.
Kendall et al. al - kann jemand darüber sprechen, warum dies für UIWindow so sein könnte ? Unterklassen ? Liegt es daran, dass sich das Fenster oben in der Nahrungskette befindet?
Ich bin auf diesen Beitrag gestoßen, als ich nach einer "erschütternden" Implementierung gesucht habe. millenomis antwort funktionierte gut für mich, obwohl ich nach etwas suchte, das etwas mehr "zittern" erfordert, um auszulösen. Ich habe den booleschen Wert durch einen intakeCount ersetzt. Ich habe auch die L0AccelerationIsShaking () -Methode in Objective-C neu implementiert. Sie können die erforderliche Menge an Schütteln anpassen, indem Sie die Menge an ShakeCount anpassen. Ich bin mir nicht sicher, ob ich die optimalen Werte gefunden habe, aber es scheint bisher gut zu funktionieren. Hoffe das hilft jemandem:
- (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: Ich habe das Aktualisierungsintervall auf 1/15 Sekunde eingestellt.
[[UIAccelerometer sharedAccelerometer] setUpdateInterval:(1.0 / 15)];
Sie müssen den Beschleunigungsmesser über den Beschleunigungsmesser überprüfen: didAccelerate: Methode, die Teil des UIAccelerometerDelegate-Protokolls ist, und prüfen, ob die Werte einen Schwellenwert für die für ein Schütteln erforderliche Bewegungsmenge überschreiten.
Im Beschleunigungsmesser befindet sich ein anständiger Beispielcode: didAccelerate: -Methode ganz unten in AppController.m im GLPaint-Beispiel, das auf der iPhone-Entwicklerseite verfügbar ist.
In iOS 8.3 (möglicherweise früher) mit Swift ist es so einfach wie das Überschreiben der motionBegan
oder motionEnded
Methoden in Ihrem View Controller:
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!")
}
}
Dies ist der grundlegende Delegatencode, den Sie benötigen:
#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];
}
Stellen Sie auch den entsprechenden Code in der Schnittstelle ein. dh:
@interface MyViewController: UIViewController <UIPickerViewDelegate, UIPickerViewDataSource, UIAccelerometerDelegate>
Schauen Sie sich das GLPaint-Beispiel an.
http://developer.apple.com/library/ios/#samplecode/GLPaint/Introduction/Intro.html
Fügen Sie die folgenden Methoden in die Datei ViewController.m ein, damit sie ordnungsgemäß funktionieren
-(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
}
Es tut mir leid, dies als Antwort und nicht als Kommentar zu veröffentlichen, aber wie Sie sehen, bin ich neu bei Stack Overflow und daher noch nicht seriös genug, um Kommentare zu veröffentlichen!
Wie auch immer, ich möchte @cire als Zweites sicherstellen, dass der Ersthelferstatus festgelegt wird, sobald die Ansicht Teil der Ansichtshierarchie ist. Das Festlegen des Ersthelferstatus in Ihrer View Controller- viewDidLoad
Methode funktioniert beispielsweise nicht. Und wenn Sie sich nicht sicher sind, ob es funktioniert, erhalten [view becomeFirstResponder]
Sie einen Booleschen Wert, den Sie testen können.
Ein weiterer Punkt: Sie können einen View-Controller verwenden, um das Shake-Ereignis zu erfassen, wenn Sie nicht unnötig eine UIView-Unterklasse erstellen möchten. Ich weiß, es ist nicht so viel Ärger, aber die Option ist immer noch da. Verschieben Sie einfach die Codefragmente, die Kendall in die UIView-Unterklasse eingefügt hat, in Ihren Controller und senden Sie die becomeFirstResponder
und resignFirstResponder
-Nachrichten an self
anstelle der UIView-Unterklasse.
Zunächst einmal weiß ich, dass dies ein alter Beitrag ist, aber er ist immer noch relevant, und ich stellte fest, dass die beiden Antworten mit den höchsten Stimmen den Shake nicht so früh wie möglich erkannt haben . So geht's:
In Ihrem ViewController:
- (BOOL)canBecomeFirstResponder {
return YES;
}
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
if (motion == UIEventSubtypeMotionShake) {
// Shake detected.
}
}
Die einfachste Lösung besteht darin, ein neues Stammfenster für Ihre Anwendung abzuleiten:
@implementation OMGWindow : UIWindow
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
if (event.type == UIEventTypeMotion && motion == UIEventSubtypeMotionShake) {
// via notification or something
}
}
@end
Dann in Ihrem Bewerbungsdelegierten:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[OMGWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
//…
}
Wenn Sie ein Storyboard verwenden, ist dies möglicherweise schwieriger. Ich kenne den Code, den Sie im Anwendungsdelegierten benötigen, nicht genau.
@implementation
seit Xcode 5
Verwenden Sie dazu einfach diese drei Methoden
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event{
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event{
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event{
Einzelheiten können Sie über ein komplettes Beispielcode überprüfen dort
Eine Swiftease-Version basierend auf der allerersten Antwort!
override func motionEnded(_ motion: UIEventSubtype, with event: UIEvent?) {
if ( event?.subtype == .motionShake )
{
print("stop shaking me!")
}
}
Um diese App-weit zu aktivieren, habe ich eine Kategorie in UIWindow erstellt:
@implementation UIWindow (Utils)
- (BOOL)canBecomeFirstResponder
{
return YES;
}
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
if (motion == UIEventSubtypeMotionShake) {
// Do whatever you want here...
}
}
@end
viewDidAppear
stattdessen meine Controller überschreibenviewWillAppear
. Ich bin mir nicht sicher warum; Vielleicht muss die Ansicht sichtbar sein, bevor sie alles tun kann, um die Shake-Ereignisse zu empfangen?