Mega Gold Challenge Solution


#1

I first created a new property on BNRLine for color. This is so that each finished line can be drawn in the color under which it was originally set.

BNRLine.h

...
@property (nonatomic) UIColor *color;
...

Now within BNRDrawView.m I set a local color property that will be used for the current selected color.

...
@property (nonatomic, strong)UIColor *color;
...

In the initWithFrame method I setup a swipe gesture and add an upward requirement as well as the need to use 3 fingers:

...
        UISwipeGestureRecognizer *swipeRecognizer = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(colorMenu:)];
        swipeRecognizer.direction = UISwipeGestureRecognizerDirectionUp;
        swipeRecognizer.numberOfTouchesRequired = 3;
        [self addGestureRecognizer:swipeRecognizer];
...

The menu was the big question so I decided to use the same menu that was taught to us prior for deleting lines. This meant that the colors I need to offer were written instead of shown. For example “Red” instead of showing a red color block. Not sure if this breaks the rules but within the confines of what was taught I thought this fair. Additionally, I could find no way of passing what menu item was selected which led me to have to create a very small method for each color choice. This bugs the hell out of me but there is no way to pass an argument in the action:selector and I could not figure out how to say get the “title” from sender. For this exercise I then just used red and blue but of course any number of other colors would work exactly the same.

-(void)colorMenu:(UIGestureRecognizer *)gr
{
    NSLog(@"%@",NSStringFromSelector(_cmd));
    CGPoint point = [gr locationInView:self];
    
    [self becomeFirstResponder];
    self.menu = [UIMenuController sharedMenuController];
    UIMenuItem *redColor = [[UIMenuItem alloc]initWithTitle:@"Red" action:@selector(lineColorRed:)];
    UIMenuItem *blueColor = [[UIMenuItem alloc]initWithTitle:@"Blue" action:@selector(lineColorBlue:)];
 
    self.menu.menuItems = @[redColor,blueColor];
    
    [self.menu setTargetRect:CGRectMake(point.x, point.y, 2, 2) inView:self];
    [self.menu setMenuVisible:YES];
}

Here are the extremely simple color methods that I would have much preferred to combine into one…

-(void)lineColorRed:(id)sender
{
    self.color = [UIColor redColor];
}

-(void)lineColorBlue:(id)sender
{
    
    self.color = [UIColor blueColor];
}

I then updated drawRect. For an earlier challenge we had to create different colors depending on angle so for this to work I had to comment out that code since the two are at odds. I then had the finished lines draw based on the “saved” color (hence the reason I made color a property of BNRLine). For current lines I set the color based on the currently selected color. I’ll remove my comment lines related to angle colors below so that the current challenge will be clearer.

-(void)drawRect:(CGRect)rect
{
    for (BNRLine *line in self.finishedLines)
    {       
        [[line color]set];
        [self strokeLine:line];
    }
    
    for (BNRCircle *circle in self.finishedCircles)
    {
        [self strokeCircle:circle];
    }
    
    [self.color set];
    
    for (NSValue *key in self.linesInProgress)
    {
        [self strokeLine:self.linesInProgress[key]];
    }
    
    if (self.selectedLine)
    {
        [[UIColor greenColor] set];
        [self strokeLine:self.selectedLine];
    }
}

Finally for touchesMoved I set the color property on BNRLine to the currently selected color. Again this is so that finished lines can draw in their original colors.

-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    BNRLine *line;
    
    NSLog(@"%@",NSStringFromSelector(_cmd));
    
    if ([touches count] != 2)
    {
       for (UITouch *t in touches)
       {
           NSValue *key = [NSValue valueWithNonretainedObject:t];
           line = self.linesInProgress[key];
           line.end = [t locationInView:self];
           line.width = [self widthFromVelocity];
           line.color = self.color;
       }
    }
    
    [self setNeedsDisplay];
}

#2

I added a new view controller with xib for color panel having a toolbar with three RGB bar button items. And DrawViewController presents it modally when swipe happens. To make the modal view transparent, it was necessary to set background color clearColor, and set self.modalPresentationStyle UIModalPresentationCurrentContext.


BNRDrawViewController.h
  -(void)showColorPanel:(BNRDrawView *)sender;
  @property (nonatomic, strong) UIColor *currentColor;

