Bronze & Silver Challege solution using Singleton


#1

Hello all,

I have came by to share my solutions to Bronze and Silver Challenges.

For the bronze challenge, I created a separate class called BNRLineStore. BNRLineStore act as data storage for line objects.

#import <Foundation/Foundation.h>
@class BNRLine;

@interface BNRLineStore : NSObject
@property (nonatomic, readonly, strong) NSArray *lines;

+ (instancetype)sharedLineStore;
- (void)addLine:(BNRLine *)line;
- (void)saveLinesToFile;
- (void)loadLinesFromFile;

@end

This is the implementation file for the class.

#import "BNRLineStore.h"
@class BNRLine;

// Path to persistent store
static NSString * const path = @"/tmp/archive.arc";

@interface BNRLineStore()
#pragma mark - instance variables
@property (nonatomic, strong) NSMutableArray<BNRLine *> *mutableLines;
@end


@implementation BNRLineStore

#pragma mark - initializers

- (instancetype)init {
    @throw [NSException exceptionWithName:@"Singleton" reason:@"Use class method sharedLineStore to get a reference to singleton instance" userInfo:nil];
}

- (instancetype)initPrivate {
    if (self = [super init]) {
        _mutableLines = [[NSMutableArray alloc] init];
    }
    return self;
}


#pragma mark - class methods
+ (instancetype)sharedLineStore {
    static BNRLineStore *sharedStore = nil;
    // Find out how dispatch_once works at:
    // https://www.bignerdranch.com/blog/dispatch_once-upon-a-time/
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedStore = [[BNRLineStore alloc] initPrivate];
    });
    
    return sharedStore;
}


#pragma mark - instance methods
- (void)addLine:(BNRLine *)line {
    [self.mutableLines addObject:line];
}

- (void)saveLinesToFile {
    
    NSArray *lines = self.lines;
    BOOL success = [NSKeyedArchiver archiveRootObject:lines toFile:path];
    
    if (success) {
        NSLog(@"Success");
    }
    
}

- (void)loadLinesFromFile {
    NSArray *lines = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
    if (lines != nil) {
        self.mutableLines = [lines mutableCopy];
    }
    
    NSLog(@"Lines loaded: %@", lines);
}

#pragma mark - accessors
- (NSArray *)lines {
    return [_mutableLines copy];
}

#pragma mark - overrides
- (void)dealloc {
    NSLog(@"Singleton LineStore has just been deallocated");
}


@end

BNRLine class is pretty much “stock”. I only added a lineAngle instance method which returns angle of line. I thought this is the appropriate place to define this method.

@interface BNRLine : NSObject <NSCoding>

@property (nonatomic) CGPoint begin;
@property (nonatomic) CGPoint end;
- (float)lineAngle;

@end 

BNRLine implementation file:

#import “BNRLine.h”

@implementation BNRLine

#pragma mark - NSCoding protocol methods
- (void)encodeWithCoder:(NSCoder *)aCoder {
    [aCoder encodeCGPoint:self.begin forKey:@"BNRLineBegin"];
    [aCoder encodeCGPoint:self.end forKey:@"BNRLineEnd"];
}

- (id)initWithCoder:(NSCoder *)aDecoder{
    if (self = [super init]) {
        _begin = [aDecoder decodeCGPointForKey:@"BNRLineBegin"];
        _end = [aDecoder decodeCGPointForKey:@"BNRLineEnd"];
    }
    
    return self;
}

- (float)lineAngle{
    float width = self.begin.x - self.end.x;
    float height = self.begin.y - self.end.y;
    float arcTangent = atan2f(height, width);
    
    // Converting radians to angle
    // PI radians = 180 degress
    // 1 radian = 180 / PI
    // 1 radian * atan2f = angle
    float angle = (arcTangent * 180) / M_PI;
    
    return angle;
}

@end

And finally DrawView It pretty much generic.

#import "BNRDrawView.h"
#import "BNRLine.h"
#import "BNRLineStore.h"

@interface BNRDrawView ()

@property (nonatomic, strong) NSMutableDictionary *linesInProgress;

@end

@implementation BNRDrawView
 
- (instancetype)initWithFrame:(CGRect)frame {
    NSLog(@"Inittin viw");
    if (self = [super initWithFrame:frame]) {
        self.linesInProgress = [[NSMutableDictionary alloc] init];
        self.backgroundColor = [UIColor lightGrayColor];
        self.multipleTouchEnabled = YES;
    }
    
    return self;
}


#pragma mark - instance methods
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
    // Drawing code
    // Draws finished lines in black
    [[UIColor blackColor] set];
    
    for (BNRLine *line in [[BNRLineStore sharedLineStore] lines]) {
        
        // Determine angle of the line
        float angle = [line lineAngle];
        
        // Set color for items to be drawn
        [[self colorWithAngle:angle] set];
        
        [self strokeLine:line];
        
        [[UIColor blackColor] set];
    }
    
    // Draw current line with red
    for (NSValue *key in self.linesInProgress) {
        [self strokeLine:self.linesInProgress[key]];
    }
}

- (void)strokeLine:(BNRLine *)line {
    
    // Configure a bezier path
    UIBezierPath *bp = [UIBezierPath bezierPath];
    bp.lineWidth = 10;
    bp.lineCapStyle = kCGLineCapRound;
    
    // Draw path
    [bp moveToPoint:line.begin];
    [bp addLineToPoint:line.end];
    
    [bp stroke];
    
}


- (UIColor *)colorWithAngle:(float)angle {
    
    UIColor *color = nil;
    
    if (angle <= 180 && angle > 90){
        color = [UIColor blueColor];
    } else if (angle <= 90 && angle >= 0){
        color = [UIColor redColor];
    } else if (angle <= 0 && angle > -90){
        color = [UIColor greenColor];
    } else {
        color = [UIColor yellowColor];
    }
    
    return color;
}



#pragma mark - UIResponder method overrides
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    // Log events
    NSLog(@"%@", NSStringFromSelector(_cmd));
    
    // Iterate through touches
    for (UITouch *t in touches) {
        CGPoint location = [t locationInView:self];
        BNRLine *line = [[BNRLine alloc] init];
        
        // Set start and end point of BNRLine instance
        line.begin = location;
        line.end = location;
        
        NSValue *key = [NSValue valueWithNonretainedObject:t];
        
        // associate line for key
        self.linesInProgress[key] = line;
    }
    
    
    [self setNeedsDisplay];
    
}

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    for (UITouch *t in touches) {
        NSValue *key = [NSValue valueWithNonretainedObject:t];
        BNRLine *line = self.linesInProgress[key];
        
        CGPoint location = [t locationInView:self];
        
        line.end = location;
    }
    
    [self setNeedsDisplay];
}

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    
    for (UITouch *t in touches) {
        NSValue *key = [NSValue valueWithNonretainedObject:t];
        
        // Let line to point original line object
        BNRLine *line = self.linesInProgress[key];
        
        // Add line to finished lines
        //[self.finishedLines addObject:line];
        
        // Get a pointer to line store
        [[BNRLineStore sharedLineStore] addLine:line];
       
        
        [self.linesInProgress removeObjectForKey:key];
        
    }
    
    [self setNeedsDisplay];
    
}

- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"%@", NSStringFromSelector(_cmd));
    
    for (UITouch *t in touches) {
        NSValue *key = [NSValue valueWithNonretainedObject:t];
        [self.linesInProgress removeObjectForKey:key];
    }
    
    [self setNeedsDisplay];
}



@end

I hope you find it useful. Thanks.