I am searching about drawing bordered text on UIView.
Implemented following method :
- (void)drawRect:(CGRect)rect {
//TODO draw bordered text here.
}
How to draw it ?
I mean each letter is bordered of whole text.
Thanks in advance.
To display bordered text (if I understand correctly what you want) you should set text drawing mode to kCGTextFillStroke (and set appropriate values for text drawing parameters, such as stroke and fill colors etc)
// Choose appropriate text font
CGContextSelectFont(context, [[UIFont boldSystemFontOfSize:24].fontName UTF8String], (int)fontSize, kCGEncodingMacRoman);
// Set text drawing mode
CGContextSetTextDrawingMode(context, kCGTextFillStroke);
// Set appropriate color/line width values
// Here we're set to draw white letters with black border
CGContextSetRGBStrokeColor(context, 0, 0, 0, 1);
CGContextSetRGBFillColor(context, 1, 1, 1, 1);
CGContextSetLineWidth(context, 1);
// Set this transformations or text will be displayed upside-down
CGAffineTransform xform = CGAffineTransformMake(1.0, 0.0, 0.0, -1.0, 0.0, 0.0);
CGContextSetTextMatrix(context, xform);
// Display text
CGContextShowTextAtPoint(...);
Edit: As Quartz does not work well with unicode, to draw unicode strings you'll need to use other APIs. I managed to draw "bordered" unicode string using NSAttributedString and OHAttributedLabel (thanks to this answer for that custom control). Sample code to get required string in some view controller:
- (void)viewDidLoad
{
[super viewDidLoad];
NSMutableAttributedString *s = [[NSMutableAttributedString alloc] initWithString:#"您好世界"];
[s addAttributes:[NSDictionary dictionaryWithObject:[NSNumber numberWithFloat:-3.0f] forKey:(NSString*)kCTStrokeWidthAttributeName]
range:NSMakeRange(0, [s length])];
[s addAttributes:[NSDictionary dictionaryWithObject:(id)[UIColor greenColor].CGColor forKey:(NSString*)kCTStrokeColorAttributeName]
range:NSMakeRange(0, [s length])];
[s addAttributes:[NSDictionary dictionaryWithObject:(id)[UIColor redColor].CGColor forKey:(NSString*)kCTForegroundColorAttributeName]
range:NSMakeRange(0, [s length])];
[s setFont:[UIFont boldSystemFontOfSize:28.0f]];
[s setTextColor:[UIColor redColor]];
OHAttributedLabel *l = [[OHAttributedLabel alloc] initWithFrame:CGRectMake(40.0f, 40.0f, 200.0f, 80.0f)];
l.centerVertically = YES;
[l setAttributedText: s];
[self.view addSubview: l];
[l release];
}
Note that you'll need to link with CoreText.framework to make that work, and code uses some convenience methods provided in OHAttributedLabel implementation
I tried this way:
draw with NSString drawAtPoint:withFont: or drawInRect:withFont:
set line joint and lin cap to round.
draw your text with the border color using drawing mode kCGTextStroke, be sure to set the line width a little wider,
then draw your text with its inner color using drawing mode kCGTextFill.
may be you need a little adjustment in the position to make it perfect.
Related
I want to apply shadow effect to text of type NSString.
Though I understand how to apply shadow effect to UILabel and other view elements,
I can't figure out a way of adding shadow effect to text.
I am currently drawing text as follows:
NSString *text = #"Hello";
[text drawAtPoint:point width withFont:font minFontSize:22.0f actualFontSize:&actualFontSize lineBreakMode:UILineBreakModeTailTruncation baselineAdjustment:UIBaselineAdjustmentAlignBaselines];
I would really appreciate any help. Thanks!
Try CGContextSetShadow() for adding shadow to text
- (void)drawRect:(CGRect)rect
{
NSString *string = #"Hello World!";
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetShadow(context, CGSizeMake(20.0f, 20.0f), 10.0f);
[string drawAtPoint:CGPointMake(100.0f, 100.0f) withFont:[UIFont boldSystemFontOfSize:36.0f]];
}
I'm trying to draw text with some part of it in different color
First issue it that when using the following code:
NSMutableAttributedString *test1 = [[NSMutableAttributedString alloc] initWithString:#"fi"];
// Should color only the first character "f"
[test1 addAttribute:(id)kCTForegroundColorAttributeName value:(id)[[UIColor redColor] CGColor] range:NSMakeRange(0, 1)];
CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)test1);
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, CGRectMake(0, 0, self.frame.size.width, self.frame.size.height));
CTFrameRef textFrame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, test1.string.length), path, NULL);
And then drawing the frame:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 0, self.bounds.size.height);
// Flip the coordinate system
CGContextScaleCTM(context, 1.0, -1.0);
CTFrameDraw(textFrame, context);
CGContextRestoreGState(context);
For some reason it shows both "f" and "i" colored in red. When I change the text to "f1" , "f!", or anything else it works OK. Even when I use "afi" and range as (1,1) it colors both "f" and "i" as if it treats it as a single character.
Second issue, when drawing inside a frame with width which is less that the width of the text:
NSMutableAttributedString *test2 = [[NSMutableAttributedString alloc] initWithString:#"aaaaaaaaaaaaaaaaaaaa"];
CTLineBreakMode lineBreakMode;
lineBreakMode = kCTLineBreakByTruncatingTail;
// Apply selected truncation
CTParagraphStyleSetting paragraphStyleSetting = {kCTParagraphStyleSpecifierLineBreakMode, sizeof(CTLineBreakMode), &lineBreakMode};
CTParagraphStyleRef paragraphStyle = CTParagraphStyleCreate(¶graphStyleSetting, 1);
[test1 addAttribute:(NSString *)kCTParagraphStyleAttributeName value:(__bridge id)paragraphStyle range:NSMakeRange(0, test2.string.length)];
[test1 addAttribute:(id)kCTForegroundColorAttributeName value:(id)[[UIColor redColor] CGColor] range:NSMakeRange(7, 16)];
CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)test2);
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, CGRectMake(0, 0, self.frame.size.width, self.frame.size.height));
CTFrameRef textFrame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, test1.string.length), path, NULL);
There are total 20 characters,
the ones at indexes 7 to 16 appear colored in red (as they should), CoreText adds a truncation mark as I requested but the problem is that it colored in red too and I want it to remain black (as the remaining truncated characters color is black).
I did some study and it seems that when increasing the frame size by some (random/font specific?) value in the command:
CGPathAddRect(path, NULL, CGRectMake(0, 0, self.frame.size.width + 10.0f, self.frame.size.height));
It keeps the truncation mark black, but because of the extra space sometimes the truncation mark is clipped
NSMutableAttributedString * string = [[NSMutableAttributedString alloc] initWithString:#"firstsecondthird"];
[string addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:NSMakeRange(0,5)];
[string addAttribute:NSForegroundColorAttributeName value:[UIColor greenColor] range:NSMakeRange(5,6)];
[string addAttribute:NSForegroundColorAttributeName value:[UIColor blueColor] range:NSMakeRange(11,5)];
This sounds like an issue with ligatures. A good font has special characters for some combinations of characters that look better than the individual characters. For example, in a lot of fonts To doesn't look good if rendered individually since there's a lot of space between the vertical bar of T and the start of the o. So ligatures are provided that have a more appealing spacing. Sometimes the characters even get connect, your fi is a good example: some fonts provide a ligature that makes the top of the f double as the dot for the i.
So you may want to turn the ligatures off but setting the attribute kCTLigatureAttributeName to 0.
The text is coming form a database. I would like to use it for a button and underline the text of the button. How can I do that?
In iOS 6, NSAttributedString is used modifying the text, you can use "NSMutableAttributedString" for multi color text, font, style, etc using single UIButton or UILabel.
NSMutableAttributedString *titleString = [[NSMutableAttributedString alloc] initWithString:#"The Underlined text"];
// making text property to underline text-
[titleString addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInteger:NSUnderlineStyleSingle] range:NSMakeRange(0, [titleString length])];
// using text on button
[button setAttributedTitle: titleString forState:UIControlStateNormal];
For this you can subclass UILabel and overwrite its -drawRect method and then use your own UILabel and add a UIButton over it of custom type.
Make your drawRect method in UILabel as
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetRGBStrokeColor(context, 207.0f/255.0f, 91.0f/255.0f, 44.0f/255.0f, 1.0f);
CGContextSetLineWidth(context, 1.0f);
CGContextMoveToPoint(context, 0, self.bounds.size.height - 1);
CGContextAddLineToPoint(context, self.bounds.size.width, self.bounds.size.height - 1);
CGContextStrokePath(context);
[super drawRect:rect];
}
In Swift 3 the following extension can be used for an underline:
extension UIButton {
func underlineButton(text: String) {
let titleString = NSMutableAttributedString(string: text)
titleString.addAttribute(NSUnderlineStyleAttributeName, value: NSUnderlineStyle.styleSingle.rawValue, range: NSMakeRange(0, text.characters.count))
self.setAttributedTitle(titleString, for: .normal)
}
}
To make this a bit simpler (it's a common requirement) I've built a simple UIButton subclass called BVUnderlineButton that you can drop straight into your projects.
It's on Github at https://github.com/benvium/BVUnderlineButton (MIT licence).
You can use it in a XIB / Storyboard or direct via code.
I'm having a problem with NSString drawInRect method.
I have a very large text to be painted over a 768x1024 size Rect and I obtain this:
As you can see the only text line that reaches the whole screen is the last one, I don't know why other lines don't.
This is my code:
//The drawing rectangle
CGRect textRect = CGRectMake(1, 4, 768, 1024);
[[UIColor blackColor] set];
CGContextSetTextDrawingMode(context, kCGTextFillStroke);
//texto_completo is a very large text String
[texto_completo drawInRect:textRect withFont:font lineBreakMode:UILineBreakModeCharacterWrap alignment:UITextAlignmentLeft];
CGImageRef cgImage = CGBitmapContextCreateImage(context);
UIImage *img_con_texto = [[UIImage alloc] initWithCGImage:cgImage];
UIGraphicsPopContext();
CGContextRelease(context);
CGImageRelease(cgImage);
Could you help me understand this behaviour?
It looks like the APple algorithm is over aggressive.
The best solution is to set the alignment to UITextAlignmentCenter, that is the only way top get the characters flush right. In my test the lime breaks were the same as with alignment UITextAlignmentLeft.
Given a rectangular area, I want to render some text using a specific font and have the rendered text fill out the rectangle. As in the image below:
This is not the same as just changing font size
Rendering it as a bitmap and then scale it is not an option (it looks horrible)
Vector graphics is the way to do it
Solution
I came up with the following which seems to work for my purposes. The code draws a single line of text scaling to fill the bounds. Subclass UIView and replace drawRect as follows.
- (void)drawRect:(CGRect)rect
{
[self drawScaledString:#"Abcde"];
}
- (void)drawScaledString:(NSString *)string
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
NSAttributedString *attrString = [self generateAttributedString:string];
CFAttributedStringSetAttribute((CFMutableAttributedStringRef)attrString, CFRangeMake(0, string.length),
kCTForegroundColorAttributeName, [UIColor redColor].CGColor);
CTLineRef line = CTLineCreateWithAttributedString((CFAttributedStringRef) attrString);
// CTLineGetTypographicBounds doesn't give correct values,
// using GetImageBounds instead
CGRect imageBounds = CTLineGetImageBounds(line, context);
CGFloat width = imageBounds.size.width;
CGFloat height = imageBounds.size.height;
CGFloat padding = 0;
width += padding;
height += padding;
float sx = self.bounds.size.width / width;
float sy = self.bounds.size.height / height;
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 1, self.bounds.size.height);
CGContextScaleCTM(context, 1, -1);
CGContextScaleCTM(context, sx, sy);
CGContextSetTextPosition(context, -imageBounds.origin.x + padding/2, -imageBounds.origin.y + padding/2);
CTLineDraw(line, context);
CFRelease(line);
}
- (NSAttributedString *)generateAttributedString:(NSString *)string
{
CTFontRef helv = CTFontCreateWithName(CFSTR("Helvetica-Bold"),20, NULL);
CGColorRef color = [UIColor blackColor].CGColor;
NSDictionary *attributesDict = [NSDictionary dictionaryWithObjectsAndKeys:
(id)helv, (NSString *)kCTFontAttributeName,
color, (NSString *)kCTForegroundColorAttributeName,
nil];
NSAttributedString *attrString = [[[NSMutableAttributedString alloc]
initWithString:string
attributes:attributesDict] autorelease];
return attrString;
}
Example usage:
CGRect rect = CGRectMake(0, 0, 50, 280);
MyCTLabel *label = [[MyCTLabel alloc] initWithFrame:rect];
label.backgroundColor = [UIColor whiteColor];
[self addSubview:label];
You can set the UILabel transform property and scale the width:
[myLabel sizeToFit];
myLabel.transform = CGAffineTransformMakeScale(0.5, 1.0);
You could try CoreText. Get a CTFramesetter, calculate its rect, then calculate the affine transform necessary to compress that rect into the bounds you want and set that as the CTM. Then when you draw the text, it should stretch it appropriately at full quality.
you can also try with the UILabel's #property(nonatomic) BOOL adjustsFontSizeToFitWidth and #property minimumFontSize
Initially you can set the much higher value for font property and also initialize the minimumFontSize with a minimum font value.