0

I am working on a photo editing application and need the user to be able to draw a path to crop out the photo. I have a class that works with a UIView for drawing smooth UIBezierPath lines. However I really need to apply this to a UIImageView so that the drawing can be done over the image. If I change the class to subclass UIImageView instead it no longer works. Any ideas on what I can do to fix this? Or better options for achieving the same goal? Below is my implementation:

    #import "DrawView.h"

@implementation DrawView
{
    UIBezierPath *path;
}

- (id)initWithCoder:(NSCoder *)aDecoder // (1)
{
    if (self = [super initWithCoder:aDecoder])
    {
        [self setMultipleTouchEnabled:NO]; // (2)
        [self setBackgroundColor:[UIColor whiteColor]];
        path = [UIBezierPath bezierPath];
        [path setLineWidth:2.0];
    }
    return self;
}

- (void)drawRect:(CGRect)rect // (5)
{
    [[UIColor blackColor] setStroke];
    [path stroke];
}


- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [touches anyObject];
    CGPoint p = [touch locationInView:self];
    [path moveToPoint:p];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [touches anyObject];
    CGPoint p = [touch locationInView:self];
    [path addLineToPoint:p]; // (4)
    [self setNeedsDisplay];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self touchesMoved:touches withEvent:event];
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self touchesEnded:touches withEvent:event];
}

@end

If I place a break point on touchesBegan or touchesMoved it does fire when expected.

Tanner Ewing
  • 337
  • 4
  • 18

1 Answers1

2

First problem - userInteractionEnabled property should be set to YES for receiving touch events.

Second one - drawRect: method is not called for UIImageView subclasses:

The UIImageView class is optimized to draw its images to the display. UIImageView will not call drawRect: a subclass. If your subclass needs custom drawing code, it is recommended you use UIView as the base class.

So, DrawView should be UIView subclass, that will draw UIImage in drawRect:, before bezier path drawing. Something like that (only changed parts of code):

// DrawView.h
@interface DrawView : UIView
@property (nonatomic, strong) UIImage* image;
@end

// DrawView.m    
@implementation DrawView

- (void)drawRect:(CGRect)rect
{
    [image drawInRect:self.bounds];
    [[UIColor blackColor] setStroke];
    [path stroke];
}

@end
Petro Korienev
  • 4,007
  • 6
  • 34
  • 43
Borys Verebskyi
  • 4,160
  • 6
  • 28
  • 42
  • Okay I have tried it with and without userInteractionEnabled, like I said even without it I still get the touchesBegan to fire. However your other solution may be what I need. Can you explain your solution in a little more depth? Thank you for your reply! – Tanner Ewing Sep 12 '15 at 23:13
  • This does work, however it runs very slow due to the calls to [self setNeedsDisplay]; redrawing the image in the background. Is there away to refresh the drawing without refreshing the image? – Tanner Ewing Sep 14 '15 at 17:18
  • As an alternative solution, you may try to place DrawView over UIImageView in Interface Builder. In this case you should not draw image in DrawView. Then, UIImageView will draw image, and DrawView will draw only bezier path. Also, you can use -[self setNeedsDisplayInRect:path.bounds] to minimize area to redraw. – Borys Verebskyi Sep 15 '15 at 11:52
  • @BorisVerebsky does that not cause the DrawView to cover the UIImageView? How do you see the image and also the line on top from DrawView? – Lightsout Jul 25 '17 at 02:00
  • @bakalolo you need to make sure that DrawView is not opaque. Then you'll be able to see both image and path – Borys Verebskyi Jul 25 '17 at 09:13