The IOS CGContext documentation says that various string output functions are now deprecated in favor of core text. The documentation just says "Use Core Text instead."
if I have
NSString *string ;
What would be the simple, currently approved method for drawing that text to the CGContext?
Here is my overridden drawRect: method to render attributed string with all explanation comments inside. By the time this method is called, UIKit has configured the drawing environment appropriately for your view and you can simply call whatever drawing methods and functions you need to render your content.
/*!
* #abstract draw the actual coretext on the context
*
*/
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
[self.backgroundColor setFill];
CGContextFillRect(context, rect);
if (_ctframe != NULL) CFRelease(_ctframe);
if (_framesetter != NULL) CFRelease(_framesetter);
//Creates an immutable framesetter object from an attributed string.
//Use here the attributed string with which to construct the framesetter object.
_framesetter = CTFramesetterCreateWithAttributedString((__bridge
CFAttributedStringRef)self.attributedString);
//Creates a mutable graphics path.
CGMutablePathRef mainPath = CGPathCreateMutable();
if (!_path) {
CGPathAddRect(mainPath, NULL, CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height));
} else {
CGPathAddPath(mainPath, NULL, _path);
}
//This call creates a frame full of glyphs in the shape of the path
//provided by the path parameter. The framesetter continues to fill
//the frame until it either runs out of text or it finds that text
//no longer fits.
CTFrameRef drawFrame = CTFramesetterCreateFrame(_framesetter, CFRangeMake(0, 0),
mainPath, NULL);
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
// draw text
CTFrameDraw(drawFrame, context);
//clean up
if (drawFrame) CFRelease(drawFrame);
CGPathRelease(mainPath);
}
Related
The following Objective-C code has worked correctly in iOS 9 - 11. It draws a checkerboard with colored squares. For some reason the callback that adds the colors is not being called in iOS 12 and Xcode 10.0. I've tried a variety of fixes but nothing obvious has worked. Something seems to have changed in iOS 12 but nothing I tried has fixed the problem.
-(id)initWithFrame:(CGRect)frame checkerSize:(CGSize)checkerSize darkColor:(UIColor *)darkShade lightColor:(UIColor *)lightShade {
self = [super initWithFrame:frame];
if(self != nil)
{
// Initialize the property values
checkerHeight = checkerSize.height;
checkerWidth = checkerSize.width;
self.darkColor = darkShade;
self.lightColor = lightShade;
// Colored Pattern setup
CGPatternCallbacks coloredPatternCallbacks = {0, ColoredPatternCallback, NULL};
CGRect clippingRectangle = CGRectMake(0.0, 0.0, 2.0*checkerWidth, 2.0*checkerHeight);
// First we need to create a CGPatternRef that specifies the qualities of our pattern.
CGPatternRef coloredPattern = CGPatternCreate(
(__bridge_retained void *)self, // 'info' pointer for our callback
clippingRectangle, // the pattern coordinate space, drawing is clipped to this rectangle
CGAffineTransformIdentity, // a transform on the pattern coordinate space used before it is drawn.
2.0*checkerWidth, 2.0*checkerHeight, // the spacing (horizontal, vertical) of the pattern - how far to move after drawing each cell
kCGPatternTilingNoDistortion,
true, // this is a colored pattern, which means that you only specify an alpha value when drawing it
&coloredPatternCallbacks); // the callbacks for this pattern.
// To draw a pattern, you need a pattern colorspace.
// Since this is an colored pattern, the parent colorspace is NULL, indicating that it only has an alpha value.
CGColorSpaceRef coloredPatternColorSpace = CGColorSpaceCreatePattern(NULL);
CGFloat alpha = 1.0;
// Since this pattern is colored, we'll create a CGColorRef for it to make drawing it easier and more efficient.
// From here on, the colored pattern is referenced entirely via the associated CGColorRef rather than the
// originally created CGPatternRef.
coloredPatternColor = CGColorCreateWithPattern(coloredPatternColorSpace, coloredPattern, &alpha);
CGColorSpaceRelease(coloredPatternColorSpace);
CGPatternRelease(coloredPattern);
}
return self;
}
void ColoredPatternCallback(void *info, CGContextRef context) {
HS_QuartzPatternView *self = (__bridge_transfer id)info; // needed to access the Obj-C properties from the C function
CGFloat checkerHeight = [self checkerHeight];
CGFloat checkerWidth = [self checkerWidth];
// "Dark" Color
UIColor *dark = [self darkColor];
CGContextSetFillColorWithColor(context, dark.CGColor);
CGContextFillRect(context, CGRectMake(0.0, 0.0, checkerWidth, checkerHeight));
CGContextFillRect(context, CGRectMake(checkerWidth, checkerHeight, checkerWidth, checkerHeight));
// "Light" Color
UIColor *light = [self lightColor];
CGContextSetFillColorWithColor(context, light.CGColor);
CGContextFillRect(context, CGRectMake(checkerWidth, 0.0, checkerWidth, checkerHeight));
CGContextFillRect(context, CGRectMake(0.0, checkerHeight, checkerWidth, checkerHeight));
}
I’m currently working on a project and I need to generate a PDF based on a bunch of information the user had entered.
I'm using PDFKit, I managed to create a PDFView and add a PDFDocument to it. My problem is that I couldn't really find a way to draw on the document's pages using PDFKit. I don't want to add annotations, I want to draw tables and texts inside that table's cells.
I've found some examples to do that but all of them were not complete and you need to have some knowledge to really understand them. I've also found some examples using Quartz and Core Graphics but I don't know if I can apply it to PDFKit.
I need only an example of drawing a line using PDFKit.
Thanks.
All you need is create pdfData:
-(NSMutableData *)createPDFData {
// Creates a mutable data object for updating with binary data, like a byte array
NSMutableData *pdfData = [NSMutableData data];
// Points the pdf converter to the mutable data object and to the UIView to be converted
CGRect bounds = self.bounds;
UIGraphicsBeginPDFContextToData(pdfData, CGRectZero, nil);
UIGraphicsBeginPDFPageWithInfo(bounds, nil);
CGContextRef currentContext = UIGraphicsGetCurrentContext();
[self drawBottomRect];
[self drawImageView:self.ivBackground];
// draw labels by section
[self drawLabel:self.walletName];
currentContext = UIGraphicsGetCurrentContext();
// remove PDF rendering context
UIGraphicsEndPDFContext();
return pdfData;
}
Example how to draw image view:
-(void) drawImageView: (UIImageView*) imageView
{
CGContextRef currentContext = UIGraphicsGetCurrentContext();
CGRect frameRect = [imageView convertRect:imageView.bounds toView:self];
CGContextTranslateCTM(currentContext, frameRect.origin.x,frameRect.origin.y);
[imageView.layer renderInContext:currentContext];
CGContextTranslateCTM(currentContext, -frameRect.origin.x,- frameRect.origin.y);
}
Save PDF data as file.
For some reason I'm unable to get NSTextAttachment images to draw when using core text, although the same image would display fine when the NSAttributedString is added to an UILabel.
On iOS this rendering will give empty spaces for the NSTextAttachments, for OS X, a placeholder [OBJECT] square image is rendered for each NSTextAttachment instead. Is there something else that needs to be done in order to render images with CoreText?
The rendering code:
CGFloat contextHeight = CGBitmapContextGetHeight(context);
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)_attributedString);
CGPathRef path = CGPathCreateWithRect(CGRectMake(rect.origin.x,
contextHeight - rect.origin.y - rect.size.height,
rect.size.width,
rect.size.height), NULL);
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL);
CFRelease(framesetter);
CGPathRelease(path);
CGContextSaveGState(context);
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 0.0f, contextHeight);
CGContextScaleCTM(context, 1.0f, -1.0f);
CTFrameDraw(frame, context);
CGContextRestoreGState(context);
CFRelease(frame);
The reason is simply that NSTextAttachment only works for rendering a NSAttributedString into an UIView/NSView. It can't be used to render into a regular CGContext.
There are two possible ways to solve the problem:
Create a UILabel, CATextLayer or similar, and render it into the graphics context.
Use CTRunDelegate to punch spaces in the text, then loop through all the lines to be rendered and draw the images directly into the CGContext manually. The way to do it is detailed here: https://www.raywenderlich.com/4147/core-text-tutorial-for-ios-making-a-magazine-app. Expect a lot of work if you go down this route, but it works.
I have a circle with a black outline, and a white fill that I need to programmatically make the white into another color (via a UIColor). I've tried a handful of other stackoverflow solutions but none of them seem to work correctly, either filling just the outside or an outline.
I have two ways I could do this but I am unsure of how I would get the right results:
Tint just the white color to whatever the UIColor should be,
or,
Make a UIImage from two circles, one being filled and one overlapping that with black.
If you decide to use two circles, one white and one black, then you may find this helpful. This method will tint a uiimage one for you but it addresses the problem of only tinting the opaque part, meaning it will only tint the circle if you provide a png with transparency around the circle. So instead of filling the entire 24x24 frame of the image it fills only the opaque parts. This isn't exactly your question but you'll probably come across this problem if you go with the second option you listed.
-(UIImage*)colorAnImage:(UIColor*)color :(UIImage*)image{
CGRect rect = CGRectMake(0, 0, image.size.width, image.size.height);
UIGraphicsBeginImageContextWithOptions(rect.size, NO, image.scale);
CGContextRef c = UIGraphicsGetCurrentContext();
[image drawInRect:rect];
CGContextSetFillColorWithColor(c, [color CGColor]);
CGContextSetBlendMode(c, kCGBlendModeSourceAtop);
CGContextFillRect(c, rect);
UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return result;
}
Extend a UIView and just implement the drawRect method. For example, this will draw a green circle with a black outline.
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
CGContextRef gc = UIGraphicsGetCurrentContext();
[[UIColor greenColor] setFill];
CGContextFillEllipseInRect(gc, CGRectMake(0,0,24,24));
[[UIColor blackColor] set];
CGContextStrokeEllipseInRect(gc, CGRectMake(0,0,24,24));
}
For such simple shapes, just use CoreGraphics to draw a square and a circle -- adding the ability to set the fill color in your implementation.
If it's just black and white - then altering the white to another color is not so difficult when you know the color representations. Unfortunately, this is more complex to write and execute so… my recommendation is to just go straight to CoreGraphics for the simple task you outlined (bad pun, sorry).
here's a quick demo:
static void InsetRect(CGRect* const pRect, const CGFloat pAmount) {
const CGFloat halfAmount = pAmount * 0.5f;
*pRect = CGRectMake(pRect->origin.x + halfAmount, pRect->origin.y + halfAmount, pRect->size.width - pAmount, pRect->size.height - pAmount);
}
static void DrawBorderedCircleWithWidthInContext(const CGRect pRect, const CGFloat pWidth, CGContextRef pContext) {
CGContextSetLineWidth(pContext, pWidth);
CGContextSetShouldAntialias(pContext, true);
CGRect r = pRect;
/* draw circle's border */
CGContextSetRGBStrokeColor(pContext, 0.8f, 0.7f, 0, 1);
InsetRect(&r, pWidth);
CGContextStrokeEllipseInRect(pContext, r);
/* draw circle's fill */
CGContextSetRGBFillColor(pContext, 0, 0, 0.3f, 1);
InsetRect(&r, pWidth);
CGContextFillEllipseInRect(pContext, r);
}
Trying to create a UIimage from a Draw Context.
Not seeing anything. Am i missing something, or completely out my mind?
Code
- (UIImage *)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextMoveToPoint(context, 100, 100);
CGContextAddLineToPoint(context, 150, 150);
CGContextAddLineToPoint(context, 100, 200);
CGContextAddLineToPoint(context, 50, 150);
CGContextAddLineToPoint(context, 100, 100);
CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
CGContextFillPath(context);
// Do your stuff here
CGImageRef imgRef = CGBitmapContextCreateImage(context);
UIImage* img = [UIImage imageWithCGImage:imgRef];
CGImageRelease(imgRef);
CGContextRelease(context);
return img;
}
I'm assuming this is not a -drawRect: method on a view, because the return value is wrong. (-[UIView drawRect:] returns void, not a UIImage*.)
If it is on an NSView, that means you must be calling it directly, to get the return value. But that means that UIKit hasn't set up a graphics context, the way it normally does before it calls -drawRect: on the views in a window.
Therefore, you shouldn't assume that UIGraphicsGetCurrentContext() is valid. It's probably nil (have you checked?).
If you just want an image: use UIGraphicsBeginImageContext() to create a context, then UIGraphicsGetImageFromCurrentImageContext() to extract a UIImage (no need for the intermediary CGImage), then UIGraphicsEndImageContext() to clean up.
If you're trying to capture an image of what your view drew: fix your -drawRect: to return void, and find some other way to get that UIImage out of the view -- either stash it in an ivar, or send it to some other object, or write it to a file, whatever you like.
Also (less importantly):
Don't CGContextRelease(context). You didn't create, copy, or retain it, so you shouldn't release it.
No need for the last CGContextAddLineToPoint(). CGContextFillPath will implicitly close the path for you.