How to change color of NSString in drawAtPoint - objective-c

I have this chunk of code here that draws a block with a one-character string on it:
CGContextDrawImage(context, CGRectMake([blok getLocation].x * xunit, [blok getLocation].y * yunit, 40, 40), [blok getImage].CGImage);
[[blok getSymbol] drawAtPoint:CGPointMake([blok getLocation].x * xunit+15, [blok getLocation].y * yunit) withFont:[UIFont fontWithName:#"Helvetica" size:24]];
It's working fine, but I've been doing some layout changes, and now I need it so that the string drawn will be white. Using the methods for setting the fill color and the stroke color haven't done anything. Is there some other way to do this?

Set the foregroundcolor in the attributes and use the draw withAttributes functions
NSDictionary *attributes = [NSDictionary dictionaryWithObjects:
#[font, [UIColor whiteColor]]
forKeys:
#[NSFontAttributeName, NSForegroundColorAttributeName]];
[string drawInRect:frame withAttributes:attributes];

Have you tried:
CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(), textColor);
For example:
CGContextDrawImage(context, CGRectMake([blok getLocation].x * xunit, [blok getLocation].y * yunit, 40, 40), [blok getImage].CGImage);
CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(), textColor);
[[blok getSymbol] drawAtPoint:CGPointMake([blok getLocation].x * xunit+15, [blok getLocation].y * yunit) withFont:[UIFont fontWithName:#"Helvetica" size:24]];

This is what I use for drawing labels:
- (void)_drawLabel:(NSString *)label withFont:(UIFont *)font forWidth:(CGFloat)width
atPoint:(CGPoint)point withAlignment:(UITextAlignment)alignment color:(UIColor *)color
{
// obtain current context
CGContextRef context = UIGraphicsGetCurrentContext();
// save context state first
CGContextSaveGState(context);
// obtain size of drawn label
CGSize size = [label sizeWithFont:font
forWidth:width
lineBreakMode:UILineBreakModeClip];
// determine correct rect for this label
CGRect rect = CGRectMake(point.x, point.y - (size.height / 2),
width, size.height);
// set text color in context
CGContextSetFillColorWithColor(context, color.CGColor);
// draw text
[label drawInRect:rect
withFont:font
lineBreakMode:UILineBreakModeClip
alignment:alignment];
// restore context state
CGContextRestoreGState(context);
}

Related

How to convert uitextview content to a image in custom size?

I create a uitextview and I can add text and images to it with NSAttributedString and NSTextAttachment
self.tvContent = [[UITextView alloc] initWithFrame:CGRectMake(0, 20,
self.view.frame.size.width, self.view.frame.size.height - 64)];
self.tvContent.font = [UIFont fontWithName:#"Arial" size:16.0f];
self.tvContent.backgroundColor = [UIColor whiteColor];
self.tvContent.textContainerInset = UIEdgeInsetsMake(10, 10, 10, 10);
self.tvContent.contentInset = UIEdgeInsetsMake(0, 0, 10, 0);
self.tvContent.layoutManager.allowsNonContiguousLayout=NO;
[self.view addSubview:self.tvContent];
And I convert its content to image with following code
- (UIImage *)imageFromView:(UITextView *)view {
CGRect tmpFrame = self.view.frame;
CGRect aFrame = view.frame;
aFrame.size.height = [view sizeThatFits:[[UIScreen mainScreen] bounds].size].height;
view.frame = aFrame;
UIGraphicsBeginImageContext([view sizeThatFits:[[UIScreen mainScreen] bounds].size]);
CGContextRef resizedContext = UIGraphicsGetCurrentContext();
[view.layer renderInContext:resizedContext];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
view.frame = tmpFrame;
return image;
}
This image's max-width is 375(iPhone 6), if I wrote less content, such as 1234, the image's width is 40, so how can I convert the content to image with custom size, such as the width is 750, and the height is as long as the content height? Any ideas? many thanks.
The UITextView only renders within it's current frame, so you need to either resize the frame to the desired size before rendering, or create a temporary UITextView object to use for rendering purpose.
Here is how rendering with a temporary object can be done:
// Your call to get the image
UIImage *image = [self imageFromTextView:self.tvContent width:750.0]; // Any width you want
// Your text view properties that also can be used for rendering purpose
- (void)setupTextViewProperties:(UITextView *)textView {
textView.font = [UIFont fontWithName:#"Arial" size:16.0f];
textView.backgroundColor = [UIColor whiteColor];
textView.textContainerInset = UIEdgeInsetsMake(10, 10, 10, 10);
textView.contentInset = UIEdgeInsetsMake(0, 0, 10, 0);
textView.layoutManager.allowsNonContiguousLayout=NO;
}
Set up of the temporary UITextView object used for rendering.
- (UIImage *)imageFromTextView:(UITextView *)view width:(CGFloat)desiredWidth {
// Create a new temporary UITextView to use for rendering
CGRect frame = CGRectMake(0.0, 0.0, desiredWidth, 0.0);
UITextView *temporaryTextView = [[UITextView alloc] initWithFrame:frame];
// Setup the temporary text view
// You can have different font, colors etc from the original view
[self setupTextViewProperties:temporaryTextView];
temporaryTextView.attributedText = view.attributedText;
// Calculate the height needed for content with width
CGSize size = CGSizeMake(desiredWidth, 0.0);
size = [temporaryTextView sizeThatFits:size];
// Resize the temporary view
// Note that the calculated size can contain a width smaller that desired with
temporaryTextView.frame = CGRectMake(0.0, 0.0, desiredWidth, size.height);
// Get the image
return [self imageFromView:temporaryTextView];
}
Helper that can render any view as an image.
- (UIImage *)imageFromView:(UIView *)view {
CGSize size = view.bounds.size;
UIGraphicsBeginImageContextWithOptions(size, YES, 0.0);
CGContextRef context = UIGraphicsGetCurrentContext();
[view.layer renderInContext:context];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}

Autosize Text in Label for PaintCode CGContext

I'm using the following to draw text inside a Bezier Path. How can i adjust this to allow the text to autosize.
EDIT
I was able to update to iOS7 methods but still nothing. I can autosize text within a UILabel fine, but because this is CGContext it is harder
NSString* textContent = #"LOCATION";
NSMutableParagraphStyle* locationStyle = NSMutableParagraphStyle.defaultParagraphStyle.mutableCopy;
locationStyle.alignment = NSTextAlignmentCenter;
NSDictionary* locationFontAttributes = #{NSFontAttributeName: [UIFont fontWithName:myFont size: 19], NSForegroundColorAttributeName: locationColor, NSParagraphStyleAttributeName: locationStyle};
CGFloat locationTextHeight = [textContent boundingRectWithSize: CGSizeMake(locationRect.size.width, INFINITY) options: NSStringDrawingUsesLineFragmentOrigin attributes: locationFontAttributes context: nil].size.height;
CGContextSaveGState(context);
CGContextClipToRect(context, locationRect);
[textContent drawInRect: CGRectMake(CGRectGetMinX(locationRect), CGRectGetMinY(locationRect) + (CGRectGetHeight(locationRect) - locationTextHeight) / 2, CGRectGetWidth(locationRect), locationTextHeight) withAttributes: locationFontAttributes];
CGContextRestoreGState(context);
Try using this method of NSAttributedString:
- (CGRect)boundingRectWithSize:(CGSize)size
options:(NSStringDrawingOptions)options
context:(NSStringDrawingContext *)context;
Where the context will provide you actualScaleFactor.
The usage is something like this:
NSAttributedString *string = ...;
NSStringDrawingContext *context = [NSStringDrawingContext new];
context.minimumScaleFactor = 0.5; // Set your minimum value.
CGRect bounds = [string boundingRectWithSize:maxSize
options:NSStringDrawingUsesLineFragmentOrigin
context:context];
CGFloat scale = context. actualScaleFactor;
// Use this scale to multiply font sizes in the string, so it will fit.

Core Text a bit smaller than NSString drawInRect:?

In theory, these should be the same size, but they're not:
The text in blue is from Core Text, in black is from -[NSString drawInRect:]. Here is the code:
//Some formatting to get the correct frame
int y = MESSAGE_FRAME.origin.y + 8;
if (month) y = y + 27;
int height = [JHomeViewCellContentView heightOfMessage:self.entry.message];
CGRect rect = CGRectMake(MESSAGE_FRAME.origin.x + 8, y, MESSAGE_FRAME.size.width - 16, height);
//Draw in rect method
UIFont *font = [UIFont fontWithName:#"Crimson" size:15.0f];
[[UIColor colorWithWhite:0.2 alpha:1.0] setFill];
[self.entry.message drawInRect:rect withFont:font lineBreakMode:UILineBreakModeWordWrap alignment:UITextAlignmentLeft];
//Frame change to suit CoreText
CGRect rect2 = CGRectMake(MESSAGE_FRAME.origin.x + 8, self.bounds.size.height - y - height, MESSAGE_FRAME.size.width - 16, height);
//Core text method
CTFontRef fontRef = CTFontCreateWithName((CFStringRef)#"Crimson", 15.0f, NULL);
NSDictionary *attDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
(__bridge_transfer id)fontRef, (NSString *)kCTFontAttributeName,
(id)[[UIColor blueColor] CGColor], (NSString *)kCTForegroundColorAttributeName,
nil];
NSAttributedString *attString = [[NSAttributedString alloc] initWithString:self.entry.message attributes:attDictionary];
//Flip the coordinate system
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, rect2);
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge_retained CFAttributedStringRef)attString);
CTFrameRef theFrame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, [attString length]), path, NULL);
CFRelease(framesetter);
CFRelease(path);
CTFrameDraw(theFrame, context);
CFRelease(theFrame);
The font is the same. I don't understand why one is being rendered differently.
It’s because NSLayoutManager uses some unusual heuristics to cope with certain fonts.
See How does line spacing work in Core Text? (and why is it different from NSLayoutManager?) for more detail.

Draw text from uitextview to pdf as it is

I have 4 editable uitextviews, user can set its font font color etc. Now i want to draw pdf of these textviews, but using [self.view.layer renderInContext:UIGraphicsGetCurrentContext()] method causes to lose its quality, so i cannot use this method, instead i am iterating over subviews and drawing the text in pdf. Now my problem is that for some textviews text is printed correctly in pdf,but for other textviews text is not drawn in proper position.This is my code to draw pdf from textview.
NSArray *arrayOfsubviews=[self.view subviews];
for (int i=0; i<[arrayOfsubviews count]; i++) {
if ([[arrayOfsubviews objectAtIndex:i]isKindOfClass:[UITextView class]]) {
UITextView *texts=[arrayOfsubviews objectAtIndex:i];
CTFontRef ctFont = CTFontCreateWithName((__bridge CFStringRef)texts.font.fontName, texts.font.pointSize,NULL);
CGColorRef color = texts.textColor.CGColor;
NSDictionary *attributesDict = [NSDictionary dictionaryWithObjectsAndKeys:
(__bridge id)ctFont, (id)kCTFontAttributeName,
color, (id)kCTForegroundColorAttributeName,nil];
NSAttributedString *stringToDraw = [[NSAttributedString alloc] initWithString:texts.text
attributes:attributesDict];
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef) stringToDraw);
CGRect rect=CGRectMake(texts.frame.origin.x,916-texts.frame.origin.y-texts.frame.size.height, texts.frame.size.width, texts.frame.size.height);
CGMutablePathRef pathRef = CGPathCreateMutable();
CGPathAddRect(pathRef, NULL,rect);
CTFrameRef frameRef = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0),pathRef, NULL);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextTranslateCTM(context, 0,916);
CGContextScaleCTM(context, 1.0, -1.0);
CTFrameDraw(frameRef, context);
CGContextRestoreGState(context);
CGPathRelease(pathRef);
}
}
UIGraphicsEndPDFContext();
I use this to draw my text, works fairly well.. maybe it can point you in the right direction.
#define kBorderInset 20.0
#define kMarginInset 10.0
- (void) drawText{
CGContextRef currentContext = UIGraphicsGetCurrentContext();
CGContextSetRGBFillColor(currentContext, 0.0, 0.0, 0.0, 1.0);
NSString *textToDraw = #"Your Text Here";
UIFont *font = [UIFont systemFontOfSize:14.0];
CGSize stringSize = [textToDraw sizeWithFont:font
constrainedToSize:CGSizeMake(pageSize.width - 2*kBorderInset-2*kMarginInset, pageSize.height - 2*kBorderInset - 2*kMarginInset)
lineBreakMode:UILineBreakModeWordWrap];
CGRect renderingRect = CGRectMake(kBorderInset + kMarginInset, kBorderInset + kMarginInset + 350.0, pageSize.width - 2*kBorderInset - 2*kMarginInset, stringSize.height);
[textToDraw drawInRect:renderingRect
withFont:font
lineBreakMode:UILineBreakModeWordWrap
alignment:UITextAlignmentLeft];
}
You want to use drawInContext: instead of renderInContext:. renderInContext will rasterize the text, drawInContext write encode it as editable, resolution-independent text.
Some looses of quality or blurry views are because of the frame. The vales for locate a frame in the screen are CGFloat, so you could have a frame located in the point (0.5, 5.7), obviously this dowsn't have any sense and the point.x must be 0 or 1. Decimal values confuse the Operative System, so it's recommend to ensure that frames have always values with no decimal numbers.
You can ges some more information here: http://www.cobiinteractive.com/blog/2011/06/objective-c-blurry-uiview/ or here: http://www.markj.net/iphone-uiview-blurred-blurry-view/

