Pergunta

Quero reagir quando alguém sacode o iPhone. Eu não me importo particularmente com a forma como eles o sacudem, apenas que foi acenado vigorosamente por uma fração de segundo. Alguém sabe como detectar isso?

Foi útil?

Solução

No 3.0, agora há uma maneira mais fácil - conectar -se aos novos eventos de movimento.

O truque principal é que você precisa ter algum uiview (não uiviewcontroller) que deseja como firstresponder receber as mensagens de evento Shake. Aqui está o código que você pode usar em qualquer UIView para obter eventos de 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

Você pode transformar facilmente qualquer UIView (mesmo visualizações do sistema) em uma visão que possa obter o evento Shake simplesmente subclassificando a exibição apenas com esses métodos (e depois selecionando esse novo tipo em vez do tipo de base no IB ou usando -o ao alocar um Visão).

No View Controller, você deseja definir essa visão para se tornar um socorrista:

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

Não se esqueça de que, se você tiver outras visualizações que se tornem socorristas das ações do usuário (como uma barra de pesquisa ou campo de entrada de texto), também precisará restaurar o status do Shaking View First Responder quando a outra visualização renunciar!

Este método funciona mesmo se você definir o ApplicationsupportSshakeTeedit como não.

Outras dicas

Do meu Diceshaker inscrição:

// 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

A HISTERREESS impede que o evento Shake desencadeie várias vezes até que o usuário interrompa o shake.

