Gold Challenge


#1

Hi to everyone… I’m Giovanni from Italy

I don’t think the mean of the author is to not use gesture recognizer. What about bronze challenge then? As far as I read this book I don’t even have the skill to save and restore objects to file yet.

I think that the “challenge” is just that. To go further by yourself. Just a little bit.

Anyway… here’s my version:

I create a new NSObject subclass “BNRCircle”. I added a BNRDrawView property in order to keep track of finished circles.
I didn’t create a dictionary for circle in progress because it makes no sense because I assume to detect just one pinch gesture at time. (I am not even sure there is a way to finger 2 or more pinch gesture on the screen at the same time…Unfortunately I’m working only on the simulator by now… so I can’t test it like on a real device because I can simulate just a pinch gesture)

BNRDrawView.h

@interface BNRDrawView : UIView

@property (nonatomic, strong) NSMutableArray *finishedLines;
@property (nonatomic, strong) NSMutableArray *finishedCircles;

@end

BNRCircle.h

@interface BNRCircle : NSObject <NSCoding>

@property (nonatomic) CGPoint point1;
@property (nonatomic) CGPoint point2;

@property (nonatomic, readonly) CGRect boundRect;

@end

I choose to keep an updated rect made from 2 points given from 2 fingers. So I can draw a realtime circle using ‘boundRect’ as the rect around the circle as gold challenge requested.

To do so I simply implement setters for both properties point1 e point2 as follows:

BNRCircle.m

#import "BNRCircle.h"

@implementation BNRCircle

- (void)setPoint1:(CGPoint)point1
{
    // do setter job first
    _point1 = point1;
    
    // calculate new circle boundRect
    float width;
    float height;
    width = _point1.x > _point2.x ? _point1.x - _point2.x : _point2.x - _point1.x;
    height = _point1.y > _point2.y ? _point1.y - _point2.y : _point2.y - _point1.y;
    
    float x1 = 0.0;
    float y1 = 0.0;
    
    if (_point1.y <= _point2.y) {
        y1 = _point1.y;
        if (_point1.x <= _point2.x) x1 = _point1.x;
        else x1 = _point1.x - width;
    }

    if (_point1.y > _point2.y) {
        y1 = _point2.y;
        if (_point1.x > _point2.x) x1 = _point2.x;
        else x1 = _point2.x - width;
    }
    
    _boundRect = CGRectMake(x1, y1, width, height);

}

- (void)setPoint2:(CGPoint)point2
{
    // do setter job first
    _point2 = point2;

    // calculate new circle boundRect
    float width;
    float height;
    width = _point1.x > _point2.x ? _point1.x - _point2.x : _point2.x - _point1.x;
    height = _point1.y > _point2.y ? _point1.y - _point2.y : _point2.y - _point1.y;
    
    float x1 = 0.0;
    float y1 = 0.0;
    
    if (_point1.y <= _point2.y) {
        y1 = _point1.y;
        if (_point1.x <= _point2.x) x1 = _point1.x;
        else x1 = _point1.x - width;
    }
    
    if (_point1.y > _point2.y) {
        y1 = _point2.y;
        if (_point1.x > _point2.x) x1 = _point2.x;
        else x1 = _point2.x - width;
    }
    
    _boundRect = CGRectMake(x1, y1, width, height);

}

here’s my BNRDrawView initWithFrame:

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    
    if (self) {
        self.linesInProgress = [[NSMutableDictionary alloc] init];
        self.finishedLines = [[NSMutableArray alloc] init];
        self.finishedCircles = [[NSMutableArray alloc] init];
        self.backgroundColor = [UIColor grayColor];
        
        self.multipleTouchEnabled = YES;
        
        UIPinchGestureRecognizer *pinchGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self
                                                   action:@selector(pinch:)];
        [self addGestureRecognizer:pinchGesture];
    }
    return self;
}

