15

How would I set the font size of text in a UITextView such that it fills the entire UITextView? I'd like the user to type in their text, then have the text fill the entire UITextView.

Any help is appreciated!

Peter Hosey
  • 95,783
  • 15
  • 211
  • 370
Johnny Tee
  • 191
  • 1
  • 1
  • 4

16 Answers16

25

I have converted dementiazz's answer to Swift:

func updateTextFont() {
    if (textView.text.isEmpty || CGSizeEqualToSize(textView.bounds.size, CGSizeZero)) {
        return;
    }

    let textViewSize = textView.frame.size;
    let fixedWidth = textViewSize.width;
    let expectSize = textView.sizeThatFits(CGSizeMake(fixedWidth, CGFloat(MAXFLOAT)));

    var expectFont = textView.font;
    if (expectSize.height > textViewSize.height) {
        while (textView.sizeThatFits(CGSizeMake(fixedWidth, CGFloat(MAXFLOAT))).height > textViewSize.height) {
            expectFont = textView.font!.fontWithSize(textView.font!.pointSize - 1)
            textView.font = expectFont
        }
    }
    else {
        while (textView.sizeThatFits(CGSizeMake(fixedWidth, CGFloat(MAXFLOAT))).height < textViewSize.height) {
            expectFont = textView.font;
            textView.font = textView.font!.fontWithSize(textView.font!.pointSize + 1)
        }
        textView.font = expectFont;
    }
}

Swift 3.0+ Update:

func updateTextFont() {
    if (textView.text.isEmpty || textView.bounds.size.equalTo(CGSize.zero)) {
        return;
    }

    let textViewSize = textView.frame.size;
    let fixedWidth = textViewSize.width;
    let expectSize = textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat(MAXFLOAT)));

    var expectFont = textView.font;
    if (expectSize.height > textViewSize.height) {
        while (textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat(MAXFLOAT))).height > textViewSize.height) {
            expectFont = textView.font!.withSize(textView.font!.pointSize - 1)
            textView.font = expectFont
        }
    }
    else {
        while (textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat(MAXFLOAT))).height < textViewSize.height) {
            expectFont = textView.font;
            textView.font = textView.font!.withSize(textView.font!.pointSize + 1)
        }
        textView.font = expectFont;
    }
}
newswiftcoder
  • 111
  • 1
  • 11
Matt Frear
  • 52,283
  • 12
  • 78
  • 86
8

This, in a category on UITextView, should do the trick. For why you need the fudgefactor, see this post

#define kMaxFieldHeight 9999 

-(BOOL)sizeFontToFit:(NSString*)aString minSize:(float)aMinFontSize maxSize:(float)aMaxFontSize 
{   
float fudgeFactor = 16.0;
float fontSize = aMaxFontSize;

self.font = [self.font fontWithSize:fontSize];

CGSize tallerSize = CGSizeMake(self.frame.size.width-fudgeFactor,kMaxFieldHeight);
CGSize stringSize = [aString sizeWithFont:self.font constrainedToSize:tallerSize lineBreakMode:UILineBreakModeWordWrap];

while (stringSize.height >= self.frame.size.height)
       {
       if (fontSize <= aMinFontSize) // it just won't fit
           return NO;

       fontSize -= 1.0;
       self.font = [self.font fontWithSize:fontSize];
       tallerSize = CGSizeMake(self.frame.size.width-fudgeFactor,kMaxFieldHeight);
       stringSize = [aString sizeWithFont:self.font constrainedToSize:tallerSize lineBreakMode:UILineBreakModeWordWrap];
       }

return YES; 
}
Community
  • 1
  • 1
Jane Sales
  • 13,526
  • 3
  • 52
  • 57
  • 2
    in iOS >7.0 you need to change UILineBreakModeWordWrap to NSLineBreakByWordWrapping – Vlad Z. Jan 05 '14 at 15:41
  • just curious, how do you know "fudgeFactor = 16.0"? I believe this value should be smaller when text size is smaller – LiangWang May 01 '15 at 03:08
7

Similar approach to Arie Litovsky's answer but without sub-classing (or use of a category) and not using contentSize which didn't return the correct height of the rendered text for me. Tested on iOS 7:

while (((CGSize) [_textView sizeThatFits:_textView.frame.size]).height > _textView.frame.size.height) {
    _textView.font = [_textView.font fontWithSize:_textView.font.pointSize-1];
}

The approach is to keep reducing the font size until the text just fits inside the frame of the text view.

