Search code examples
iphonecocoacocoa-touchiphone-sdk-3.0

How to Prevent or Compensate For Scaling Transforms of Core Graphic Paths?


I am attempting to create a "marching ants" (an animated dashed line) selection indicator around UIView that contains a UIImageView subview. Drawing the dashed line around the perimeter of the UIView is trivial using core graphics:

-(void) drawSelectedMarquee{
     CGContextRef c=UIGraphicsGetCurrentContext();
 CGContextSetStrokeColorWithColor(c, [[UIColor whiteColor] CGColor] );
 CGRect f=self.bounds;
 CGContextSetLineWidth(c, 2.0);
 CGFloat dashes[]={1,2};
 CGContextSetLineDash(c, 0, dashes, 2);
 CGContextBeginPath(c);
 CGContextAddRect(c, f);
 CGContextStrokePath(c);
}

However, I need to scale (and rotate) the view using affine transforms so that the UIImageView inside can be scaled and rotated:

-(void) scaleByX:(CGFloat) xScale andY:(CGFloat) yScale{
 [UIView beginAnimations:nil context:nil];
 [UIView setAnimationDuration:0.1];
 [UIView setAnimationBeginsFromCurrentState:YES];
 self.transform=CGAffineTransformScale(self.transform, xScale, yScale);
 [UIView commitAnimations];
}

However, because core graphics draws in relative "user space points" which apparently are scaled by the transform as well, scaling the view causes the dashed line to scale in proportion. So, as the view scale smaller, the line grows thinner and the dashes closer together and has the view scales larger, the line grows thicker and the dashes father apart.

As an interface element, the selection marquee should remain of fixed width with fixed sized dashes. I've tried just rotating and scaling the UIImageView subview of the main view but that causes the UIImageView to scale out of the superview.

I can think of some brute force methods to properly draw the marquee. I've could track the absolute size of the view and then recalculate the end user space units every time it redraws or I create a new view with selection marquee every time the UIImageView. But those solutions are inelegant and bug prone.

I think I've missed something in documentation about how to draw and transform graphics in UIViews.

[Update: I've decided that using marching ants isn't useful on the iPhone's small screen so I've adopted another method. However I'm still interested in the solution to this particular problem.]


Solution

  • I don't think you've missed anything in the documentation, but rather you may have missed the ideas of what scaling provides. If you scale the contents of a view or layer, it will resize everything in the contents. This is actually desirable. It makes it easy to scale without having to manage all of the subview scaling yourself. What it sounds like you need is to add an overlay layer in front of your view.

    I'm not sure how you are causing your view to resize, but when your view loads, add an overlay Core Animation layer that you can resize as your view scales. Notice I used the word resize rather than scale when referring to the overlay layer.

    To make this work correctly you have to make sure that the overlay layer is a sibling to the view you are scaling rather than a child otherwise it will get scaled as well.

    Interestingly, you can use a CAShapeLayer and take advantage of its lineDashPattern property which takes an array of floats to specify a dash pattern that can outline the CGRect you specify. This pattern can then be animated using the lineDashPhase property and a CABasicAnimation giving it the marching ants effect. I can elaborate on this if it interests you.

    UPDATE: I went ahead and wrote a blog post on getting the marching ants effect working using Core Animation. Sorry, I didn't have time to try to address your problem, but the blog post might help point you in the right direction.