Solution?


#1

Hey, could someone please post the solution to this chapters challenge, or direct me to where I can find it please. I am struggling with trying to solve this question.


#2

Here’s my take. Another property was created on HypnosisView for the stroke color. The controller (which is the UIAcceleratorDelegate) calculates the RGB values and sets the color.

//  HypnosisView.h
#import <Foundation/Foundation.h>

@interface HypnosisView : UIView
{
    float xShift;
    float yShift;
    UIColor *strokeColor;

    BOOL initializationDone;
    CGRect bounds;
    CGPoint center;
    float maxRadius;
    NSString *text;
    UIFont *textFont;
    CGSize shadowOffset;
    CGColorRef shadowColor;
    CGRect textRect;
}

@property (nonatomic, assign) float xShift;
@property (nonatomic, assign) float yShift;
@property (nonatomic, retain) UIColor *strokeColor;

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

@implementation HypnosisView

@synthesize xShift, yShift, strokeColor;

- (id)initWithFrame:(CGRect)r
{
    self = [super initWithFrame:r];
    
    if (self)
    {
        strokeColor = [UIColor lightGrayColor];
        bounds = [self bounds];
    }
    
    return self;
}

- (void)drawRect:(CGRect)rect
{
    if (!initializationDone)
    {
        // Ignore the dirty rectangle (rect), just draw entire content.
        bounds = [self bounds];
        maxRadius = hypot(bounds.size.width, bounds.size.height) / 2.0;
        text = @"You are getting sleepy.";
        textFont = [UIFont boldSystemFontOfSize:28];
        shadowOffset = CGSizeMake(4, 3);
        shadowColor = [[UIColor darkGrayColor] CGColor];

        // Size of rectangle in which to draw text (not allowing for shadow?)
        textRect.size = [text sizeWithFont];

        initializationDone = YES;
    }
    
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    
    // Set line width and color in context
    CGContextSetLineWidth(ctx, 10);
    [strokeColor setStroke];
    
    center.x = bounds.origin.x + bounds.size.width / 2.0;
    center.y = bounds.origin.y + bounds.size.height / 2.0;

    // Draw arcs from outside to center
    for (float r = maxRadius; r > 0 ; r-=20)
    {
        center.x += xShift;
        center.y += yShift;
        CGContextAddArc(ctx, center.x, center.y, r, 0.0, M_PI * 2.0, YES);
        CGContextStrokePath(ctx);
    }

    // Set corner of text Rectangle
    textRect.origin.x = center.x - textRect.size.width / 2.0;
    textRect.origin.y = center.y - textRect.size.height / 2.0;

    // Set fill color in context (for text color)
    [[UIColor blackColor] setFill];

    CGContextSetShadowWithColor(ctx, shadowOffset, 2.0, shadowColor);
   
    [text drawInRect:textRect withFont];
}

@end

Here is the controller:

//  HypnosisViewController.h
#import <Foundation/Foundation.h>

@interface HypnosisViewController : UIViewController <UIAccelerometerDelegate>
@end
//  HypnosisViewController.m
#import "HypnosisViewController.h"
#import "HypnosisView.h"

@implementation HypnosisViewController

// HypnosisViewController designated initializer
- (id) init
{
    // Call superclass's designated initializer
    self = [super initWithNibName:nil bundle:nil];
    
    if (self)
    {
        UITabBarItem *tbi = [self tabBarItem];
        [tbi setTitle:@"Hypnosis"];
        [tbi setImage:[UIImage imageNamed:@"Hypno.png"]];
    }
    
    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");
    UIAccelerometer *uia = [UIAccelerometer sharedAccelerometer];
    [uia setUpdateInterval:0.01];
    [uia setDelegate:self];
}

- (void)viewWillDisappear:(BOOL)animated
{
    [[UIAccelerometer sharedAccelerometer] setDelegate:nil];
}

- (void)accelerometer:(UIAccelerometer *)accmeter didAccelerate:(UIAcceleration *)acc
{
    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];
    
    // axis range is -1 to 1.  Make it 0 to 1
    CGFloat red = ([acc x] + 1.0) / 2.0;
    CGFloat blue = ([acc y] + 1.0) / 2.0;
    CGFloat green = ([acc z] + 1.0) / 2.0;
    
    [hv setStrokeColor:[UIColor colorWithRed:red green:green blue:blue alpha:1]];
    [hv setNeedsDisplay];
}

@end

#3

// axis range is -1 to 1. Make it 0 to 1 CGFloat red = ([acc x] + 1.0) / 2.0; CGFloat blue = ([acc y] + 1.0) / 2.0; CGFloat green = ([acc z] + 1.0) / 2.0;

I love how you translated the acceleration data into R,G,B values. I used a very clumsy if/else construct. Yours is much more elegant.


#4

Thank you for your feedback :slight_smile:


#5

Hello BigNerdWes, I was just wondering why you declared all the variables for your view as instance variables? Have I missed something?


#6

Very elegant. Thanks for the post.


#7

You are right, they could’ve been static variables. No need for instance variables, since the values don’t change.

Why variables at all, you may be asking… just my personal preference, the code seems more readable that way.
I value readability and maintainability above other factors, unless I’m writing code that needs to be speed-optimized.

Not sure about the Objective-C compiler, but many compilers will optimize away the variables in the final code anyhow, so there may be little to no speed tradeoff in not declaring the variables.