BNRDrawViewController.m
  -(void)showColorPanel:(BNRDrawView *)sender
      BNRColorViewController *colorVC = [[BNRColorViewController alloc] init];
      self.modalPresentationStyle = UIModalPresentationCurrentContext;
      [self presentViewController:colorVC animated:YES completion:nil];

  -(void)loadView
      BNRDrawView *view = [[BNRDrawView alloc] initWithFrame:CGRectZero];
      view.myController = self;
      self.view = view;

  -(void)setCurrentColor:(UIColor *)currentColor
      BNRDrawView *view = (BNRDrawView *)self.view;
      view.currentColor = currentColor;

  -(UIColor *)currentColor
      BNRDrawView *view = (BNRDrawView *)self.view;
      return view.currentColor;

BNRDrawView.h
  @property (nonatomic, weak) BNRDrawViewController *myController;
  @property (nonatomic, strong) UIColor *currentColor;

BNRDrawView.m
  -(instancetype)initWithFrame:(CGRect)r
          UISwipeGestureRecognizer *swipeRecognizer =
              [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipe:)];
          swipeRecognizer.numberOfTouchesRequired = 3;
          swipeRecognizer.direction = UISwipeGestureRecognizerDirectionUp;
          swipeRecognizer.delaysTouchesBegan = YES;
          [self addGestureRecognizer:swipeRecognizer];

  -(void)swipe:(UIGestureRecognizer *)gr
    [self.myController showColorPanel:self];

  -(void)drawRect:(CGRect)rect
    for (BNRLine *line in self.finishedLines)
        [line.color set];
        [self strokeLine:line];

  -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
        line.color = self.currentColor;

BNRColorViewController.m
  -(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    return self;

  - (IBAction)selectedColor:(UIBarButtonItem *)sender
    BNRDrawViewController *dvc = (BNRDrawViewController *)self.presentingViewController;
    if ([sender.title isEqualToString: @"Red"]) {
        dvc.currentColor = [UIColor redColor];
    } else if ([sender.title isEqualToString:@"Green"]) {
        dvc.currentColor = [UIColor greenColor];
    } else if ([sender.title isEqualToString:@"Blue"]) {
        dvc.currentColor = [UIColor blueColor];
    }
    [dvc dismissViewControllerAnimated:YES completion:nil];
}

BNRLine.h
  @property (nonatomic, strong) UIColor *color;

#3

First off, I had a hard time figuring out how to actually get the new viewController pushed on to the view. I was trying to add a gestureRecognizer to the BNRDrawViewController, but a ViewController doesn’t have the addgesturerecognizer instance method.
I ended up borrowing sunny4s’s approach, so thanks for that!
I took a similar approach as sunny4s, although I didn’t create a toolbar with barbuttonitems.

Here is my BNRLine.h

[code]#import <Foundation/Foundation.h>

@interface BNRLine : NSObject

@property (nonatomic) CGPoint begin;
@property (nonatomic) CGPoint end;
@property (nonatomic) float lineWidth;
@property (nonatomic, strong) UIColor *lineColor;

@end
[/code]

BNRDrawView.h:

[code]#import <Foundation/Foundation.h>

@class BNRDrawViewController;

@interface BNRDrawView : UIView

@property (nonatomic, weak) BNRDrawViewController *drawViewController;
@property (nonatomic, strong) UIColor *lineColor;

@end
[/code]

This is what my class extension in BNRDrawView.m looks like:

[code]@interface BNRDrawView ()

@property (nonatomic, strong) UIPanGestureRecognizer *moveRecognizer;
@property (nonatomic, strong) NSMutableDictionary *linesInProgress;
@property (nonatomic, strong) NSMutableArray *finishedLines;
@property (nonatomic, weak) BNRLine *selectedLine;

@property (nonatomic, strong) UIMenuController *menu;
@property (nonatomic, strong) UIBezierPath *bp;

@end[/code]

In touchesBegan I added a line which sets the linecolor for the instance of BNRLine to the property lineColor of the BNRDrawView:

Then in the drawRect: method I implemented the lineColor property of the BNRLine class to be used when drawing the line onto the view:

- (void)drawRect:(CGRect)rect { // Draw finished lines for (BNRLine *line in self.finishedLines) { self.bp.lineWidth = line.lineWidth; [line.lineColor set]; [self strokeLine:line]; }

in my initWithFrame method I added a swipeGestureRecognizer like this:

UISwipeGestureRecognizer *threeFingerSwipeRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(threeFingerSwipe:)]; threeFingerSwipeRecognizer.numberOfTouchesRequired = 3; threeFingerSwipeRecognizer.direction = UISwipeGestureRecognizerDirectionUp; threeFingerSwipeRecognizer.delaysTouchesBegan = YES; [self addGestureRecognizer:threeFingerSwipeRecognizer];

