Solution using CMMotionManager


#1

So that others may learn, here is a successful attempt at using the CMMotionManager for this chapter’s program, instead of the deprecated UIAccelerometer/UIAccelerometerDelegate.

Only showing the ViewController with the block, as the View and the AppDelegate remain trivial.
I’m no expert on multithreaded code on iOS, so please let me know if anything I’m doing here is out of place (especially the “performSelectorOnMainThread:withObject:waitUntilDone:” call). On my machine, if I didn’t do that call and instead try to call “setNeedsDisplay” directly from the block, the drawRect calls come very infrequently.

//  HypnosisViewController2.h
#import <Foundation/Foundation.h>
#import <CoreMotion/CoreMotion.h>

@interface HypnosisViewController2 : UIViewController
{
    CMMotionManager *mm;
    NSOperationQueue *opQ;
}

@end
//  HypnosisViewController2.m
#import "HypnosisViewController2.h"
#import "HypnosisView.h"

@implementation HypnosisViewController2
- (id) init
{
    // Call superclass's designated initializer
    self = [super initWithNibName:nil bundle:nil];
    
    if (self)
    {
        UITabBarItem *tbi = [self tabBarItem];
        [tbi setTitle:@"Hypnosis2"];
        [tbi setImage:[UIImage imageNamed:@"Hypno.png"]];
        
        mm = [[CMMotionManager alloc] init];
        opQ = [[NSOperationQueue alloc] init];
    }
    
    return self;
}

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    return [self init];
}

- (void)loadView
{
    HypnosisView *hv = [[HypnosisView alloc] initWithFrame:CGRectZero];
    [hv setBackgroundColor:[UIColor whiteColor]];
    [self setView:hv];
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear];
    
    NSLog(@"Monitoring Accelerometer");
    [mm setAccelerometerUpdateInterval:0.01];       //100 times a second
    
    CMAccelerometerHandler ah = ^(CMAccelerometerData *data, NSError *error)
    {
        CMAcceleration acc = [data acceleration];
        
        NSLog(@"%f, %f, %f", acc.x, acc.y, acc.z);
        HypnosisView *hv = (HypnosisView *)[self view];

        float xShift = [hv xShift] * 0.8 + acc.x * 2.0;
        float yShift = [hv yShift] * 0.8 - acc.y * 2.0;
        [hv setXShift:xShift];
        [hv setYShift:yShift];

        [hv performSelectorOnMainThread:@selector(setNeedsDisplay) withObject:nil waitUntilDone:NO];
        //[hv setNeedsDisplay];         // Don't do this, it won't behave like the line above
    };
    
    [mm startAccelerometerUpdatesToQueue:opQ withHandler:ah];
}

- (void)viewWillDisappear:(BOOL)animated
{
    [mm stopAccelerometerUpdates];
}

@end

#2

If you follow the book to get the shake in place, you have to become the First Responder

add one line to the code - the self view bit

    [mm startAccelerometerUpdatesToQueue:opQ 
                             withHandler:ah];
    
        // become firstResponder to allow you to pick up the shake
    [[self view] becomeFirstResponder];
}

Nice work translating to the new version of the controls.