Swift - Drawing text with drawInRect:withAttributes:
The problem is that font
is optional because the convenience contructors now return optional values, so font
needs to be unwrapped to be a value in your dictionary:
if let actualFont = font {
let textFontAttributes = [
NSFontAttributeName: actualFont,
NSForegroundColorAttributeName: textColor,
NSParagraphStyleAttributeName: textStyle
]
text.drawInRect(NSOffsetRect(textRect, 0, 1), withAttributes: textFontAttributes)
}
Swift 3 - NSString.draw(in: rect, withAttributes:) -- Text not being drawn at expected point
How are you setting atPoint
? If you are using the same coordinate space as the screen, that won't work... which is what I suspect is happening.
Suppose your image is 1000 x 2000
, and you are showing it in a UIImageView
that is 100 x 200
. If you tap at x: 50 y: 100
in the view (at the center), and then send that point to your function, it will draw the text at x: 50 y: 100
of the image -- which will be in the upper-left corner, instead of in the center.
So, you need to convert your point from the Image View size to the actual image size.. either before you call your function, or by modifying your function to handle it.
An example (not necessarily the best way to do it):
// assume:
// View Size is 100 x 200
// Image Size is 1000 x 2000
// tapPoint is CGPoint(x: 50, y: 100)
let xFactor = image.size.width / imageView.frame.size.width
// xFactor now equals 10
let yFactor = image.size.height / imageView.frame.size.height
// yFactor now equals 10
let convertedPoint = CGPoint(x: tapPoint.x * xFactor, y: tapPoint.y * yFactor)
convertedPoint
now equals CGPoint(x: 500, y: 1000), and you can send that as the atPoint
value in your call to addTextToImage
.
drawInRect: withAttributes text center vertically or put some padding
First you need to calculate the height of the text, with this information and the height of your bounding rectangle, you can easily compute the new rectangle to center the text.
I will share a piece of code I use to center vertically. In my case I use a different drawInRect function (drawInRect:withFont...) and I use sizeWithFont
to calculate the size of the text. YOu would either adapt this code to use the functions you're already using (with attributes), or either use the functions I'm posting here.
UIFont *font = [UIFont systemFontOfSize:14];
CGSize size = [text sizeWithFont:font];
if (size.width < rect.size.width)
{
CGRect r = CGRectMake(rect.origin.x,
rect.origin.y + (rect.size.height - size.height)/2,
rect.size.width,
(rect.size.height - size.height)/2);
[text drawInRect:r withFont:font lineBreakMode:UILineBreakModeClip alignment:UITextAlignmentLeft];
}
Draw text at 90 degree on NSImage
Here is demo of approach with calculable location
class DemoView: NSView {
override func draw(_ rect: NSRect) {
// .. your drawing code here
NSColor.red.set() // just for demo
rect.fill() // just for demo
if let context = NSGraphicsContext.current?.cgContext {
context.saveGState()
let text: NSString = "Hello World"
let attributes = [
NSAttributedString.Key.foregroundColor: NSColor.white,
NSAttributedString.Key.font: NSFont.systemFont(ofSize: 24)
]
let size = text.size(withAttributes: attributes)
context.translateBy(x: size.height, y: (rect.height - size.width) / 2)
context.rotate(by: CGFloat.pi / 2)
text.draw(at: .zero, withAttributes: attributes)
context.restoreGState()
}
}
}
NSString drawInRect:withAttributes: is not drawing text with greater width than the rect in iOS 7.0.3
Try this:
-(void)drawText:(NSString *)string {
// this method must be called after a valid graphic context
// is configured, it can be called in drawRect:, or a bitmap or pdf context is configured.
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [UIColor blackColor].CGColor);
NSDictionary *attrsDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
myFontForContentBold, NSFontAttributeName,
[NSNumber numberWithFloat:1.0], NSBaselineOffsetAttributeName, nil];
NSAttributedString *richText = [[NSAttributedString alloc] initWithString:string attributes:attrsDictionary];
CGRect textRect = [richText boundingRectWithSize:CGSizeMake(400, CGFLOAT_MAX)
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
context:nil];
textRect = CGRectMake(130, 80, 400, textRect.size.height);
[richText drawInRect:textRect];
}
If you need to draw large chunk of text, consider Text Kit
.
The Text Kit
approach:
- (void)drawAddressList:(NSArray *)list atPoint:(CGPoint)point {
NSTextStorage *textStorage = [[NSTextStorage alloc] init];
NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:CGSizeMake(400.0f, FLT_MAX)];
[textStorage addLayoutManager:layoutManager];
[layoutManager addTextContainer:textContainer];
textContainer.lineFragmentPadding = 0.0f;
NSDictionary *attributes = @{NSFontAttributeName:[UIFont preferredFontForTextStyle:UIFontTextStyleBody]};
// it's a simple attributes for illustration
for (NSString *address in list) {
NSString *buffer = [NSString stringWithFormat:@"%@\n", address];
[textStorage appendAttributedString:[[NSAttributedString alloc] initWithString:buffer attributes:attributes]];
}
[layoutManager ensureLayoutForTextContainer:textContainer];
CGRect rect = [layoutManager usedRectForTextContainer:textContainer];
rect.size.width = 400.0f;
rect.size.height = ceilf(rect.size.height);
rect.origin = point; // this is the frame needed to draw the address list
[layoutManager drawGlyphsForGlyphRange:NSMakeRange(0, textStorage.length) atPoint:point];
}
How to set text color when using drawInRect?
You need to set the color of the text in the "attributes
" dictionary you pass in via:
[textContent drawInRect: CGRectInset(textRect, 0, 6) withAttributes:attributes];
My suggest would be:
NSDictionary *attributes = @{ NSFontAttributeName:helveticaFont,
NSParagraphStyleAttributeName:paragraphStyle,
NSForegroundColorAttributeName: [NPSColor whiteColor]}
How to use drawInRect:withAttributes: instead of drawAtPoint:forWidth:withFont:fontSize:lineBreakMode:baselineAdjustment: in iOS 7
You can use NSDictionary
and apply attributes like this:
NSFont *font = [NSFont fontWithName:@"Palatino-Roman" size:14.0];
NSDictionary *attrsDictionary =
[NSDictionary dictionaryWithObjectsAndKeys:
font, NSFontAttributeName,
[NSNumber numberWithFloat:1.0], NSBaselineOffsetAttributeName, nil];
Use attrsDictionary
as argument.
Refer: Attributed String Programming Guide
Refer: Standard Attributes
SWIFT:
IN String drawInRect is not available but we can use NSString instead:
let font = UIFont(name: "Palatino-Roman", size: 14.0)
let baselineAdjust = 1.0
let attrsDictionary = [NSFontAttributeName:font, NSBaselineOffsetAttributeName:baselineAdjust] as [NSObject : AnyObject]
let str:NSString = "Hello World"
str.drawInRect(CGRectZero, withAttributes: attrsDictionary)
NSString drawInRect:withAttributes: not correctly centered when using NSKernAttributeName
The kerning must be applied only to the first character of each kerning pair.
If you want to display a string with kerning between all n
characters then the kerning
attribute must be set for the first n-1
characters.
Instead of:
[text drawInRect:textRect withAttributes:@{NSKernAttributeName: @(self.kerning),
NSForegroundColorAttributeName: textColor,
NSFontAttributeName: font,
NSParagraphStyleAttributeName: paragraphStyle}];
you have to create an attributed string so that you can set the kerning attribute
for a specific range instead of the entire string:
NSMutableAttributedString *as = [[NSMutableAttributedString alloc]
initWithString:text
attributes:@{
NSForegroundColorAttributeName: textColor,
NSFontAttributeName: font,
NSParagraphStyleAttributeName: paragraphStyle}];
[as addAttribute:NSKernAttributeName
value:@(self.kerning)
range:NSMakeRange(0, [text length] - 1)];
[as drawInRect:textRect];
Here the result for the string "1234" and kerning -4.0:
Related Topics
Swiftui - Wait Until Firestore Getdocuments() Is Finished Before Moving On
Self' Captured by a Closure Before All Members Were Initialized
Can an Enum Contain Another Enum Values in Swift
Swiftyjson - Call Can Throw, But It Is Marked with 'Try' and the Error Is Not Handled
How to Handle Closure Recursivity
Count Elements of Array Matching Condition in Swift
How to Test If Objects Conforming to the Same Protocol Are Identical in Swift Without Casting
Parse.Com Querying User Class (Swift)
Why I Can Change/Reassigned a Constant Value That Instantiated from a Class
Unexpectedly Large Realm File Size
Why Can't I Use a Tuple Constant as a Case in a Switch Statement
Converting a C-Style for Loop That Uses Division for the Step to Swift 3
Know When an Iteration Over Array with Async Method Is Finished
How to Test Whether Generic Variable Is of Type Anyobject
Extending Collection with a Recursive Property/Method That Depends on the Element Type
Does Swift Guarantee the Storage Order of Fields in Classes and Structs