0

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?

HangarRash
  • 7,314
  • 5
  • 5
  • 32
meaning-matters
  • 21,929
  • 10
  • 82
  • 142
  • 1
    What exactly do you mean by *"How do I get the text at the desired point (10, 10)"* ??? Do you want the **label frame** at (10, 10)? The first **character**? The text's glyph bounding box? Examples: https://i.stack.imgur.com/gM1Zz.png – DonMag Jul 21 '23 at 18:59
  • @DonMag I want the top-left corner of the label's bounding box to be at (10, 10). – meaning-matters Jul 21 '23 at 21:13
  • OK - before we get into complicated sizing issues... is this your actual goal? `100x100` pixel image, with MAX font-size of `16`? Or is this just an "example" and you may have a target of `300x300` image, with maybe a MAX font-size of `50`? – DonMag Jul 22 '23 at 14:25
  • @DonMag The size is an example. For your understanding, this text image will act as an overlay to an icon image of for example 100x100. I need to place the text in a certain box at a certain position on the icon image. (Already have logic to compose the icon from one or more image assets.) – meaning-matters Jul 23 '23 at 08:53
  • 1
    Is there a reason you don't simply add the label as a subview, constrain its top/leading/trailing (or top/leading/width), and render the view as a UIImage? I guess you need to ***show us*** what you're getting, and explain why it's not what you want. – DonMag Jul 23 '23 at 15:38
  • I'm really trying to understand exactly what you want, and why it's problematic... see this image: https://i.stack.imgur.com/zyyaU.png – DonMag Jul 24 '23 at 12:34
  • @DonMag I didn't understand why the label did not appear at (10, 10). And, related to that, what the purpose of `[screenshot drawInRect:drawRect]` is (nobody commented/answered on that). I don't understand your confusion, because my only question was about how to get the label at the desired (10, 10) instead of (0, 0); I asked nothing about alignment. Of course in any conversation there are two, so it appears I could have been more clear (and replied quicker). Your suggestion above worked, yet I'm still interested to get my questions answered. – meaning-matters Jul 27 '23 at 07:27

3 Answers3

2

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

DonMag
  • 69,424
  • 5
  • 50
  • 86
  • Does `UIGraphicsImageRenderer` have a better performance too perhaps? Thanks for that suggestion, easy to miss by starting with copy-paste of older code. – meaning-matters Jul 28 '23 at 08:01
  • Without diving deep into profiling I couldn't give you a definitive answer. That said, it's rare that Apple introduces a new API that is less efficient. Aside from performance, there are other reasons why it might be wise to switch to `UIGraphicsImageRenderer` -- such as color formats, memory optimization, etc... But for *your specific task* I can't imagine you would see any difference. – DonMag Jul 28 '23 at 12:44
1

It's quite simple, just wrap your label in a view then capture the view instead of the label.

CGRect drawRect = CGRectMake(0, 0, 90, 35);
UIView *container = [[UIView alloc] initWithFrame: drawRect]
...
UILabel *myLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 80, 25)];
[container addSubview:myLabel];
...
[[container layer] renderInContext:UIGraphicsGetCurrentContext()];

Before

enter image description here

After

enter image description here

son
  • 1,058
  • 6
  • 7
1

Inset the rectangle used to initialize the label's frame...

// ...
let drawRect = CGRectMake(10, 10, 80, 25);

// Inset the drawRect with UIEdgeInsetsInsetRect()
// I've set the (... bottom, left) insets to (... -20, -20) in case
// you wish to center the label. If not, set those to (... 0, 0)
const UIEdgeInsets insets = UIEdgeInsetsMake(10, 10, -20, -20);
const CGRect insetRect = UIEdgeInsetsInsetRect(drawRect, insets);

// changed drawRect to insetRect
UILabel *myLabel = [[UILabel alloc] initWithFrame: insetRect];
// ...

Disclaimer: it has been years since I used Objective-C.

(Regarding what the drawInRect: is for, that's how you might choose to use the image after creating it. You might choose to draw it. If you don't need to, or are drawing it elsewhere later, you can remove that line).

danh
  • 62,181
  • 10
  • 95
  • 136