How to work around poor text rendering in text backed by a CALayer

I have some variable text in an NSTextField that renders on a CALayer background view. As a CALayer does not support sub-pixel aliasing for text rendering of any text on top of it, this text looks rubbish.
A bit of googling reveals the reasons why this is, and that text must be rendered onto an opaque background to have SPA enabled. Rendering onto an opaque background is something I'd like to avoid if at all possible in this case. Is there a better workaround?
I am completely amenable to rendering the text myself into an NSImage, if that will help, but I can't find any confirmed reports that it will.
It looks absolutely fine in Interface Builder so I know the secret is somewhere inside this computer just straining to get out.
Workaround found. Nothing in Quartz can render text with Sub-Pixel Aliasing on top of a transparent background, seemingly. However, you CAN render text to an offscreen bitmap buffer, providing that offscreen bitmap buffer has been created in the correct fashion. The background of this buffer must be opaque.
My view previously had a PNG background that was slightly transparent. I could have simply made this background opaque and rendered to it without problem, but as this view needs to fade in and out, it needed to be CALayer-backed, so the text renders once properly, and then subsequently renders without Sub-Pixel Aliasing.
Here's my code. It seems incredibly verbose, I'd love it if anyone could help me whittle it down. It assumes you have an NSImageView called _title and an NSString called title.
// Create the attributed string
NSMutableAttributedString *attStr = [[[NSMutableAttributedString alloc] initWithString: title] autorelease];
NSRange strRange = NSMakeRange(0, [attStr length]);
NSFont *font = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize: NSSmallControlSize]];
[attStr addAttribute: NSForegroundColorAttributeName value: [NSColor whiteColor] range: strRange];
[attStr addAttribute: NSFontAttributeName value: font range: strRange];
NSMutableParagraphStyle *paraStyle = [[[NSParagraphStyle defaultParagraphStyle] mutableCopy] autorelease];
[paraStyle setAlignment: NSCenterTextAlignment];
[paraStyle setLineBreakMode: NSLineBreakByTruncatingMiddle];
[attStr addAttribute: NSParagraphStyleAttributeName value: paraStyle range: strRange];
// Set up the image context
NSUInteger bytesPerPixel = 4;
NSUInteger bytesPerRow = bytesPerPixel * [_title frame].size.width;
NSUInteger bitsPerComponent = 8;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
// These constants are necessary to enable sub-pixel aliasing.
CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;
// Create the memory buffer to be used as the context's workspace
unsigned char *contextBuffer = malloc([_title frame].size.height * [_title frame].size.width * 4);
CGContextRef context = CGBitmapContextCreate(contextBuffer,
[_title frame].size.width,
[_title frame].size.height,
bitsPerComponent,
bytesPerRow,
colorSpace,
bitmapInfo);
[NSGraphicsContext saveGraphicsState];
[NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:context flipped:NO]];
CGContextSetFillColorWithColor(context, CGColorGetConstantColor(kCGColorBlack));
CGRect rectangle = CGRectMake(0, 0, [_title frame].size.width,[_title frame].size.height);
CGContextAddRect(context, rectangle);
CGContextFillPath(context);
CTLineRef line = CTLineCreateWithAttributedString((CFAttributedStringRef)attStr);
CGContextSetTextPosition(context, 10.0, 10.0);
CTLineDraw(line, context);
CFRelease(line);
// Create a data provider from the context buffer in memory
CGDataProviderRef dataProvider = CGDataProviderCreateWithData(NULL, contextBuffer, bytesPerRow * [_title frame].size.height, NULL);
// Create an image from the data provider
CGImageRef imageRef = CGImageCreate ([_title frame].size.width,
[_title frame].size.height,
bitsPerComponent,
bytesPerPixel * 8,
bytesPerRow,
colorSpace,
bitmapInfo,
dataProvider,
NULL,
false,
kCGRenderingIntentDefault
);
// Turn it into an NSImage
NSImage *newImage = [[[NSImage alloc] initWithCGImage:imageRef size: NSZeroSize] autorelease];
CGDataProviderRelease(dataProvider);
CGColorSpaceRelease(colorSpace);
CGContextRelease(context);
CGImageRelease(imageRef);
free(contextBuffer);
[NSGraphicsContext restoreGraphicsState];
[_title setImage: newImage];
I'm not quite sure what your limitations are, or why you absolutely have to draw to a layer, but new in os4.1 among other things, Core Text was ported to iOS from the desktop. You can probably take advantage of it's advanced type setting features to render glyphs along lines or even arcs and have it do most of the heavy lifting for you. It is a relatively low-level API, but it is very fast.
- (void)drawLayer:(CALayer *)theLayer
inContext:(CGContextRef)context
{
CFStringRef string; CTFontRef font; // assuming these exist
// Initialize string, font, and context
CFStringRef keys[] = { kCTFontAttributeName };
CFTypeRef values[] = { font };
CFDictionaryRef attributes =
CFDictionaryCreate(kCFAllocatorDefault, (const void**)&keys,
(const void**)&values, sizeof(keys) / sizeof(keys[0]),
&kCFTypeDictionaryCallBacks,
&kCFTypeDictionaryValueCallbacks);
CFAttributedStringRef attrString =
CFAttributedStringCreate(kCFAllocatorDefault, string, attributes);
CFRelease(string);
CFRelease(attributes);
CTLineRef line = CTLineCreateWithAttributedString(attrString);
// Set text position and draw the line into the graphics context
CGContextSetTextPosition(context, 10.0, 10.0);
CTLineDraw(line, context);
CFRelease(line);
}
Other examples include CoreTextArcCocoa and CoreTextTest
Here is how the Original poster did not want to do it, but it was OK for me to have an opaque background...
when you make the layer:
layer.opaque = YES;
Then in
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context
[NSGraphicsContext saveGraphicsState];
NSGraphicsContext *nscg = [NSGraphicsContext graphicsContextWithGraphicsPort:context flipped:NO];
[NSGraphicsContext setCurrentContext:nscg];
NSRect ourframe = self.frame;
// white background enables antialiasing
[[NSColor whiteColor] setFill];
NSRect ourNSFrame = ourframe;
ourNSFrame.origin = NSZeroPoint;
NSRectFill(ourNSFrame);
[sometext drawInRect:ourNSFrame withAttributes:someattrs];
[NSGraphicsContext restoreGraphicsState];
What are the X, Y positions of the Text once it gets it's variable string filled in? I had a similar problem for a UILabel which was caused by the fact that the XY position of the text were floats. (it was trying to center variable data and the XY vals were half pixels)
I truncated the values and it fixed the blurry text.