added to drawRect: in order to draw finishedCircles and cirleInProgress:


    // Draw finished circles black
    [[UIColor blackColor] set];
    for (BNRCircle *circle in self.finishedCircles) {
        [self strokeCircle:circle];
    }

    // Draw circle in progress in green
    [[UIColor greenColor] set];
    [self strokeCircle:self.circleInProgress];

and of course ‘strokeCircle’:


- (void)strokeCircle:(BNRCircle *)circle
{
    if (circle) {
    
    UIBezierPath *bp = [UIBezierPath bezierPath];
    bp.lineWidth = 10;
    bp.lineCapStyle = kCGLineCapRound;
    
    CGPoint center;
    
    center.x = circle.boundRect.origin.x + circle.boundRect.size.width / 2.0;
    center.y = circle.boundRect.origin.y + circle.boundRect.size.height / 2.0;
    
    // calculate radius
    float radius = circle.boundRect.size.height >= circle.boundRect.size.width ?
                                        circle.boundRect.size.width : circle.boundRect.size.height;
    radius /= 2;
    
    [bp addArcWithCenter:center radius:radius startAngle:0 endAngle:M_PI * 2.0 clockwise:YES];
    
    [bp stroke];
    }
}

and last… my ‘pinch handler’:


- (void)pinch:(UIGestureRecognizer *)gr
{
    NSLog(@"pich detected!");
    
    if (gr.state == UIGestureRecognizerStateBegan) self.circleInProgress = [[BNRCircle alloc] init];
    
    if (gr.state == UIGestureRecognizerStateChanged )
    {
        NSLog(@"pinch changed!");
        self.circleInProgress.point1 = [gr locationOfTouch:0 inView:self];
        
        // following 'if' is needed in order to prevent app from crash when user lift one of the 2 pinch fingers
        if ([gr numberOfTouches] == 2)
            self.circleInProgress.point2 = [gr locationOfTouch:1 inView:self];
    }
    
    if (gr.state == UIGestureRecognizerStateEnded)
    {
        NSLog(@"pinch ended!");
        [self.finishedCircles addObject:self.circleInProgress];
        self.circleInProgress = nil;
    }
    
    if (gr.state == UIGestureRecognizerStateCancelled)
    {
        NSLog(@"pinch aborted!");
        self.circleInProgress = nil;
    }
    
    
    
    [self setNeedsDisplay];
}

That’s it!

I also implemented a bronze, silver and gold all together version. If anyone interested, I can post the whole project.

Greetings from Italy :slight_smile:


#2

Giovannict73,
If you do see this and get a chance, please post your all Inclusive Ch12 Solution as you mention in your July 15 post. I still having a problem getting the Bronze Challenge to save & restore the file. Thanks, Lon


#3

Hi Lon, I’ll post the complete BSG solution ASAP. I’m on vacation right now, away from my iMac. Maybe I’ll have a chance next monday.

:slight_smile:


#4

Ciao Giovanni,

I had been trying to do this without using a gesture recognizer and was running into endless issues with timing of using two fingers to start the circle and crash issues of when one of two fingers lifted while a circle was in progress. Your approach and explanation of the why you were using the “if” statement in your pinch method made this all clear.

I did still see an issue with your solution of sometimes the beginning of lines getting drawn for a short distance before the pinch was recognized. These aborted short lines then stayed in the lines in progress dictionary. So I added a statement to remove all objects in that dictions when the pinch gesture was recognized as beginning, and that solved that issue.

I also added a property declaration for a BNRCircle to hold the circleInProgress which I’m sure you have but just didn’t show.

Bravo Giovanni. Di dove abita in l’Italia? Io e mia moglie andremo in Italia per una vacanza all fine di settembre. È un posto preferito per noi.

Ciao ciao,

Marty


#5

I’m glad you liked my gold solution. I’ve just come back from vacation and I’m restarting IOS learning… I’ll post soon the solution bronze-silver-gold as promised to Loan.

I’m from Sicily. I live in Catania. Did you have ever been here? Elsewhere in Sicily?

Ciao ciao dalla Sicilia. A presto


#6

Here you go… My bronze - silver and gold challenge all together solution…

BNRAppDelegate.h:

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

