iPhone PDF creation text inverting issue - iphone

i am creating an iphone app for creating pdf. I am using the following code:
[self drawRect:CGRectMake(0, 0, 100, 20 )text:#"last Name"xVal:10 yVal:-30 len:9];
[self drawRect:CGRectMake(0, 0, 100, 20 )text:#"First Name"xVal:180 yVal:30 len:10];
[self drawRect:CGRectMake(0, 0, 100, 20 )text:#"Middle Name"xVal:330 yVal:-30 len:11];
+ (void) drawRect:(CGRect)rect text:(NSString *)string xVal:(int)x yVal:(int)y len:(int)length{
CGContextRef theContext = UIGraphicsGetCurrentContext();
CGRect viewBounds = rect;
CGContextTranslateCTM(theContext, 0, viewBounds.size.height);
CGContextScaleCTM(theContext, 1, -1);
// Draw the text using the MyDrawText function
MyDrawText(theContext, viewBounds,string, x, y,length);
}
void MyDrawText (CGContextRef myContext, CGRect contextRect, NSString *textToDraw, int x, int y, int len){
float w, h;
w = contextRect.size.width;
h = contextRect.size.height;
CGAffineTransform myTextTransform;
CGContextSelectFont (myContext,"Helvetica-Bold", h, kCGEncodingMacRoman);
CGContextSetCharacterSpacing (myContext, 3);
CGContextSetTextDrawingMode (myContext, kCGTextFill);
CGContextSetRGBFillColor(myContext, 0, 0, 0, 1);
CGContextSetRGBStrokeColor (myContext, 0, 0, 0, 0);
myTextTransform = CGAffineTransformMakeRotation(0.0);
CGContextSetTextMatrix (myContext, myTextTransform);
const char *str = [textToDraw UTF8String];
CGContextShowTextAtPoint (myContext, x, y,str, len);
}
When I use this code I am getting the output like, first and third word is fine but the second one is coming like inverted (head down). Why so? Any idea?

The issue was with CGContextRef theContext = UIGraphicsGetCurrentContext();, when each time the function calls, the CGContext taking the last state, that's why getting the inverting output. So, i added,
CGContextRef theContext = UIGraphicsGetCurrentContext();
CGContextSaveGState(theContext);
//code to draw....
CGContextRestoreGState(theContext);
this solved the issue. Thanks :)

Core Graphics considers Bottom-Left corner as the origin for drawing.(In UIKit its Top-Left).
Here in your method call for second string you are providing yVal as 30 and other values as -30.
Thanks,

Related

CGContextShowTextAtPoint producing non-Retina output on Retina device

I'm trying to add a text to an UIImage and I'm getting a pixelated drawing.
I have tried some other answers with no success:
Drawing on the retina display using CoreGraphics - Image pixelated
Retina display core graphics font quality
Drawing with Core Graphics looks chunky on Retina display
My code:
-(UIImage *)addText:(UIImage *)imgV text:(NSString *)text1
{
int w = self.frame.size.width * 2;
int h = self.frame.size.height * 2;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate
(NULL, w, h, 8, 4 * w, colorSpace, kCGImageAlphaPremultipliedFirst);
CGContextDrawImage(context, CGRectMake(0, 0, w, h), imgV.CGImage);
CGContextSetRGBFillColor(context, 0.0, 0.0, 1.0, 1);
char* text = (char *)[text1 cStringUsingEncoding:NSASCIIStringEncoding];
CGContextSelectFont(context, "Arial", 24, kCGEncodingMacRoman);
// Adjust text;
CGContextSetTextDrawingMode(context, kCGTextInvisible);
CGContextShowTextAtPoint(context, 0, 0, text, strlen(text));
CGPoint pt = CGContextGetTextPosition(context);
float posx = (w/2 - pt.x)/2.0;
float posy = 54.0;
CGContextSetTextDrawingMode(context, kCGTextFill);
CGContextSetRGBFillColor(context, 255, 255, 255, 1.0);
CGContextShowTextAtPoint(context, posx, posy, text, strlen(text));
CGImageRef imageMasked = CGBitmapContextCreateImage(context);
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
return [UIImage imageWithCGImage:imageMasked];
}
Like Peter said, use UIGraphicsBeginImageContextWithOptions. You might want to pass image.scale to the scale attribute (where image.scale is the scale of one of the images you're drawing), or simply use [UIScreen mainScreen].scale.
This code can be made simpler overall. Try something like:
// Create the image context
UIGraphicsBeginImageContextWithOptions(_baseImage.size, NO, _baseImage.scale);
// Draw the image
CGRect rect = CGRectMake(0, 0, _baseImage.size.width, _baseImage.size.height);
[_baseImage drawInRect:rect];
// Get a vertically centered rect for the text drawing
rect.origin.y = (rect.size.height - FONT_SIZE) / 2 - 2;
rect = CGRectIntegral(rect);
rect.size.height = FONT_SIZE;
// Draw the text
UIFont *font = [UIFont boldSystemFontOfSize:FONT_SIZE];
[[UIColor whiteColor] set];
[text drawInRect:rect withFont:font lineBreakMode:NSLineBreakByClipping alignment:NSTextAlignmentCenter];
// Get and return the new image
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
Where text is an NSString object.
I was facing the exact same problem and posted a question last night to which no-one replied. I have combined fnf's suggested changes with Clif Viegas's answer in the following question:
CGContextDrawImage draws image upside down when passed UIImage.CGImage
to come up with the solution. My addText method is slightly different from yours:
+(UIImage *)addTextToImage:(UIImage *)img text:(NSString *)text1{
int w = img.size.width;
int h = img.size.height;
CGSize size = CGSizeMake(w, h);
if (UIGraphicsBeginImageContextWithOptions != NULL) {
UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);
} else {
UIGraphicsBeginImageContext(size);
}
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, 0,h);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextDrawImage(context, CGRectMake(0, 0, w, h), img.CGImage);
CGContextSetRGBFillColor(context, 0.0, 0.0, 1.0, 1);
char* text = (char *)[text1 cStringUsingEncoding:NSASCIIStringEncoding];// \"05/05/09\";
CGContextSelectFont(context, "Times New Roman", 14, kCGEncodingMacRoman);
CGContextSetTextDrawingMode(context, kCGTextFill);
CGContextSetRGBFillColor(context, 0, 0, 0, 1);
//rotate text
CGContextSetTextMatrix(context, CGAffineTransformMakeRotation( -M_PI/8 ));
CGContextShowTextAtPoint(context, 70, 88, text, strlen(text));
CGColorSpaceRelease(colorSpace);
UIGraphicsEndImageContext();
return newImg;
}
In summary, what I have done is add
if (UIGraphicsBeginImageContextWithOptions != NULL) {
UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);
} else {
UIGraphicsBeginImageContext(size);
}
Remove
// CGContextRef context = CGBitmapContextCreate(NULL, w, h, 8, 4 * w, colorSpace, kCGImageAlphaPremultipliedFirst);
and put
CGContextRef context = UIGraphicsGetCurrentContext();
instead. But this causes the image to be upside down. Hence I put
CGContextTranslateCTM(context, 0,h);
CGContextScaleCTM(context, 1.0, -1.0);
just before
CGContextDrawImage(context, CGRectMake(0, 0, w, h), img.CGImage);
This way you can use CG functions instead of using drawInRect etc.
Don't forget
UIGraphicsEndImageContext
at the end. Hope this helps.

