Chcę zareagować, gdy ktoś potrząśnie iPhone'em. Nie obchodzi mnie szczególnie, jak się nim potrząsają, tylko to, że machał energicznie przez ułamek sekundy. Czy ktoś wie, jak to wykryć?
Chcę zareagować, gdy ktoś potrząśnie iPhone'em. Nie obchodzi mnie szczególnie, jak się nim potrząsają, tylko to, że machał energicznie przez ułamek sekundy. Czy ktoś wie, jak to wykryć?
Odpowiedzi:
W 3.0 jest teraz łatwiejszy sposób - dołącz do nowych zdarzeń ruchu.
Główną sztuczką jest to, że musisz mieć UIView (nie UIViewController), który jako pierwszy odpowiada, aby otrzymywać komunikaty o zdarzeniach wstrząsających. Oto kod, którego możesz użyć w dowolnym UIView, aby uzyskać zdarzenia wstrząsania:
@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
Możesz łatwo przekształcić dowolne UIView (nawet widoki systemowe) w widok, który może uzyskać zdarzenie wstrząśnięcia, po prostu podklasując widok tylko tymi metodami (a następnie wybierając ten nowy typ zamiast typu podstawowego w IB lub używając go podczas przydzielania widok).
W kontrolerze widoku chcesz ustawić ten widok, aby stał się pierwszą odpowiedzią:
- (void) viewWillAppear:(BOOL)animated
{
[shakeView becomeFirstResponder];
[super viewWillAppear:animated];
}
- (void) viewWillDisappear:(BOOL)animated
{
[shakeView resignFirstResponder];
[super viewWillDisappear:animated];
}
Nie zapominaj, że jeśli masz inne widoki, które stają się pierwszymi respondentami na podstawie działań użytkownika (np. Pasek wyszukiwania lub pole wprowadzania tekstu), musisz także przywrócić status pierwszego odpowiadającego widoku wstrząsającego, gdy inny widok zrezygnuje!
Ta metoda działa, nawet jeśli ustawisz applicationSupportsShakeToEdit na NIE.
motionEnded
zanim wstrząs rzeczywiście się zatrzyma. Tak więc, stosując to podejście, otrzymujesz rozłączną serię krótkich wstrząsów zamiast jednego długiego. Druga odpowiedź działa w tym przypadku znacznie lepiej.
[super respondsToSelector:
nie zrobi tego, co chcesz, ponieważ jest to równoważne z połączeniem, [self respondsToSelector:
które powróci YES
. Potrzebujesz tego [[ShakingView superclass] instancesRespondToSelector:
.
Z mojej aplikacji 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
Histereza zapobiega wielokrotnemu wyzwalaniu zdarzenia wstrząsania, dopóki użytkownik nie zatrzyma wstrząsania.
motionBegan
i motionEnded
wydarzenia nie są bardzo precyzyjne ani dokładne pod względem wykrywania dokładnego początku i końca wstrząsu. Takie podejście pozwala być tak precyzyjnym, jak chcesz.
W końcu udało mi się go uruchomić, korzystając z przykładów kodu z tego samouczka Menedżera cofania / ponawiania .
To jest dokładnie to, co musisz zrobić:
- (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
jest YES
.
[self resignFirstResponder];
przed [super viewWillDisappear:animated];
? To wydaje się dziwne.
Po pierwsze, odpowiedź Kendalla z 10 lipca jest natychmiastowa.
Teraz ... chciałem zrobić coś podobnego (w iPhone OS 3.0+), tylko w moim przypadku chciałem, aby obejmowała całą aplikację, aby móc ostrzegać różne części aplikacji, gdy wystąpi wstrząs. Oto co skończyłem.
Najpierw podklasowałem UIWindow . To jest łatwe. Utwórz nowy plik klasy z interfejsem takim jak MotionWindow : UIWindow
(możesz wybrać własny, natch). Dodaj taką metodę:
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
if (event.type == UIEventTypeMotion && event.subtype == UIEventSubtypeMotionShake) {
[[NSNotificationCenter defaultCenter] postNotificationName:@"DeviceShaken" object:self];
}
}
Zmień @"DeviceShaken"
na wybraną nazwę powiadomienia. Zapisz plik.
Teraz, jeśli używasz pliku MainWindow.xib (zapasowe szablony Xcode), wejdź tam i zmień klasę swojego obiektu Window z UIWindow na MotionWindow lub jakkolwiek to nazwałeś . Zapisz xib. Jeśli skonfigurujesz UIWindow programowo , użyj tam nowej klasy Window.
Teraz Twoja aplikacja korzysta ze specjalistycznej klasy UIWindow . Gdziekolwiek chcesz otrzymywać informacje o shake'u, zarejestruj się, aby otrzymywać powiadomienia! Lubię to:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(deviceShaken) name:@"DeviceShaken" object:nil];
Aby usunąć siebie z roli obserwatora:
[[NSNotificationCenter defaultCenter] removeObserver:self];
Wyświetlam mój widok WillAppear: i viewWillDisappear: przypadku kontrolerów View. Upewnij się, że Twoja reakcja na zdarzenie wstrząsowe wie, czy „już trwa”, czy nie. W przeciwnym razie, jeśli urządzenie zostanie wstrząśnięte dwa razy pod rząd, będziesz miał mały korek. W ten sposób możesz zignorować inne powiadomienia, dopóki nie skończysz odpowiadać na oryginalne powiadomienie.
Ponadto: możesz wybrać cue off z motionBegan vs. motionEnded . To zależy od Ciebie. W moim przypadku efekt zawsze musi mieć miejsce po zatrzymaniu urządzenia (w przeciwieństwie do momentu, w którym zaczyna się trząść), więc używam motionEnded . Spróbuj obu i zobacz, który z nich jest bardziej sensowny ... lub wykryj / powiadom o obu!
Jeszcze jedna (ciekawa?) Obserwacja tutaj: zauważ, że w tym kodzie nie ma śladu zarządzania pierwszą odpowiedzią. Do tej pory próbowałem tego tylko z kontrolerami widoku tabeli i wszystko wydaje się działać całkiem dobrze razem! Nie mogę jednak ręczyć za inne scenariusze.
Kendall i in. al - czy ktoś może mówić, dlaczego tak jest w przypadku podklas UIWindow ? Czy to dlatego, że okno znajduje się na górze łańcucha pokarmowego?
Natknąłem się na ten post w poszukiwaniu „wstrząsającej” implementacji. Odpowiedź millenomi działała dla mnie dobrze, chociaż szukałem czegoś, co wymagałoby nieco więcej „wstrząsania”, aby uruchomić. Zamieniłem na wartość logiczną int shakeCount. Ponownie wdrożyłem metodę L0AccelerationIsShaking () w Objective-C. Możesz dostosować ilość wstrząsów wymaganą przez ulepszenie ilości dodanej do shakeCount. Nie jestem pewien, czy znalazłem optymalne wartości, ale wydaje się, że jak dotąd działa dobrze. Mam nadzieję, że to pomoże komuś:
- (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: Ustawiłem interwał aktualizacji na 1/15 sekundy.
[[UIAccelerometer sharedAccelerometer] setUpdateInterval:(1.0 / 15)];
Musisz sprawdzić akcelerometr za pomocą akcelerometru: didAccelerate: metoda będąca częścią protokołu UIAccelerometerDelegate i sprawdzić, czy wartości przekraczają próg dla ilości ruchu potrzebnej do wstrząśnięcia.
W akcelerometrze znajduje się przyzwoity przykładowy kod: didAccelerate: metoda na dole AppController.m w przykładzie GLPaint, który jest dostępny na stronie programisty iPhone'a.
W iOS 8.3 (być może wcześniejszym) z Swift jest to tak proste, jak zastąpienie metod motionBegan
lub motionEnded
w kontrolerze widoku:
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!")
}
}
To jest podstawowy kod delegowany, którego potrzebujesz:
#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];
}
Ustaw także odpowiedni kod w interfejsie. to znaczy:
@ interfejs MyViewController: UIViewController <UIPickerViewDelegate, UIPickerViewDataSource, UIAccelerometerDelegate>
Sprawdź przykład GLPaint.
http://developer.apple.com/library/ios/#samplecode/GLPaint/Introduction/Intro.html
Dodaj następujące metody w pliku ViewController.m, działa poprawnie
-(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
}
Przepraszam, że opublikowałem to jako odpowiedź, a nie jako komentarz, ale jak widać, jestem nowy w stosie przepełnienia stosu, więc nie jestem jeszcze wystarczająco wiarygodny, aby publikować komentarze!
Tak czy inaczej, po raz drugi @ zastanawiam się nad ustawieniem statusu pierwszego odpowiadającego, gdy widok jest częścią hierarchii widoku. Tak więc na przykład ustawienie statusu pierwszego odpowiadającego w viewDidLoad
metodzie kontrolerów widoku nie zadziała. A jeśli nie masz pewności, czy działa, [view becomeFirstResponder]
zwraca wartość logiczną, którą możesz przetestować.
Kolejny punkt: Państwo może użyć kontrolera widoku, aby uchwycić zdarzenia wstrząsnąć, jeśli nie chcesz utworzyć podklasę UIView niepotrzebnie. Wiem, że to nie tyle kłopotów, ale wciąż istnieje opcja. Po prostu przenieś fragmenty kodu, które Kendall umieścił w podklasie UIView do swojego kontrolera i wysyłaj wiadomości becomeFirstResponder
oraz resignFirstResponder
do self
podklasy UIView.
Po pierwsze, wiem, że jest to stary post, ale nadal jest aktualny i stwierdziłem, że dwie najwyżej ocenione odpowiedzi nie wykryły wstrząsu tak wcześnie, jak to możliwe . Oto jak to zrobić:
W twoim ViewController:
- (BOOL)canBecomeFirstResponder {
return YES;
}
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
if (motion == UIEventSubtypeMotionShake) {
// Shake detected.
}
}
Najłatwiejszym rozwiązaniem jest uzyskanie nowego okna głównego dla aplikacji:
@implementation OMGWindow : UIWindow
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
if (event.type == UIEventTypeMotion && motion == UIEventSubtypeMotionShake) {
// via notification or something
}
}
@end
Następnie w aplikacji deleguj:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[OMGWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
//…
}
Jeśli używasz Storyboard, może to być trudniejsze, nie wiem dokładnie, jaki kod będzie potrzebny w delegacie aplikacji.
@implementation
od Xcode 5.
Wystarczy użyć tych trzech metod, aby to zrobić
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event{
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event{
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event{
Szczegółowe informacje można sprawdzić kompletny przykładowy kod na nie
Swiftease wersja oparta na pierwszej odpowiedzi!
override func motionEnded(_ motion: UIEventSubtype, with event: UIEvent?) {
if ( event?.subtype == .motionShake )
{
print("stop shaking me!")
}
}
Aby włączyć tę aplikację, utworzyłem kategorię w 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
zamiastviewWillAppear
. Nie jestem pewien dlaczego; może widok musi być widoczny, zanim będzie mógł zrobić wszystko, aby zacząć otrzymywać zdarzenia wstrząsania?