CGPathCreateCopyByStrokingPath equivalent on iOS4? - ios

I found CGPathCreateCopyByStrokingPath on iOS 5.0 quite convenient to use but it is available on iOS 5 and later.
Is there any simple way to achieve the same path copying on iOS 4?

I use this, which is compatible across IOS5 and IOS4+. It works 100% if you use the same fill + stroke color. Apple's docs are a little shady about this - they say "it works if you fill it", they don't say "it goes a bit wrong if you stroke it" - but it seems to go slightly wrong in that case. YMMV.
// pathFrameRange: you have to provide something "at least big enough to
// hold the original path"
static inline CGPathRef CGPathCreateCopyByStrokingPathAllVersionsOfIOS( CGPathRef
incomingPathRef, CGSize pathFrameRange, const CGAffineTransform* transform,
CGFloat lineWidth, CGLineCap lineCap, CGLineJoin lineJoin, CGFloat miterLimit )
{
CGPathRef result;
if( CGPathCreateCopyByStrokingPath != NULL )
{
/**
REQUIRES IOS5!!!
*/
result = CGPathCreateCopyByStrokingPath( incomingPathRef, transform,
lineWidth, lineCap, lineJoin, miterLimit);
}
else
{
CGSize sizeOfContext = pathFrameRange;
UIGraphicsBeginImageContext( sizeOfContext );
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(c, lineWidth);
CGContextSetLineCap(c, lineCap);
CGContextSetLineJoin(c, lineJoin);
CGContextSetMiterLimit(c, miterLimit);
CGContextAddPath(c, incomingPathRef);
CGContextSetLineWidth(c, lineWidth);
CGContextReplacePathWithStrokedPath(c);
result = CGContextCopyPath(c);
UIGraphicsEndImageContext();
}
}

Hmmm -- don't know if this qualifies as "simple", but check out Ed's method in this SO post.

Related

Animate multiple shapes in UIView

I have a custom class that inherit from UIView. In the draw method I draw several shapes including some circles. I want to animate the color (now stroke color) of the circles independent of each other, e.g. I would like the color of one or more the circles to "pulse" or flash (using ease-in/ease-out and not linearly).
What would be the best way to archive this?
It would be great to be able to use the built-in animation code (CABasicAnimation and the like) but I'm not sure how?
EDIT: Here's the code involved. (I am using Xamarin.iOS but my question is not specific to this).
CGColor[] circleColors;
public override void Draw (RectangleF rect)
{
base.Draw (rect);
using (CGContext g = UIGraphics.GetCurrentContext ()) {
g.SetLineWidth(4);
float size = rect.Width > rect.Height ? rect.Height : rect.Width;
float xCenter = ((rect.Width - size) / 2) + (size/2);
float yCenter = ((rect.Height - size) / 2) + (size/2);
float d = size / (rws.NumCircles*2+2);
var circleRect = new RectangleF (xCenter, yCenter, 0, 0);
for (int i = 0; i < rws.NumCircles; i++) {
circleRect.X -= d;
circleRect.Y -= d;
circleRect.Width += d*2;
circleRect.Height += d*2;
CGPath path = new CGPath ();
path.AddEllipseInRect (circleRect);
g.SetStrokeColor (circleColors [i]);
g.AddPath (path);
g.StrokePath ();
}
}
}
You need to move all your drawing code to a subclass of CALayer, and decide parameters which, once varied, will produce the desired animations. Convert these parameters to the layer's properties, and you can animate the layer's properties with CABasicAnimation (or even [UIView animateXXX]).
See this SO question for more information.
Make sure that you set the layer's rasterizationScale to [UIScreen mainScreen].scale to avoid blurs on Retina.

Access CGContext outside drawRect to recognize touch event on pie chart and identify touched piece

