0

It´s a pretty basic problem but I couldn´t find a proper solution for it. I have several circles which have text in it like you can see in the picture. The text gets loaded dynamically and has a size from one word up to five words or more. The goal is to put the text as big as possible into the circle. New lines can appear but every individual word should stay together. The example image is kind of ok but I would prefer the text to be bigger because there is still some free space between the text and the circle. The circle is 80x80. All solution I tried cropped the text strangly or the text is too small.

How I create the label:

UILabel *buttonlabel = [[UILabel alloc] initWithFrame:CGRectMake(12,7,57,64)];

    [buttonlabel setText: @"Recipes"];
    buttonlabel.font = [UIFont fontWithName:@"HelveticaNeue-Light" size:18.0f];
    buttonlabel.textColor = [UIColor whiteColor];
    buttonlabel.textAlignment = NSTextAlignmentCenter;
    buttonlabel.lineBreakMode = NSLineBreakByWordWrapping;
    buttonlabel.numberOfLines = 3;
   [button addSubview:buttonlabel];
   [buttonlabel release];

enter image description here

EDIT: So I tried the solution of Rufel. I think the shrinking kind of works but my words get ripped apart. Even though I have buttonlabel.lineBreakMode = NSLineBreakByWordWrapping;

It looks like this:

enter image description here

This is my code. I also implemented the other methods mentioned in an answer.