@interface BNRAppDelegate : UIResponder

@property (strong, nonatomic) UIWindow *window;

@end
[/code]

BNRAppDelegate.m:

[code]#import “BNRAppDelegate.h”

#import “BNRDrawViewController.h”
#import “BNRDrawView.h”

@implementation BNRAppDelegate

  • (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

    // Override point for customization after application launch.
    BNRDrawViewController *dvc = [[BNRDrawViewController alloc] init];
    self.window.rootViewController = dvc;

    BNRDrawView *dv = [[BNRDrawView alloc] initWithFrame:CGRectZero];
    dvc.view = dv;

    // Get path to documents directory
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

    if ([paths count] > 0)
    {
    // full Path to load lines array
    NSString *arrayPath = [[paths objectAtIndex:0] stringByAppendingPathComponent:@“CGlinesArrayCG.out”];

      // read array of lines from file
      NSMutableArray *array = [NSKeyedUnarchiver unarchiveObjectWithFile:arrayPath];
      if (array)
      {
          dv.finishedLines = array;
          NSLog(@"lines readed from file!");
      }
      else NSLog(@"error reading archived lines array from file!");
    
      // full Path to load circles array
      arrayPath = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"CGcirclesArrayCG.out"];
      
      // read array of cirles from file
      array = [NSKeyedUnarchiver unarchiveObjectWithFile:arrayPath];
      if (array)
      {
          dv.finishedCircles = array;
          NSLog(@"circles readed from file!");
      }
      else NSLog(@"error reading archived circles array from file!");
    

    }

    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
    }

  • (void)applicationWillResignActive:(UIApplication *)application
    {
    // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
    // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
    }

  • (void)applicationDidEnterBackground:(UIApplication *)application
    {
    // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
    // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
    NSLog(@“app backgrounded or killed!”);

    // Get path to documents directory
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

    if ([paths count] > 0)
    {
    // Path to save lines array
    NSString *arrayPath = [[paths objectAtIndex:0] stringByAppendingPathComponent:@“CGlinesArrayCG.out”];

      // Write array of lines to file
      BNRDrawView *dv = (BNRDrawView *)self.window.rootViewController.view;
      
      if ([NSKeyedArchiver archiveRootObject:dv.finishedLines toFile:arrayPath]) NSLog(@"write succesfully!");
      else NSLog(@"error writing archived lines array to file!");
    
    
      // Path to save circles array
      arrayPath = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"CGcirclesArrayCG.out"];
      
      // Write array
      if ([NSKeyedArchiver archiveRootObject:dv.finishedCircles toFile:arrayPath]) NSLog(@"write succesfully!");
      else NSLog(@"error writing archived circles array to file!");
    

    }

}

  • (void)applicationWillEnterForeground:(UIApplication *)application
    {
    // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
    }

  • (void)applicationDidBecomeActive:(UIApplication *)application
    {
    // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
    }

  • (void)applicationWillTerminate:(UIApplication *)application
    {
    // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
    }

@end
[/code]

BNRDrawViewController.h:

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

@interface BNRDrawViewController : UIViewController

@end
[/code]

BNRDrawViewController.m:

[code]#import “BNRDrawViewController.h”

@implementation BNRDrawViewController

@end[/code]

BNRDrawView.h:

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

@interface BNRDrawView : UIView

@property (nonatomic, strong) NSMutableArray *finishedLines;
@property (nonatomic, strong) NSMutableArray *finishedCircles;

@end
[/code]

BNRDrawView.m:

[code]#import “BNRDrawView.h”

#import “BNRLine.h”
#import “BNRCircle.h”

@interface BNRDrawView ()

@property (nonatomic, strong) NSMutableDictionary *linesInProgress;
@property (nonatomic, strong) BNRCircle *circleInProgress;

@end