I am implementing a custom UIControl looking like a pie chart with n pieces:
Therefor I subclass UIControl and override its drawRect: method. In there, I am drawing n pieces using CGContextAddArc. So, I am going trough a loop depending on how many pieces I have to draw and then set startAngle and endAngle accordingly.
Now, for the interaction with the control, it's important for me to recognize from a touch event which of the pieces has been touched. My approach for that would be to use CGContextPathContainsPoint. But I need to call this outside of drawRect:, so the CGContext is not available.
func indexOfTrackForPoint(point: CGPoint) -> Int
{
// Configure parameters
let basicTrackAngle = CGFloat(360 / tracks.count)
let strokeWidth = 60.0
let radius = CGFloat((CGFloat(self.frame.size.width) - CGFloat(strokeWidth)) / 2) - CGFloat(margin)
// Initialize the context
var context = UIGraphicsGetCurrentContext()
CGContextSetLineWidth(context, CGFloat(strokeWidth))
CGContextSetStrokeColorWithColor(context, UIColor.blackColor().CGColor)
for i in 0...tracks.count-1 {
// Start and end angles of current track
var currentTrackStartAngle = CGFloat(i) * basicTrackAngle
var currentTrackEndAngle = currentTrackStartAngle + basicTrackAngle
CGContextAddArc(context, center.x, center.y, CGFloat(radius), toNormalizedRadian(currentTrackStartAngle), toNormalizedRadian(currentTrackEndAngle), 0)
if pointOnPath(point, inContext: context){
println("point found on track: \(i)")
return i
}
CGContextDrawPath(context, kCGPathStroke) // or kCGPathFillStroke to fill and stroke the circle
}
}
Does anyone have an idea how to realize this? Is it possible to get the CGContext outside of drawRect:?
You can store an array of UIBezierPath objects in your drawRect implementation. You can then easily hit test them later using the UIBezierPath instance method containsPoint:
Figured it out myself. The issue can be solved by creating my own context using UIGraphicsBeginImageContext(CGSizeMake(250.0, 250.0)) having the same size as the initial context of the view. The whole method looks like this now:
func indexOfTrackForPoint(point: CGPoint) -> Int
{
// Configure parameters
let basicTrackAngle = CGFloat(360 / tracks.count)
let strokeWidth = 60.0
let radius = CGFloat((CGFloat(self.frame.size.width) - CGFloat(strokeWidth)) / 2) - CGFloat(margin)
// Initialize the context
UIGraphicsBeginImageContext(CGSizeMake(250.0, 250.0)) // THIS LINE SOLVES MY ISSUE
var context = UIGraphicsGetCurrentContext()
CGContextSetLineWidth(context, CGFloat(strokeWidth))
CGContextSetStrokeColorWithColor(context, UIColor.blackColor().CGColor)
for i in 0...tracks.count-1 {
// Start and end angles of current track
var currentTrackStartAngle = CGFloat(i) * basicTrackAngle
var currentTrackEndAngle = currentTrackStartAngle + basicTrackAngle
CGContextAddArc(context, center.x, center.y, CGFloat(radius), toNormalizedRadian(currentTrackStartAngle), toNormalizedRadian(currentTrackEndAngle), 0)
if pointOnPath(point, inContext: context){
println("point found on track: \(i)")
return i
}
CGContextDrawPath(context, kCGPathStroke)
}
println("point not found on arcs")
return -1
}

Does MKOverlayPathView need drawMapRect?

I'm having some inconsistencies modifying the Breadcrumb example, to have the CrumbPathView subclassed from MKOverlayPathView (like it's supposed to) rather than subclassed from MKOverlayView.
Trouble is, the docs are limited in stating the difference in how these 2 should be implemented. For a subclass of MKOverlayPathView it's advised to use:
- createPath
- applyStrokePropertiesToContext:atZoomScale:
- strokePath:inContext:
But is this in place of drawMapRect, or in addition to? It doesn't seem like much point if it's in addition to, because both would be used for similar implementations. But using it instead of drawMapRect, leaves the line choppy and broken.
Struggling to find any real world examples of subclassing MKOverlayPathView too...is there any point?
UPDATE - modified code from drawMapRect, to what should work:
- (void)createPath
{
CrumbPath *crumbs = (CrumbPath *)(self.overlay);
CGMutablePathRef newPath = [self createPathForPoints:crumbs.points
pointCount:crumbs.pointCount];
if (newPath != nil) {
CGPathAddPath(newPath, NULL, self.path);
[self setPath:newPath];
}
CGPathRelease(newPath);
}
- (void)applyStrokePropertiesToContext:(CGContextRef)context atZoomScale:(MKZoomScale)zoomScale
{
CGContextSetStrokeColorWithColor(context, [[UIColor greenColor] CGColor]);
CGFloat lineWidth = MKRoadWidthAtZoomScale(zoomScale);
CGContextSetLineWidth(context, lineWidth);
CGContextSetLineJoin(context, kCGLineJoinRound);
CGContextSetLineCap(context, kCGLineCapRound);
}
- (void)strokePath:(CGPathRef)path inContext:(CGContextRef)context
{
CGContextAddPath(context, path);
CGContextStrokePath(context);
[self setPath:path];
}
This draws an initial line, but fails to continue the line...it doesn't add the path. I've confirmed that applyStrokePropertiesToContext and strokePath are getting called, upon every new location.
Here's a screenshot of the broken line that results (it draws for createPath, but not after that):
Here's a screenshot of the "choppy" path that happens when drawMapRect is included with createPath:
Without having seen more of your code I'm guessing, but here goes.
I suspect the path is being broken into segments, A->B, C->D, E->F rather than a path with points A,B,C,D, E and F. To be sure of that we'd need to see what is happening to self.overlay and whether it is being reset at any point.
In strokePath you set self.path to be the one that is being stroked. I doubt that is a good idea since the stroking could happen at any time just like viewForAnnotations.
As for the choppiness it may be a side effect or a poor bounds calculation on Apple's part. If your like ends near the boundary of a tile that Apple uses to cover the map it would probably only prompt the map to draw the one the line is within. But your stroke width extends into a neighbouring tile that hasn't been draw. I'm guessing again but you could test this out by moving the point that is just north of the W in "Queen St W" a fraction south, or by increasing the stroke width and see if the cut off line stays in the same place geographically.

