I've been searching a lot lately for a good PDF tutorial, documentation, etc.
I ended up using this code, but there are few problems.
Scenario
I have a view which contains a label, a textView and an imageView.
Now, we'll call the label name
, the textView description
and the imageView image
.
The name works as header.
The description is very very mutable, it can be from 2 lines to some pages.
Image should go at the end of the description text.
I'm using this code:
- (void)generatePDF{
NSString *fileName = [NSString stringWithFormat:@"%@.pdf",nameString];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *pdfFileName = [documentsDirectory stringByAppendingPathComponent:fileName];
CFAttributedStringRef currentText = CFAttributedStringCreate(NULL,
(CFStringRef)descriptionString, NULL);
if (currentText) {
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(currentText);
if (framesetter) {
// Create the PDF context using the default page: currently constants at the size
// of 612 x 792.
UIGraphicsBeginPDFContextToFile(pdfFileName, CGRectZero, nil);
CFRange currentRange = CFRangeMake(0, 0);
NSInteger currentPage = 0;
BOOL done = NO;
do {
// Mark the beginning of a new page.
UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, kDefaultPageWidth,
kDefaultPageHeight), nil);
[self drawHeader]
// Draw a page number at the bottom of each page
currentPage++;
[self drawPageNumber:currentPage];
// Render the current page and update the current range to
// point to the beginning of the next page.
currentRange = [self renderPage:currentPage withTextRange:
currentRange andFramesetter:framesetter];
// If we're at the end of the text, exit the loop.
if (currentRange.location == CFAttributedStringGetLength
((CFAttributedStringRef)currentText))
done = YES;
} while (!done);
// Close the PDF context and write the contents out.
UIGraphicsEndPDFContext();
// Release the framewetter.
CFRelease(framesetter);
} else {
NSLog(@"Could not create the framesetter needed to lay out the atrributed string.");
}
// Release the attributed string.
CFRelease(currentText);
} else {
NSLog(@"Could not create the attributed string for the framesetter");
}
}
- (CFRange)renderPage:(NSInteger)pageNum withTextRange:(CFRange)currentRange
andFramesetter:(CTFramesetterRef)framesetter
{
// Get the graphics context.
CGContextRef currentContext = UIGraphicsGetCurrentContext();
// Put the text matrix into a known state. This ensures
// that no old scaling factors are left in place.
CGContextSetTextMatrix(currentContext, CGAffineTransformIdentity);
// Create a path object to enclose the text. Use 72 point
// margins all around the text.
CGRect frameRect = CGRectMake(22,72, 468, 648);
CGMutablePathRef framePath = CGPathCreateMutable();
CGPathAddRect(framePath, NULL, frameRect);
// Get the frame that will do the rendering.
// The currentRange variable specifies only the starting point. The framesetter
// lays out as much text as will fit into the frame.
CTFrameRef frameRef = CTFramesetterCreateFrame(framesetter, currentRange, framePath, NULL);
CGPathRelease(framePath);
// Core Text draws from the bottom-left corner up, so flip
// the current transform prior to drawing.
CGContextTranslateCTM(currentContext, 0, kDefaultPageHeight);
CGContextScaleCTM(currentContext, 1.0, -1.0);
// Draw the frame.
CTFrameDraw(frameRef, currentContext);
// Update the current range based on what was drawn.
currentRange = CTFrameGetVisibleStringRange(frameRef);
currentRange.location += currentRange.length;
currentRange.length = 0;
CFRelease(frameRef);
return currentRange;
}
- (void)drawPageNumber:(NSInteger)pageNum
{
NSString* pageString = [NSString stringWithFormat:NSLocalizedString(@"Page", nil), pageNum];
UIFont* theFont = [UIFont systemFontOfSize:12];
CGSize maxSize = CGSizeMake(kDefaultPageWidth, 72);
CGSize pageStringSize = [pageString sizeWithFont:theFont
constrainedToSize:maxSize
lineBreakMode:UILineBreakModeClip];
CGRect stringRect = CGRectMake(((kDefaultPageWidth - pageStringSize.width) / 2.0),
720.0 + ((72.0 - pageStringSize.height) / 2.0) ,
pageStringSize.width,
pageStringSize.height);
[pageString drawInRect:stringRect withFont:theFont];
}
I would like to know how to draw the image at the end of the page, right after the end of the description.
I've drawn the header this way:
-(void)drawHeader{
NSString *headerString = nameString;
UIFont* theFont = [UIFont boldSystemFontOfSize:15];
CGSize maxSize = CGSizeMake(kDefaultPageWidth, 72);
CGSize pageStringSize = [headerString sizeWithFont:theFont constrainedToSize:maxSize lineBreakMode:UILineBreakModeClip];
CGRect stringRect = CGRectMake(22,22,pageStringSize.width,pageStringSize.height);
[headerString drawInRect:stringRect withFont:theFont];
}
and it's being shown at the beginning of every page.
Now I don't know how to draw the image after the contents (description)!
Well Drawing a pdf in the context is like drawing in a canvas.
The best way to draw text and images which are dynamic in nature is to create and use methods to draw, which returns the height used to draw in the page and simply keeping a variable to calculate the next drawing orgin y position by incrementing with the returned value. Also put a separate offset variable to check the page frame.
To draw an image into pdf context you can make use of the following sample.You can also render CGImage
via Core Graphics,but you have to consider rotation of the canvas(CGContext
) since those will appear in the upside down way(flipped) since the coordinate origin in Core Graphics is on the bottom right.
UIImage *gymLogo=[UIImage imageNamed:@"logo.png"];
CGPoint drawingLogoOrigin = CGPointMake(5,5);
[gymLogo drawAtPoint:drawingLogoOrigin];
Here's a subclass of UIView
that does what you want...
You can place it in a UIScrollView
. Call -sizeThatFits:
to get the correct frame size during your superview's -layoutSubviews
. (also for your enclosing scroll view's contentSize.)
Note that I included a category on UILabel that returns a reasonable size to -sizeThatFits:
, you may want to provide your own implementation.
View.h
#import <UIKit/UIKit.h>
@interface View : UIView
@property ( nonatomic, strong, readonly ) UILabel * descriptionLabel ;
@property ( nonatomic, strong, readonly ) UILabel * nameLabel ;
@property ( nonatomic, strong, readonly ) UIImageView * pdfImageView ;
@property ( nonatomic ) CGPDFDocumentRef pdf ;
@end
View.m
#import "View.h"
@implementation UILabel (SizeThatFits)
-(CGSize)sizeThatFits:(CGSize)fitSize
{
return [ self.text sizeWithFont:self.font constrainedToSize:fitSize ] ;
}
@end
@interface View ()
@property ( nonatomic, strong, readonly ) UIImage * pdfImage ;
@end
@implementation View
@synthesize descriptionLabel = _descriptionLabel ;
@synthesize nameLabel = _nameLabel ;
@synthesize pdfImageView = _pdfImageView ;
@synthesize pdfImage = _pdfImage ;
-(void)dealloc
{
CGPDFDocumentRelease( _pdf ) ;
}
-(CGSize)sizeThatFits:(CGSize)size
{
CGSize fitSize = (CGSize){ size.width, CGFLOAT_MAX } ;
CGSize result = { size.width, 0 } ;
result.height = [ self.headerLabel sizeThatFits:fitSize ].height
+ [ self.label sizeThatFits:fitSize ].height
+ self.pdfImage.size.height * fitSize.width / self.pdfImage.size.width ;
return result ;
}
-(void)layoutSubviews
{
CGRect bounds = self.bounds ;
CGRect slice ;
CGRectDivide( bounds, &slice, &bounds, [ self.headerLabel sizeThatFits:bounds.size ].height, CGRectMinYEdge ) ;
self.headerLabel.frame = slice ;
CGRectDivide( bounds, &slice, &bounds, [ self.label sizeThatFits:bounds.size ].height, CGRectMinYEdge ) ;
self.label.frame = slice ;
self.pdfImageView.frame = bounds ;
}
-(void)setPdf:(CGPDFDocumentRef)pdf
{
CGPDFDocumentRelease( _pdf ) ;
_pdf = CGPDFDocumentRetain( pdf ) ;
_pdfImage = nil ;
}
-(UILabel *)descriptionLabel
{
if ( !_descriptionLabel )
{
UILabel * label = [[ UILabel alloc ] initWithFrame:CGRectZero ] ;
label.numberOfLines = 0 ;
//
// ... configure label here
//
[ self addSubview:label ] ;
_descriptionLabel = label ;
}
return _descriptionLabel ;
}
-(UILabel *)nameLabel
{
if ( !_nameLabel )
{
UILabel * label = [[ UILabel alloc ] initWithFrame:CGRectZero ] ;
label.numberOfLines = 0 ;
//
// ... configure label here
//
[ self addSubview:label ] ;
_nameLabel = label ;
}
return _nameLabel ;
}
-(UIView *)pdfImageView
{
if ( !_pdfImageView )
{
UIImageView * imageView = [[ UIImageView alloc ] initWithFrame:CGRectZero ] ;
[ self addSubview:imageView ] ;
_pdfImageView = imageView ;
}
return _pdfImageView ;
}
-(UIImage *)pdfImage
{
if ( !_pdfImage )
{
CGPDFPageRef page = CGPDFDocumentGetPage( self.pdf, 1 ) ; // 1 indexed
CGRect mediaBox = CGPDFPageGetBoxRect( page, kCGPDFMediaBox ) ;
UIGraphicsBeginImageContextWithOptions( mediaBox.size, NO, self.window.screen.scale ) ;
CGContextRef c = UIGraphicsGetCurrentContext() ;
CGContextScaleCTM( c, 1.0f, -1.0f ) ;
CGContextTranslateCTM( c, 0.0, -mediaBox.size.height ) ;
CGContextDrawPDFPage( c, page ) ;
_pdfImage = UIGraphicsGetImageFromCurrentImageContext() ;
self.pdfImageView.image = _pdfImage ;
UIGraphicsEndImageContext() ;
}
return _pdfImage ;
}
@end
If you need a page number footer, you can add another UILabel at the bottom, following the same pattern.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With