Solved

Getting text to fit inside UITextView

Posted on 2011-02-24
6
9,920 Views
Last Modified: 2013-11-25
Hi, I'm trying to dynamically change the size of text so that when it is displayed in a UITextView it entirely fits. My UITextView size cannot change but I want to display the text to be the largest size that can to fit inside the view. I have the following code:

- (IBAction) displayText:(id)sender
{
      
      BOOL run=TRUE;
      CGSize sizeOfText;
      int intFontSize =  [fontSize.text intValue];
      CGSize txtViewSize = self.textView.frame.size;
      CGSize txtViewAdjustedSize = CGSizeMake(self.textView.frame.size.width-16, self.textView.frame.size.height-16);  // 16 is assumed scrollbar size
      
      NSString *stringText = [[NSString alloc] initWithString:labelTextView.text];
      
      while(run){
            
            sizeOfText = [stringText sizeWithFont:[UIFont boldSystemFontOfSize:intFontSize] constrainedToSize:txtViewAdjustedSize lineBreakMode:UILineBreakModeWordWrap];
            
            NSLog(@"sizeOfText %@, fontSize %d, txtViewSize %@, txtViewAdjustedSize %@", NSStringFromCGSize(sizeOfText), (int)intFontSize, NSStringFromCGSize(txtViewSize), NSStringFromCGSize(txtViewAdjustedSize));
            
            //if(size.width<=txtViewWidth || size.height <= txtViewHeight )
            if(sizeOfText.width <= txtViewAdjustedSize.width && sizeOfText.height <= txtViewAdjustedSize.height)
                  run = FALSE;
            else
                  intFontSize--;
      }
      
      self.textView.font = [UIFont boldSystemFontOfSize:intFontSize];
      self.textView.text = [NSString stringWithFormat:@"%@", stringText];
}


This displays the text but it is truncated until it gets down to font size 16 when it actually goes through the while loop to make it font size 15 which actually does fit in. Please see below log output:

sizeOfText {153, 57}, fontSize 48, txtViewSize {176, 114}, txtViewAdjustedSize {160, 98}

sizeOfText {129, 86}, fontSize 36, txtViewSize {176, 114}, txtViewAdjustedSize {160, 98}

sizeOfText {128, 87}, fontSize 24, txtViewSize {176, 114}, txtViewAdjustedSize {160, 98}

sizeOfText {157, 88}, fontSize 18, txtViewSize {176, 114}, txtViewAdjustedSize {160, 98}

sizeOfText {159, 100}, fontSize 16, txtViewSize {176, 114}, txtViewAdjustedSize {160, 98}

sizeOfText {153, 95}, fontSize 15, txtViewSize {176, 114}, txtViewAdjustedSize {160, 98}

I'm confused by the fontSize. It is width and height isn't it. Why is fontSize 48 = 153, 57 and fontSize 16 = 159,100?

. What am I doing wrong?  I have the project code available if you need it. Cheers.     iPhone Simulator Image
0
Comment
Question by:englishchrissy
  • 4
  • 2
6 Comments
 
LVL 5

Expert Comment

by:mad_mac
ID: 34978614
is there any reason you are using a UITextView...?

you could simply override you view draw method and use the NSString drawing methods.

use the sizeWithFont methods to calculate the correct font size and then on of drawInRect methods to actually draw it.

obviously this is only any good if you want to simply display text to the user, if you want them to be able to interact with the text then this will not work

http://developer.apple.com/library/mac/#documentation/cocoa/conceptual/TextLayout/TextLayout.html

The drawing strings section of the above document should help.
0
 

Author Comment

by:englishchrissy
ID: 34978994
I use the UITextView only to display the text. It cannot be edited and will only ever be one sentence but it must wrap as the text won't fit on one line. I am new to obj-c so maybe I don't need a UITextView. What else could I use and would that make any difference to what I'm trying to achieve.