Quartz UIImage with rounded corners and transparent background on iOS

I have used this solution to apply corner radius to UIImage in Quartz. It works fine like I predicted.
However the corners area outside of the image are not transparent but somewhat colored in white:
The image above shows top left corner of processed image that is opaque and not transparent.
I want cropped-out edges to be totally transparent. How can I achieve this?
EDIT: If I want to do this with UIImageView I would already done this. So please keep in mind that I want to do this with Quartz on UIImage object not UIImageView.
SOLUTION: The problem is actually not in the drawing code but rather in writing that image to the file system. I've saved image as JPEG an not as PNG. That's why corners were not transparent cos JPEG does not have alpha filter.
I usually use a custom category implemented on UIImage to crop the images with round corners. This was taken from this answer.
void addRoundedRectToPath(CGContextRef context, CGRect rect, float ovalWidth, float ovalHeight, BOOL top, BOOL bottom)
{
float fw, fh;
if (ovalWidth == 0 || ovalHeight == 0) {
CGContextAddRect(context, rect);
return;
}
CGContextSaveGState(context);
CGContextTranslateCTM (context, CGRectGetMinX(rect), CGRectGetMinY(rect));
CGContextScaleCTM (context, ovalWidth, ovalHeight);
fw = CGRectGetWidth (rect) / ovalWidth;
fh = CGRectGetHeight (rect) / ovalHeight;
CGContextMoveToPoint(context, fw, fh/2);
CGContextAddArcToPoint(context, fw, fh, fw/2, fh, 0);
NSLog(#"bottom? %d", bottom);
if (top) {
CGContextAddArcToPoint(context, 0, fh, 0, fh/2, 3);
} else {
CGContextAddArcToPoint(context, 0, fh, 0, fh/2, 0);
}
if (bottom) {
CGContextAddArcToPoint(context, 0, 0, fw/2, 0, 3);
} else {
CGContextAddArcToPoint(context, 0, 0, fw/2, 0, 0);
}
CGContextAddArcToPoint(context, fw, 0, fw, fh/2, 0);
CGContextClosePath(context);
CGContextRestoreGState(context);
}
- (UIImage *)roundCornersOfImage:(UIImage *)source roundTop:(BOOL)top roundBottom:(BOOL)bottom {
int w = source.size.width;
int h = source.size.height;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL, w, h, 8, 4 * w, colorSpace, kCGImageAlphaPremultipliedFirst);
CGContextBeginPath(context);
CGRect rect = CGRectMake(0, 0, w, h);
addRoundedRectToPath(context, rect, 4, 4, top, bottom);
CGContextClosePath(context);
CGContextClip(context);
CGContextDrawImage(context, CGRectMake(0, 0, w, h), source.CGImage);
CGImageRef imageMasked = CGBitmapContextCreateImage(context);
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
return [UIImage imageWithCGImage:imageMasked];
}
add these bellow lines in your drawRect method ..
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClearRect(context, rect);
///your another code here
}
after create Context it required to clear another context from that Rect... so after that line add this line CGContextClearRect(context, rect);.
For transparent use this:
const float colorMasking[6] = {1.0, 1.0, 0.0, 0.0, 1.0, 1.0};
yourImage = [UIImage imageWithCGImage: CGImageCreateWithMaskingColors(yourImage.CGImage, colorMasking)];
EDIT : Add this both line in method of UIImage category
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClearRect(context, rect);
The problem is actually not in the drawing code but rather in writing that image to the file system. I've saved image as JPEG an not as PNG. That's why corners were not transparent cos JPEG does not have alpha filter.
Just replaced this line of code:
[UIImageJPEGRepresentation(image, 0.75) writeToFile:path atomically:YES];
... with this one:
[UIImagePNGRepresentation(image) writeToFile:path atomically:YES];
Try this code
Image.layer.cornerRadius = 10.0;
Image.layer.borderColor = [UIColor blackColor].CGColor;
Image.layer.borderWidth = 0.5;
Image.clipsToBounds = YES;
Image.backgroundColor = [UIColor clearColor];
Hope it helps you
EDIT :-
I think you just need to add this :-
Image.clipsToBounds = YES;

