Sign Up

Sign Up to our social questions and Answers Engine to ask questions, answer people’s questions, and connect with other people.

Have an account? Sign In

Have an account? Sign In Now

Sign In

Login to our social questions & Answers Engine to ask questions answer people’s questions & connect with other people.

Sign Up Here

Forgot Password?

Don't have account, Sign Up Here

Forgot Password

Lost your password? Please enter your email address. You will receive a link and will create a new password via email.

Have an account? Sign In Now

You must login to ask a question.

Forgot Password?

Need An Account, Sign Up Here

Please briefly explain why you feel this question should be reported.

Please briefly explain why you feel this answer should be reported.

Please briefly explain why you feel this user should be reported.

Sign InSign Up

The Archive Base

The Archive Base Logo The Archive Base Logo

The Archive Base Navigation

  • SEARCH
  • Home
  • About Us
  • Blog
  • Contact Us
Search
Ask A Question

Mobile menu

Close
Ask a Question
  • Home
  • Add group
  • Groups page
  • Feed
  • User Profile
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Buy Points
  • Users
  • Help
  • Buy Theme
  • SEARCH
Home/ Questions/Q 8891827
In Process

The Archive Base Latest Questions

Editorial Team
  • 0
Editorial Team
Asked: June 14, 20262026-06-14T22:50:22+00:00 2026-06-14T22:50:22+00:00

I’ve created a circular animation using CAShapeLayer and masking. Here is my code: –

  • 0

I’ve created a circular animation using CAShapeLayer and masking.
Here is my code:

- (void) maskAnimation{


    animationCompletionBlock theBlock;
    imageView.hidden = FALSE;//Show the image view

    CAShapeLayer *maskLayer = [CAShapeLayer layer];

    CGFloat maskHeight = imageView.layer.bounds.size.height;
    CGFloat maskWidth = imageView.layer.bounds.size.width;


    CGPoint centerPoint;
    centerPoint = CGPointMake( maskWidth/2, maskHeight/2);

    //Make the radius of our arc large enough to reach into the corners of the image view.
    CGFloat radius = sqrtf(maskWidth * maskWidth + maskHeight * maskHeight)/2;

    //Don't fill the path, but stroke it in black.
    maskLayer.fillColor = [[UIColor clearColor] CGColor];
    maskLayer.strokeColor = [[UIColor blackColor] CGColor];

    maskLayer.lineWidth = 60;

    CGMutablePathRef arcPath = CGPathCreateMutable();

    //Move to the starting point of the arc so there is no initial line connecting to the arc
    CGPathMoveToPoint(arcPath, nil, centerPoint.x, centerPoint.y-radius/2);

    //Create an arc at 1/2 our circle radius, with a line thickess of the full circle radius
    CGPathAddArc(arcPath,
                 nil,
                 centerPoint.x,
                 centerPoint.y,
                 radius/2,
                 3*M_PI/2,
                 -M_PI/2,
                 NO);



    maskLayer.path = arcPath;//[aPath CGPath];//arcPath;

    //Start with an empty mask path (draw 0% of the arc)
    maskLayer.strokeEnd = 0.0;


    CFRelease(arcPath);

    //Install the mask layer into out image view's layer.
    imageView.layer.mask = maskLayer;

    //Set our mask layer's frame to the parent layer's bounds.
    imageView.layer.mask.frame = imageView.layer.bounds;

    //Create an animation that increases the stroke length to 1, then reverses it back to zero.
    CABasicAnimation *swipe = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
    swipe.duration = 5;
    swipe.delegate = self;
    [swipe setValue: theBlock forKey: kAnimationCompletionBlock];
    swipe.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
    swipe.fillMode = kCAFillModeForwards;
    swipe.removedOnCompletion = NO;
    swipe.autoreverses = YES;
    swipe.toValue = [NSNumber numberWithFloat: 1.0];

    [maskLayer addAnimation: swipe forKey: @"strokeEnd"];


}

This is my background image:
enter image description here

This what is looks like when I run the animation:
enter image description here

But what I want, the arrow head is missing how to add this?
enter image description here

  • 1 1 Answer
  • 0 Views
  • 0 Followers
  • 0
