Я хочу реагувати, коли хтось трясе iPhone. Мені особливо не байдуже, як вони його струшують, тільки що він енергійно махав на частку секунди. Хтось знає, як це виявити?
Я хочу реагувати, коли хтось трясе iPhone. Мені особливо не байдуже, як вони його струшують, тільки що він енергійно махав на частку секунди. Хтось знає, як це виявити?
Відповіді:
У версії 3.0 тепер є простіший спосіб - підключити до нових подій руху.
Основна хитрість полягає в тому, що вам потрібно мати деякий UIView (а не UIViewController), який ви хочете як першийResponder отримувати повідомлення про події тремтіння. Ось код, який можна використовувати в будь-якому 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 (навіть системні подання) у вигляд, який може отримати подію похитування, просто підкласифікувавши перегляд лише цими методами (а потім вибравши цей новий тип замість базового типу в ІБ або використовуючи його при розподілі вид).
У контролері перегляду потрібно встановити цей вид, щоб він став першим відповіддю:
- (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
події не дуже точні чи точні з точки зору виявлення точного початку та кінця тремтіння. Такий підхід дозволяє бути точним, наскільки ви хочете.
Нарешті я змусив це працювати, використовуючи приклади коду з цього підручника для скасування / повторного управління .
Це саме те, що вам потрібно зробити:
- (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
IS YES
.
[self resignFirstResponder];
попереду [super viewWillDisappear:animated];
? Це здається своєрідним.
По-перше, відповідь Кендалла 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: де йдеться про контролери перегляду. Будьте впевнені, що ваша відповідь на подію тремтіння знає, чи вона "вже триває" чи ні. В іншому випадку, якщо пристрій струшуватиметься два рази поспіль, у вас з'явиться затор. Таким чином ви можете ігнорувати інші сповіщення до тих пір, поки по-справжньому не будете відповідати на оригінальне сповіщення.
Також: Ви можете вирішити режим руху Біг проти руху Енд . Тобі вирішувати. У моєму випадку ефект завжди має відбуватися після спокою пристрою (порівняно, коли він починає тремтіти ), тому я використовую motionEnded . Спробуйте обидва і подивіться, який з них має більше сенсу ... або виявити / повідомити для обох!
Ще одне (цікаве?) Зауваження тут: Зауважте, у цьому коді немає ознак управління першим відповіддю. Я пробував це тільки з контролерами таблиці View до цих пір, і все, здається, працює дуже добре разом! Я не можу поручитися на інші сценарії.
Кендалл та ін. al - чи може хтось говорити, чому це може бути так для підкласів UIWindow ? Це тому, що вікно знаходиться вгорі ланцюга харчування?
Я натрапив на цю посаду, шукаючи «тремтячої» реалізації. Відповідь тисячоліття спрацювало для мене добре, хоча я шукав щось, що потребувало б трохи більше «тремтячих дій», щоб спрацювати. Я замінив булеве значення на 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
}
Вибачте, що опублікували це як відповідь, а не коментар, але, як ви бачите, я новачок у Стек Overflow, тому я ще не досить авторитетний, щоб розміщувати коментарі!
У будь-якому разі я другий @cire про те, щоб встановити статус першого відповіді, як тільки перегляд є частиною ієрархії перегляду. Тому встановлення статусу першого відповіді у вашому viewDidLoad
методі контролерів перегляду , наприклад, не працюватиме. І якщо ви не впевнені, чи працює він[view becomeFirstResponder]
ви повернете собі булевий виклик, який ви можете перевірити.
Ще один момент: ви можете використовувати контролер перегляду для зйомки події тремтіння, якщо ви не хочете створювати підклас UIView без потреби. Я знаю, що це не так вже й багато клопоту, але все ж є варіант. Просто перемістіть фрагменти коду, які Кендалл помістив у підклас 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
. Я не впевнений, чому; може бути, погляд повинен бути видно, перш ніж він зможе зробити все, що робити, щоб почати отримувати потрясіння?