To detect when the user is shaking the device, you could perform some intricate math on the signal that comes from the accelerometer. However, the class UIResponder has been kind enough to implement methods that do the math for you.
// Triggered when a shake is detected - (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event; // Triggered when the shake is complete - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event; // Triggered when a shake is interrupted (by a call for example) // Or if a shake lasts for more than a second - (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event;
Now in HypnoTime, let’s override motionBegan:withEvent: to change the stripe color when the phone is shaken. First, add an instance variable to HypnosisView.h to hold the new color:
@interface HypnosisView : UIView { UIColor *stripeColor; float xShift, yShift; } @property (nonatomic, assign) float xShift; @property (nonatomic, assign) float yShift; @end
The designated initializer for UIView is initWithFrame:. In HypnosisView.m, initialize the stripeColor in initWithFrame:.
- (id)initWithFrame:(CGRect)r { self = [super initWithFrame:r]; if (self) { // Notice we explicitly retain the UIColor instance // returned by the convenience method lightGrayColor, // because it is autoreleased and we need to keep it around // so we can use it in drawRect:. stripeColor = [[UIColor lightGrayColor] retain]; } return self; }
Finally, use the stripeColor in your drawRect: method of HypnosisView.m.
CGContextSetLineWidth(context, 10); // Set the stroke color to the current stripeColor [stripeColor setStroke]; // Draw concentric circles for (float currentRadius = maxRadius; currentRadius > 0; currentRadius -= 20) {
Build and run the application just to make sure you haven’t broken anything. It should work exactly as before.
Because stripeColor is owned by HypnosisView, it must be released in the view’s dealloc method. Override dealloc in HypnosisView.m.
- (void)dealloc { [stripeColor release]; [super dealloc]; }
Now override motionBegan:withEvent: to change the color and redraw the view in HypnosisView.m.
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event { // Shake is the only kind of motion for now, // but we should (for future compatibility) // check the motion type. if (motion == UIEventSubtypeMotionShake) { NSLog(@"shake started"); float r, g, b; // Notice the trailing .0 on the dividends... this is necessary // to tell the compiler the result is a floating point number.. otherwise, // you will always get 0 r = random() % 256 / 256.0; g = random() % 256 / 256.0; b = random() % 256 / 256.0; [stripeColor release]; stripeColor = [UIColor colorWithRed:r green:g blue:b alpha:1]; [stripeColor retain]; [self setNeedsDisplay]; } }
There’s one more important detail: the window’s firstResponder is the object that gets sent all of the motion events. Right now, the first responder is not HypnosisView, but you can change that in two steps. First, you need to override canBecomeFirstResponder in HypnosisView.m:
- (BOOL)canBecomeFirstResponder { return YES; }
Then, when your view appears on the screen, you need to make it the first responder. In HypnosisViewController.m, add the following line of code to viewWillAppear:animated.
- (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; NSLog(@"Monitoring accelerometer"); UIAccelerometer *a = [UIAccelerometer sharedAccelerometer]; [a setUpdateInterval:0.1]; [a setDelegate:self]; [[self view] becomeFirstResponder]; }
Build and run the application. Shake the device and watch the color of the stripes change. Notice that the color does not continue to change if you continue shaking. This is because there isn’t a “while motion continues” method. To change the color, you have to shake the device, stop shaking it, and then shake it again. (If you wanted the color to continue to change, you could use an NSTimer to send periodic “change the color now” messages. You would create the timer in motionBegan:withEvent: and destroy it in motionEnded:withEvent: and motionCancelled:withEvent:.)
Also, it’s important to note that motion events have nothing to do with the UIAccelerometer delegate. The system determines there is a shake by querying the accelerometer hardware and then sends the appropriate messages to the firstResponder.
18.118.28.179