I have a scenario where I will have to draw a text on a UIView. For which my code is
- (void)drawRect:(CGRect)iRect {
[super drawRect:iRect];
CGContextRef aContext = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(aContext, [[UIColor greenColor] CGColor]);
[#"s" drawInRect:CGRectMake(0, 0, iRect.size.width, iRect.size.height) withFont:[UIFont systemFontOfSize:500] lineBreakMode:NSLineBreakByCharWrapping alignment:NSTextAlignmentCenter];
}
With this code I can achieve:
However, since I'm using a FontSize of 500, the text lineWidth is big.
I would like to reduce the lineWidth to 1px.
Please advice on how to achieve this.
Is there another method apart from the conventional drawInRect.
Will CoreText help me get the solution
Thanks.
EDIT
Using UIBezierPath for the Glyph, I was able to get the Path of the Character. But the path that is achieved is through the border of the character. hence, I get the text as:
Can this be made a single line ?
Even though it looks as if the letter S is drawn as a line of a certain width, that's not the case. If you look closer, then you'll see that the line width varies. It's in fact a complex graphical construct defined by it's outline. It's even more obvious if you use a font with serifs. As a consequence, there is no such thing as a text line width.
The best solution probably is to use a different font that has the look as if it was drawn using a narrow pen. I'm not sure if such a font is preinstalled on iOS.
Related
I just saw this recent question on SO, which gets somewhat close to what I'm asking here.
So I'm trying to typeset a mathematical equation using Core Text and NSAttributedString. It was working pretty well for expressions like x2, but then I came across a problem when I use y. Namely, 'y' has a descender which I don't know how to take into account in my drawing code:
CGContextSaveGState(context);
CGContextTranslateCTM(context, x, sizeOfGlyph.height / 2.0);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CTLineRef line = CTLineCreateWithAttributedString((__bridge CFAttributedStringRef)attributedSubstring);
CGContextSetTextPosition(context, 0.0, 0.0);
CTLineDraw(line, context);
CFRelease(line);
CGContextRestoreGState(context);
Basically, I translate the context by half the glyph's height (as calculated here) to start the text at the baseline of the glyph. As you can imagine, this runs into problems with glyphs like 'y', because half the glyph's height isn't the baseline of the glyph. I don't think it's sufficient to use the font's descender, because I have no way of knowing whether this glyph has a descender or not. So what I'd like to find out is whether there is a way to find the descender (or any desired metrics) for a particular glyph?
Any know-how on the topic would be appreciated, I'm just getting started with Core Text.
What I ended up doing was to use the origin of the glyph returned from the image bounds function. I calculated the bounds of the glyph using the method outlined in this answer. The origin of the resulting CGRect gives the y-position of the glyph's bottom-left corner, which in the case of 'y' would be negative. So by adding together glyphRect.origin.y + glyphRect.size.height, you get the distance from the top of the glyph to the baseline.
I'm trying to draw a checkmark in green with UIKit, but it is drawn in black instead. Here is the code:
[[UIColor greenColor] set];
[#"✔" drawAtPoint:CGPointZero withFont:[UIFont systemFontOfSize:[UIFont systemFontSize]]];
Other strings are properly drawn in green with this method. I suspect that the checkmark glyph contains color information that overrides my choice of fill color, but drawing the same glyph with color in an UIWebView works.
Is there a way to get the checkmark drawn in green anyway?
I suspect that the checkmark glyph in question might not be available in the system font, and the text system is doing its best to fulfill your request using a different font (it's probably coming from ZapfDingbatsITC). In the process, perhaps, the color is getting stripped.
One thing you might try is turning that glyph into a bezier path, then filling that with the desired color.
I'm drawing the 'slices' in a custom pie chart in Core Graphics, using a UIBezierPath and the [path addArcWithCenter:radius:startAngle:endAngle:clockwise:] method. My problem is that often the pointy end of the slice actually juts past the center point, intruding into other slices space.
Is there a way to 'round' this edge?
Here is the code im using to draw the path
[path moveToPoint:center];
[path addArcWithCenter:center radius:radius startAngle:interiorAngle endAngle:totalAngle clockwise:YES];
[path addLineToPoint:center];
[path closePath];
Here is an image of the problem:
The white pointy end of the blue piece on the lower left intrudes slightly into the large blue piece in the upper right.
It's not "past" the center point. Your confusion lies in the fact that you're stroking the path. When you stroke a path, the stroke lies centered upon the path, and therefore half of the stroke is outside the path and half of the stroke is inside the path. If you want an accurate stroke, you have two options:
Fill your path with your stroke color, then construct another path that's inset into your first one by the desired line width, and then fill that path with your fill color. This will simulate an "inside" stroke, although it's not usable if your stroke or fill colors are semi-transparent.
Clip to your path, double the stroke width, and then stroke your path. The clipping will force the stroke to only draw inside the path. However, this may not look quite "accurate" at corners (not really sure) since it's doubling the stroke width rather than calculating the "desired" path.
Alternatively, you could try just setting the lineJoinStyle to something other than kCGLineJoinMiter. With the default miter style, the lines actually draw out as far as they need to from the corner in order to meet, which means they can go past 1/2 the line width. If you use kCGLineJoinRound or kCGLineJoineBevel they cannot go past 1/2 the line width. This may not be quite accurate, but it may be good enough for what you want.
I would suspect the problem is with the line width of the white line. For example, a line that is 8 points wide, would have 4 points on either side of the path.
I can't see the problem in that image (which slice is showing it?), but I can suggest that you remove the addLineToPoint: message. It isn't necessary; closePath will return to the center, since that's where you started.
The attributed string has only one attribute - 17 point Helvetica NeueUI font - covering the whole string. Line 1~3 are purely English, line 4~6 are mixtures of English and Chinese, line 7~8 are purely Chinese.
It is then layouted with CTFramesetter and resultant frame is drawn with CTFrameDraw.
// UIView subclass
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor whiteColor];
CTFontRef font = CTFontCreateWithName(CFSTR("Helvetica NeueUI"), 17.f, NULL);
_text = [[NSAttributedString alloc] initWithString:string attributes:
[NSDictionary dictionaryWithObject:(id)font forKey:(id)kCTFontAttributeName]];
CFRelease(font);
_framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)_text);
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, self.bounds);
_frame = CTFramesetterCreateFrame(_framesetter, CFRangeMake(0, 0), path, NULL);
CGPathRelease(path);
}
return self;
}
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
// Flip the coordinate system.
CGContextTranslateCTM(context, 0.f, self.bounds.size.height);
CGContextScaleCTM(context, 1.f, -1.f);
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CTFrameDraw(_frame, context);
CGContextRestoreGState(context);
}
The problem is that the space between line 7 and 8 (purely Chinese lines) is much smaller than others.
For contrast, I put a UILabel below it, with the same font and text. Ignoring the rendering difference of single characters, you can see the line spaces in UILabel are uniform including the last one between two Chinese lines.
(source: skitch.com)
Notice: this results above is got on a real device. If you run the same code on the simulator, you'll get very different result for the Core Text view — spaces between lines containing Chinese are much larger than others.
Why is the space between Chinese lines smaller(larger)?
How can I layout multi-language text with uniform line height using Core Text?
PS: here is the sample project you can try yourself.
Similarly to the space between each letter in European languages, Chinese writing uses a narrow space between characters, though it does not observe the equivalent to the wider space between words, except in rare occasions. (In this respect, it may somewhat resemble a form of scriptio continua.)
When space is used, it is also fullwidth. One instance of its usage is as an honorific marker. A modern example in Taiwan, is found in the reference to Chiang Kai-shek as 先總統 蔣公 (Former President, Lord Chiang), in which the preceding space serves as an honorific marker for 蔣公. This use is also still current in very formal letters or other old-style documents.
When Chinese is written in transliterated form (as in Hanyu Pinyin), spaces are used to assist in reading. source:http://en.wikipedia.org/wiki/Chinese_punctuation
So its because in chinese as space is used a bit differently than in english.
The reason for the space is depending on the characters, as it could be found when querying parts of the text for its dimension, but this does not declare why a label simply ignores this problem.
However the space is in fact introduced by the FrameSetter in order to achieve even space you could use the FrameSetter to get the line breaks and use a TypeSetter (CTTypesetterCreateWithAttributedString) to produce the lines and point them at a desired line distance, as shown in the Manual Line Break from Apple. This also works for your given sample, as i have briefly checked.
Anyway I could only agree that the behaviour of the label is somehow odd, but not this bad as it looks better.
I finally got an official answer from Apple engineer:
CTLine metrics are based on the maximum of each of {ascent, descent,
leading}; this has not changed, nor will it. Clients may override the
line metrics using paragraph style specifiers or iOS 7 text styles,
for example [UIFont preferredFontForTextStyle:UIFontTextStyleBody].
I see APIs in Quartz for drawing lines and circles. But all I want to do is to specify the (x,y) cartesian coordinate to color a pixel a particular value. How do I do that?
CGContextFillRect(context, CGRectMake(x,y,1,1));
Quartz is not a pixel-oriented API, and its contexts aren’t necessarily pixel buffers. If you want to draw pixmaps, create a bitmap context with CGBitmapContextCreate(). You provide a buffer, which you can manipulate directly, and can copy to another context by creating a CGImage from the same buffer using CGImageCreate() and drawing that.
Draw a very small ellipse/circle and fill it!
CGContextAddEllipseInRect(Context,(CGRectMake (x_dot, y_dot, 3.0, 3.0));
CGContextDrawPath(Context, kCGPathFill);
CGContextStrokePath(Context);
I am using this code to create a small dot (3x3 pixels) in a dotted music note.
I got a point (zero length line) to draw after setting the line-caps to kCGLineCapRound. The default line-cap has no length so can't be drawn.
The argument that a point has no size is silly. The lines have no width but we can draw those (by using the "line width" from the draw state). A point should draw in exactly the same way, and with different line caps I believe it does.
Maybe this behavior is new?
You can draw a 1-pixel-length line at the coordinate in question; that should accomplish what you want.
I'm having the same issue - i find the best solution is similar to the last, but at least it doesn't leave something that looks like a "dash"... of course, should ensure x/y are both > 0.
CGContextFillRect(context, CGRectMake(x - 0.5, y - 0.5, 1.0 , 1.0));
Swift 3 :
UIRectFill(CGRect(x: x, y: y, width: 1, height: 1))
UIRectFill()