Share
  • Facebook
  • Report

Leave an answer
Cancel reply

You must login to add an answer.

Forgot Password?

Need An Account, Sign Up Here

1 Answer

  • Voted
  • Oldest
  • Recent
  • Random
  1. Editorial Team
    Editorial Team
    2026-06-14T22:50:23+00:00Added an answer on June 14, 2026 at 10:50 pm

    Since my other answer (animating two levels of masks) has some graphics glitches, I decided to try redrawing the path on every frame of animation. So first let’s write a CALayer subclass that’s like CAShapeLayer, but just draws an arrow. I originally tried making it a subclass of CAShapeLayer, but I could not get Core Animation to properly animate it.

    Anyway, here’s the interface we’re going to implement:

    @interface ArrowLayer : CALayer
    
    @property (nonatomic) CGFloat thickness;
    @property (nonatomic) CGFloat startRadians;
    @property (nonatomic) CGFloat lengthRadians;
    @property (nonatomic) CGFloat headLengthRadians;
    
    @property (nonatomic, strong) UIColor *fillColor;
    @property (nonatomic, strong) UIColor *strokeColor;
    @property (nonatomic) CGFloat lineWidth;
    @property (nonatomic) CGLineJoin lineJoin;
    
    @end
    

    The startRadians property is the position (in radians) of the end of the tail. The lengthRadians is the length (in radians) from the end of the tail to the tip of the arrowhead. The headLengthRadians is the length (in radians) of the arrowhead.

    We also reproduce some of the properties of CAShapeLayer. We don’t need the lineCap property because we always draw a closed path.

    So, how do we implement this crazy thing? As it happens, CALayer will take care of storing any old property you want to define on a subclass. So first, we just tell the compiler not to worry about synthesizing the properties:

    @implementation ArrowLayer
    
    @dynamic thickness;
    @dynamic startRadians;
    @dynamic lengthRadians;
    @dynamic headLengthRadians;
    @dynamic fillColor;
    @dynamic strokeColor;
    @dynamic lineWidth;
    @dynamic lineJoin;
    

    But we need to tell Core Animation that we need to redraw the layer if any of those properties change. To do that, we need a list of the property names. We’ll use the Objective-C runtime to get a list so we don’t have to retype the property names. We need to #import <objc/runtime.h> at the top of the file, and then we can get the list like this:

    + (NSSet *)customPropertyKeys {
        static NSMutableSet *set;
        static dispatch_once_t once;
        dispatch_once(&once, ^{
            unsigned int count;
            objc_property_t *properties = class_copyPropertyList(self, &count);
            set = [[NSMutableSet alloc] initWithCapacity:count];
            for (int i = 0; i < count; ++i) {
                [set addObject:@(property_getName(properties[i]))];
            }
            free(properties);
        });
        return set;
    }
    

    Now we can write the method that Core Animation uses to find out which properties need to cause a redraw:

    + (BOOL)needsDisplayForKey:(NSString *)key {
        return [[self customPropertyKeys] containsObject:key] || [super needsDisplayForKey:key];
    }
    

    It also turns out that Core Animation will make a copy of our layer in every frame of animation. We need to make sure we copy over all of these properties when Core Animation makes the copy:

    - (id)initWithLayer:(id)layer {
        if (self = [super initWithLayer:layer]) {
            for (NSString *key in [self.class customPropertyKeys]) {
                [self setValue:[layer valueForKey:key] forKey:key];
            }
        }
        return self;
    }
    

    We also need to tell Core Animation that we need to redraw if the layer’s bounds change:

    - (BOOL)needsDisplayOnBoundsChange {
        return YES;
    }
    

    Finally, we can get to the nitty-gritty of drawing the arrow. First, we’ll change the graphic context’s origin to be at the center of the layer’s bounds. Then we’ll construct the path outlining the arrow (which is now centered at the origin). Finally, we’ll fill and/or stroke the path as appropriate.

    - (void)drawInContext:(CGContextRef)gc {
        [self moveOriginToCenterInContext:gc];
        [self addArrowToPathInContext:gc];
        [self drawPathOfContext:gc];
    }
    

    Moving the origin to the center of our bounds is trivial:

    - (void)moveOriginToCenterInContext:(CGContextRef)gc {
        CGRect bounds = self.bounds;
        CGContextTranslateCTM(gc, CGRectGetMidX(bounds), CGRectGetMidY(bounds));
    }
    

    Constructing the arrow path is not trivial. First, we need to get the radial position at which the tail starts, the radial position at which the tail ends and the arrowhead starts, and the radial position of the tip of the arrowhead. We’ll use a helper method to compute those three radial positions:

    - (void)addArrowToPathInContext:(CGContextRef)gc {
        CGFloat startRadians;
        CGFloat headRadians;
        CGFloat tipRadians;
        [self getStartRadians:&startRadians headRadians:&headRadians tipRadians:&tipRadians];
    

    Then we need to figure out the radius of the inside and outside arcs of the arrow, and the radius of the tip:

        CGFloat thickness = self.thickness;
    
        CGFloat outerRadius = self.bounds.size.width / 2;
        CGFloat tipRadius = outerRadius - thickness / 2;
        CGFloat innerRadius = outerRadius - thickness;
    

    We also need to know whether we’re drawing the outer arc in a clockwise or counterclockwise direction:

        BOOL outerArcIsClockwise = tipRadians > startRadians;
    

    The inner arc will be drawn in the opposite direction.

    Finally, we can construct the path. We move to the tip of the arrowhead, then add the two arcs. The CGPathAddArc call automatically adds a straight line from the path’s current point to the starting point of the arc, so we don’t need to add any straight lines ourselves:

        CGContextMoveToPoint(gc, tipRadius * cosf(tipRadians), tipRadius * sinf(tipRadians));
        CGContextAddArc(gc, 0, 0, outerRadius, headRadians, startRadians, outerArcIsClockwise);
        CGContextAddArc(gc, 0, 0, innerRadius, startRadians, headRadians, !outerArcIsClockwise);
        CGContextClosePath(gc);
    }
    

    Now let’s figure out how to compute those three radial positions. This would be trivial, except we want to be graceful when the head length is larger than the overall length, by clipping the head length to the overall length. We also want to let the overall length be negative to draw the arrow in the opposite direction. We’ll start by picking up the start position, the overall length, and the head length. We’ll use a helper that clips the head length to be no larger than the overall length:

    - (void)getStartRadians:(CGFloat *)startRadiansOut headRadians:(CGFloat *)headRadiansOut tipRadians:(CGFloat *)tipRadiansOut {
        *startRadiansOut = self.startRadians;
        CGFloat lengthRadians = self.lengthRadians;
        CGFloat headLengthRadians = [self clippedHeadLengthRadians];
    

    Next we compute the radial position where the tail meets the arrowhead. We do so carefully, so that if we clipped the head length, we compute exactly the start position. This is important so that when we call CGPathAddArc with the two positions, it doesn’t add an unexpected arc due to floating-point rounding.

        // Compute headRadians carefully so it is exactly equal to startRadians if the head length was clipped.
        *headRadiansOut = *startRadiansOut + (lengthRadians - headLengthRadians);
    

    Finally we compute the radial position of the tip of the arrowhead:

        *tipRadiansOut = *startRadiansOut + lengthRadians;
    }
    

    We need to write the helper that clips the head length. It also needs to ensure that the head length has the same sign as the overall length, so the computations above work correctly:

    - (CGFloat)clippedHeadLengthRadians {
        CGFloat lengthRadians = self.lengthRadians;
        CGFloat headLengthRadians = copysignf(self.headLengthRadians, lengthRadians);
    
        if (fabsf(headLengthRadians) > fabsf(lengthRadians)) {
            headLengthRadians = lengthRadians;
        }
        return headLengthRadians;
    }
    

    To draw the path in the graphics context, we need to set the filling and stroking parameters of the context based on our properties, and then call CGContextDrawPath:

    - (void)drawPathOfContext:(CGContextRef)gc {
        CGPathDrawingMode mode = 0;
        [self setFillPropertiesOfContext:gc andUpdateMode:&mode];
        [self setStrokePropertiesOfContext:gc andUpdateMode:&mode];
    
        CGContextDrawPath(gc, mode);
    }
    

    We fill the path if we were given a fill color:

    - (void)setFillPropertiesOfContext:(CGContextRef)gc andUpdateMode:(CGPathDrawingMode *)modeInOut {
        UIColor *fillColor = self.fillColor;
        if (fillColor) {
            *modeInOut |= kCGPathFill;
            CGContextSetFillColorWithColor(gc, fillColor.CGColor);
        }
    }
    

    We stroke the path if we were given a stroke color and a line width:

    - (void)setStrokePropertiesOfContext:(CGContextRef)gc andUpdateMode:(CGPathDrawingMode *)modeInOut {
        UIColor *strokeColor = self.strokeColor;
        CGFloat lineWidth = self.lineWidth;
        if (strokeColor && lineWidth > 0) {
            *modeInOut |= kCGPathStroke;
            CGContextSetStrokeColorWithColor(gc, strokeColor.CGColor);
            CGContextSetLineWidth(gc, lineWidth);
            CGContextSetLineJoin(gc, self.lineJoin);
        }
    }
    

    The end!

    @end
    

    So now we can go back to the view controller and use an ArrowLayer as the image view’s mask:

    - (void)setUpMask {
        arrowLayer = [ArrowLayer layer];
        arrowLayer.frame = imageView.bounds;
        arrowLayer.thickness = 60;
        arrowLayer.startRadians = -M_PI_2;
        arrowLayer.lengthRadians = 0;
        arrowLayer.headLengthRadians = M_PI_2 / 8;
        arrowLayer.fillColor = [UIColor whiteColor];
        imageView.layer.mask = arrowLayer;
    }
    

    And we can just animate the lengthRadians property from 0 to 2 π:

    - (IBAction)goButtonWasTapped:(UIButton *)goButton {
        goButton.hidden = YES;
        [CATransaction begin]; {
            [CATransaction setAnimationDuration:2];
            [CATransaction setCompletionBlock:^{
                goButton.hidden = NO;
            }];
    
            CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"lengthRadians"];
            animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
            animation.autoreverses = YES;
            animation.fromValue = @0.0f;
            animation.toValue = @((CGFloat)(2.0f * M_PI));
            [arrowLayer addAnimation:animation forKey:animation.keyPath];
        } [CATransaction commit];
    }
    

    and we get a glitch-free animation:

    arrow animation with no glitches

    I profiled this on my iPhone 4S running iOS 6.0.1 using the Core Animation instrument. It seems to get 40-50 frames per second. Your mileage may vary. I tried turning on the drawsAsynchronously property (new in iOS 6) but it didn’t make a difference.

    I’ve uploaded the code in this answer as a gist for easy copying.

    • 0
    • Reply
    • Share
      Share
      • Share on Facebook
      • Share on Twitter
      • Share on LinkedIn
      • Share on WhatsApp
      • Report

Sidebar

Related Questions

I have this code: - (void)parser:(NSXMLParser *)parser foundCDATA:(NSData *)CDATABlock { NSString *someString = [[NSString
I am reading a book about Javascript and jQuery and using one of the
I have a string like this: La Torre Eiffel paragonata all&#8217;Everest What PHP function
link Im having trouble converting the html entites into html characters, (&# 8217;) i
That's pretty much it. I'm using Nokogiri to scrape a web page what has
I have this code to decode numeric html entities to the UTF8 equivalent character.
I'm using v2.0 of ClassTextile.php, with the following call: $testimonial_text = $textile->TextileRestricted($_POST['testimonial']); ... and
I'm parsing an RSS feed that has an &#8217; in it. SimpleXML turns this
We're building an app, our first using Rails 3, and we're having to build
We are using XSLT to translate a RIXML file to XML. Our RIXML contains

Explore

  • Home
  • Add group
  • Groups page
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Users
  • Help
  • SEARCH

Footer

© 2021 The Archive Base. All Rights Reserved
With Love by The Archive Base

Insert/edit link

Enter the destination URL

Or link to existing content

    No search term specified. Showing recent items. Search or use up and down arrow keys to select an item.