iosobjective-cuiimageuigraphicscontext

How to render UILabel at certain point in UIImage?


I'm using code found here to create an image with text that is scaled to available size:

CGFloat size = 100.0;
CGRect drawRect = CGRectMake(10, 10, 80, 25);

UILabel *myLabel = [[UILabel alloc] initWithFrame:drawRect];
myLabel.font = [UIFont fontWithName:@"HelveticaNeue-BoldItalic" size:16];
myLabel.text = "Hello text!";
myLabel.minimumScaleFactor = 0.5;
myLabel.adjustsFontSizeToFitWidth = YES;
myLabel.textAlignment = NSTextAlignmentCenter;
myLabel.backgroundColor = [UIColor clearColor];

UIGraphicsBeginImageContextWithOptions(CGSizeMake(size, size), NO, 0);
[[myLabel layer] renderInContext:UIGraphicsGetCurrentContext()];
UIImage *screenshot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

[screenshot drawInRect:drawRect];

return screenshot;

This creates a 100x100 image with the rendered label in the top-left corner: (0, 0). How do I get the text at the desired point (10, 10)?

To clarify: I want the label to be the size I specify, be centred horizontally, and its text to scale according to the available size.

Also, what is the purpose of [screenshot drawInRect:drawRect] because I seem to get the same result without it?


Solution

  • To answer your specific questions...

    First, as to "what is the purpose of [screenshot drawInRect:drawRect]" ...

    It has no purpose there.

    Presumably, the post you got that from intended to say something like: "Now you have a 100 x 100 UIImage "screenshot" which you can draw into some other rect in some other graphics context."


    Second...

    The label is not drawing at (10, 10) because you are rendering the label's layer -- which has an origin of (0, 0).

    If you want it rendered at (10, 10) you can modify the layer's bounds before drawing it:

    myLabel.backgroundColor = [UIColor clearColor];
    
    // change the label's layer bounds
    //  to the drawRect rect
    myLabel.layer.bounds = drawRect;
    
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(size, size), NO, 0);
    

    So, if we run your code (no idea why you have two let statements):

    CGFloat size = 100.0;
    CGRect drawRect = CGRectMake(10, 10, 80, 25);
    
    UILabel *myLabel = [[UILabel alloc] initWithFrame:drawRect];
    myLabel.font = [UIFont fontWithName:@"HelveticaNeue-BoldItalic" size:16];
    myLabel.text = "Hello text!";
    myLabel.minimumScaleFactor = 0.5;
    myLabel.adjustsFontSizeToFitWidth = YES;
    myLabel.textAlignment = NSTextAlignmentCenter;
    myLabel.backgroundColor = [UIColor clearColor];
    
    // change the label's layer bounds
    //  to match the drawRect
    myLabel.layer.bounds = drawRect;
    
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(size, size), NO, 0);
    [[myLabel layer] renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *screenshot = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    // do something with screenshot, such as
    someImageView.image = screenshot;
    

    You will now have a 100 x 100 point (not pixel) UIImage named "screenshot" with the label frame at (10, 10) (again, 10-points not pixels).

    This is the result... note that the code you posted specifies a CGContextRef at the device’s main screen scale factor. I'm running this on an iPhone 14 Pro which has a @3x factor, so the output is a 300x300 pixel image, with the label frame at (30, 30) pixels:

    enter image description here

    Since it has a clear background, that's how it looks in a paint app.

    If we modify the code just a little to add color to fill the image rect and the label frame, it looks like this:

    enter image description here


    As a side-note, you might want to look into the more "modern" UIGraphicsImageRenderer