How can I generate a PDF with "real" text content on iOS? - ios

I want to generate a good-looking PDF in my iOS 6 app.
I've tried:
UIView render in context
Using CoreText
Using NSString drawInRect
Using UILabel drawRect
Here is a code example:
-(CGContextRef) createPDFContext:(CGRect)inMediaBox path:(NSString *) path
{
CGContextRef myOutContext = NULL;
NSURL * url;
url = [NSURL fileURLWithPath:path];
if (url != NULL) {
myOutContext = CGPDFContextCreateWithURL ((__bridge CFURLRef) url,
&inMediaBox,
NULL);
}
return myOutContext;
}
-(void)savePdf:(NSString *)outputPath
{
if (!pageViews.count)
return;
UIView * first = [pageViews objectAtIndex:0];
CGContextRef pdfContext = [self createPDFContext:CGRectMake(0, 0, first.frame.size.width, first.frame.size.height) path:outputPath];
for(UIView * v in pageViews)
{
CGContextBeginPage (pdfContext,nil);
CGAffineTransform transform = CGAffineTransformIdentity;
transform = CGAffineTransformMakeTranslation(0, (int)(v.frame.size.height));
transform = CGAffineTransformScale(transform, 1, -1);
CGContextConcatCTM(pdfContext, transform);
CGContextSetFillColorWithColor(pdfContext, [UIColor whiteColor].CGColor);
CGContextFillRect(pdfContext, v.frame);
[v.layer renderInContext:pdfContext];
CGContextEndPage (pdfContext);
}
CGContextRelease (pdfContext);
}
The UIViews that are rendered only contain a UIImageView + a bunch of UILabels (some with and some without borders).
I also tried a suggestion found on stackoverflow: subclassing UILabel and doing this:
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
BOOL isPDF = !CGRectIsEmpty(UIGraphicsGetPDFContextBounds());
if (!layer.shouldRasterize && isPDF)
[self drawRect:self.bounds]; // draw unrasterized
else
[super drawLayer:layer inContext:ctx];
}
But that didn't change anything either.
No matter what I do, when opening the PDF in Preview the text parts are selectable as a block, but not character per character, and zooming the pdf shows it is actually a bitmap image.
Any suggestions?

This Tutorial From Raywenderlich Saved my Day.Hope it will work for you too.
http://www.raywenderlich.com/6818/how-to-create-a-pdf-with-quartz-2d-in-ios-5-tutorial-part-2

My experience when I did this last year was that Apple didn't provide any library to do it. I ended up importing an open source C library (libHaru). Then I added a function for outputting to it in each class in my view hierarchy. Any view with subviews would call render on its subviews. My UILabels, UITextFields, UIImageViews, UISwitches etc would output their content either as text or graphics accordingly I also rendered background colors for some views.
It wasn't very daunting, but libHaru gave me some problems with fonts so iirc I ended up just using the default font and font size.

It works good with UILabels except that you have to work around a bug:
Rendering a UIView into a PDF as vectors on an iPad - Sometimes renders as bitmap, sometimes as vectors

Related

Core plot graph is rendered as black box when converting into pdf

I my application used Core Plot to render graphs, and finally there is a need to create a pdf file, there should be this graph, but when it is saved to pdf file, instead of graph I see black box.
The method I use to create a pdf:
UIGraphicsBeginPDFContextToFile(outputPath, CGRectZero, nil);
for(UIView *view in views) {
UIGraphicsBeginPDFPageWithInfo(view.bounds, nil);
CGContextRef pdfContext = UIGraphicsGetCurrentContext();UIGraphicsBeginPDFContextToFile
[view.layer renderInContext:pdfContext];
}
UIGraphicsEndPDFContext();
Could someone help how to make it render properly?
As a temporary solution before creating a pdf I convert graph to image and put it on the top of it as a result pdf is rendered properly, Is there any more elegant solution?
Edited:
I applied the above advice the code looks like this:
if([child isKindOfClass:[CPTGraphHostingView class]]) {
CGContextTranslateCTM(pdfContext, child.center.x, child.center.y);
CGAffineTransform transform = CGAffineTransformMakeRotation(0);
transform.d = -1;
CGContextConcatCTM(pdfContext, transform);
CGContextTranslateCTM(pdfContext,
child.bounds.size.width * 0.05,
-child.bounds.size.height * 0.75);
CPTLayer *layer = (CPTLayer *)((CPTGraphHostingView *)child).hostedGraph;
[layer layoutAndRenderInContext:pdfContext];
}
Result on UI:
and as a result is pdf:
Are you using any fills with partially transparent colors? The alpha channel doesn't render into PDF. This is a long-standing issue with the Apple PDF frameworks.