iOS - scaling text vertically on Quartz

I am writing some text to a quartz context.
I have a box of 500 x 200 pixels and I am writing some text inside. The box can have any aspect ratio but the text will always write with the font proportions as it is. What I want is to scale the font vertically and keep the horizontal scale.
See the picture. The first box represents what I am getting, the second one, what I want.
this is how I am writing it...
CGRect rect = CGRectMake(0,0,500,200);
[myText drawInRect:rect
withFont:aFont
lineBreakMode:UILineBreakModeTailTruncation
alignment:UITextAlignmentCenter];
is there a way to scale the font vertically?
NOTE: as this is being written in a PDF context, I don't want to
render the text to a image context and scale it vertically, because I
don't want to lose resolution.
thanks
- (void) scaleTextVerticallyWithContext:(CGContextRef)myContext withRect:(CGRect)contextRect
{
CGFloat w, h;
w = contextRect.size.width;
h = contextRect.size.height;
CGAffineTransform myTextTransform;
CGContextSelectFont (myContext, "Helvetica-Bold", h/10, kCGEncodingMacRoman);
CGContextSetCharacterSpacing (myContext, 10);
CGContextSetTextDrawingMode (myContext, kCGTextFillStroke);
CGContextSetRGBFillColor (myContext, 0, 1, 0, .5);
CGContextSetRGBStrokeColor (myContext, 0, 0, 1, 1);
/*
* CGAffineTransformMakeScale ( CGFloat sx, CGFloat sy );
* sx: The factor by which to scale the x-axis of the coordinate system.
* sy: The factor by which to scale the y-axis of the coordinate system.
*/
myTextTransform = CGAffineTransformMakeScale(1,8);
CGContextSetTextMatrix (myContext, myTextTransform);
CGContextShowTextAtPoint (myContext, 40, 0, "Hello There", 9);
}
- (void)drawRect:(CGRect)rect{
// Drawing code
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect viewBounds = self.bounds;
CGContextTranslateCTM(context, 0, viewBounds.size.height);
CGContextScaleCTM(context, 1, -1);
[self scaleTextVerticallyWithContext:context withRect:viewBounds];
}