@implementation BNRDrawView

  • (void)pinch:(UIGestureRecognizer *)gr
    {
    NSLog(@“pich detected!”);

    if (gr.state == UIGestureRecognizerStateBegan) self.circleInProgress = [[BNRCircle alloc] init];

    if (gr.state == UIGestureRecognizerStateChanged )
    {
    NSLog(@“pinch started or changed!”);
    self.circleInProgress.point1 = [gr locationOfTouch:0 inView:self];

      // following 'if' is needed in order to prevent app from crash when user lifts one of the 2 pinch fingers
      if ([gr numberOfTouches] == 2)
          self.circleInProgress.point2 = [gr locationOfTouch:1 inView:self];
    

    }

    if (gr.state == UIGestureRecognizerStateEnded)
    {
    NSLog(@“pinch ended!”);
    [self.finishedCircles addObject:self.circleInProgress];
    self.circleInProgress = nil;
    }

    if (gr.state == UIGestureRecognizerStateCancelled)
    {
    NSLog(@“pinch aborted!”);
    self.circleInProgress = nil;
    }

    [self setNeedsDisplay];
    }

  • (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
    {
    // Let’s put in a log statement to see the order of events
    NSLog(@"%@", NSStringFromSelector(_cmd));

    for (UITouch *t in touches)
    {
    NSValue *key = [NSValue valueWithNonretainedObject:t];
    [self.linesInProgress removeObjectForKey:key];
    }

    [self setNeedsDisplay];
    }

  • (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
    {
    // Let’s put in a log statement to see the order of events
    NSLog(@"%@", NSStringFromSelector(_cmd));

    for (UITouch *t in touches)
    {
    NSValue *key = [NSValue valueWithNonretainedObject:t];
    BNRLine *line = self.linesInProgress[key];
    [self.finishedLines addObject:line];
    [self.linesInProgress removeObjectForKey:key];
    }

    [self setNeedsDisplay];
    }

  • (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
    {
    // Let’s put in a log statement to see the order of events
    NSLog(@"%@", NSStringFromSelector(_cmd));

    for (UITouch *t in touches)
    {
    NSValue *key = [NSValue valueWithNonretainedObject:t];
    BNRLine *line = self.linesInProgress[key];
    line.end = [t locationInView:self];
    }

    [self setNeedsDisplay];
    }

  • (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    {

    // Let’s put in a log statement to see the order of events
    NSLog(@"%@", NSStringFromSelector(_cmd));

    for (UITouch *t in touches)
    {
    CGPoint location = [t locationInView:self];
    BNRLine *line = [[BNRLine alloc] init];
    line.begin = location;
    line.end = location;
    NSValue *key = [NSValue valueWithNonretainedObject:t];
    self.linesInProgress[key] = line;
    }

    [self setNeedsDisplay];
    }

  • (void)strokeLine:(BNRLine *)line
    {
    UIBezierPath *bp = [UIBezierPath bezierPath];
    bp.lineWidth = 10;
    bp.lineCapStyle = kCGLineCapRound;

    [bp moveToPoint:line.begin];
    [bp addLineToPoint:line.end];
    [bp stroke];
    }

  • (void)strokeCircle:(BNRCircle *)circle
    {
    if (circle)
    {
    UIBezierPath *bp = [UIBezierPath bezierPath];
    bp.lineWidth = 10;
    bp.lineCapStyle = kCGLineCapRound;

      CGPoint center;
    
      center.x = circle.boundRect.origin.x + circle.boundRect.size.width / 2.0;
      center.y = circle.boundRect.origin.y + circle.boundRect.size.height / 2.0;
    
      // The largest circle will circumscribe the circle boundRect
      float radius = circle.boundRect.size.height >= circle.boundRect.size.width ?
                                      circle.boundRect.size.width : circle.boundRect.size.height;
      radius /= 2;
    
      [bp addArcWithCenter:center radius:radius startAngle:0 endAngle:M_PI * 2.0 clockwise:YES];
    
      [bp stroke];
    

    }
    }

  • (void)drawRect:(CGRect)rect
    {
    // Draw finished lines colored according angle from orizzontal line
    for (BNRLine *line in self.finishedLines) {

      float xdiff = line.end.x - line.begin.x;
      float ydiff = line.end.y - line.begin.y;
      float angle = atan2f(ydiff, xdiff) / M_PI;
    
      [[UIColor colorWithRed:angle green:1 - angle blue:0.5 - angle alpha:1.0] set];
    
      [self strokeLine:line];
    

    }

    // Draw finished circles black (by now)
    [[UIColor blackColor] set];
    for (BNRCircle *circle in self.finishedCircles)
    {
    [self strokeCircle:circle];
    }

    // Draw lines in progress in red
    [[UIColor redColor] set];
    for (NSValue *key in self.linesInProgress)
    {
    [self strokeLine:self.linesInProgress[key]];
    }

    // Draw circle in progress in green
    [[UIColor greenColor] set];
    [self strokeCircle:self.circleInProgress];
    }

  • (instancetype)initWithFrame:(CGRect)frame
    {
    self = [super initWithFrame:frame];

    if (self) {
    self.linesInProgress = [[NSMutableDictionary alloc] init];
    self.finishedLines = [[NSMutableArray alloc] init];
    self.finishedCircles = [[NSMutableArray alloc] init];
    self.backgroundColor = [UIColor grayColor];

      self.multipleTouchEnabled = YES;
      
      UIPinchGestureRecognizer *pinchGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self
                                                 action:@selector(pinch:)];
      [self addGestureRecognizer:pinchGesture];
    

    }
    return self;
    }

@end

[/code]

BNRLine.h:

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

@interface BNRLine : NSObject

@property (nonatomic) CGPoint begin;
@property (nonatomic) CGPoint end;

@end
[/code]

BNRLine.m:

[code]#import “BNRLine.h”

@implementation BNRLine

  • (void)encodeWithCoder:(NSCoder *)aCoder
    {
    [aCoder encodeCGPoint:self.begin forKey:@“BNRLineBegin”];
    [aCoder encodeCGPoint:self.end forKey:@“BNRLineEnd”];
    }

  • (instancetype)initWithCoder:(NSCoder *)aDecoder
    {
    self = [super init];
    if (self) {
    self.begin = [aDecoder decodeCGPointForKey:@“BNRLineBegin”];
    self.end = [aDecoder decodeCGPointForKey:@“BNRLineEnd”];
    }
    return self;
    }

@end
[/code]

BNRCircle.h:

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

@interface BNRCircle : NSObject

@property (nonatomic) CGPoint point1;
@property (nonatomic) CGPoint point2;

@property (nonatomic, readonly) CGRect boundRect;

@end
[/code]

BNRCircle.m:

[code]#import “BNRCircle.h”

@implementation BNRCircle

  • (void)encodeWithCoder:(NSCoder *)aCoder
    {
    [aCoder encodeCGPoint:self.point1 forKey:@“point1”];
    [aCoder encodeCGPoint:self.point2 forKey:@“point2”];
    }

  • (instancetype)initWithCoder:(NSCoder *)aDecoder
    {
    self = [super init];
    if (self) {
    self.point1 = [aDecoder decodeCGPointForKey:@“point1”];
    self.point2 = [aDecoder decodeCGPointForKey:@“point2”];
    }
    return self;
    }

// let’s implement getter method for boundRect in order to do the magic…

  • (CGRect)boundRect
    {
    // calculate new circle boundRect
    float width;
    float height;
    width = _point1.x > _point2.x ? _point1.x - _point2.x : _point2.x - _point1.x;
    height = _point1.y > _point2.y ? _point1.y - _point2.y : _point2.y - _point1.y;

    float x1 = 0.0;
    float y1 = 0.0;

    if (_point1.y <= _point2.y)
    {
    y1 = _point1.y;
    if (_point1.x <= _point2.x) x1 = _point1.x;
    else x1 = _point1.x - width;
    }

    if (_point1.y > _point2.y)
    {
    y1 = _point2.y;
    if (_point1.x > _point2.x) x1 = _point2.x;
    else x1 = _point2.x - width;
    }

    return CGRectMake(x1, y1, width, height);

}

@end
[/code]

Hope this helps…

:unamused: