我想在有人摇动iPhone时做出反应。我并不特别在乎他们如何摇动它,只是它剧烈地挥动了一秒钟。有人知道如何检测吗?
我想在有人摇动iPhone时做出反应。我并不特别在乎他们如何摇动它,只是它剧烈地挥动了一秒钟。有人知道如何检测吗?
Answers:
在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中选择此新类型而不是基本类型,或者在分配a时使用它)来获得震动事件的视图。视图)。
在视图控制器中,您想要将此视图设置为第一响应者:
- (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
IS YES
。
[self resignFirstResponder];
提前打电话[super viewWillDisappear:animated];
?这似乎很奇怪。
首先,肯德尔(Kendall)在7月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:放在与View Controller有关的地方。确保您对震动事件的响应知道它是否“已在进行中”。否则,如果将设备连续摇动两次,将会造成交通拥堵。这样,您可以忽略其他通知,直到您真正完成对原始通知的响应为止。
此外,您也可以选择提示的关闭motionBegan与motionEnded。由你决定。就我而言,效果总是需要在设备静止后才发生(相对于它开始晃动时),因此我使用motionEnded。尝试两者,看看哪个更有意义...或同时检测/通知两者!
这里还有一个(好奇吗?)观察:请注意,此代码中没有急救人员管理的迹象。到目前为止,我仅使用Table View Controllers进行了尝试,并且一切似乎都可以很好地协作!我不能保证其他情况。
肯德尔等。al-谁能说出为什么UIWindow子类可能会这样?是因为窗口位于食物链的顶部吗?
我偶然发现了这篇文章,希望能实现“摇晃”的实现。尽管我一直在寻找一些需要更多“摇晃动作”才能触发的东西,但是millenomi的答案对我来说效果很好。我已将int shakeCount替换为布尔值。我还在Objective-C中重新实现了L0AccelerationIsShaking()方法。您可以通过调整添加到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)];
在带有Swift的iOS 8.3(也许更早)中,这就像覆盖视图控制器中的motionBegan
or 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>
在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的新手,因此我还没有足够的声誉发表评论!
无论如何,一旦视图成为视图层次结构的一部分,我都会确保确保设置第一响应者状态。因此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];
//…
}
如果您使用的是Storyboard,这可能会比较棘手,我不知道您在应用程序委托中确切需要的代码。
@implementation
,因为Xcode的5
viewDidAppear
而不是viewWillAppear
。我不知道为什么;可能需要先使视图可见,然后视图才能开始接收震动事件?