//Create the button labels
    UILabel *buttonlabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 60, 60)];


    [buttonlabel setText: @"text";
    buttonlabel.textColor = [UIColor whiteColor];
    buttonlabel.textAlignment = NSTextAlignmentCenter;
    buttonlabel.lineBreakMode = NSLineBreakByWordWrapping;

    buttonlabel.numberOfLines = 0;

    CGFloat fontSize = 20; // The max font size you want to use
    CGFloat labelHeightWithFont = 0;
    UIFont *labelFont = nil;

    do {
        // Trying the current font size if it fits
        labelFont = [UIFont systemFontOfSize:fontSize--];
        CGRect boundingRect = [self boundingRectForString:subcatbuttontitlesarray[buttonTag-1] font:labelFont];
        labelHeightWithFont = boundingRect.size.height;
        // Loop until the text at the current size fits the maximum width/height.
    } while (labelHeightWithFont > [self buttonLabelMaxWidth]);

    buttonlabel.text = subcatbuttontitlesarray[buttonTag-1];
    buttonlabel.font = labelFont;

- (CGRect)boundingRectForString:(NSString *)string font:(UIFont *)font
 {

return [string boundingRectWithSize:CGSizeMake([self buttonLabelMaxWidth], MAXFLOAT)
                                      options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
                                   attributes:@{NSFontAttributeName: font}
                                      context:nil];
 }



- (CGFloat)buttonLabelMaxWidth
      {
   CGFloat hypotenuse = CGRectGetWidth(CGRectMake(0, 0, 60, 60));
   CGFloat rightTriangleCathetus = sqrtf((hypotenuse*hypotenuse)/2);
   return rightTriangleCathetus;
      }

I found this thread here:

iOS7 - Adjusting font size of multiline label to fit its frame

which has the same problem.

Edit 2:

After searching a complete day for the solution and trying all kinds of combinations of the label attributes I somehow figured out that the "numberoflines" is my culprit. So I came up with this dumb solution of counting the words in the string and adjust the number of lines based on the numbers of the string:

    NSString *samplestring = @"Three words string";
//Count the words in this string
int times = [[samplestring componentsSeparatedByString:@" "] count]-1;

UILabel *testlabel = [[UILabel alloc]initWithFrame:CGRectMake(30, 30, 60, 60)];
[testlabel setText:samplestring];



[testlabel setFont:[UIFont fontWithName:@"HelveticaNeue-UltraLight" size:40.0f]];
[testlabel setBackgroundColor:[UIColor redColor]];

 [testlabel setAdjustsFontSizeToFitWidth:YES];
  [testlabel setTextAlignment:NSTextAlignmentCenter];

//My workaround
if(times ==0){
[testlabel setNumberOfLines:1];
}else{
    if(times==1){
        [testlabel setNumberOfLines:2];
    }
        else{
            [testlabel setNumberOfLines:3];
        }}




 [self.view addSubview:testlabel];
Community
  • 1
  • 1
tyler
  • 424
  • 4
  • 16
  • My answer has been updated to prevent word truncation, since ```boundingRectForString:``` always return a width equal or smaller than the one provided, even if a single word is wider than that width. – Rufel Apr 27 '15 at 01:56

2 Answers2

2

What you want to do, I think, is to ask the NSString for its boundingRectWithSize:options:attributes:context:. By setting the width of the bounding rect, you can find out what the height would be. You can use the parametric formula for the circle to determine whether that bounding rect fits entirely within the center of the circle. Unfortunately you will have to perform a kind of trial-and-error sequence of approximations, where the text gets larger and larger until the top and bottom stick out of the circle, and then narrow the proposed width and see whether this causes the height to grow too much because the text now wraps an extra time.

matt
  • 515,959
  • 87
  • 875
  • 1,141
1

Say you have a custom view in which you draw a circle that fits its frame (80x80 in your example).

You will first want to find the maximum width your label can take without letters crossing the circle:

- (CGFloat)buttonLabelMaxWidth
{
    CGFloat hypotenuse = CGRectGetWidth(self.bounds);
    CGFloat rightTriangleCathetus = sqrtf((hypotenuse*hypotenuse)/2);
    return floorf(rightTriangleCathetus);
}

Next, when you pass the title to display, you will want to iterate by decreasing an initially oversized font until the resulting string boundary fits the width previously calculated (which is also the maximum height since it's a circle). UPDATE: You will also want to check every words in the title to be sure they are not being truncated (that they fit the maximum width).

- (void)setButtonTitle:(NSString *)title
{
    CGFloat fontSize = 20; // The max font size you want to use
    CGFloat minimumFontSize = 5; // The min font size you want to use
    CGFloat labelHeightWithFont = 0;
    CGFloat longestWordWidth = 0;
    UIFont *labelFont = nil;

    CGFloat buttonLabelMaxWidth = [self buttonLabelMaxWidth];

    do {

        if (fontSize < minimumFontSize) {
            // Handle exception where the title just won't fit
            break;
        }

        // Trying the current font size if it fits
        labelFont = [UIFont systemFontOfSize:fontSize--];
        CGSize boundingSize = [self boundingSizeForString:title font:labelFont];
        labelHeightWithFont = boundingSize.height;

        // Be sure that words are not truncated (that they fits in the maximum width)
        longestWordWidth = 0;
        for (NSString *word in [title componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]) {
            CGSize wordSize = [word sizeWithAttributes:@{NSFontAttributeName: labelFont}];
            longestWordWidth = MAX(longestWordWidth, wordSize.width);
        }

        // Loop until the text at the current size fits the maximum width/height.
    } while (labelHeightWithFont > buttonLabelMaxWidth || longestWordWidth > buttonLabelMaxWidth);

    self.buttonLabel.text = title;
    self.buttonLabel.font = labelFont;
}

- (CGSize)boundingSizeForString:(NSString *)string font:(UIFont *)font
{

    CGRect boundingRect = [string boundingRectWithSize:CGSizeMake([self buttonLabelMaxWidth], MAXFLOAT)
                                options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
                             attributes:@{NSFontAttributeName: font}
                                context:nil];
    return CGSizeMake(ceilf(boundingRect.size.width), ceilf(boundingRect.size.height));
}
Rufel
  • 2,630
  • 17
  • 15
  • I assume you mean while (labelHeightWithFont > [self buttonLabelMaxWidth]); ? – tyler Apr 26 '15 at 11:53
  • Yes, sorry, I fixed it. I made this answer from a part of one of my own project, and changed the method/variable names to reflect your case. – Rufel Apr 26 '15 at 14:17
  • I´ve edited my question. It doesn´t work properly :( I accidentally edited your answer as well. Sorry. – tyler Apr 26 '15 at 15:03
  • @tyler: Ah! Sorry, you also need to be sure words are not truncated. I edited my answer and added a loop that do just that. I also added a few number roundings here and there. This should work now :) – Rufel Apr 26 '15 at 22:00