The UITextView area above (after adjusting for scroll bars) is (160,98). All I'm trying to do above is see if the text fits that area and if it doesn't then I reduce the size of the font until it does. Sounds simple in theory.

Can you explain why I'm getting the sizeOfText for fontSize 48 is (153, 57). Whereas it is (159,100) for fontSize 16. Surely 48 is bigger than 16. Am I doing something stupid?

When I run the app with fontSize = 16 it goes through the while loop twice and reduces the fontSize to 15 where the text does actually fit inside the UITextView. Why is fontSize 48 size smaller than fontSize 16?


0
 
LVL 5

Accepted Solution

by:
mad_mac earned 500 total points
ID: 34980185
the issue here is you are not fully understanding what "sizeWithFont:constrainedToSize:lineBreakMode:" is doing.  It is making assumptions about the truncation of the display string, therefore yes at 48pt a smaller CGSize is returned but if you were to render the text using "drawInRect:withFont:lineBreakMode:" it would simply truncate a significant amount of the text away.

from the description of  sizeWithFont:constrainedToSize:lineBreakMode:

If the receiver’s text does not completely fit in the specified size, it lays out as much of the text as possible and truncates it (for layout purposes only) according to the specified line break mode. It then returns the size of the resulting truncated string.

which is why these days i tend to use either the the drawing extensions of NSString or use core text to draw strings where i can predicts the exact behaviour.

If you were to use something like this to look at the size needed to place the string it may become more apparent what is going on..
NSString *testText = @"This is the text that I want to fit into a UITextView so that it does not get truncated!";
CGSize maxSize = CGSizeMake(160, 100);
UIFont *tmpFont = [UIFont systemFontOfSize:10.0];

for (int i=48; i > 9; i--) {
	
	CTFontRef fnt = CTFontCreateWithName( (CFStringRef)tmpFont.fontName, i, NULL);
	CFMutableAttributedStringRef theText = CFAttributedStringCreateMutable(NULL, 0);
	CFAttributedStringBeginEditing(theText);
	CFAttributedStringReplaceString (theText, CFRangeMake(0,0), (CFStringRef)testText);
	CFAttributedStringSetAttribute(theText, CFRangeMake(0,[testText length]), kCTFontAttributeName, fnt);
	CFAttributedStringEndEditing(theText);
	
	CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString(theText);
	CFRange fitRange;
	CGSize textSize = CTFramesetterSuggestFrameSizeWithConstraints( frameSetter, CFRangeMake(0,[testText length]), NULL, maxSize, &fitRange);

	NSLog(@"sizeOfText %@, fontSize %d, maxSize %@, rangefit {%d,%d}", 
		  NSStringFromCGSize(textSize), 
		  (int)i, 
		  NSStringFromCGSize(maxSize), 
		  fitRange.location, 
		  fitRange.length);
	
	CFRelease (theText);
	CFRelease (frameSetter);
}

Open in new window