If you were going to use this in production you would still need to:

  • Handle the case where even with the smallest-possible font size the text still won't fit.
  • Use a similar approach to increase the font size if you would also like to scale text up to fit the frame.
Stefan Magnuson
  • 870
  • 1
  • 10
  • 10
6

Try this, it's a lot simpler:

while (self.contentSize.height > self.frame.size.height)
{
    self.font = [self.font fontWithSize:self.font.pointSize -1];
    [self layoutSubviews];//force .contentSize to update
}
Albert Renshaw
  • 17,282
  • 18
  • 107
  • 195
Arie Litovsky
  • 4,893
  • 2
  • 35
  • 40
  • 3
    @BradMoore, you need to add one more line: [textView layoutSubviews]; – LiangWang Mar 09 '14 at 23:43
  • @Jacky, good to know! If I revisit this code I'll see if its worth updating. – Brad Moore Mar 10 '14 at 23:57
  • @Jacky - it's actually [textView layoutIfNeeded] you want to call – amergin May 31 '16 at 15:22
  • @BradMoore This should be top answer. I also noticed this was answered in 2012, resolution is so high these days that fractional font sizes should be considered. I used -0.5 instead -1 and it worked very smoothly on all device sizes. – Albert Renshaw Jan 18 '22 at 01:32
3

Here is my sample code. Basically, I check the expect size to know if font size need to increase or decrease. You need to add UITextViewDelegate to your class to make sure it workings

