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;
    }
}

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; 
}

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.

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
}