Я хочу реагировать, когда кто-то трясет iPhone. Меня не особо волнует, как они его трясут, просто то, что его энергично размахивали на долю секунды. Кто-нибудь знает, как это обнаружить?
Я хочу реагировать, когда кто-то трясет iPhone. Меня не особо волнует, как они его трясут, просто то, что его энергично размахивали на долю секунды. Кто-нибудь знает, как это обнаружить?
Ответы:
В 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.
motionEnded
до того, как встряска фактически прекратилась. Таким образом, используя этот подход, вы получаете несвязанную серию коротких встряхиваний вместо одной длинной. Другой ответ работает намного лучше в этом случае.
[super respondsToSelector:
не будет делать то, что вы хотите, так как это эквивалентно вызову, [self respondsToSelector:
который вернется YES
. Что тебе нужно [[ShakingView superclass] instancesRespondToSelector:
?
Из моего приложения 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
события не очень точны или точны с точки зрения определения точного начала и конца дрожания. Такой подход позволяет вам быть настолько точным, насколько вы хотите.
Я наконец заставил его работать, используя примеры кода из этого руководства Undo / Redo Manager .
Это именно то, что вам нужно сделать:
- (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
.
[self resignFirstResponder];
раньше [super viewWillDisappear:animated];
? Это кажется странным.
Во-первых, ответ Кендалла 10 июля - точный.
Теперь ... я хотел сделать что-то подобное (в iPhone OS 3.0+), только в моем случае я хотел, чтобы это было в масштабе всего приложения, чтобы я мог предупреждать различные части приложения, когда происходит сотрясение. Вот что я в итоге сделал.
Во-первых, я подкласс UIWindow . Это легко peasy. Создайте новый файл класса с интерфейсом, таким как MotionWindow : UIWindow
(не стесняйтесь выбирать свой, естественно). Добавьте метод, например, так:
- (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 . Попробуйте оба и посмотрите, какой из них имеет больше смысла ... или обнаружите / сообщите обоим!
Еще одно (любопытное?) Наблюдение: обратите внимание, что в этом коде нет признаков управления первым респондентом. Я только попробовал это с Контроллерами Табличного представления, и все, кажется, работает довольно хорошо вместе! Я не могу поручиться за другие сценарии, хотя.
Kendall, et. al - кто-нибудь может сказать, почему это может быть так для подклассов UIWindow ? Это потому, что окно находится наверху пищевой цепи?
Я наткнулся на этот пост в поисках «потрясающей» реализации. Ответ 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)];
Вам необходимо проверить акселерометр с помощью акселерометра: didAccelerate: метод, который является частью протокола UIAccelerometerDelegate, и проверить, превышают ли значения пороговое значение для количества движения, необходимого для встряхивания.
В акселерометре есть пример кода: didAccelerate: метод в нижней части AppController.m в примере GLPaint, который доступен на сайте разработчиков iPhone.
В 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!")
}
}
Это основной код делегата, который вам нужен:
#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 без необходимости. Я знаю, что это не так уж много хлопот, но все же есть возможность. Просто переместите фрагменты кода , которые Kendall поместить в UIView подкласс в контроллер и отправить becomeFirstResponder
и resignFirstResponder
сообщения self
вместо подкласса UIView.
Во-первых, я знаю, что это старый пост, но он все еще актуален, и я обнаружил, что два ответа с наибольшим количеством голосов не выявили сотрясение как можно раньше . Вот как это сделать:
В вашем 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.
Просто используйте эти три метода, чтобы сделать это
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event{
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event{
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event{
для деталей вы можете проверить полный пример кода там
Версия Swiftease, основанная на самом первом ответе!
override func motionEnded(_ motion: UIEventSubtype, with event: UIEvent?) {
if ( event?.subtype == .motionShake )
{
print("stop shaking me!")
}
}
Чтобы включить это приложение, я создал категорию в 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
вместоviewWillAppear
. Я не уверен почему; может быть, вид должен быть видимым, прежде чем он сможет делать все, что он делает, чтобы начать получать события встряхивания?