Create PDF with proper format and Images in Objective - C

Please read my scenario carefully,
I have one UITextView and one UIImageView bottom of TextView.
Each time there will be dynamic content in TextView and accordingly that, I am asking User to make a signature and it will be displayed as an image in bottom ImageView.
Now the requirement is I have to pass these details on the server along with Signature in one PDF file, So I have to create PDF file which contains both TextView text and ImageView image.
Note: TextView is containing Html text also, so it should show in the same format in PDF also.
Check below Images as required and current pdfs.
This is required PDF
This is current PDF
Only Put the code which can be helpful for both HTML support and Image merge with text. Please don't show simple PDF creation as I have done it already.
you don't need a 3rd party library, Cocoa and Cocoa touch have rich PDF support. I've stubbed you out a little start, do this in your viewController. There may be a few small errors, Ive been using swift for a couple of years now but I used my very rusty objC here because you tagged the question that way. Let me know any problems, good luck
-(NSData *)drawPDFdata{
// default pdf..
// 8.5 X 11 inch #72dpi
// = 612 x 792
CGRect rct = {{0.0 , 0.0 } , {612.0 , 792.0}}
NSMutableData *pdfData = [[NSMutableData alloc]init];
UIGraphicsBeginPDFContextToData(pdfData, rct, nil);
CGContextRef pdfContext = UIGraphicsGetCurrentContext();
UIGraphicsBeginPDFPage();
//textView drawing
CGContextSaveGState(pdfContext);
CGContextConcatCTM(pdfContext, CGAffineTransformMakeTranslation(50.0,50.0));//this is just an offset for the textView drawing. You will want to play with the values, espeecially if supporting multiple screen sizes you might tranform the scale as well..
[textView.layer renderInContext:pdfContext]
CGContextRestoreGState(pdfContext);
//imageView drawing
CGContextSaveGState(pdfContext);
CGContextConcatCTM(pdfContext, CGAffineTransformMakeTranslation(50.0,50.0)); //this is just an offset for the imageView drawing. Thee same stuff applies as above..
[imageView.layer renderInContext:pdfContext]
CGContextRestoreGState(pdfContext);
//cleanup
UIGraphicsEndPDFContext();
return pdfData;
}
here's a couple of client functions to use this NSData
//ways to use the pdf Data
-(Bool)savePDFtoPath: (NSString *)path {
return [ [self drawPDFdata] writeToFile:path atomically:YES] ;
}
//requires Quartz framework.. (can be drawn straight to a UIView)
// note you MAY owe a CGPDFDocumentRelease() on the result of this function (sorry i've not used objC in a couple of years...)
-(CGPDFDocument *)createPDFdocument {
NSData *data = [self drawPDFdata];
CGDataProviderRef provider = CGDataProviderCreateWithData(NULL , data , sizeof(data) ,NULL);
CGPDFDocument result = CGPDFDocumentCreateWithProvider(provider);
CGDataProviderRelease(provider); //not sure if this is still required under ARC?? (def not in swift)
return result;
}
Try this useful third party library :
https://github.com/iclems/iOS-htmltopdf
Use this function for your problem :
+ (id)createPDFWithHTML:(NSString*)HTML pathForPDF:(NSString*)PDFpath pageSize:(CGSize)pageSize margins:(UIEdgeInsets)pageMargins successBlock:(NDHTMLtoPDFCompletionBlock)successBlock errorBlock:(NDHTMLtoPDFCompletionBlock)errorBlock;