The output of this is something like below
sizeOfText {152.016, 57.6}, fontSize 48, maxSize {160, 100}, rangefit {0,8}
sizeOfText {148.849, 56.4}, fontSize 47, maxSize {160, 100}, rangefit {0,8}
sizeOfText {145.682, 55.2}, fontSize 46, maxSize {160, 100}, rangefit {0,8}
sizeOfText {142.515, 54}, fontSize 45, maxSize {160, 100}, rangefit {0,8}
sizeOfText {139.348, 52.8}, fontSize 44, maxSize {160, 100}, rangefit {0,8}
sizeOfText {136.181, 51.6}, fontSize 43, maxSize {160, 100}, rangefit {0,8}
sizeOfText {133.014, 50.4}, fontSize 42, maxSize {160, 100}, rangefit {0,8}
sizeOfText {145.862, 98.4}, fontSize 41, maxSize {160, 100}, rangefit {0,17}
sizeOfText {142.305, 96}, fontSize 40, maxSize {160, 100}, rangefit {0,17}
sizeOfText {138.747, 93.6}, fontSize 39, maxSize {160, 100}, rangefit {0,17}
sizeOfText {135.189, 91.2}, fontSize 38, maxSize {160, 100}, rangefit {0,17}
sizeOfText {131.632, 88.8}, fontSize 37, maxSize {160, 100}, rangefit {0,17}
sizeOfText {128.074, 86.4}, fontSize 36, maxSize {160, 100}, rangefit {0,17}
sizeOfText {169.224, 84}, fontSize 35, maxSize {160, 100}, rangefit {0,24}
sizeOfText {164.389, 81.6}, fontSize 34, maxSize {160, 100}, rangefit {0,24}
sizeOfText {159.554, 79.2}, fontSize 33, maxSize {160, 100}, rangefit {0,24}
sizeOfText {154.719, 76.8}, fontSize 32, maxSize {160, 100}, rangefit {0,24}
sizeOfText {149.884, 74.4}, fontSize 31, maxSize {160, 100}, rangefit {0,24}
sizeOfText {145.049, 72}, fontSize 30, maxSize {160, 100}, rangefit {0,24}
sizeOfText {140.214, 69.6}, fontSize 29, maxSize {160, 100}, rangefit {0,24}
sizeOfText {135.379, 67.2}, fontSize 28, maxSize {160, 100}, rangefit {0,24}
sizeOfText {130.544, 97.2}, fontSize 27, maxSize {160, 100}, rangefit {0,36}
sizeOfText {125.709, 93.6}, fontSize 26, maxSize {160, 100}, rangefit {0,36}
sizeOfText {161.194, 90}, fontSize 25, maxSize {160, 100}, rangefit {0,41}
sizeOfText {161.391, 86.4}, fontSize 24, maxSize {160, 100}, rangefit {0,43}
sizeOfText {162.359, 82.8}, fontSize 23, maxSize {160, 100}, rangefit {0,43}
sizeOfText {155.3, 79.2}, fontSize 22, maxSize {160, 100}, rangefit {0,43}
sizeOfText {148.241, 75.6}, fontSize 21, maxSize {160, 100}, rangefit {0,43}
sizeOfText {163.438, 96}, fontSize 20, maxSize {160, 100}, rangefit {0,74}
sizeOfText {164.738, 91.2}, fontSize 19, maxSize {160, 100}, rangefit {0,78}
sizeOfText {156.067, 86.4}, fontSize 18, maxSize {160, 100}, rangefit {0,78}
sizeOfText {158.429, 81.6}, fontSize 17, maxSize {160, 100}, rangefit {0,78}
sizeOfText {149.109, 96}, fontSize 16, maxSize {160, 100}, rangefit {0,88}
sizeOfText {153.442, 72}, fontSize 15, maxSize {160, 100}, rangefit {0,88}
sizeOfText {162.627, 67.2}, fontSize 14, maxSize {160, 100}, rangefit {0,88}
sizeOfText {151.011, 62.4}, fontSize 13, maxSize {160, 100}, rangefit {0,88}
sizeOfText {157.189, 43.2}, fontSize 12, maxSize {160, 100}, rangefit {0,88}
sizeOfText {159.387, 39.6}, fontSize 11, maxSize {160, 100}, rangefit {0,88}
sizeOfText {159.355, 36}, fontSize 10, maxSize {160, 100}, rangefit {0,88}

Open in new window


as you can see it is not until you get down to a text size of 16 or below does the entire string fit.

i hope this helps, programming guide is a good starting place for it
0
IT, Stop Being Called Into Every Meeting

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

 

Author Comment

by:englishchrissy
ID: 34981182
Thanks for the help mad mac, it's very much appreciated. I have replaced my UITextViews with labels as you suggested. I will also implement your other suggestions. It has certainly pointed me in the right direction. I did not know how complicated working with fonts can be. I was expecting it to be easy but have had a look at the Apple docs and there is certainly a lot to it.
0
 