- (void)updateTextFont:(UITextView *)textView
{
    // Only run if has text, otherwise it will make infinity loop
    if (textView.text.length == 0 || CGSizeEqualToSize(textView.bounds.size, CGSizeZero)) return;

    /*
     - Update textView font size
     If expectHeight > textViewHeight => descrease font size n point until it reach textViewHeight
     If expectHeight < textViewHeight => inscrease font size n point until it reach textViewHeight
     */
    CGSize textViewSize = textView.frame.size;
    CGFloat fixedWidth = textViewSize.width;
    CGSize expectSize = [textView sizeThatFits:CGSizeMake(fixedWidth, MAXFLOAT)];

    UIFont *expectFont = textView.font;
    if (expectSize.height > textViewSize.height) {
        while ([textView sizeThatFits:CGSizeMake(fixedWidth, MAXFLOAT)].height > textViewSize.height) {
            expectFont = [textView.font fontWithSize:(textView.font.pointSize-1)];
            textView.font = expectFont;
        }
    } else {
        while ([textView sizeThatFits:CGSizeMake(fixedWidth, MAXFLOAT)].height < textViewSize.height) {
            expectFont = textView.font;
            textView.font = [textView.font fontWithSize:(textView.font.pointSize+1)];
        }
        textView.font = expectFont;
    }
}
nahung89
  • 7,745
  • 3
  • 38
  • 40
  • 1
    I'm curious what the else clause is doing here. You set an `expectFont` to the `textView's` `font` before you modify it... then you set it back to the unmodified `font`? Is that to prevent the jumping up and down of the text size? – Stephen Paul Feb 16 '18 at 09:44
  • @StephenPaul in else clause, if while doesn't run, the `textView.font` doesn't change (because `expectFont` is actually original `textView.font`). But if while run at least 1 time, `expectFont` is changed and we need to update it after finish the loop. I could make a flag to determine while run or not, but I didn't. Instead, I group it into last line `textView.font = expectFont` – nahung89 Feb 20 '18 at 05:17
  • if the while loop runs at least 1 time, you store the current font in a variable `expectFont`. Then you increase the `textView`'s font by one point, but then when that while loop exits, you see that `textView` font back to what it was... – Stephen Paul Feb 28 '18 at 10:08
  • you set*, I meant. – Stephen Paul Feb 28 '18 at 23:41
  • Yes, because the last time `textView.font` is setted, the while condition is false (I'm using textView to calculate the condition directly). That mean the font I'm setting is not correct, and I have to roll back to expect font. – nahung89 Mar 01 '18 at 03:11
3

I have converted dementiazz's & Matt Frear's answer to Swift 3:

func updateTextFont() {
        if (textView.text.isEmpty || textView.bounds.size.equalTo(CGSize.zero)) {
            return
        }

    let textViewSize = textView.frame.size
    let fixedWidth = textViewSize.width
    let expectSize = textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat(MAXFLOAT)))

    var expectFont = textView.font
    if (expectSize.height > textViewSize.height) {
        while (textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat(MAXFLOAT))).height > textViewSize.height) {
            expectFont = textView.font!.withSize(textView.font!.pointSize - 1)
            textView.font = expectFont
        }
    }
    else {
        while (textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat(MAXFLOAT))).height < textViewSize.height) {
            expectFont = textView.font
            textView.font = textView.font!.withSize(textView.font!.pointSize + 1)
        }
        textView.font = expectFont
    }
}
George Asda
  • 2,119
  • 2
  • 28
  • 54
3

for swift 3

    func updateTextFont(textView: UITextView) {
     if (textView.text.isEmpty || textView.bounds.size.equalTo(CGSize.zero)) {
        return
     }
     let textViewSize = textView.frame.size
     let fixedWidth = textViewSize.width
     let expectSize = textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat(MAXFLOAT)))
     var expectFont = textView.font;
     if (expectSize.height > textViewSize.height) {
        while (textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat(MAXFLOAT))).height > textViewSize.height) {
            expectFont = textView.font!.withSize(textView.font!.pointSize - 1)
            textView.font = expectFont
        }
     }
     else {
        while (textView.sizeThatFits(CGSize(width: fixedWidth,height: CGFloat(MAXFLOAT))).height < textViewSize.height) {
            expectFont = textView.font
            textView.font = textView.font!.withSize(textView.font!.pointSize + 1)
        }
        textView.font = expectFont
     }
 }
Tarek hemdan
  • 874
  • 2
  • 10
  • 24
3

I have converted Matt Frear's answer to Swift 4.1 as extension for UITextView:

extension UITextView {
func updateTextFont() {
    if (self.text.isEmpty || self.bounds.size.equalTo(CGSize.zero)) {
        return;
    }

    let textViewSize = self.frame.size;
    let fixedWidth = textViewSize.width;
    let expectSize = self.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat(MAXFLOAT)))


    var expectFont = self.font
    if (expectSize.height > textViewSize.height) {

        while (self.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat(MAXFLOAT))).height > textViewSize.height) {
            expectFont = self.font!.withSize(self.font!.pointSize - 1)
            self.font = expectFont
        }
    }
    else {
        while (self.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat(MAXFLOAT))).height < textViewSize.height) {
            expectFont = self.font
            self.font = self.font!.withSize(self.font!.pointSize + 1)
        }
        self.font = expectFont
    }
}
}
Joseph Astrahan
  • 8,659
  • 12
  • 83
  • 154
Robin Kment
  • 115
  • 4
3

Max Font Size

If you would like the size of the font in your UITextView to not exceed a certain point size, I added that functionality to Matt's answer. I also put it in an extension of UITextView to make it easier to use.

extension UITextView {
    /// Autosizes the font to fit the UITextView's text in its frame
    /// - Parameter max: The maximum point size the font should get
    func updateTextFont(max: CGFloat) {
        if (self.text!.isEmpty || self.bounds.size.equalTo(CGSize.zero)) {
            return;
        }
        
        let textViewSize = self.frame.size;
        let fixedWidth = textViewSize.width;
        let expectSize = self.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat(MAXFLOAT)));
        
        var expectFont = self.font;
        if (expectSize.height > textViewSize.height) {
            while (self.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat(MAXFLOAT))).height > textViewSize.height) && (self.font!.pointSize <= max) {
                expectFont = self.font!.withSize(self.font!.pointSize - 1)
                self.font = expectFont
            }
        } else {
            while (self.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat(MAXFLOAT))).height < textViewSize.height) && (self.font!.pointSize <= max) {
                expectFont = self.font;
                self.font = self.font!.withSize(self.font!.pointSize + 1)
            }
            self.font = expectFont;
        }
    }
}

I found it best to call in UITextView's delegate method textViewDidChange

class UIViewController: UIViewController, UITextViewDelegate {
    
    @IBOutlet var textView: UITextView!
    
    func textViewDidChange(_ textView: UITextView) {
        textView.updateTextFont(max: 40)
    }
}

This has also been updated for Swift 5.1

Jacob Cavin
  • 2,169
  • 3
  • 19
  • 47
2

This property is only available on UITextFields. To do this in a UITextView, you'd have to constantly watch the text and manually adjust the font size as that changed.

Ben Gottlieb
  • 85,404
  • 22
  • 176
  • 172
2

Hope that's clearer:

extension UITextView {
    @objc public func fitText() {
        fitText(into: frame.size)
    }

    @objc public func fitText(into originalSize: CGSize) {
        let originalWidth = originalSize.width
        let expectedSize = sizeThatFits(CGSize(width: originalWidth, height: CGFloat(MAXFLOAT)))

        if expectedSize.height > originalSize.height {
            while let font = self.font, sizeThatFits(CGSize(width: originalWidth, height: CGFloat(MAXFLOAT))).height > originalSize.height {
                self.font = font.withSize(font.pointSize - 1)
            }
        } else {
            var previousFont = self.font
            while let font = self.font, sizeThatFits(CGSize(width: originalWidth, height: CGFloat(MAXFLOAT))).height < originalSize.height {
                previousFont = font
                self.font = font.withSize(font.pointSize + 1)
            }
            self.font = previousFont
        }
    }
}
blyabtroi
  • 2,046
  • 1
  • 15
  • 22
1

In viewDidLoad:

textView.textContainer.lineFragmentPadding = 0;
textView.textContainerInset = UIEdgeInsetsMake(0, 0, 0, 0);

You need to add UITextViewDelegate:

- (void)updateTextFont:(UITextView *)textView {

    CGSize textViewSize = textView.frame.size;
    CGSize sizeOfText = [textView.text sizeWithAttributes:@{NSFontAttributeName: textView.font}];

    CGFloat fontOfWidth = floorf(textView.font.pointSize / sizeOfText.height * textViewSize.height);
    CGFloat fontOfHeight = floorf(textView.font.pointSize / sizeOfText.width * textViewSize.width);

    textView.font = [textView.font fontWithSize:MIN(fontOfHeight, fontOfWidth)];
}
Amby
  • 11
  • 1
0

An updated version of @Arie Litovsky's code. This works in iOS7 and later and stretches the font size both up and down so the text fits.

- (void) stretchFontToFit:(UITextView*)textView
{

    while( textView.contentSize.height < textView.frame.size.height )
    {
        textView.font = [textView.font fontWithSize:textView.font.pointSize +1];
        [textView layoutIfNeeded];
    }

    while( textView.contentSize.height > textView.frame.size.height )
    {
        textView.font = [textView.font fontWithSize:textView.font.pointSize -1];
        [textView layoutIfNeeded];
    }
}
amergin
  • 3,106
  • 32
  • 44
0

Here is a conversion of dementiazz's and Matt Frear's answer to Swift 5. Put this function into viewDidLayoutSubviews, referencing the UITextView's who's font you want to dynamically resize.

func updateTextFont(textView: UITextView) {
    if (textView.text.isEmpty || textView.bounds.size.equalTo(CGSize.zero)) {
        return;
    }

    let textViewSize = textView.frame.size;
    let fixedWidth = textViewSize.width;
    let expectSize = textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat(MAXFLOAT)))

    var expectFont = textView.font;
    if (expectSize.height > textViewSize.height) {
        while (textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat(MAXFLOAT))).height > textViewSize.height) {
            expectFont = textView.font!.withSize(textView.font!.pointSize - 1)
            textView.font = expectFont
        }
    }
    else {
        while (textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat(MAXFLOAT))).height < textViewSize.height) {
            expectFont = textView.font;
            textView.font = textView.font!.withSize(textView.font!.pointSize + 1)
        }
        textView.font = expectFont;
    }
}
Jack Gruber
  • 163
  • 1
  • 14
-2

U can easily do this by using

  • self.textview.font =

self.textview.font = UIFont(name: self.textview.font!.fontName,
size: self.textview.frame.size.height / 10)!

divide textview.frame.size.height upon some constant based on your requirement..

Amit Verma
  • 427
  • 4
  • 6
-2

Here is a solution for Swift 4 (based on Arie Litovsky answer), you can put this code into textViewDidChange(_ textView: UITextView) delegate method :

Scaling down the font :

    while (textView.contentSize.height > textView.frame.size.height) {
        guard let fontName = textView.font?.fontName, let fontSize = textView.font?.pointSize else {return}
        if fontSize < 12 {
            return
        }
        textView.font = UIFont(name: fontName, size: fontSize - 1)
        textView.layoutIfNeeded()
    }

And scaling up the font:

        while (textView.contentSize.height < textView.frame.size.height) {
        guard let fontName = textView.font?.fontName, let fontSize = textView.font?.pointSize else {return}
        if fontSize > 15 {
            return
        }
        textView.font = UIFont(name: fontName, size: fontSize + 1)
        textView.layoutIfNeeded()
    }

You can change the fontSize < 12 and fontSize > 15 to fit your needs in minimum and maximum font size.

Skaal
  • 1,224
  • 12
  • 10