开发者

*Accurately* calculating text height in Cocoa (for Mac, not iOS)

How can I cal开发者_运维技巧culate the height of a string within a particular label (NSTextField), for some given fixed width?

I Googled up various methods and tried this method from Apple. It works, except that the height becomes one line too short for longer strings. Thus, there appear to be some inaccuracies in the present methods.

I also had the same issue with some other code I Googled up.

Does someone here have any accurate code for finding text height (given a particular width)?


I solved the problem using the NS(Attributed)String+Geometrics category provided at http://www.sheepsystems.com/sourceCode/sourceStringGeometrics.html Within the header file of that category is a good explanation of how these things work. Specifically, they mention that one should use NSTextView instead of NSTextField because it's virtually impossible to get an accurate height for the latter.

Thus, I replaced my NSTextField with NSTextView. First, I made my NSTextView look like my NSTextField with this code:

[textView setEditable:YES];
[textView insertText:someString];
[textView setFont:[NSFont fontWithName:@"Lucida Grande"
   size:11] range:NSMakeRange(0,[someString length])];
[textView setEditable:NO];
[textView setSelectable:NO];

Then I got the height with this code:

NSDictionary *attributes
  = [NSDictionary dictionaryWithObjectsAndKeys:
     [textView font],NSFontAttributeName,nil];
NSAttributedString *attributedString
  = [[NSAttributedString alloc] initWithString:
     [textView string] attributes:attributes];
CGFloat height = [attributedString heightForWidth:
                 [textView frame].size.width];

The height does indeed turn out to be accurate for NSTextView.

I may have omitted some minor details, but you get the picture. I hope this helps someone out there.


(a solution in Swift is at the end of the post)

I tried all of these methods and none worked for me. The 'official' way (as mentioned by OP) was off most of the time for my NSTextField. Switching to NSTextView was impossible because of XCode/IB always crashing after adding a "NSTextView in an NSScrollView".

Long story short, I found this easy way to determine the height of my NSTextField but this can be very easily adapted to width.

CGFloat minHeight = [((NSTextFieldCell *)[yourTextField cell]) cellSizeForBounds:NSMakeRect(0, 0, YOUR_MAX_WIDTH, FLT_MAX)].height;

This code has worked for every single test-case that failed with the other methods. I hope this helps someone. It always makes me feel so darn stupid when I take 2 hours to find the solution to a seemingly easy problem.

SLIGHT EDIT: Upon closer inspection this only works if you read the stringValue of the NSTextField before I ask the cell to calculate its height. So I added the following nonsensical line before the actual calculation:

[yourTextField setStringValue:yourTextField.stringValue];

I know this is really really bad but at this point I don't care anymore. I just want a reliable solution. Feel free to downvote me or better yet: suggest a better solution.


Solution in Swift (thank you iphaaw for bringing this to my attention!)

myTextField.cell!.cellSizeForBounds(NSMakeRect(CGFloat(0.0), CGFloat(0.0), width, CGFloat(FLT_MAX))).height


Thanks indeed to iphaaw and Enchilada for this really cool little solution. I found it worked so well at setting row heights etc that I converted iphaaw's Swift into a couple of NSTextField extensions. Enjoy

extension NSTextField {
func bestheight(text: String, width: CGFloat) -> CGFloat {

    self.stringValue = text
    let getnumber = self.cell!.cellSizeForBounds(NSMakeRect(CGFloat(0.0), CGFloat(0.0), width, CGFloat(FLT_MAX))).height

    return getnumber
   }
}

extension NSTextField {
func bestwidth(text: String, height: CGFloat) -> CGFloat {

    self.stringValue = text
    let getnumber = self.cell!.cellSizeForBounds(NSMakeRect(CGFloat(0.0), CGFloat(0.0), CGFloat(FLT_MAX), height)).width

    return getnumber
   }
}

I hope someone finds these helpful. regards KGH


This is ArtbyKGH's answer for Swift 4+:

extension NSTextField {
    func bestHeight(for text: String, width: CGFloat) -> CGFloat {
        stringValue = text
        let height = cell!.cellSize(forBounds: NSRect(x: 0, y: 0, width: width, height: .greatestFiniteMagnitude)).height

        return height
    }

    func bestWidth(for text: String, height: CGFloat) -> CGFloat {
        stringValue = text
        let width = cell!.cellSize(forBounds: NSRect(x: 0, y: 0, width: .greatestFiniteMagnitude, height: height)).width

        return width
    }
}


Take a look at the NSString Application Kit Additions Reference. It seems like – sizeWithAttributes: or – boundingRectWithSize:options:attributes: could do what you want.


Vigorouscoding's answer works so well! Is this really bad? It seems to work perfectly. Finally after spending many hours of trying other solutions.

Here is my Swift translation of the code:

myTextField.cell!.cellSizeForBounds(NSMakeRect(CGFloat(0.0), CGFloat(0.0), width, CGFloat(FLT_MAX))).height

Please don't up vote this answer. Upvote vigorouscoding's entry instead.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