I used CoreText to create the following text which fills a rectangle and surrounds an area in which I'll insert an image.
attrString = [[NSMutableAttributedString alloc] initWithString:string];
CFAttributedStringSetAttribute((__bridge CFMutableAttributedStringRef) attrString, CFRangeMake(0, attrString.length), kCTFontAttributeName, font);
CFAttributedStringSetAttribute((__bridge CFMutableAttributedStringRef) attrString, CFRangeMake(0, attrString.length), kCTForegroundColorAttributeName, ForegroundTextColor);
CFAttributedStringSetAttribute((__bridge CFMutableAttributedStringRef) attrString, CFRangeMake(0, attrString.length), kCTParagraphStyleAttributeName, paragraphStyle);
/* Get a framesetter to draw the actual text */
CTFramesetterRef fs = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef) attrString);
CTFrameRef frame = CTFramesetterCreateFrame(fs, CFRangeMake(0, attrString.length), pathToRenderIn, NULL);
/* Draw the text */
CTFrameDraw(frame, ctx);
/* Draw the text */
CTFrameDraw(frame, ctx);
In a later method, I'm using the attached statement to change the color of a subset of the text:
[attrString addAttribute:NSForegroundColorAttributeName value:[UIColor blackColor] range:NSMakeRange(startRangeValue, endRangeValue)];
Unfortunately, the color doesn't change. Thanks in advance for your help.
I'm adding my comment as an answer since following it solved the problem:
After you add an attribute to a string you have to redraw. Basically what you see on the screen is not an NSString object that is changeable, it is a graphical drawing of an object that it read. If you change the object you have to tell it to draw the object again.
So the answer to the problem is that you need to call the drawing code every time you try to change anything about the string you are displaying.
Related
So I am currently working on a subclass of UIView which uses TBXML to parse a SVG XML files and strip the 'g' path instructions out. Then it passes these sets of instructions to SvgToBezier - a simple parser that takes the path instructions as input and returns a UIBezierPath object. Then it draws them in the drawRect method.
The paths render fine, but the problem is they don't line up with each other the way they're supposed to (they way they show up in google chrome, adobe illustrator, etc)
I know the SVGKit library allows parsing of complex svg files, however I want to write this myself so I can expand my understanding of the concepts involved.
First I'll show you my drawRect method, followed by an example SVG file, it's expected output, and it's actual output drawn on the iPhone in a 300x300 rect.
- (void)drawRect:(CGRect)rect
{
//getting the XML from set property
TBXMLElement *root = self.svgRoot.rootXMLElement;
//getting the rect tag, which is the background
TBXMLElement *rectColor = [TBXML childElementNamed:#"rect" parentElement:root];
//getting the first g tag
TBXMLElement *g1 = [TBXML childElementNamed:#"g" parentElement:root];
//and it's child path tag
TBXMLElement *path1 = [TBXML childElementNamed:#"path" parentElement:g1];
//getting the sibbling g tag
TBXMLElement *g2 = g1->nextSibling;
//and it's child path tag
TBXMLElement *path2 = [TBXML childElementNamed:#"path" parentElement:g2];
//create a context reference
CGContextRef ctx = UIGraphicsGetCurrentContext();
//flip the context so the origin points jive
CGContextTranslateCTM(ctx, 0, rect.size.height);
CGContextScaleCTM(ctx, 1.0, -1.0);
//get fill color for first path
const char *str1 = [[TBXML valueOfAttributeNamed:#"fill" forElement:g1] cStringUsingEncoding:NSASCIIStringEncoding];
long x1 = strtol(str1+1, NULL, 16);
UIColor *rgb1 = UIColorFromRGB(x1);
//get fill color for second path
const char *str2 = [[TBXML valueOfAttributeNamed:#"fill" forElement:g2] cStringUsingEncoding:NSASCIIStringEncoding];
long x2 = strtol(str2+1, NULL, 16);
UIColor *rgb2 = UIColorFromRGB(x2);
//get fill color for rect background
const char *str3 = [[TBXML valueOfAttributeNamed:#"fill" forElement:rectColor] cStringUsingEncoding:NSASCIIStringEncoding];
long x3 = strtol(str3+1, NULL, 16);
UIColor *rgb3 = UIColorFromRGB(x3);
//turn them into CGColors
CGColorRef color1 = rgb1.CGColor;
CGColorRef color2 = rgb2.CGColor;
CGColorRef color3 = rgb3.CGColor;
//draw the rect background and fill it
CGContextSetFillColorWithColor(ctx, color3);
CGContextFillRect(ctx, CGRectMake(0, 0, rect.size.width, rect.size.height));
//make a bezier path from the first path tag using SvgToBezier
SvgToBezier *bezier1 = [[SvgToBezier alloc] initFromSVGPathNodeDAttr:[TBXML valueOfAttributeNamed:#"d" forElement:path1] rect:rect];
//make second bezier path
SvgToBezier *bezier2 = [[SvgToBezier alloc] initFromSVGPathNodeDAttr:[TBXML valueOfAttributeNamed:#"d" forElement:path2] rect:rect];
//set fill color for first bezier
CGContextSetFillColorWithColor(ctx, color1);
//draw it
UIBezierPath *bezierPath1 = bezier1.bezier;
CGContextAddPath(ctx, bezierPath1.CGPath);
CGContextDrawPath(ctx, kCGPathEOFill);
//second bezier
CGContextSetFillColorWithColor(ctx, color2);
UIBezierPath *bezierPath2 = bezier2.bezier;
CGContextAddPath(ctx, bezierPath2.CGPath);
CGContextDrawPath(ctx, kCGPathEOFill);
}
Here's the Raw SVG
Here's the Expected Drawing Result
And here's the Actual iPhone Output (screenshot from iOS Simulator)
I've been experimenting with all kinds of calculations and CGAffineTransforms.. left all that out of the code because it will just confuse things imo.
Thanks for any and all help!
Cheers
EDIT:
Including another case, just to illustrate the inconsistency (consistency?) of the problem.
Raw
Expected
Actual
Hey just wanted to let anyone who read this know, I haven't solved the issue and I don't plan on it. I'm using UIWebView instead. Safari seems to render SVGs just fine. I used an HTML wrapper to make the SVG scale to fit its viewport.
I like the UIWebView better anyway because it deems to draw faster than my method.
Cheers
I have heard that I can display a NSAttributedString using CoreText, can anyone say me how (The simplest way)?
Please, don't answer with CATextLayer or OHAttributedLabel.
I know that there are a lot of questions about this in this forum, but I haven't find the answer
Thanks!!
Simplest way? Something like this:
CGContextRef context = UIGraphicsGetCurrentContext();
// Flip the coordinate system
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
// Create a path to render text in
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, self.bounds );
// An attributed string containing the text to render
NSAttributedString* attString = [[NSAttributedString alloc]
initWithString:...];
// create the framesetter and render text
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attString);
CTFrameRef frame = CTFramesetterCreateFrame(framesetter,
CFRangeMake(0, [attString length]), path, NULL);
CTFrameDraw(frame, context);
// Clean up
CFRelease(frame);
CFRelease(path);
CFRelease(framesetter);
I think that the simplest way (using Core Text) is:
// Create the CTLine with the attributed string
CTLineRef line = CTLineCreateWithAttributedString(attrString);
// Set text position and draw the line into the graphics context called context
CGContextSetTextPosition(context, x, y);
CTLineDraw(line, context);
// Clean up
CFRelease(line);
Using a Framesetter is more efficient IF you are drawing lots of text but this is the method recommended by Apple if you just need to display a small amount of text (like a label) and doesn't require you to create a path or frame (since it is done for you automatically by CTLineDraw).
Starting from ios 6 you can do the following :
NSMutableParagraphStyle *paragrahStyle = [[NSMutableParagraphStyle alloc] init];
[paragrahStyle setLineSpacing:40];
[attributedString addAttribute:NSParagraphStyleAttributeName value:paragrahStyle range:NSMakeRange(0, [labelText length])];
cell.label.attributedText = attributedString ;
I have a leak that I can't track down. I am new to CoreText (and C in general) so please be gentle! The static analyser doesn't show any problems but Instruments does in this method:
- (void)drawAttributedStringInBubbleInContext:(CGContextRef)context {
static CGFloat const kTextInset = 10;
// Add the text to the bubble using an ellipse path inside the main speech bubble if the text property is set
if (text) {
// Create an attributed string from the text property
NSMutableAttributedString *bubbleText = [[NSMutableAttributedString alloc] initWithString:text];
// Justify the text by adding a paragraph style
CFIndex stringLength = CFAttributedStringGetLength((CFAttributedStringRef)bubbleText);
CTTextAlignment alignment = kCTJustifiedTextAlignment;
CTParagraphStyleSetting _settings[] = {
{kCTParagraphStyleSpecifierAlignment, sizeof(alignment), &alignment}
};
CTParagraphStyleRef paragraphStyle = CTParagraphStyleCreate(_settings, sizeof(_settings) / sizeof(_settings[0]));
CFRange stringRange = CFRangeMake(0, stringLength);
CFAttributedStringSetAttribute((CFMutableAttributedStringRef)bubbleText, stringRange, kCTParagraphStyleAttributeName, paragraphStyle);
CFRelease(paragraphStyle);
// Layout the text within an elliptical frame
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)bubbleText);
// Create elliptical path that is inset from the frame of the view
CGMutablePathRef path = CGPathCreateMutable();
CGRect drawingRect = self.bounds;
drawingRect.origin.x = kTextInset;
drawingRect.origin.y = kTextInset;
drawingRect.size.width -= 2 * kTextInset;
drawingRect.size.height -= 2 * kTextInset;
CGPathAddEllipseInRect(path, NULL, drawingRect);
// Create a text frame from the framesetter and the path
CTFrameRef textFrame = CTFramesetterCreateFrame(framesetter,CFRangeMake(0,0), path, NULL);
// Draw the text frame in the view's graphics context
CTFrameDraw(textFrame, context);
// Clean up
CGPathRelease(path);
CFRelease(framesetter);
[bubbleText release];
}
}
The main culprit according to Instruments is the CTFrameRef textFrame = line, although I thought I have released everything properly.
That is the culprit, the Core Foundation rule for Create methods are you have to release them. Apple releases it properly in an example in the Core Text Programming Guide.
// Clean up
CGPathRelease(path);
CFRelease(framesetter);
CFRelease(textFrame);
Hi all I am working on Objective-C. my previous Question was How can I edit PDF files in an iOS application?
after a lot of googling I found out the following. display the pdf in UIWebView, extract the data using C/javascript and edit it. I am still not sure about this procedure. now what I have planned is
1) display the pdf
2) when user wants to edit the pdf I covert the pdf to text and allow him to edit it
3) tryin to save wil convert the content back to pdf.
is this a gud way to proceed?? im k with step 1. now how do i convert pdf--> text and text-->pdf.
thanks in advance
When you load a custom document type (doc, ppt, pdf, etc) into a UIWebView, the webview returns a nil HTML string, even via javascript. There's a few suggestions for extracting PDF text here.
But turning the string back into a PDF is different. If you want to retain the formatting of the original PDF, I'm rather sure that's impossible because NSAttributedString on iOS doesn't do much. But this will work for plain text or NSAttributedString, if its possible:
NSData *PDFDataFromString(NSString *str) {
NSMutableData *data = [NSMutableData data];
//Create an NSAttributedString for CoreText. If you find a way to translate
//PDF into an NSAttributedString, you can skip this step and simply use an
//NSAttributedString for this method's argument.
NSAttributedString* string = [[[NSAttributedString alloc] initWithString:str] autorelease];
//612 and 792 are the dimensions of the paper in pixels. (8.5" x 11")
CGRect paperRect = CGRectMake(0.0, 0.0, 612, 792);
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef) string);
CGSize requiredSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, [string length]), NULL, CGSizeMake(paperRect.size.width - 144, 1e40), NULL);
//Subtract the top and bottom margins (72 and 72), so they aren't factored in page count calculations.
NSUInteger pageCount = ceill(requiredSize.height / (paperRect.size.height - 144));
CFIndex resumePageIndex = 0;
UIGraphicsBeginPDFContextToData(data, paperRect, nil);
for(NSUInteger i = 0; i < pageCount; i++)
{
//After calculating the required number of pages, break up the string and
//draw them into sequential pages.
UIGraphicsBeginPDFPage();
CGContextRef currentContext = UIGraphicsGetCurrentContext();
CGContextSaveGState (currentContext);
CGContextSetTextMatrix(currentContext, CGAffineTransformIdentity);
CGMutablePathRef framePath = CGPathCreateMutable();
//72 and 72 are the X and Y margins of the page in pixels.
CGPathAddRect(framePath, NULL, CGRectInset(paperRect, 72.0, 72.0));
CTFrameRef frameRef = CTFramesetterCreateFrame(framesetter, CFRangeMake(resumePageIndex, 0), framePath, NULL);
resumePageIndex += CTFrameGetVisibleStringRange(frameRef).length;
CGPathRelease(framePath);
CGContextTranslateCTM(currentContext, 0, paperRect.size.height);
CGContextScaleCTM(currentContext, 1.0, -1.0);
CTFrameDraw(frameRef, currentContext);
CFRelease(frameRef);
CGContextRestoreGState (currentContext);
}
CFRelease(framesetter);
UIGraphicsEndPDFContext();
return data;
}
I m a newbie in iphone.
Currently m doing a project to create a pdf having tables.
I created that. I can even write the text in table cells.
But now the problem is the data will come dynamically from database. That is also not a big deal if i m using drawinrect function. But now i want to display a rich text i.e. The text can be bold or italic.
How to write such rich text in a bounded rectangle...???
I can have text in html format.
Any solution for this???
// Initialize an attributed string.
NSString *html = text;
NSData *data = [html dataUsingEncoding:NSUTF8StringEncoding];
// Create attributed string from HTML
NSAttributedString *string = [[NSAttributedString alloc] initWithHTML:data baseURL:nil documentAttributes:NULL];
// CGColorRef red = CGColorCreate(rgbColorSpace, components); CGColorSpaceRelease(rgbColorSpace);
// CFAttributedStringSetAttribute(attrString, CFRangeMake(0, 50),kCTForegroundColorAttributeName, red);
// Create the framesetter with the attributed string.
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(string);
// Create the frame and draw it into the graphics context
CTFrameRef frame = CTFramesetterCreateFrame(framesetter,CFRangeMake(0, 0), path, NULL);
CFRelease(framesetter);
CTFrameDraw(frame, pdfContext);
CFRelease(frame);
This solved my issue :)