I try to rotate a text along a stroke. The user can create strokes by touch and move the finger in an direction he wants, the stroke then scales to the point where the finger is. I want to show the current length along the stroke and the text show stay scale and rotate with the stroke.
I think im not so far away from the working solution. Currently the text is not always at the right position, its depends on the rotation. I think there is something wrong with Context Translation, but lets see my code.
This is my method to draw the text:
- (void)drawText:(NSString *)text withFrame:(CGRect)rect withFont:(UIFont *)font rotation:(float)radians alignment:(NSTextAlignment)alignment context:(CGContextRef)context {
NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping;
paragraphStyle.alignment = alignment;
NSDictionary *attributes = #{ NSFontAttributeName: font,
NSParagraphStyleAttributeName: paragraphStyle,
NSForegroundColorAttributeName: [UIColor blackColor] };
CGContextSaveGState(context);
CGContextScaleCTM(context, 1.0, 1.0);
//CGRect textSize = [text boundingRectWithSize:rect.size options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil];
CGContextTranslateCTM(context, rect.origin.x + (rect.size.width * 0.5), rect.origin.y + (rect.size.height * 0.5));
CGContextRotateCTM(context, radians);
CGContextTranslateCTM(context, -(rect.origin.x + (rect.size.width * 0.5)), -(rect.origin.y + (rect.size.height * 0.5)));
[[UIColor redColor] set];
CGContextFillRect(context, rect);
[text drawInRect:rect withAttributes:attributes];
CGContextRestoreGState(context);
}
I call it like this:
CGFloat a = shapeToBeDrawn.startPoint.y - shapeToBeDrawn.endPoint.y;
CGFloat c = shapeToBeDrawn.length;
CGFloat alpha = -asin(a/c);
CGRect r = CGRectMake(shapeToBeDrawn.startPoint.x, shapeToBeDrawn.startPoint.y - 30, shapeToBeDrawn.endPoint.x - shapeToBeDrawn.startPoint.x, 20);
[self drawText:lengthStr withFrame:r withFont:[UIFont fontWithName:#"Helvetica" size:18.0f] rotation:alpha alignment:NSTextAlignmentCenter context:context];
There im calculation the angle alpha and pass the string i want to display. And also create the frame for the text above the frame of the shape.
Here a small video how it looks currently:
Click
Hope someone can help me and my problem is clear. Thanks :)
To calculate angle properly in all quadrants, use atan2 function
alpha = atan2(endPoint.y - startPoint.y, endPoint.x - startPoint.x)
To define a rect - calculate starting coordinates for rotated rectangle:
x0 = startPoint.x + Sin(alpha) * 30
y0 = startPoint.y - Cos(alpha) * 30
rect.width = shapeToBeDrawn.length
Related
okie i am using a PieChart i got from github Astrokin Piechart
But in this piechart label has not been added to different slices and i want something as shown below
I have looked up on stackoverflow already and am trying to draw text in draw rect but doesnt seem to work. code is below
for (int i = 0; i < slicesCount; i++)
{
double value = [self.datasource pieChartView:self valueForSliceAtIndex:i];
NSString *title = [self.datasource pieChartView:self titleForSliceAtIndex:i];
endAngle = startAngle + M_PI*2*value/sum;
/*CGFloat x = centerX + (radius+10) * (cos((startAngle-90)*M_PI/180.0) + cos((endAngle-90)*M_PI/180.0)) / 2;
CGFloat y = centerY + (radius+10) * (sin((startAngle-90)*M_PI/180.0) + sin((endAngle-90)*M_PI/180.0)) / 2;
[title drawAtPoint:CGPointMake(x, y) withFont:[UIFont fontWithName:FONT_HELVETICA size:12.0]];*/
CGFloat x = centerX + ((radius + 10) * cos(startAngle));
CGFloat y = centerY + ((radius + 10) * sin(endAngle));
// NSLog(#"X Y: %f %f %f", x, y, degree);
NSString *text = #"Test";
[text drawInRect:CGRectMake(x, y, 50, 44) withFont:[UIFont systemFontOfSize:14.0f]];
CGContextAddArc(context, centerX, centerY, radius, startAngle, endAngle, false);
UIColor *drawColor = [self.datasource pieChartView:self colorForSliceAtIndex:i];
CGContextSetStrokeColorWithColor(context, drawColor.CGColor);
CGContextSetLineWidth(context, lineWidth);
CGContextStrokePath(context);
startAngle += M_PI*2*value/sum;
}
right now it looks like this
What am i possibly doing wrong?
You draw text with
[text drawInRect:CGRectMake(x, y, 50, 44) withFont:[UIFont systemFontOfSize:14.0f]];
That makes your text's top left corner to appear at point (x,y). As I can see, you want text's center to appear at point (x,y). What you need to do is to subtract half of the text's width from x and the same for y, so that it becomes:
CGFloat height = 50.0;
CGFloat width = 44.0;
[text drawInRect:CGRectMake(x - width / 2.0, y - height / 2.0, width, height) withFont:[UIFont systemFontOfSize:14.0f]];
Also you'll need to dynamically get text's width based on the string you have, "95%" is wider than "5%", yeah? So you'll need something like:
UIFont *font = [UIFont systemFontOfSize:14.0f];
CGSize textSize = [text sizeWithFont:font]; // -sizeWithFont is deprecated in iOS7, you'll need something else
CGFloat height = textSize.height;
CGFloat width = textSize.width;
[text drawInRect:CGRectMake(x - width / 2.0, y - height / 2.0, width, height) withFont:font];
I want to know how could I add an activity indicator (in the middle of a HUD) to this drawrect method (i'm trying to create a hud with the activity on it)
- (void)drawRect:(CGRect)rect {
// Sets the rectangle to be 96 X 96.
const CGFloat boxWidth = 120.0f;
const CGFloat boxHeight = 120.0f;
// This method is used to calculate the position of the rectangle.
CGRect boxRect = CGRectMake( roundf(self.bounds.size.width - boxWidth) / 2.0f, roundf(self.bounds.size.height - boxHeight) / 2.0f, boxWidth,boxHeight);
// This draws the rectangle with rounded corners.
UIBezierPath *roundedRect = [UIBezierPath bezierPathWithRoundedRect:boxRect cornerRadius:10.0f];
[[UIColor colorWithWhite:0.0f alpha:0.75] setFill];
[roundedRect fill];
[[UIColor whiteColor] set]; // Sets the color of the font to white.
UIFont *font = [UIFont boldSystemFontOfSize:16.0f]; // Sets the size of the font to 16.
CGSize textSize = [self.text sizeWithFont:font];
// Calculates where to draw the text.
CGPoint textPoint = CGPointMake( self.center.x - roundf(textSize.width / 2.0f), self.center.y - roundf(textSize.height / 2.0f) + boxHeight / 4.0f);
// Draws the text on the rectangle.
[self.text drawAtPoint:textPoint withFont:font];
}
Thanks!
You don't. Activity indicator is a type of view, so you'd add it as a subview of your HUD and it will draw itself.
I create pdf file. I need to draw rotation text. In current time I have this pdf file
My code which draw the text
............
CGContextTranslateCTM(context, 0, frameRect.origin.y*2);
CGContextScaleCTM(context, 1.0, -1.0);
// Draw the frame.
CTFrameDraw(frame, context);
// Add these two lines to reverse the earlier transformation.
CGContextScaleCTM(context, 1.0, -1.0);
CGContextTranslateCTM(context, 0, (-1)*frameRect.origin.y*2);
I know that I must use CGContextRotateCTM(CGContextRef c, GFloat angle), but I bad understand transformation and I can't do as I need.
The following adds text rotated -90 degrees to the specified context at the yOffset from the top of the page:
+(void)addRotatedLabel:(CGContextRef)context atYOffset:(CGFloat)yOffset{
// -90 degrees
CGFloat rotation = -M_PI / 2.f;
CGContextRotateCTM(context, rotation);
NSString *label = [NSString stringWithFormat:#"%#", #"some text"];
CGSize maxSize = CGSizeMake(300, 12);
UIFont *font = [UIFont systemFontOfSize:7.5f];
CGSize textSize = [label sizeWithFont:font constrainedToSize:maxSize lineBreakMode:NSLineBreakByClipping];
CGRect titleRect = CGRectMake(-yOffset, 32.f - textSize.height + 3.f, textSize.width, textSize.height);
[[UIColor blackColor] setFill];
[label drawInRect:titleRect withFont:font];
CGContextRotateCTM(context, -rotation);
}
It can be easily modified to handle other rotations, font sizes, pass in the NSString, etc.
I am trying to draw slightly rotated text with Core Graphics on the iOS platform. Text renders fine when not rotating but the rendering system tries to lock onto pixels for rotated text.
For example: If you rotate a Core Graphics context by some small amount (like 2 degrees) and then draw text the individual characters will appear to jump up and down as Core Graphics locks the characters to the pixels (font hinting). I know that the text may become blurry if it would not lock onto the pixel grid but that's acceptable. Jumping characters are not. So how can I disable vertical font hinting? Hinting horizontal would be ok, but turning it all off is ok too.
Code for custom UIView:
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
[self.backgroundColor setFill];
CGContextFillRect(context, rect);
CGContextSaveGState(context);
// rotate context
CGContextTranslateCTM(context, self.bounds.size.width / 2.0, self.bounds.size.width / 2.0);
CGContextRotateCTM(context, 2.0 * M_PI / 180.0);
CGContextTranslateCTM(context, -self.bounds.size.width / 2.0, -self.bounds.size.width / 2.0);
[[UIColor blackColor] setFill];
[self.title drawInRect:[self bounds] withFont:[UIFont systemFontOfSize:15.0]];
CGContextRestoreGState(context);
}
Result (not exactly of this code but similar, red lines inserted to guide to the "error"):
The only solution I have found is to get the actual Bezier paths of the glyphs with Core Text and draw those, which circumvents any vertical hinting. The following code excerpt is rather lengthy:
CGRect textRect = CGRectMake(0.0, 0.0, 300.0, 190.0);
CGContextRef context = UIGraphicsGetCurrentContext();
// Flip coordinate system vertically.
CGContextSaveGState(context);
CGFloat rectHeight = textRect.size.height;
CGContextTranslateCTM(context, 0.0, rectHeight);
CGContextScaleCTM(context, 1.0f, -1.0f);
// Positive degrees because of flip.
CGAffineTransform rotationTransform = CGAffineTransformMakeRotation(2.0 * M_PI/180.0);
CGContextConcatCTM(context, rotationTransform);
CGFloat pointSize = 15.0;
CTFontRef font = CTFontCreateUIFontForLanguage(kCTFontSystemFontType,
pointSize,
NULL);
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
NSDictionary *initialAttributes = (
#{
(NSString *)kCTFontAttributeName : (__bridge id)font,
(NSString *)kCTForegroundColorAttributeName : (__bridge id)[UIColor blackColor].CGColor
}
);
NSMutableAttributedString *attributedString =
[[NSMutableAttributedString alloc] initWithString:[self string]
attributes:initialAttributes];
//
// For typesetting a frame, we should create a paragraph style.
// Includes fix for CTFramesetter’s wrong line spacing behavior.
// See Technical Q&A QA1698: “How do I work-around an issue where some lines
// in my Core Text output have extra line spacing?”
//
// Center alignment looks best when filling an ellipse.
CTTextAlignment alignment = kCTLeftTextAlignment;
CTLineBreakMode lineBreakMode = kCTLineBreakByWordWrapping;
// This is the leading in the historical sense, which is added to the point
// size but does not include it like the line height does.
CGFloat leading = 2.0;
// Still, for the fix we do need the line height.
CGFloat lineHeight = pointSize + leading;
CTParagraphStyleSetting paragraphStyleSettings[] =
{
{
kCTParagraphStyleSpecifierAlignment,
sizeof(alignment),
&alignment
},
{
kCTParagraphStyleSpecifierLineBreakMode,
sizeof(lineBreakMode),
&lineBreakMode
},
// These two specifiers fix the line spacing when set to line height.
{
kCTParagraphStyleSpecifierMinimumLineHeight,
sizeof(lineHeight),
&lineHeight
},
{
kCTParagraphStyleSpecifierMaximumLineHeight,
sizeof(lineHeight),
&lineHeight
}
// Very important: Do not set kCTParagraphStyleSpecifierLineSpacing too,
// or it will be added again!
};
CTParagraphStyleRef paragraphStyle = CTParagraphStyleCreate(
paragraphStyleSettings,
sizeof(paragraphStyleSettings) / sizeof(paragraphStyleSettings[0])
);
// Apply paragraph style to entire string. This cannot be done when the
// string is empty, by the way, because attributes can only be applied to
// existing characters.
NSRange stringRange = NSMakeRange(0, [attributedString length]);
[attributedString addAttribute:(NSString *)kCTParagraphStyleAttributeName
value:(__bridge id)(paragraphStyle)
range:stringRange];
// Create bezier path to contain our text.
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, textRect);
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)(attributedString));
// Range with length 0 indicates that we want to typeset until we run out of
// text or space.
CTFrameRef frame = CTFramesetterCreateFrame(
framesetter,
CFRangeMake(0, 0),
path,
NULL
);
CFArrayRef lines = CTFrameGetLines(frame);
CFIndex lineCount = CFArrayGetCount(lines);
CFRange range = CFRangeMake(0, 0);
CGPoint lineOrigins[lineCount];
CTFrameGetLineOrigins(frame, range, lineOrigins);
for (NSUInteger lineIndex = 0; lineIndex < lineCount; ++lineIndex)
{
CTLineRef line = CFArrayGetValueAtIndex(lines, lineIndex);
CGPoint lineOrigin = lineOrigins[lineIndex];
CFArrayRef runs = CTLineGetGlyphRuns(line);
CFIndex runCount = CFArrayGetCount(runs);
for (NSUInteger runIndex = 0; runIndex < runCount; ++runIndex)
{
CTRunRef run = CFArrayGetValueAtIndex(runs, runIndex);
CFIndex glyphCount = CTRunGetGlyphCount(run);
CGGlyph glyphBuffer[glyphCount];
CTRunGetGlyphs(run, range, glyphBuffer);
CGPoint positionsBuffer[glyphCount];
CTRunGetPositions(run, range, positionsBuffer);
for (NSUInteger glyphIndex = 0; glyphIndex < glyphCount; ++glyphIndex)
{
CGGlyph glyph = glyphBuffer[glyphIndex];
CGPoint position = positionsBuffer[glyphIndex];
CGAffineTransform positionTransform = CGAffineTransformMakeTranslation(lineOrigin.x + position.x,
lineOrigin.y + position.y);
CGPathRef glyphPath = CTFontCreatePathForGlyph(font, glyph, &positionTransform);
CGContextAddPath(context, glyphPath);
}
}
}
CGContextSetFillColorWithColor(context, [UIColor blackColor].CGColor);
CGContextFillPath(context);
CFRelease(font);
CFRelease(framesetter);
// Use specialized release function when it exists.
CGPathRelease(path);
CGContextRestoreGState(context);
How do I change the vertical alignment of the text in a CTFramesetter frame? I want my text to be in the middle instead of being at the top. I am using Core Text framework. There is a setting of the paragraph to change horizontal aligment but not vertical.
Finally figured it out ...
CGRect boundingBox = CTFontGetBoundingBox(font);
//Get the position on the y axis
float midHeight = self.frame.size.height / 2;
midHeight -= boundingBox.size.height / 2;
CGPathAddRect(path, NULL, CGRectMake(0, midHeight, self.frame.size.width, boundingBox.size.height));
Thanks Nick, that was a great snippet.
Just expanding on that, if your doing Top, Middle and Bottom alignment with an enum, for example you could do it like so:
if (VerticalAlignmentTop == currentTextAlignment) {
CGPathAddRect(path, NULL, rect); // Draw normally (top)
}
else if (VerticalAlignmentMiddle == currentTextAlignment) {
CGRect boundingBox = CTFontGetBoundingBox(fontRef);
//Get the position on the y axis (middle)
float midHeight = rect.size.height / 2;
midHeight -= boundingBox.size.height / 2;
CGPathAddRect(path, NULL, CGRectMake(0, midHeight, rect.size.width, boundingBox.size.height));
}
else {
CGRect boundingBox = CTFontGetBoundingBox(fontRef);
CGPathAddRect(path, NULL, CGRectMake(0, 0, rect.size.width, boundingBox.size.height));
}
You can use [NSString boundingRectWithSize:options:attributes:context:] to get the rectangle of your string's bounding box, which allows multi-line text as well.
In your draw text method, do the following (RECT is the rectangle where you want to draw the text):
// get the graphics context
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
// flip the context coordinate
CGContextTranslateCTM(context, 0.0f, 2*RECT.origin.y+RECT.size.height);
CGContextScaleCTM(context, 1.0f, -1.0f);
// Set the text matrix.
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
// set text horizontal alignment
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.alignment = NSTextAlignmentCenter;
NSDictionary *attributes = #{NSParagraphStyleAttributeName:paragraphStyle, NSFontAttributeName:YOUR_FONT, NSForegroundColorAttributeName:TEXT_COLOR};
NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:YOUR_TEXT attributes:attributes];
CGMutablePathRef path = CGPathCreateMutable();
// set text vertical alignment
CGSize textSize = [text boundingRectWithSize:RECT.size options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil].size;
CGPathAddRect(path, NULL, CGRectMake(RECT.origin.x, RECT.origin.y-(RECT.size.height-textSize.height)/2.0f, RECT.size.width, RECT.size.height));
CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attrString);
CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, attrString.length), path, NULL);
CTFrameDraw(frame, context);
CFRelease(frame);
CFRelease(path);
CFRelease(frameSetter);
[attrString release];
[paragraphStyle release];
CGContextRestoreGState(context);
This accounts for the fact that multiple font types and styles can be used in a frame (calculates both height and width of text, look in the if(index == lastLineIndex) block to see where the height is calculated):
- (CGSize) measureFrame: (CTFrameRef) frame forContext: (CGContext *) cgContext
{
CGPathRef framePath = CTFrameGetPath(frame);
CGRect frameRect = CGPathGetBoundingBox(framePath);
CFArrayRef lines = CTFrameGetLines(frame);
CFIndex numLines = CFArrayGetCount(lines);
CGFloat maxWidth = 0;
CGFloat textHeight = 0;
// Now run through each line determining the maximum width of all the lines.
// We special case the last line of text. While we've got it's descent handy,
// we'll use it to calculate the typographic height of the text as well.
CFIndex lastLineIndex = numLines - 1;
for(CFIndex index = 0; index < numLines; index++)
{
CGFloat ascent, descent, leading, width;
CTLineRef line = (CTLineRef) CFArrayGetValueAtIndex(lines, index);
width = CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
if(width > maxWidth)
{
maxWidth = width;
}
if(index == lastLineIndex)
{
// Get the origin of the last line. We add the descent to this
// (below) to get the bottom edge of the last line of text.
CGPoint lastLineOrigin;
CTFrameGetLineOrigins(frame, CFRangeMake(lastLineIndex, 1), &lastLineOrigin);
// The height needed to draw the text is from the bottom of the last line
// to the top of the frame.
textHeight = CGRectGetMaxY(frameRect) - lastLineOrigin.y + descent;
}
}
// For some text the exact typographic bounds is a fraction of a point too
// small to fit the text when it is put into a context. We go ahead and round
// the returned drawing area up to the nearest point. This takes care of the
// discrepencies.
return CGSizeMake(ceil(maxWidth), ceil(textHeight));
}
Reference: Scott Thompson (http://lists.apple.com/archives/quartz-dev/2008/Mar/msg00079.html)