سؤال

I am relatively new to iOS programming and I am trying to add a disappearing highlighter to my drawing app. The goal is to allow the user to select the highlighter pen and circle something on the screen. After about 15 seconds, I would like the circle to fade and disappear, leaving behind the original drawing. I got some starter code from a drawing app tutorial. It works as follows --

The user is allowed to draw a line by touching the screen. This is implemented by constructing a UIBezierPath in touches began/ touches moved/ touches ended. Each time the path is completed, it is drawn to an image and the image is saved. As new lines are drawn they are added to the background image. Undo/redo stacks are kept separately to allow the user to correct mistakes.

I have tried to implement the fading path in a separate layer and use a CABasicAnimation to fade the highlighter by changing it's opacity and then removing the layer when the opacity hits 0. Right now the highlighter is drawing on the screen, but it immediately disappears once the user lifts his/her finger. Any ideas how I can get this to fade correctly and keep the undo/redo stack for the underlying image?

- (void)drawRect:(CGRect)rect
{
    if(incrementalImage){
        [incrementalImage drawInRect:rect];
    }
    [self.strokeColor setStroke];
    [path setLineWidth:[self.strokeSize floatValue]];
    [path stroke];
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    ctr = 0;
    UITouch *touch = [touches anyObject];
    pts[0] = [touch locationInView:self];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [touches anyObject];
    CGPoint p = [touch locationInView:self];
    ctr++;
    pts[ctr] = p;
    if (ctr == 4)
    {
        pts[3] = CGPointMake((pts[2].x + pts[4].x)/2.0, (pts[2].y + pts[4].y)/2.0); // move the endpoint to the middle of the line joining the second control point of the first Bezier segment and the first control point of the second Bezier segment
        [path moveToPoint:pts[0]];
        [path addCurveToPoint:pts[3] controlPoint1:pts[1] controlPoint2:pts[2]]; // add a cubic Bezier from pt[0] to pt[3], with control points pt[1] and pt[2]
        [self setNeedsDisplay];
        // replace points and get ready to handle the next segment
        pts[0] = pts[3];
        pts[1] = pts[4];
        ctr = 1;
    }
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self drawBitmap];
    [self setNeedsDisplay];
    [path removeAllPoints];
    ctr = 0;
}

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

- (void)drawBitmap
{
    CABasicAnimation *fadeAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];

    UIGraphicsBeginImageContextWithOptions(self.bounds.size, YES, 0.0);

    if (!incrementalImage) // first time this is called it shout paint background white
    {
        UIBezierPath *rectpath = [UIBezierPath bezierPathWithRect:self.bounds];
        [[UIColor whiteColor] setFill];
        [rectpath fill];
    }

    // each time through, you should paint the background with the stored background
    if(self.strokeColor)
    [incrementalImage drawAtPoint:CGPointZero];


    // time to branch based on the characteristics of the stroke color.
    // If the stroke has alpha less than 1, the pen is a highlighter and should vanish over time.

    [self.strokeColor getRed:&red green:&green blue:&blue alpha:&alpha];

    if(alpha == 1.0) {
        [imageHistoryArray addObject:UIGraphicsGetImageFromCurrentImageContext()];
        [self.strokeColor setStroke];
        [path stroke];
        incrementalImage = UIGraphicsGetImageFromCurrentImageContext();
    }  else{
        disappearingLayer = [CALayer layer];
        disappearingLayer.frame = self.bounds;
        [self.layer addSublayer:disappearingLayer];
        fadeAnimation.fromValue = [NSNumber numberWithFloat: 0.5];
        fadeAnimation.toValue = [NSNumber numberWithFloat: 0.0];
        fadeAnimation.duration = 15;
        [disappearingLayer addAnimation:fadeAnimation forKey:@"opacity"];
        disappearingLayer.opacity = 0.0;
    }
    UIGraphicsEndImageContext();
}

- (void) undoLastStroke
{
    if([imageHistoryArray count]>0)
    {
        [imageRedoArray addObject:incrementalImage];
        incrementalImage = [imageHistoryArray lastObject];
        [imageHistoryArray removeLastObject];
        [self setNeedsDisplay];
    }
}

- (void) redoStroke
{
    if([imageRedoArray count]>0)
    {
        [imageHistoryArray addObject:incrementalImage];
        incrementalImage = [imageRedoArray lastObject];
        [imageRedoArray removeLastObject];
        [self setNeedsDisplay];
    }
}

I have solved the problem using a CAShapeLayer. Here is the modified code

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self.strokeColor getRed:&red green:&green blue:&blue alpha:&alpha];
    if(alpha > 0.99){
        [self drawBitmap];
    } else{
        [self drawVanishingPath];
    }
    [self setNeedsDisplay];
    [path removeAllPoints];
    ctr = 0;
}


- (void)drawVanishingPath
{
    disappearingLayer = [[CAShapeLayer alloc] init];
    disappearingLayer.strokeColor = self.strokeColor.CGColor;
    disappearingLayer.fillColor = [UIColor clearColor].CGColor;
    disappearingLayer.lineWidth = [self.strokeSize floatValue];
    disappearingLayer.path = path.CGPath;
    [self.layer addSublayer:disappearingLayer];
    [fadeAnimation setValue:disappearingLayer forKey:@"parentLayer"];
    [disappearingLayer addAnimation:fadeAnimation forKey:@"opacity"];
}

It took a while to get rid of an annoying shadow in the center of the stroke. I determined that the path was being filled. Since the color has an alpha less than one, the fill and the stroke were acting together to create a darker area in the center of the path. Setting the fill color to clear solved this problem.

هل كانت مفيدة؟

المحلول 2

The reason why your animation was not happening, but instead the layer was vanishing instantly, was these lines:

[disappearingLayer addAnimation:fadeAnimation forKey:@"opacity"];
disappearingLayer.opacity = 0.0; // this line is the problem

The second line is canceling the animation and setting the opacity to zero instantly. Cut it (or move it to before the construction of the animation), so that the animation can run.

نصائح أخرى

Here is the solution I discovered after quite a bit of hunting around and some help. This is the same thing I entered above. I didn't realize I was allowed to answer my own question. Hopefully this will show up as answered now. I have included Matt's fix in this code also.

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self.strokeColor getRed:&red green:&green blue:&blue alpha:&alpha];
    if(alpha > 0.99){
        [self drawBitmap];
    } else{
        [self drawVanishingPath];
    }
    [self setNeedsDisplay];
    [path removeAllPoints];
    ctr = 0;
}


- (void)drawVanishingPath
{
    disappearingLayer = [[CAShapeLayer alloc] init];
    disappearingLayer.strokeColor = self.strokeColor.CGColor;
    disappearingLayer.fillColor = [UIColor clearColor].CGColor;
    disappearingLayer.lineWidth = [self.strokeSize floatValue];
    disappearingLayer.path = path.CGPath;
    [self.layer addSublayer:disappearingLayer];
    [fadeAnimation setValue:disappearingLayer forKey:@"parentLayer"];
    [disappearingLayer addAnimation:fadeAnimation forKey:@"opacity"];
}

It took a while to get rid of an annoying shadow in the center of the stroke. I determined that the path was being filled. Since the color has an alpha less than one, the fill and the stroke were acting together to create a darker area in the center of the path. Setting the fill color to clear solved this problem.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top