I also set the property lineColor for BNRDrawView to blackColor at this point as the default line colour.

This is what my threeFingerSwipe action method looks like:

- (void)threeFingerSwipe:(UISwipeGestureRecognizer *)gr { NSLog(@"Three finger swipe recognized"); [self.drawViewController showColorPanel:self]; self.drawViewController.drawView = self; }

Basically this sends a message to the BNRDrawViewContoller instance. The implementation file of my BNRDrawViewController looks like this:

[code]#import <Foundation/Foundation.h>

@class BNRDrawView;

@interface BNRDrawViewController : UIViewController

@property (nonatomic, weak) BNRDrawView *drawView;
@property (nonatomic, strong) UIColor *lineColor;

  • (void)showColorPanel:(BNRDrawView *)sender;

@end
[/code]

And this is what the implementation file looks like:

[code]#import “BNRDrawViewController.h”
#import “BNRDrawView.h”
#import “BNRColorViewController.h”

@interface BNRDrawViewController ()

@end

@implementation BNRDrawViewController

#pragma mark - View cycle methods

  • (void)loadView
    {
    BNRDrawView *drawView = [[BNRDrawView alloc] initWithFrame:CGRectZero];
    self.view = drawView;
    drawView.drawViewController = self;
    }

  • (void)showColorPanel:(BNRDrawView *)sender
    {
    NSLog(@“Message sent succesfully”);
    BNRColorViewController *colorViewController = [[BNRColorViewController alloc] init];
    colorViewController.drawViewController = self;
    self.modalPresentationStyle = UIModalPresentationCurrentContext;
    [self presentViewController:colorViewController animated:YES completion:nil];
    }

  • (void)setLineColor:(UIColor *)lineColor
    {
    _lineColor = lineColor;
    self.drawView.lineColor = _lineColor;
    }

@end
[/code]

This pushes the new BNRColorViewController class onto the view.
I created a XIB file with 6 labels overlayed with transparent buttons. The labels were given background colours corresponding with the colours that it would set as the new line colour. I gave each button it’s own action method in the implementation file.
This is what the header and implementation files for BNRColorViewController look like:

[code]#import <UIKit/UIKit.h>

@class BNRDrawViewController;

@interface BNRColorViewController : UIViewController

@property (nonatomic, weak) BNRDrawViewController *drawViewController;

@end[/code]

[code]#import “BNRColorViewController.h”
#import “BNRDrawViewController.h”
#import “BNRDrawView.h”

@interface BNRColorViewController ()

@end

@implementation BNRColorViewController

  • (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
    {
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
    }
    return self;
    }

  • (void)viewDidLoad
    {
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.
    }

  • (void)didReceiveMemoryWarning
    {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
    }

  • (IBAction)blueButton:(id)sender
    {
    self.drawViewController.lineColor = [UIColor blueColor];
    [self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
    }

  • (IBAction)greenButton:(id)sender
    {
    self.drawViewController.lineColor = [UIColor greenColor];
    [self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
    }

  • (IBAction)yellowColor:(id)sender
    {
    self.drawViewController.lineColor = [UIColor yellowColor];
    [self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
    }

  • (IBAction)blackColor:(id)sender
    {
    self.drawViewController.lineColor = [UIColor blackColor];
    [self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
    }

  • (IBAction)grayColor:(id)sender
    {
    self.drawViewController.lineColor = [UIColor lightGrayColor];
    [self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
    }

  • (IBAction)redColor:(id)sender
    {
    self.drawViewController.lineColor = [UIColor redColor];
    [self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
    }

@end
[/code]

And that’s it.
Pfew. I don’t think this post is actually readable for anyone and for that I’m sorry. I felt like sharing my solution but it got a bit more complicated than I thought t would!
Really the most important and tricky part of this challenge is getting the linecolor property from a viewcontroller a few levels removed from the property of BNRLine that you want it to set.
Maybe the way I went about it is a silly one, but it’s the only way I got it to work.
If someone has questions about why I did some things, or, even better, maybe ways for me to improve on this, please let me know!


#4

I created a custom view in Interface Builder (about 1/4 the height of the screen) and then animated it pulling up from the bottom (similar to the control panel). It has three color squares on it. When you select one of the squares, or tap or double-tap anywhere else, the little view animates back off the bottom of the screen and then is removed from the superview. I had to add code throughout to test if the color menu was visible, but in the end it seemed to be a pretty nice solution (and did not require another view controller).


#5

PneumaPilot, can you please share your solution? I almost gave up trying to solve it without another view controller.