How do I make a part of a UILabel visually be a block quote?

How do I make a specific portion of a UILabel look like a blockquote, or have there be a vertical line on the left side of the text? Would TextKit come in here? If so, how?
Mail.app does this (see the colored portions and the line on the side of them):
How would I replicate this effect without using multiple UILabels (which as I'm creating it dynamically would be rather gross)?
Create a view (XIB) with this general layout like the picture above. There is a UILabel, a UITextView and a UIView (the blue rectangle is a UIView with the background color set). Let's call it ThreadView.xib. Hook up the label, textview and view as properties to the view.
We can then have a method to generate one of these views for us to use and a method to add more ThreadViews as subviews based on how many comments/replies a post has.
+ (instancetype)threadViewWithLabelText:(NSString *)labelText
textViewText:(NSString *)textViewText
color:(UIColor *)color
{
ThreadView *threadView = [[[NSBundle mainBundle] loadNibNamed:#"ThreadView"
owner:self
options:nil] firstObject];
if (threadView) {
threadView.label.text = labelText;
threadView.textView.text = textViewText;
threadView.colorView.backgroundColor = color;
}
return threadView;
}
- (void)addCommentView:(ThreadView *)threadView
toViewController:(UIViewController *)viewController
{
threadView.frame = CGRectMake(self.frame.origin.x + 25,
self.textView.frame.origin.y + self.textView.frame.size.height,
self.frame.size.width - (self.frame.origin.x + 10),
self.frame.size.height - (self.textView.frame.origin.y + self.textView.frame.size.height));
[viewController.view addSubview:threadView];
}
Now, in the main view controller, we can create and add these views with just these two method calls:
- (void)viewDidLoad
{
[super viewDidLoad];
// Load the first post
ThreadView *originalPost = [ThreadView threadViewWithLabelText:#"10 Some Words 2014 More Words"
textViewText:loremIpsum
color:[UIColor blueColor]];
originalPost.frame = CGRectMake(self.view.frame.origin.x + 8,
self.view.frame.origin.y + 15,
self.view.frame.size.width - 8,
self.view.frame.size.height - 15);
[self.view addSubview:originalPost];
// Load a comment post
ThreadView *commentPost = [ThreadView threadViewWithLabelText:#"12 December 2014 Maybe A Username"
textViewText:loremIpsum
color:[UIColor greenColor]];
[originalPost addCommentView:commentPost
toViewController:self];
}
This will give us a result like in the picture below. This code could use some refactoring/restructuring, but this should get you started. You can also mix up use of autolayout and/or setting the frames of the views.
Try this?
NSString *html =[NSString stringWithFormat:
#"<html>"
" <head>"
" <style type='text/css'>"
"ul"
"{"
" list-style-type: none;"
"}"
" </style>"
" </head>"
" <body>"
"%# - PARENT"
"<ul>"
"<li>"
"%# - CHILD 1"
"</li>"
"<li>"
"%# - CHILD 2 "
"</li>"
"</ul>"
"</body>"
"</html>"
,#"Parent Title", #"Child Description 1", #"Child Description 2"];
NSError *err = nil;
_label.attributedText =
[[NSAttributedString alloc]
initWithData: [html dataUsingEncoding:NSUTF8StringEncoding]
options: #{ NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType }
documentAttributes: nil
error: &err];
if(err)
NSLog(#"Unable to parse label text: %#", err);
And the Result is like this .
This can be easily done with Text Kit. I do stuff like this in my app. The difference is I use boxes (nested if needed) to mark each text block. Here is what you should do:
Parse html string (or whatever you use to mark text), mark each text block quote with a custom attribute, like MyTextBlockAttribute, save ranges of each text block (i.e. block quote) and add it as a attribute to the related range of the attributed string(construct this attributed string from your content) and the list attached to the content. Lets call this list MyTextBlockList.
draw text with Text Kit yourself. draw background first (white color, light gray color.. etc, whatever), draw text or vertical lines next. Since you can get each text block's range by loop through the list, you can get bounding rect of these blocks with method [NSLayoutManager range: inTextContainer:textContainer].
Here is the code I used in my app:
// subclass of NSTextContainer
#import "MyTextContainer.h"
#import "MyBlockAttribute.h"
#interface MyTextContainer ()
#property (nonatomic) BOOL isBlock;
#end
#implementation MyTextContainer
- (CGRect)lineFragmentRectForProposedRect:(CGRect)proposedRect
atIndex:(NSUInteger)characterIndex
writingDirection:(NSWritingDirection)baseWritingDirection
remainingRect:(CGRect *)remainingRect {
CGRect output = [super lineFragmentRectForProposedRect:proposedRect
atIndex:characterIndex
writingDirection:baseWritingDirection
remainingRect:remainingRect];
NSUInteger length = self.layoutManager.textStorage.length;
MyTextBlockAttribute *blockAttribute;
if (characterIndex < length) {
blockAttribute = [self.layoutManager.textStorage attribute:MyTextBlockAttributeName atIndex:characterIndex effectiveRange:NULL]; // MyTextBlockAttributeName is a global NSString constant
}
if (blockAttribute) { // text block detected, enter "block" layout mode!
output = CGRectInset(output, blockAttribute.padding, 0.0f); // set the padding when constructing the attributed string from raw html string, use padding to control nesting, inner boxes have bigger padding, again, this is done in parsing pass
if (!self.isBlock) {
self.isBlock = YES;
output = CGRectOffset(output, 0.0f, blockAttribute.padding);
}
} else if (self.isBlock) {
self.isBlock = NO; // just finished a block, return back to the "normal" layout mode
}
// no text block detected, not just finished a block either, do nothing, just return super implementation's output
return output;
}
#end
// drawing code, with drawRect: or other drawing technique, like drawing into bitmap context, doesn't matter
- (void)drawBlockList:(NSArray *)blockList content:(MyContent *)content {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 0.5f);
[[UIColor colorWithWhite:0.98f alpha:1.0f] setFill];
CGContextSaveGState(context);
MyTextContainer *textContainer = content.textContainer;
// since I draw boxes, I have to draw inner text block first, so use reverse enumerator
for (MyTextBlockAttribute *blockAttribute in [blockList reverseObjectEnumerator]) {
if (blockAttribute.noBackground) { // sometimes I don't draw boxes in some conditions
continue;
}
CGRect frame = CGRectIntegral([content.layoutManager boundingRectForGlyphRange:blockAttribute.range inTextContainer:textContainer]);
frame.size.width = textContainer.size.width - 2 * (blockAttribute.padding - MyDefaultMargin); // yeah... there is some margin around the boxes, like html's box model, just some simple math to calculate the accurate rectangles of text blocks
frame.origin.x = blockAttribute.padding - MyDefaultMargin;
frame = CGRectInset(frame, 0, -MyDefaultMargin);
if (blockAttribute.backgroundColor) { // some text blocks may have specific background color
CGContextSaveGState(context);
[blockAttribute.backgroundColor setFill];
CGContextFillRect(context, frame);
CGContextRestoreGState(context);
} else {
CGContextFillRect(context, frame);
}
CGContextStrokeRect(context, frame); // draw borders of text blocks in the last
}
CGContextRestoreGState(context);
}
- (UIImage *)drawContent:(MyContent *)content {
UIImage *output;
UIGraphicsBeginImageContextWithOptions(content.bounds.size, YES, 0.0f); // bounds is calculated in other places
[[UIColor whiteColor] setFill];
UIBezierPath *path = [UIBezierPath bezierPathWithRect:content.bounds];
[path fill];
[self drawBlockList:content.blockList content:content]; // draw background first!
[content.layoutManager drawGlyphsForGlyphRange:NSMakeRange(0, content.textStorage.length) atPoint:CGPointZero]; // every content object has a set of Text Kit core objects, textStorage, textContainer, layoutManager
output = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return output;
}
In your case, you don't draw boxes, you draw left borders instead. The technique is the same, hope this can help you!
If you are targeting iOS lower than 7, You can do somethingsimilar by using Core Text, but since Core Text is kind of old C opaque types implementation, I suggest you to use DTCoreText.
If you are using >=iOS7 you can use NSAttributed string and NSXMLDocument. Even if attributed string are available from 3.x they only added them into UIKIT objects into ios6 and changed radically the UIKit behavior in managing them into iOS7.
NSXMLDocument it's helpful because you can render your string representing them as HTML.
This may sound counterintuitive, but have you considered popping it all in a tableView ? you can exploit the indentLevelAtIndexPath: stuff....

Image Repetition in ios siimilar to CSS background-repeat function

Is it possible to repeat an image in ios similar to CSS function
background-image:imageurl;
background-repeat :repeat-x;
so that an image is perfectly scaled for iphone and iPad screen sizes
You could try this:
- (UIImage *) imageFromAssetImageNamed: (NSString *) name {
NSString * fullKeyPath = [[NSBundle mainBundle] pathForResource:name
ofType:#"png"
inDirectory:#"assets"] ;
return [UIImage imageWithContentsOfFile:fullKeyPath] ;
}
- (UIColor *) colorPatternFromAssetImageNamed: (NSString *) name {
return [UIColor colorWithPatternImage:[self imageFromAssetImageNamed:name]] ;
}
You can then set the background color, for example, using:
self.window.backgroundColor = [self colorPatternFromAssetImageNamed:#"my-bg-color"] ;
You will still need to adjust the frame to control how much of the width/height is covered.
You have loads of options.
Core Graphics gives you
CGContextDrawTiledImage()
UIImage gives you
drawPatternInRect:
(Probably a wrapper of the above )
But the most useful thing is to look at transformations.
CGAffineTransform in the Quartz 2D Drawing guide is the thing you want to read about.
It's pretty cheap and easy in draw rect to just do some iteration that draws the same image at a bunch of locations that are in CG terms translations of the image, meaning it's drawn at another place.
You can even draw to an image context before drawing to a view and get a cached representation so you don't need to always redraw every thing.
Core Animation has transforms as well.

iOS - display PDF in a different Orientation

I'm using the following code to display a PDF inside a UIView
Its a simple application, displaying a single PDF (which is all I need) and I"m handling zooming separately: The problem I'm having with this is when the iPad changes to Landscape everything is distorted - any help as to where I should look ? / what i should do to handle the orientation issue ?
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
//PDF might be transparent, assume white paper - set White Background
[[UIColor whiteColor] set];
CGContextFillRect(ctx, rect);
//Flip coordinates
CGContextGetCTM(ctx);
CGContextScaleCTM(ctx, 1, -1);
CGContextTranslateCTM(ctx, 0, -rect.size.height);
//PDF File Path
NSURL *pdfURL = [[NSBundle mainBundle] URLForResource:#"TEST" withExtension:#"pdf"];
CGPDFDocumentRef pdf = CGPDFDocumentCreateWithURL((__bridge CFURLRef)pdfURL);
CGPDFPageRef page1 = CGPDFDocumentGetPage(pdf, 1);
//Get the rectangle of the cropped inside
CGRect mediaRect = CGPDFPageGetBoxRect(page1, kCGPDFCropBox);
CGContextScaleCTM(ctx, rect.size.width / mediaRect.size.width,
rect.size.height / mediaRect.size.height);
//Draw PDF
CGContextDrawPDFPage(ctx, page1);
CGPDFDocumentRelease(pdf);
}
Hi For Anybody Who may be interested in this. . .
This may be the best way to do it :
Supporting two orientations with separate view controllers in iOS
The following question discusses how to register to device orientation change, When a change in orientation is detected, a modal View is pushed -displaying the PDF with dimensions
The Other options may be as follows : when you initialize the UIView decide how you want it to react when the orientation changes
UIView.contentMode = UIViewContentModeScaleAspectFill;
Play around with the various UIViewContentMode... : to achieve the best effect for you
The current one allows the PDF to be redrawn maintaining its Aspect ration and filling the entire size of the screen:so it cuts of about half the PDF - - -

Resources