Silver Challenge Solution


#1

Hi all,

Here’s my solution for the silver challenge.
JJHypnosisView.h

#import <UIKit/UIKit.h>

@protocol JJHypnosisViewDelegate <NSObject>

-(void) didFinishTapping;

@end

@interface JJHypnosisView : UIView

@property (strong, nonatomic) UIColor *circleColor;
@property (weak, nonatomic) id <JJHypnosisViewDelegate> delegate;

@end

Made the circleColor property public so it can be accessed by other classes. Also wrote a quick protocol to enable passing messages back to delegates (in this case - JJHypnosisViewController). More on this in a second.

JJHypnosisView.m

#import "JJHypnosisView.h"



@implementation JJHypnosisView

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        self.backgroundColor = [UIColor clearColor];
        self.circleColor = [UIColor lightGrayColor];
    }
    return self;
}

- (void)drawRect:(CGRect)rect
{
    CGRect bounds = self.bounds;
    
    CGPoint center;
    center.x = bounds.size.width / 2.0;
    center.y = bounds.size.height / 2.0;
    
    float maxRadius = hypotf(bounds.size.width, bounds.size.height) / 2.0;
    
    UIBezierPath *path = [[UIBezierPath alloc] init];
    
    for (float currentRadius = maxRadius; currentRadius > 0; currentRadius -= 20) {
        [path moveToPoint:CGPointMake(center.x + currentRadius, center.y)];
        [path addArcWithCenter:center radius:currentRadius startAngle:0.0 endAngle:M_PI * 2.0 clockwise:YES];
    }
    
    [self.circleColor setStroke];
    
    path.lineWidth = 10;
    [path stroke];
}

-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    float red = (arc4random() % 100) / 100.0;
    float green = (arc4random() % 100) / 100.0;
    float blue = (arc4random() % 100) / 100.0;
    
    UIColor *randomColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0];
    
    self.circleColor = randomColor;
    
    [self.delegate didFinishTapping];
}

-(void) setCircleColor:(UIColor *)circleColor
{
    _circleColor = circleColor;
    [self setNeedsDisplay];
}

@end

In the touchesBegan:withEvent: method, made sure to call the method specified in the protocol (declared in the .h) file on the delegate. This way, the delegate class can find out when this method is finished executing and take some course of action.

JJHypnosisViewController.m

#import "JJHypnosisViewController.h"
#import "JJHypnosisView.h"

@interface JJHypnosisViewController () <JJHypnosisViewDelegate>

@property (strong, nonatomic) JJHypnosisView *backgroundView;
@property (strong, nonatomic) UISegmentedControl *segmentedControl;

@property (nonatomic) CGRect screenRect;
@property (nonatomic) float screenWidth;
@property (nonatomic) float screenHeight;

@end



@implementation JJHypnosisViewController



#pragma mark - Loading

-(void) viewDidLoad
{
    [super viewDidLoad];
    
    [self getScreenDimensions];
    
    [self setUpSegmentedControlWidth:200 andHeight:42];
    
    self.backgroundView.delegate = self;
}

-(instancetype) initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    
    if (self) {
        // Set the tab bar item's title
        self.tabBarItem.title = @"Hypnotize";
        
        // Create a UIImage from a file
        // This will use Hypno@2x.png on retina display devices
        UIImage *i = [UIImage imageNamed:@"Hypno.png"];
        
        // Put that image on the tab bar item
        self.tabBarItem.image = i;
        
        
        
    }
    return self;
}

-(void)loadView
{
    self.backgroundView = [[JJHypnosisView alloc] init];
    
    self.view = self.backgroundView;
}



#pragma mark - Screen Dimensions and Controls

-(void) getScreenDimensions
{
    self.screenRect = [[UIScreen mainScreen] bounds];
    self.screenWidth = self.screenRect.size.width;
    self.screenHeight = self.screenRect.size.height;
}

-(void) setUpSegmentedControlWidth:(float)width andHeight:(float)height
{
    NSArray *itemsArray = [[NSArray alloc] initWithObjects:@"Red", @"Green", @"Blue", nil];
    self.segmentedControl = [[UISegmentedControl alloc] initWithItems:itemsArray];
    
    CGPoint controlLocation;
    controlLocation.x = (self.screenWidth / 2.0) - (width / 2.0);
    controlLocation.y = self.screenHeight * 0.8;
    
    self.segmentedControl.frame = CGRectMake(controlLocation.x, controlLocation.y, width, height);
    self.segmentedControl.backgroundColor = [UIColor whiteColor];
    self.segmentedControl.layer.cornerRadius = 5.0;
    self.segmentedControl.selectedSegmentIndex = 3;
    
    [self.segmentedControl addTarget:self action:@selector(changeCircleColor:) forControlEvents:UIControlEventValueChanged];
    
    [self.view addSubview:self.segmentedControl];
}

-(IBAction)changeCircleColor:(UISegmentedControl *)segmentedControl
{
    if (segmentedControl.selectedSegmentIndex == 0) {
        [self.backgroundView setCircleColor:[UIColor redColor]];
    } else if (segmentedControl.selectedSegmentIndex == 1) {
        [self.backgroundView setCircleColor:[UIColor greenColor]];
    } else if (segmentedControl.selectedSegmentIndex == 2) {
        [self.backgroundView setCircleColor:[UIColor blueColor]];
    }
}



#pragma mark - JJHypnosisView Delegate

-(void) didFinishTapping
{
    [self.segmentedControl setSelectedSegmentIndex:UISegmentedControlNoSegment];
}



@end

Included several private properties, including an instance of JJHypnosisView (to gain access to the setCircleColor method), an instance of UISegmentedControl (to gain access to the control across several methods), and three properties to contain screen dimensions. The last three weren’t absolutely necessary but in case this class ever expanded it may be helpful to have access to those screen dimensions across multiple methods. I also made sure to conform the view controller class to the protocol (the protocol established above).

In an attempt to clean up viewDidLoad, I split up the code into three manageable methods. The first, getScreenDimensions handles setting the screen dimension properties mentioned above. The second, setUpSegmentedControlWidth:andHeight sets up the segmentedControl property with all the necessary dimensions, a background color, a corner radius, and a target before finally adding it to as a subview to the backgroundView property. The third, changeCircleColor checks the selected index of the segmentedControl and responds accordingly, calling setCircleColor on self.backgroundView. These three methods are called in viewDidLoad and finally the View Controller sets itself up as the delegate of self.backgroundView (so it can receive messages via conforming to the protocol).

Finally - to get back to the reason for the protocol / delegate. The last method listed is a a JJHypnosisViewDelegate method, and “listens” for the touchesBegan:withEvent: method. When it detects the completion of that method, it sets the index value of the segmentedControl property to UISegmentedControlNoSegment. This way, if the user taps around the screen and the circleColor starts going crazy, the segmented control is turned off.