I am currently creating PDF documents from a UIView in iOS by using CALayer
and the renderInContext
method.
The problem I am facing is the sharpness of labels. I have created a UILabel
subclass that overrides drawLayer
like so:
/** Overriding this CALayer delegate method is the magic that allows us to draw a vector version of the label into the layer instead of the default unscalable ugly bitmap */
- (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];
}
This method lets me draw nice crisp text, however, the problem is with other views that I don't have control over. Is there any method that would allow me to do something similar for labels embedded in UITableView
or UIButton
. I guess I'm looking for a way to iterate through the view stack and do something to let me draw sharper text.
Here is an example:
This text renders nicely (my custom UILabel subclass)
The text in a standard segmented control isn't as sharp:
Edit: I am getting the context to draw into my PDF as follows:
UIGraphicsBeginPDFContextToData(self.pdfData, CGRectZero, nil);
pdfContext = UIGraphicsGetCurrentContext();
UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, 612, 792), nil);
[view.layer renderInContext:pdfContext];
I ended up traversing the view hierarchy and setting every UILabel
to my custom subclass that overrides drawLayer
.
Here is how I traverse the views:
+(void) dumpView:(UIView*) aView indent:(NSString*) indent {
if (aView) {
NSLog(@"%@%@", indent, aView); // dump this view
if ([aView isKindOfClass:[UILabel class]])
[AFGPDFDocument setClassForLabel:aView];
if (aView.subviews.count > 0) {
NSString* subIndent = [[NSString alloc] initWithFormat:@"%@%@",
indent, ([indent length]/2)%2==0 ? @"| " : @": "];
for (UIView* aSubview in aView.subviews)
[AFGPDFDocument dumpView:aSubview indent:subIndent];
}
}
}
And how I change the class:
+(void) setClassForLabel: (UIView*) label {
static Class myFancyObjectClass;
myFancyObjectClass = objc_getClass("UIPDFLabel");
object_setClass(label, myFancyObjectClass);
}
The comparison:
Old:
New:
Not sure if there is a better way to do this, but it seems to work for my purposes.
EDIT: Found a more generic way to do this that doesn't involve changing the class or traversing through the whole view hierarchy. I am using method swizzling. This method also lets you do cool things like surrounding every view with a border if you want. First I created a category UIView+PDF
with my custom implementation of the drawLayer
method, then in the load
method I use the following:
// The "+ load" method is called once, very early in the application life-cycle.
// It's called even before the "main" function is called. Beware: there's no
// autorelease pool at this point, so avoid Objective-C calls.
Method original, swizzle;
// Get the "- (void) drawLayer:inContext:" method.
original = class_getInstanceMethod(self, @selector(drawLayer:inContext:));
// Get the "- (void)swizzled_drawLayer:inContext:" method.
swizzle = class_getInstanceMethod(self, @selector(swizzled_drawLayer:inContext:));
// Swap their implementations.
method_exchangeImplementations(original, swizzle);
Worked from the example here: http://darkdust.net/writings/objective-c/method-swizzling