Я хочу реагировать, когда кто-то трясет iPhone. Меня не особо заботит, как они его встряхивают, только то, что его энергично махали в течение доли секунды. Кто-нибудь знает, как это обнаружить?
Как определить, когда кто-то трясет iPhone?
Ответы (17)
В версии 3.0 теперь есть более простой способ - подключиться к новым событиям движения.
Основная хитрость заключается в том, что вам нужно иметь некоторый UIView (а не UIViewController), который вы хотите в качестве firstResponder для получения сообщений о событиях встряхивания. Вот код, который вы можете использовать в любом UIView для получения событий встряхивания:
@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
Вы можете легко преобразовать любой UIView (даже системные представления) в представление, которое может получить событие встряхивания, просто создав подкласс представления только с этими методами (и затем выбрав этот новый тип вместо базового типа в IB или используя его при выделении Посмотреть).
В контроллере представления вы хотите, чтобы это представление стало первым респондентом:
- (void) viewWillAppear:(BOOL)animated
{
[shakeView becomeFirstResponder];
[super viewWillAppear:animated];
}
- (void) viewWillDisappear:(BOOL)animated
{
[shakeView resignFirstResponder];
[super viewWillDisappear:animated];
}
Не забывайте, что если у вас есть другие представления, которые становятся первыми респондентами в результате действий пользователя (например, строка поиска или поле ввода текста), вам также необходимо восстановить статус первого респондента дрожащего представления, когда другое представление уходит в отставку!
Этот метод работает, даже если для параметра applicationSupportsShakeToEdit установлено значение NO.
viewDidAppear
моего контроллера вместо viewWillAppear
. Я не знаю почему; может быть, представление должно быть видимым, прежде чем оно сможет делать то, что делает, чтобы начать получать события встряхивания?
- person Kristopher Johnson; 09.08.2009
motionEnded
до того, как встряхивание действительно прекратится. Таким образом, используя этот подход, вы получаете разрозненную серию коротких встряхиваний вместо одной длинной. Другой ответ в этом случае работает намного лучше.
- person aroth; 13.04.2011
[super respondsToSelector:
не будет делать то, что вы хотите, поскольку это эквивалентно вызову [self respondsToSelector:
, который вернет YES
. Вам нужно [[ShakingView superclass] instancesRespondToSelector:
.
- person Vadim Yelagin; 21.02.2014
Из моего приложения 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
Гистерезис предотвращает многократное срабатывание события встряхивания, пока пользователь не прекратит встряхивание.
motionBegan
и motionEnded
iOS 3.0 не очень точны или точны с точки зрения определения точного начала и конца встряски. Такой подход позволяет вам быть максимально точным.
- person aroth; 13.04.2011
Я наконец заставил его работать, используя примеры кода из этого Учебное пособие по управлению отменой / повтором выполнения.
Это именно то, что вам нужно сделать:
- (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
.
- person uɥƃnɐʌuop; 22.04.2016
[self resignFirstResponder];
раньше [super viewWillDisappear:animated];
? Это кажется странным.
- person JaredH; 17.11.2016
Во-первых, ответ Кендалла от 10 июля точен.
Теперь ... Я хотел сделать что-то подобное (в iPhone OS 3.0+), только в моем случае я хотел, чтобы это было для всего приложения, чтобы я мог предупреждать различные части приложения при возникновении встряски. Вот что я в итоге сделал.
Сначала я создал подкласс UIWindow. Это очень просто. Создайте новый файл класса с таким интерфейсом, как MotionWindow : UIWindow
(не стесняйтесь выбирать свой, natch). Добавьте такой метод:
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
if (event.type == UIEventTypeMotion && event.subtype == UIEventSubtypeMotionShake) {
[[NSNotificationCenter defaultCenter] postNotificationName:@"DeviceShaken" object:self];
}
}
Измените @"DeviceShaken"
на название уведомления по вашему выбору. Сохраните файл.
Теперь, если вы используете MainWindow.xib (стандартный материал для шаблонов Xcode), зайдите в него и измените класс вашего объекта Window с UIWindow на MotionWindow или как вы его называете. . Сохраните xib. Если вы настроили UIWindow программно, используйте вместо этого свой новый класс Window.
Теперь ваше приложение использует специализированный класс UIWindow. Где бы вы ни хотели, чтобы вам рассказали о встряхивании, подпишитесь на их уведомления! Нравится:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(deviceShaken) name:@"DeviceShaken" object:nil];
Чтобы удалить себя в качестве наблюдателя:
[[NSNotificationCenter defaultCenter] removeObserver:self];
Я поместил свой в viewWillAppear: и viewWillDisappear:, когда речь идет о контроллерах представления. Убедитесь, что ваш ответ на событие встряски знает, «уже происходит» оно или нет. В противном случае, если устройство встряхнуть дважды подряд, вы получите небольшую пробку. Таким образом, вы можете игнорировать другие уведомления, пока действительно не закончите отвечать на исходное уведомление.
Также: вы можете выбрать отключение motionBegan или motionEnded. Тебе решать. В моем случае эффект всегда должен иметь место после, когда устройство находится в состоянии покоя (а не когда оно начинает трястись), поэтому я использую motionEnded. Попробуйте оба и посмотрите, какой из них имеет больше смысла ... или обнаружите / уведомите для обоих!
Еще одно (любопытное?) Наблюдение: обратите внимание, что в этом коде нет никаких признаков управления первым респондентом. Пока что я пробовал это только с контроллерами табличного представления, и, похоже, все отлично работает вместе! Однако я не могу поручиться за другие сценарии.
Кендалл и др. al - кто-нибудь может объяснить, почему это может быть так для подклассов UIWindow? Это потому, что окно находится на вершине пищевой цепи?
super
, не так ли?
- person beebcon; 22.06.2017
Я наткнулся на этот пост в поисках "потрясающей" реализации. Ответ millenomi хорошо сработал для меня, хотя я искал что-то, что требовало немного большего «встряхивания» для срабатывания. Я заменил логическое значение на int shakeCount. Я также переопределил метод L0AccelerationIsShaking () в Objective-C. Вы можете настроить количество встряхивания, необходимое для настройки количества, добавляемого в shakeCount. Я еще не уверен, что нашел оптимальные значения, но, похоже, пока он работает хорошо. Надеюсь, это поможет кому-то:
- (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: Я установил интервал обновления 1/15 секунды.
[[UIAccelerometer sharedAccelerometer] setUpdateInterval:(1.0 / 15)];
В iOS 8.3 (возможно, ранее) с Swift это так же просто, как переопределение методов motionBegan
или motionEnded
в вашем контроллере представления:
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!")
}
}
Вам нужно проверить акселерометр с помощью метода accelerometer: didAccelerate:, который является частью протокола UIAccelerometerDelegate, и проверить, превышают ли значения пороговое значение количества движения, необходимого для встряхивания.
В акселерометре есть достойный образец кода: didAccelerate: метод прямо в нижней части AppController.m в примере GLPaint, который доступен на сайте разработчика iPhone.
Это базовый код делегата, который вам нужен:
#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];
}
Также установите соответствующий код в интерфейсе. то есть:
@interface MyViewController: UIViewController ‹UIPickerViewDelegate, UIPickerViewDataSource, UIAccelerometerDelegate›
Посмотрите пример GLPaint.
http://developer.apple.com/library/ios/#samplecode/GLPaint/Introduction/Intro.html
Добавьте следующие методы в файл ViewController.m, он работает правильно
-(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
}
Извините, что опубликовал это как ответ, а не комментарий, но, как вы можете видеть, я новичок в Stack Overflow, и поэтому я еще недостаточно авторитетен, чтобы публиковать комментарии!
В любом случае, я второй @cire об установке статуса первого респондента, когда представление является частью иерархии представлений. Таким образом, установка статуса первого респондента в ваших контроллерах представления viewDidLoad
метод, например, не будет работать. И если вы не уверены, работает ли он, [view becomeFirstResponder]
возвращает вам логическое значение, которое вы можете проверить.
Еще один момент: вы можете использовать контроллер представления для захвата события встряхивания, если вы не хотите без необходимости создавать подкласс UIView. Я знаю, что это не так уж и сложно, но вариант все же есть. Просто переместите фрагменты кода, которые Кендалл поместил в подкласс UIView, в ваш контроллер и отправьте сообщения becomeFirstResponder
и resignFirstResponder
в self
вместо подкласса UIView.
Во-первых, я знаю, что это старый пост, но он все еще актуален, и я обнаружил, что два самых популярных ответа не обнаружили встряску как можно раньше. Вот как это сделать:
- Свяжите CoreMotion с вашим проектом на этапах сборки цели:
В вашем ViewController:
- (BOOL)canBecomeFirstResponder { return YES; } - (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event { if (motion == UIEventSubtypeMotionShake) { // Shake detected. } }
Самое простое решение - создать новое корневое окно для вашего приложения:
@implementation OMGWindow : UIWindow
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
if (event.type == UIEventTypeMotion && motion == UIEventSubtypeMotionShake) {
// via notification or something
}
}
@end
Затем в делегате вашего приложения:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[OMGWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
//…
}
Если вы используете раскадровку, это может быть сложнее, я не знаю точно, какой код вам понадобится в делегате приложения.
@implementation
, начиная с Xcode 5.
- person mxcl; 12.07.2014
Просто используйте эти три метода, чтобы сделать это
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event{
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event{
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event{
для получения дополнительной информации вы можете проверить полный пример кода на странице там
Быстрая версия, основанная на самом первом ответе!
override func motionEnded(_ motion: UIEventSubtype, with event: UIEvent?) {
if ( event?.subtype == .motionShake )
{
print("stop shaking me!")
}
}
В Swift 5 вы можете захватывать движение и проверять,
override func motionEnded(_ motion: UIEventSubtype, with event: UIEvent?) {
if motion == .motionShake
{
print("shaking")
}
}
Чтобы включить это приложение для всего приложения, я создал категорию в UIWindow:
@implementation UIWindow (Utils)
- (BOOL)canBecomeFirstResponder
{
return YES;
}
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
if (motion == UIEventSubtypeMotionShake) {
// Do whatever you want here...
}
}
@end