Tôi muốn phản ứng khi ai đó làm rung iPhone. Tôi không đặc biệt quan tâm làm thế nào họ lắc nó, chỉ là nó được vẫy mạnh mẽ trong một giây. Có ai biết làm thế nào để phát hiện điều này?
Tôi muốn phản ứng khi ai đó làm rung iPhone. Tôi không đặc biệt quan tâm làm thế nào họ lắc nó, chỉ là nó được vẫy mạnh mẽ trong một giây. Có ai biết làm thế nào để phát hiện điều này?
Câu trả lời:
Trong 3.0, giờ đây có một cách dễ dàng hơn - nối vào các sự kiện chuyển động mới.
Thủ thuật chính là bạn cần phải có một số UIView (không phải UIViewControll) mà bạn muốn là FirstResponder để nhận các thông báo sự kiện lắc. Đây là mã mà bạn có thể sử dụng trong bất kỳ UIView nào để nhận các sự kiện lắc:
@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
Bạn có thể dễ dàng chuyển đổi bất kỳ UIView nào (ngay cả chế độ xem hệ thống) thành chế độ xem có thể nhận được sự kiện rung chỉ bằng cách phân lớp chế độ xem chỉ bằng các phương thức này (và sau đó chọn loại mới này thay vì loại cơ sở trong IB hoặc sử dụng nó khi phân bổ lượt xem).
Trong trình điều khiển chế độ xem, bạn muốn đặt chế độ xem này trở thành phản hồi đầu tiên:
- (void) viewWillAppear:(BOOL)animated
{
[shakeView becomeFirstResponder];
[super viewWillAppear:animated];
}
- (void) viewWillDisappear:(BOOL)animated
{
[shakeView resignFirstResponder];
[super viewWillDisappear:animated];
}
Đừng quên rằng nếu bạn có các chế độ xem khác trở thành phản hồi đầu tiên từ hành động của người dùng (như thanh tìm kiếm hoặc trường nhập văn bản), bạn cũng cần khôi phục trạng thái phản hồi rung đầu tiên khi chế độ xem khác từ bỏ!
Phương pháp này hoạt động ngay cả khi bạn đặt applicationSupportsShakeToEdit thành NO.
motionEnded
trước khi rung thực sự dừng lại. Vì vậy, bằng cách sử dụng phương pháp này, bạn sẽ có được một loạt các lần lắc ngắn thay vì một lần lắc dài. Câu trả lời khác hoạt động tốt hơn nhiều trong trường hợp này.
[super respondsToSelector:
sẽ không làm những gì bạn muốn, vì nó tương đương với việc gọi [self respondsToSelector:
sẽ trả lại YES
. Những gì bạn cần là [[ShakingView superclass] instancesRespondToSelector:
.
Từ ứng dụng Diceshaker của tôi :
// 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
Các histeresis ngăn sự kiện lắc kích hoạt nhiều lần cho đến khi người dùng dừng lắc.
motionBegan
và motionEnded
các sự kiện không chính xác hoặc chính xác về mặt phát hiện bắt đầu và kết thúc chính xác của một lần rung. Cách tiếp cận này cho phép bạn chính xác như bạn muốn.
Cuối cùng tôi đã làm cho nó hoạt động bằng cách sử dụng các ví dụ mã từ Hướng dẫn quản lý hoàn tác / Làm lại này .
Đây chính xác là những gì bạn cần làm:
- (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
là YES
.
[self resignFirstResponder];
trước [super viewWillDisappear:animated];
? Điều này có vẻ kỳ dị.
Đầu tiên, câu trả lời ngày 10 tháng 7 của Kendall là tại chỗ.
Bây giờ ... tôi muốn làm một cái gì đó tương tự (trong iPhone OS 3.0+), chỉ trong trường hợp của tôi, tôi muốn nó rộng khắp ứng dụng để tôi có thể cảnh báo các phần khác nhau của ứng dụng khi xảy ra rung lắc. Đây là những gì tôi đã làm.
Đầu tiên, tôi phân lớp UIWindow . Điều này là dễ dàng. Tạo một tệp lớp mới với một giao diện như MotionWindow : UIWindow
(cứ tự nhiên chọn lấy, chốt). Thêm một phương thức như vậy:
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
if (event.type == UIEventTypeMotion && event.subtype == UIEventSubtypeMotionShake) {
[[NSNotificationCenter defaultCenter] postNotificationName:@"DeviceShaken" object:self];
}
}
Thay đổi @"DeviceShaken"
tên thông báo của sự lựa chọn của bạn. Lưu các tập tin.
Bây giờ, nếu bạn sử dụng MainWindow.xib (công cụ mẫu Xcode stock), hãy vào đó và thay đổi lớp của đối tượng Window của bạn từ UIWindow sang MotionWindow hoặc bất cứ điều gì bạn gọi nó. Lưu xib. Nếu bạn thiết lập UIWindow theo chương trình, thay vào đó hãy sử dụng lớp Window mới của bạn.
Bây giờ ứng dụng của bạn đang sử dụng lớp UIWindow chuyên biệt . Bất cứ nơi nào bạn muốn được thông báo về một cái lắc, hãy đăng ký thông báo cho họ! Như thế này:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(deviceShaken) name:@"DeviceShaken" object:nil];
Để loại bỏ bản thân bạn như một người quan sát:
[[NSNotificationCenter defaultCenter] removeObserver:self];
Tôi đặt cái của tôi vào viewWillAppear: và viewWillDisappear: nơi Bộ điều khiển View có liên quan. Hãy chắc chắn rằng phản ứng của bạn đối với sự kiện lắc sẽ biết liệu nó có "đang diễn ra" hay không. Mặt khác, nếu thiết bị bị rung hai lần liên tiếp, bạn sẽ bị kẹt xe. Bằng cách này, bạn có thể bỏ qua các thông báo khác cho đến khi bạn thực sự hoàn thành việc trả lời thông báo ban đầu.
Ngoài ra: Bạn có thể chọn để mở khóa cho khỏi motionBegan vs motionEnded . Tuỳ bạn. Trong trường hợp của tôi, hiệu ứng luôn cần diễn ra sau khi thiết bị ở trạng thái nghỉ (so với khi thiết bị bắt đầu rung), vì vậy tôi sử dụng MotionEnded . Hãy thử cả hai và xem cái nào có ý nghĩa hơn ... hoặc phát hiện / thông báo cho cả hai!
Thêm một quan sát (tò mò?) Ở đây: Lưu ý rằng không có dấu hiệu quản lý phản hồi đầu tiên trong mã này. Cho đến nay tôi chỉ thử điều này với Bộ điều khiển xem bảng và mọi thứ dường như hoạt động khá tốt với nhau! Tôi không thể đảm bảo cho các kịch bản khác mặc dù.
Kendall, et. al - có ai có thể nói lý do tại sao điều này có thể như vậy đối với các lớp con UIWindow không? Có phải vì cửa sổ nằm ở đầu chuỗi thức ăn?
Tôi đã xem qua bài đăng này để tìm kiếm một thực hiện "lắc". Câu trả lời của millenomi hoạt động tốt với tôi, mặc dù tôi đang tìm kiếm thứ gì đó đòi hỏi một chút "hành động lắc" để kích hoạt. Tôi đã thay thế giá trị Boolean bằng int ShakeCount. Tôi cũng đã thực hiện lại phương thức L0AccelutionsIsShaking () trong Objective-C. Bạn có thể điều chỉnh lượng đạn cần thiết bằng cách điều chỉnh số lượng đạn được thêm vào ShakeCount. Tôi không chắc chắn tôi đã tìm thấy các giá trị tối ưu chưa, nhưng có vẻ như nó vẫn hoạt động tốt cho đến nay. Hy vọng điều này sẽ giúp ai đó:
- (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);
}
Tái bút: Tôi đã đặt khoảng thời gian cập nhật là 1/15 giây.
[[UIAccelerometer sharedAccelerometer] setUpdateInterval:(1.0 / 15)];
Bạn cần kiểm tra gia tốc kế thông qua gia tốc kế: didAccelates: phương thức là một phần của giao thức UIAccelerometerDelegate và kiểm tra xem các giá trị có vượt quá ngưỡng chuyển động cần thiết cho một lần lắc hay không.
Có một mã mẫu khá trong gia tốc kế: didAccelREE: phương thức ngay ở cuối AppControll.m trong ví dụ GLPaint có sẵn trên trang web dành cho nhà phát triển iPhone.
Trong iOS 8.3 (có lẽ sớm hơn) với Swift, đơn giản như ghi đè motionBegan
hoặc motionEnded
các phương thức trong trình điều khiển chế độ xem của bạn:
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!")
}
}
Đây là mã đại biểu cơ bản bạn cần:
#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];
}
Đồng thời đặt mã thích hợp trong Giao diện. I E:
@interface MyViewCont Bộ điều khiển: UIViewCont Bộ điều khiển <UIPickerViewDelegate, UIPickerViewDataSource, UIAccelerometerDelegate>
Kiểm tra ví dụ GLPaint.
Thêm các phương thức sau trong tệp ViewControll.m, nó hoạt động đúng
-(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
}
Xin lỗi để đăng bài này dưới dạng câu trả lời thay vì nhận xét nhưng như bạn có thể thấy tôi mới biết về Stack Overflow và vì vậy tôi chưa đủ uy tín để đăng bình luận!
Dù sao, tôi thứ hai @cire về việc đảm bảo đặt trạng thái phản hồi đầu tiên một khi chế độ xem là một phần của hệ thống phân cấp chế độ xem. Vì vậy, thiết lập trạng thái phản hồi đầu tiên trong viewDidLoad
phương thức bộ điều khiển xem của bạn sẽ không hoạt động. Và nếu bạn không chắc chắn liệu nó có hoạt động hay không [view becomeFirstResponder]
sẽ trả về cho bạn một boolean mà bạn có thể kiểm tra.
Một điểm khác: bạn có thể sử dụng bộ điều khiển chế độ xem để ghi lại sự kiện rung nếu bạn không muốn tạo một lớp con UIView một cách không cần thiết. Tôi biết nó không quá rắc rối nhưng vẫn có tùy chọn ở đó. Chỉ cần di chuyển các đoạn mã mà Kendall đưa vào lớp con UIView vào bộ điều khiển của bạn và gửi becomeFirstResponder
và resignFirstResponder
tin nhắn đến self
thay vì lớp con UIView.
Trước hết, tôi biết đây là một bài viết cũ, nhưng nó vẫn có liên quan và tôi thấy rằng hai câu trả lời được bình chọn cao nhất đã không phát hiện ra sự rung chuyển càng sớm càng tốt . Đây là cách thực hiện:
Trong ViewContoder của bạn:
- (BOOL)canBecomeFirstResponder {
return YES;
}
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
if (motion == UIEventSubtypeMotionShake) {
// Shake detected.
}
}
Giải pháp dễ nhất là lấy ra một cửa sổ gốc mới cho ứng dụng của bạn:
@implementation OMGWindow : UIWindow
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
if (event.type == UIEventTypeMotion && motion == UIEventSubtypeMotionShake) {
// via notification or something
}
}
@end
Sau đó, trong đại biểu ứng dụng của bạn:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[OMGWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
//…
}
Nếu bạn đang sử dụng Storyboard, điều này có thể phức tạp hơn, tôi không biết chính xác mã bạn sẽ cần trong ứng dụng ủy nhiệm chính xác.
@implementation
Xcode 5.
Chỉ cần sử dụng ba phương pháp này để làm điều đó
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event{
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event{
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event{
để biết chi tiết bạn có thể kiểm tra một mã ví dụ hoàn chỉnh ở đó
Một phiên bản swiftease dựa trên câu trả lời đầu tiên!
override func motionEnded(_ motion: UIEventSubtype, with event: UIEvent?) {
if ( event?.subtype == .motionShake )
{
print("stop shaking me!")
}
}
Để kích hoạt toàn ứng dụng này, tôi đã tạo một danh mục trên 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
thay vìviewWillAppear
. Tôi cung không chăc tại sao; có lẽ khung nhìn cần phải được nhìn thấy trước khi nó có thể làm bất cứ điều gì để bắt đầu nhận các sự kiện rung lắc?