Eu finalmente fiz funcionar usando exemplos de código deste Desfazer/refazer o tutorial do gerente.
Isso é exatamente o que você precisa fazer:

  • Colocou o ApplicationsUpportSShakeToEdit Propriedade no delegado do aplicativo:
  • 
        - (void)applicationDidFinishLaunching:(UIApplication *)application {
    
            application.applicationSupportsShakeToEdit = YES;
    
            [window addSubview:viewController.view];
            [window makeKeyAndVisible];
    }
    

  • Adicione/substitua canbecomefirstronder, ViewDidappear: e ViewWillDisappear: Métodos em seu controlador de exibição:
  • 
    -(BOOL)canBecomeFirstResponder {
        return YES;
    }
    
    -(void)viewDidAppear:(BOOL)animated {
        [super viewDidAppear:animated];
        [self becomeFirstResponder];
    }
    
    - (void)viewWillDisappear:(BOOL)animated {
        [self resignFirstResponder];
        [super viewWillDisappear:animated];
    }
    

  • Adicione o em movimento Método para o seu controlador de visualização:
  • 
    - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
    {
        if (motion == UIEventSubtypeMotionShake)
        {
            // your code
        }
    }
    

    Primeiro, a resposta de 10 de julho de Kendall é spot-on.

    Agora ... eu queria fazer algo semelhante (no iPhone OS 3.0+), apenas no meu caso que eu queria em todo o aplicativo para poder alertar vários partes do aplicativo quando ocorreu um shake. Aqui está o que eu acabei fazendo.

    Primeiro, subclassei Uiwindow. Isso é fácil. Crie um novo arquivo de classe com uma interface como MotionWindow : UIWindow (Sinta -se à vontade para escolher o seu próprio, Natch). Adicione um método como assim:

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

    Mudar @"DeviceShaken" para o nome de notificação de sua escolha. Salve o arquivo.

    Agora, se você usar um MainWindow.xib (material do modelo Xcode), vá lá e mude a classe do seu objeto de janela do Uiwindow para MotionWindow ou o que você chamou. Salve o xib. Se você configurar Uiwindow Programmaticamente, use sua nova classe de janela lá em vez disso.

    Agora seu aplicativo está usando o especializado Uiwindow classe. Onde quer que você queira ser informado sobre um shake, inscreva -se nelas notificações! Assim:

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

    Para remover -se como um observador:

    [[NSNotificationCenter defaultCenter] removeObserver:self];
    

    Eu coloquei o meu em ViewWillappear: e ViewWillDisappear: No que diz respeito aos controladores de visualização. Certifique -se de que sua resposta ao evento Shake saiba se está "em andamento" ou não. Caso contrário, se o dispositivo for abalado duas vezes em sucessão, você terá um engarrafamento de tráfego. Dessa forma, você pode ignorar outras notificações até que você realmente termine de responder à notificação original.

    Além disso: você pode optar por sugerir Motionbegan vs. em movimento. Você decide. No meu caso, o efeito sempre precisa ocorrer depois O dispositivo está em repouso (vs. quando começa a tremer), então eu uso em movimento. Experimente os dois e veja qual deles faz mais sentido ... ou detectar/notificar para ambos!

    Mais uma (curiosa?) Observação aqui: Observe que não há sinal de gerenciamento de socorristas neste código. Eu só tentei isso com controladores de exibição de tabela até agora e tudo parece funcionar bem juntos! Eu não posso garantir outros cenários.

    Kendall, et. al - alguém pode falar sobre por que isso pode ser assim por Uiwindow subclasses? É porque a janela está no topo da cadeia alimentar?

    Me deparei com este post procurando uma implementação de "agitação". A resposta de Millenomi funcionou bem para mim, embora eu estivesse procurando algo que exigisse um pouco mais de "ação de agitação" para desencadear. Substituí -me ao valor booleano por um Int Shakecount. Eu também reimplei o método L0ACELETRINGISSHISHAKING () no Objective-C. Você pode ajustar a munição de agitação exigida, ajustando a amostragem adicionada ao Shakecount. Ainda não tenho certeza de que encontrei os valores ideais, mas parece estar funcionando bem até agora. Espero que isso ajude alguém:

    - (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: Eu defini o intervalo de atualização para 1/15 de segundo.

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

    Você precisa verificar o acelerômetro via acelerômetro: Didaccelerate: Método que faz parte do protocolo UiacCellerometerDelegate e verificar se os valores ultrapassam um limite para a quantidade de movimento necessária para um shake.

    Há um código de amostra decente no acelerômetro: Didacceleate: Método logo na parte inferior do appController.m no exemplo do GlPaint, disponível no site do desenvolvedor do iPhone.

    No iOS 8.3 (talvez antes) com Swift, é tão simples quanto substituir o motionBegan ou motionEnded Métodos em seu controlador de exibição:

    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!")
        }
    }
    

    Este é o código de delegado básico que você precisa:

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

    Defina também o código apropriado na interface. ou seja:

    @Interface myViewController: UiviewControlleru003CUIPickerViewDelegate, UIPickerViewDataSource, UIAccelerometerDelegate>

    Adicione os seguintes métodos no arquivo ViewController.m, está funcionando corretamente

        -(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 
         }
    

    Lamento postar isso como uma resposta, em vez de um comentário, mas como você pode ver, sou novo para empilhar o transbordamento e, portanto, ainda não sou respeitável o suficiente para postar comentários!

    De qualquer forma, o segundo @cire sobre como definir o status do primeiro respondedor quando a visualização fizer parte da hierarquia de visualizações. Portanto, definir o status do primeiro resposta em seus controladores de visualização viewDidLoad O método não funcionará, por exemplo. E se você não tem certeza de se está funcionando [view becomeFirstResponder] Retorna um booleano que você pode testar.

    Outro ponto: você posso Use um controlador de exibição para capturar o evento Shake se você não deseja criar uma subclasse UIView desnecessariamente. Eu sei que não é muito aborrecimento, mas ainda assim a opção está lá. Basta mover os trechos de código que Kendall colocou na subclasse UIView para o seu controlador e envie o becomeFirstResponder e resignFirstResponder mensagens para self Em vez da subclasse UIView.

    Primeiro, eu sei que este é um post antigo, mas ainda é relevante, e descobri que as duas respostas mais altas de votos não detecte o shake o mais cedo possível. É assim que fazer:

    1. Ligue a Coremotion ao seu projeto nas fases de construção do alvo:CoreMotion
    2. Em seu viewcontroller:

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

    A solução mais fácil é derivar uma nova janela de raiz para o seu aplicativo:

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

    Então, no seu delegado de aplicativo:

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

    Se você estiver usando um storyboard, isso pode ser mais complicado, não conheço o código de que você precisará no delegado do aplicativo com precisão.

    Basta usar esses três métodos para fazer isso

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

    Para detalhes, você pode verificar um código de exemplo completo sobre

    Uma versão rápida com base na primeira resposta!

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

    Para ativar este aplicativo, criei uma categoria no UIWindow:

    @implementation UIWindow (Utils)
    
    - (BOOL)canBecomeFirstResponder
    {
        return YES;
    }
    
    - (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
    {
        if (motion == UIEventSubtypeMotionShake) {
            // Do whatever you want here...
        }
    }
    
    @end
    
    Licenciado em: CC-BY-SA com atribuição
    Não afiliado a StackOverflow
    scroll top