Problems when using Quartz 2D on NSOperation

I need to paint an image using some data and show it on my iphone application. Since the painting takes significant time (2-3 seconds on device), I want to perform painting on a different thread. Also, I want to be able to cancel painting, change something in data and start it again. So it's best for me to use NSOperation.
Now, when I do the drawing on the main thread, everything looks fine.
When I do exactly the same thing using NSOperation subclass, everything looks fine, but only 95% of the time. Sometimes it doesnt draw the full picture. Sometimes it doesnt draw text. Sometimes it uses different colors, there might be red/green/blue dots scattered all over the image etc etc etc.
I made a very short example to illustrate this:
First, we do all the painting on a main thread in a regular method:
//setting up bitmap context
size_t width = 400;
size_t height = 400;
size_t bitsPerComponent = 8;
size_t bytesPerRow = 4 * width;
void* imageData = malloc(bytesPerRow * height);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(imageData, width, height, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast);
CFRelease(colorSpace);
//transforming it to usual coordinate system
CGRect mapRect = CGRectMake(0, 0, width, height);
UIGraphicsPushContext(context);
CGContextTranslateCTM(context, 0, mapRect.size.height);
CGContextScaleCTM(context, 1, -1);
//actull drawing - nothing complicated here, 2 lines and 3 text strings on white background
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
CGContextFillRect(context, mapRect);
CGContextSetLineWidth(context, 3);
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
CGContextMoveToPoint(context, 10, 10);
CGContextAddLineToPoint(context, 20, 20);
CGContextStrokePath(context);
CGContextMoveToPoint(context, 20, 20);
CGContextAddLineToPoint(context, 100, 100);
CGContextStrokePath(context);
[UIColor blackColor].set;
[[NSString stringWithString:#"tag1"] drawInRect:CGRectMake(10, 10, 40, 15) withFont:[UIFont systemFontOfSize:15]];
[[NSString stringWithString:#"tag2"] drawInRect:CGRectMake(20, 20, 40, 15) withFont:[UIFont systemFontOfSize:15]];
[[NSString stringWithString:#"tag3"] drawInRect:CGRectMake(100, 100, 40, 15) withFont:[UIFont systemFontOfSize:15]];
//getting UIImage from bitmap context
CGImageRef _trueMap = CGBitmapContextCreateImage(context);
if (_trueMap) {
UIImage* _map = [UIImage imageWithCGImage:_trueMap];
CFRelease(_trueMap);
//displaying what we got
//self.map leads to UIImageView
self.map = _map;
}
//releasing context and memmory
UIGraphicsPopContext();
CFRelease(context);
free(imageData);
No errors here. Always works.
Now, I'll subclass NSOperation and copy-paste this code there: Interface:
#interface Painter : NSOperation {
//The controller which contains UIImageView we will use to display image
MapViewController* mapViewController;
CGContextRef context;
void* imageData;
}
#property (nonatomic, assign) MapViewController* mapViewController;
- (id) initWithRootController:(MapViewController*)mvc__;
#end
Now the methods:
- (id) initWithRootController:(MapViewController*)mvc__ {
if (self = [super init]) {
self.mapViewController = mvc__;
size_t width = 400;
size_t height = 400;
size_t bitsPerComponent = 8;
size_t bytesPerRow = 4 * width;
imageData = malloc(bytesPerRow * height);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
context = CGBitmapContextCreate(imageData, width, height, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast);
CFRelease(colorSpace);
}
return self;
}
- (void) main {
size_t width = 400;
size_t height = 400;
//transforming it to usual coordinate system
CGRect mapRect = CGRectMake(0, 0, width, height);
UIGraphicsPushContext(context);
CGContextTranslateCTM(context, 0, mapRect.size.height);
CGContextScaleCTM(context, 1, -1);
//actull drawing - nothing complicated here, 2 lines and 3 text strings on white background
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
CGContextFillRect(context, mapRect);
CGContextSetLineWidth(context, 3);
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
CGContextMoveToPoint(context, 10, 10);
CGContextAddLineToPoint(context, 20, 20);
CGContextStrokePath(context);
CGContextMoveToPoint(context, 20, 20);
CGContextAddLineToPoint(context, 100, 100);
CGContextStrokePath(context);
[UIColor blackColor].set;
[[NSString stringWithString:#"tag1"] drawInRect:CGRectMake(10, 10, 40, 15) withFont:[UIFont systemFontOfSize:15]];
[[NSString stringWithString:#"tag2"] drawInRect:CGRectMake(20, 20, 40, 15) withFont:[UIFont systemFontOfSize:15]];
[[NSString stringWithString:#"tag3"] drawInRect:CGRectMake(100, 100, 40, 15) withFont:[UIFont systemFontOfSize:15]];
//getting UIImage from bitmap context
CGImageRef _trueMap = CGBitmapContextCreateImage(context);
if (_trueMap) {
UIImage* _map = [UIImage imageWithCGImage:_trueMap];
CFRelease(_trueMap);
//displaying what we got
[mapViewController performSelectorOnMainThread:#selector(setMap:) withObject:_map waitUntilDone:YES];
}
//releasing context and memmory
UIGraphicsPopContext();
CFRelease(context);
free(imageData);
}
Again, no significant code changes between this 2 pieces of code. And when I start this operation like this:
NSOperationQueue* repaintQueue = [[NSOperationQueue alloc] init];
repaintQueue.maxConcurrentOperationCount = 1;
[repaintQueue addOperation:[[[Painter alloc] initWithRootController:self] autorelease]];
It will work. But not always, sometimes image will contain artifacts.
I've also made few screenshots to illustrate the issue, but couldn't post them =(
Anyways, there is a screenshot which shows red line and 3 text lines (which is fine) and a screenshot which shows red line, no text lines and "tag2" written upside down on tab bar controller.
So what's the problem?
I cant use Quartz with NSOperation? Is there some kind of restriction on drawing on separate threads? Is there a way to bypass those restrictions if so?
If anyone ever seen this problem, please reply.
A few calls in your code are form UIKit (the ones prefixed UI), and UIKit is not threadsafe. Any UI operations should be called on the main thread, or you risk weird things happening, such as artefacting.
I can't speak for Quartz2d (or Core Grahics) itself, as I haven't used a lot of it directly. But I do know that UIKit is not threadsafe.
The method drawInRect:withFont: is from a category added to NSString from UIKit (UIStringDrawing). These methods are not threadsafe, and that is why you are seeing strange behaviour.

Is it possible to alter the letter-spacing/kerning of a font with Cocoa Touch?

I've been scouring the Internet for a while now for information on how one can alter the letter-spacing/kerning of a font within UIKit.
My fear is, that like using your own custom fonts, you simply can't. Which would be terrible news.
I know Apple is protecting us from bad design with these constraints, but they're also preventing us from implementing really great design too.
What do people suggest I do?
Use the font with the standard kerning, people probably wouldn't notice the difference anyway.
Find someone's hacked class to get the look the user deserves after parting with their hard earned cash.
Use the method that I've somehow overlooked, and do it with UIKit, pledging my eternal gratitude to the person who imparts this hidden nugget of knowledge.
The accepted answer lost a lot of the formatting, and user363349's answer mostly worked for me but text alignment wasn't working. I modified the code to fix that:
- (void) drawRect:(CGRect)rect
{
// Drawing code
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSelectFont (context, [self.font.fontName cStringUsingEncoding:NSASCIIStringEncoding], self.font.pointSize, kCGEncodingMacRoman);
CGContextSetCharacterSpacing(context, characterSpacing);
CGContextSetFillColorWithColor(context, [[UIColor clearColor] CGColor]);
CGAffineTransform myTextTransform = CGAffineTransformScale(CGAffineTransformIdentity, 1.f, -1.f );
CGContextSetTextMatrix (context, myTextTransform);
// draw 1 but invisbly to get the string length.
CGPoint p =CGContextGetTextPosition(context);
float centeredY = (self.font.pointSize + (self.frame.size.height- self.font.pointSize)/2)-2;
CGContextShowTextAtPoint(context, 0, centeredY, [self.text cStringUsingEncoding:NSASCIIStringEncoding], [self.text length]);
CGPoint v =CGContextGetTextPosition(context);
// calculate width and draw second one.
float width = v.x - p.x;
float destX = 0;
// calculate X position based on alignment
if([self textAlignment] == UITextAlignmentLeft){
destX = 0;
}else if([self textAlignment] == UITextAlignmentCenter){
destX = (self.frame.size.width- width)/2;
}else if([self textAlignment] == UITextAlignmentRight){
destX = self.frame.size.width - width;
}
CGContextSetFillColorWithColor(context, [self.textColor CGColor]);
CGContextShowTextAtPoint(context, destX, centeredY, [self.text cStringUsingEncoding:NSASCIIStringEncoding], [self.text length]);
}
I landed here from Google trying to do the same with UITextField. I implemented something that'll work generically for UILabel below.
#interface AdjustableUILable : UILabel {
CGFloat characterSpacing;
}
#property CGFloat characterSpacing;
#end
#implementation AdjustableUILable
#synthesize characterSpacing;
- (void)drawTextInRect:(CGRect)rect
{
if (characterSpacing)
{
// Drawing code
CGContextRef context = UIGraphicsGetCurrentContext();
CGFloat size = self.font.pointSize;
CGContextSelectFont (context, [self.font.fontName UTF8String], size, kCGEncodingMacRoman);
CGContextSetCharacterSpacing (context, characterSpacing);
CGContextSetTextDrawingMode (context, kCGTextFill);
// Rotate text to not be upside down
CGAffineTransform xform = CGAffineTransformMake(1.0, 0.0, 0.0, -1.0, 0.0, 0.0);
CGContextSetTextMatrix(context, xform);
const char *cStr = [self.text UTF8String];
CGContextShowTextAtPoint (context, rect.origin.x, rect.origin.y + size, cStr, strlen(cStr));
}
else
{
// no character spacing provided so do normal drawing
[super drawTextInRect:rect];
}
}
#end
Then to use it, just set up the label in viewDidLoad or something
- (void)viewDidLoad
{
myLabel.characterSpacing = 2; // point size
}
I tried it with UITextField, but unfortunately it only adds the character spacing after the user has edited the field. -drawTextInRect is only called after the -textFieldShouldEndEditing delegate method. From other forums some suggested this was because UITextField needs to know the rendering of the font in order to display the cursor. Never found a solution for that piece...
You may be able to do what you need using Quartz 2D. Specifically, the CGContextSetCharacterSpacing property can control spacing between letters. I'm not sure about kerning though.
Quartz 2D Programming Guide: Text
I've extended UILabel to change the character spacing. This should work out the box and pulls font, text, color etc from the UILabel itself (proper coding!).
You may notice I draw the text twice, first with clear color. This is to auto center the text in the label. Whilst this may be inefficient - isn't it nice to be auto centered?
Enjoy!
#interface RALabel : UILabel {
}
#end
#implementation RALabel
- (void) drawRect:(CGRect)rect
{
// Drawing code
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSelectFont (context, [self.font.fontName cStringUsingEncoding:NSASCIIStringEncoding], self.font.pointSize, kCGEncodingMacRoman);
CGContextSetCharacterSpacing(context, 1);
CGContextSetFillColorWithColor(context, [[UIColor clearColor] CGColor]);
CGAffineTransform myTextTransform = CGAffineTransformScale(CGAffineTransformIdentity, 1.f, -1.f );
CGContextSetTextMatrix (context, myTextTransform);
// draw 1 but invisbly to get the string length.
CGPoint p =CGContextGetTextPosition(context);
float centeredY = (self.font.pointSize + (self.frame.size.height- self.font.pointSize)/2)-2;
CGContextShowTextAtPoint(context, 0, centeredY, [self.text cStringUsingEncoding:NSASCIIStringEncoding], [self.text length]);
CGPoint v =CGContextGetTextPosition(context);
// calculate width and draw second one.
float width = v.x - p.x;
float centeredX =(self.frame.size.width- width)/2;
CGContextSetFillColorWithColor(context, [self.textColor CGColor]);
CGContextShowTextAtPoint(context, centeredX, centeredY, [self.text cStringUsingEncoding:NSASCIIStringEncoding], [self.text length]);
}
The main problem of CGContextShowText methods - they are diaplays only ASCII strings.
To display UTF strings you need to map your string symbols to glyphs and show glyphs.
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSelectFont (context, [self.font.fontName cStringUsingEncoding:NSASCIIStringEncoding], self.font.pointSize, kCGEncodingMacRoman);
CGContextSetCharacterSpacing(context, characterSpacing);
CGContextSetFillColorWithColor(context, [self.textColor CGColor]);
CGAffineTransform myTextTransform = CGAffineTransformScale(CGAffineTransformIdentity, 1.f, -1.f );
CGContextSetTextMatrix (context, myTextTransform);
CGGlyph glyphs[self.text.length];
CTFontRef fontRef = CTFontCreateWithName((CFStringRef)self.font.fontName, self.font.pointSize, NULL);
CTFontGetGlyphsForCharacters(fontRef, (const unichar*)[self.text cStringUsingEncoding:NSUnicodeStringEncoding], glyphs, self.text.length);
float centeredY = (self.font.pointSize + (self.frame.size.height- self.font.pointSize)/2)-2;
CGContextShowGlyphsAtPoint(context, rect.origin.x, centeredY, (const CGGlyph *)glyphs, self.text.length);