CGContextSetRGBFillColor too few arguments

I am trying to set a color to my CGContextSetRGBFillColor in this way:
- (void) drawArrowWithContext:(CGContextRef)context atPoint:(CGPoint)startPoint withSize: (CGSize)size lineWidth:(float)width arrowHeight:(float)aheight andColor:(UIColor *)color
{
CGContextSetRGBFillColor (context,color,color,color,1);
CGContextSetRGBStrokeColor (context, color.CGColor);
....
}
...but I am getting in both cases the error "Too few arguments, should be 5, are 2". How can I fix this issue?
Seeing your other question, I would suggest that you stop for an hour and do some reading of the docs rather than simply trying to hammer your way through without understanding or learning anything.
You have a problem in your code: you are passing in a UIColor and trying to use it in a function which takes floats as arguments. Either change the params for you method or use a different CoreGraphics function which can accept a UIColor (or rather the CGColor represenation of that).
CGContextSetFillColorWithColor(context, [color CGColor]);
CGContextSetStrokeColorWithColor(context,[color CGColor]);
From the documentation:
void CGContextSetRGBFillColor (
CGContextRef c,
CGFloat red,
CGFloat green,
CGFloat blue,
CGFloat alpha
);
All you need to do is break apart your UIColor using
- (BOOL)getRed:(CGFloat *)red green:(CGFloat *)green blue:(CGFloat *)blue alpha:(CGFloat *)alpha

Creating gradient and returning for a method

Sorry for noobish question about iPhone and Quartz programming. Just started my conversion from C++ to Objective-C :)
So, I have such a class method
+(CGGradientRef)CreateGradient:(UIColor*)startColor endColor:(UIColor*)endColor
{
CGGradientRef result;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGFloat locations[2] = {0.0f, 1.0f};
CGFloat startRed, startGreen, startBlue, startAlpha;
CGFloat endRed, endGreen, endBlue, endAlpha;
[endColor getRed:&endRed green:&endGreen blue:&endBlue alpha:&endAlpha];
[startColor getRed:&startRed green:&startGreen blue:&startBlue alpha:&startAlpha];
CGFloat componnents[8] = {
startRed, startGreen, startBlue, startAlpha,
endRed, endGreen, endBlue, endAlpha
};
result = CGGradientCreateWithColorComponents(colorSpace, componnents, locations, 2);
CGColorSpaceRelease(colorSpace);
return result;
}
and its usage.
-(void)FillGradientRect:(CGRect)area startColor:(UIColor *)startColor endColor:(UIColor *)endColor isVertical:(BOOL)isVertical
{
CGContextRef context = UIGraphicsGetCurrentContext();
UIGraphicsPushContext(context);
CGGradientRef gradient = [Graphics CreateGradient:startColor endColor:endColor];
CGPoint startPoint, endPoint;
if (isVertical) {
startPoint = CGPointMake(CGRectGetMinX(area), area.origin.y);
endPoint = CGPointMake(startPoint.x, area.origin.y + area.size.height);
}else{
startPoint = CGPointMake(0, area.size.height / 2.0f);
endPoint = CGPointMake(area.size.width, startPoint.y);
}
CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0);
CGGradientRelease(gradient);
UIGraphicsPopContext();
}
everything works as expected. But, when I run the Analyze tool from Xcode 4, I'm getting a warning about memory leak in method CreateGradient for result variable. Well, I understand what's that about, but in my calling method I'm releasing the gradient object (CGGradientRelease(gradient);).
So, who is wrong and how to make Analyze tool happy?
Thx
Since CGGradientRef is a Core Foundation type of object, you can autorelease it. Just add this line before returning the gradient:
[(id)result autorelease];
If the goal is solely to keep the analyzer happy in ARC, then just make it a C function rather than objective-C - i.e.:
CGGradientRef CreateGradient(UIColor *startColor, UIColor * endColor)
The Core Foundation naming scheme then applies which says that a function with Create in the name is treated as returning a retained object (and it is the caller's responsibility to release it). This satisfies the analyser.
If you want an autoreleased variable, then transfer ownership of the CG type to ARC:
id arc_result = (__bridge_transfer id)result
However, if you do that, you need to return the objective-c type (arc_result), not the CG-type. If you return the CG type, there will be no retained references to arc_result, and so the compiler will clean it up as you return from the function.
You could use this hack to effect a CG-type autorelease:
dispatch_async(dispatch_get_main_queue(), ^{
CGGradientRelease(result);
});
It would satisfy the analyser and probably work - though I would consider it to be pretty unsafe!

Resources