Author Comment

by:englishchrissy
ID: 34986650
Ok, this is my re-worked example. In the app I'm coding the font size will never be more than 22 and I've shortened the string here because it will never be that long. So this now corresponds to what my app actually needs. I've added a bit of code to see if the fitRange < stringLength and if it is I subtract 4 from the font size. A bit of a bodge, I know but it works.

If anyone has advice or improvements I would love to hear. I'm sure there are many. If anyone wants the project source code, just contact me.

- (IBAction) displayText:(id)sender
{
      
      BOOL run = TRUE;
      NSString *testText = [[NSString alloc] initWithString:labelTextToBeDisplayed.text];

      int stringLength = [testText length];
      CGSize maxSize = self.labelNewText.frame.size;
      UIFont *tmpFont = [UIFont boldSystemFontOfSize:22.0];
      int i = [fontSize.text intValue];
      int fitRangeLength = 0;
      
      while(run)
      {
            
            CTFontRef fnt = CTFontCreateWithName( (CFStringRef)tmpFont.fontName, i, NULL);
            CFMutableAttributedStringRef theText = CFAttributedStringCreateMutable(NULL, 0);
            CFAttributedStringBeginEditing(theText);
            CFAttributedStringReplaceString (theText, CFRangeMake(0,0), (CFStringRef)testText);
            CFAttributedStringSetAttribute(theText, CFRangeMake(0,[testText length]), kCTFontAttributeName, fnt);
            CFAttributedStringEndEditing(theText);
            
            CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString(theText);
            CFRange fitRange;
            CGSize textSize = CTFramesetterSuggestFrameSizeWithConstraints( frameSetter, CFRangeMake(0,[testText length]), NULL, maxSize, &fitRange);
            
            NSLog(@"sizeOfText %@, fontSize %d, maxSize %@, rangefit {%d,%d}, stringLength %d",
                              NSStringFromCGSize(textSize),
                              (int)i,
                              NSStringFromCGSize(maxSize),
                              fitRange.location,
                              fitRange.length,
                              stringLength);
            
            CFRelease (theText);
            CFRelease (frameSetter);
            fitRangeLength = (int)fitRange.length;
            
            float floatWidth = textSize.width;
            float floatHeight = textSize.height;
            int intWidth = (int)(floatWidth + 0.5);
            int intHeight = (int)(floatHeight + 0.5);
            
            if(intWidth <= maxSize.width && intHeight <= maxSize.height)
            {
                  run = FALSE;                  
            }
            else
            {                  
                  i--;
            }
      }
      if (fitRangeLength < stringLength) {
             i = i - 4;
            NSLog(@"i = %d", (int)i);
      }
      
      self.labelNewText.font = [UIFont boldSystemFontOfSize:i];
      self.labelNewText.text = [NSString stringWithFormat:@"%@", testText];
      [testText release];

}

 iPhone App Screenshot
0
 

Author Closing Comment

by:englishchrissy
ID: 35018635
It was enough to point me in the right direection. I would have been lost without mad mac's help
0

Featured Post

IT, Stop Being Called Into Every Meeting

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

Join & Write a Comment

Windows programmers of the C/C++ variety, how many of you realise that since Window 9x Microsoft has been lying to you about what constitutes Unicode (http://en.wikipedia.org/wiki/Unicode)? They will have you believe that Unicode requires you to use…
This is a short and sweet, but (hopefully) to the point article. There seems to be some fundamental misunderstanding about the function prototype for the "main" function in C and C++, more specifically what type this function should return. I see so…
The goal of this video is to provide viewers with basic examples to understand recursion in the C programming language.
The goal of this video is to provide viewers with basic examples to understand how to use strings and some functions related to them in the C programming language.

744 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

11 Experts available now in Live!

Get 1:1 Help Now