How to resize NSTextView according to its content?
Asked Answered
P

4

23

I am trying to set an attributed string within NSTextView. I want to increase its height based on its content, initially it is set to some default value.

So I tried this method:

I set content in NSTextView. When we set some content in NSTextView its size automatically increases. So I increased height of its super view that is NSScrollView to its height, but NSScrollView is not getting completely resized, it is showing scroller on right.

float xCoordinate = 15.0;

[xContentViewScroller setFrame:NSMakeRect(xCoordinate, 0.0, 560.0, 10.0)];

[[xContentView textStorage] setAttributedString:xContents];

float xContentViewScrollerHeight = [xfContentView frame].size.height + 2;

[xContentViewScroller setFrame:NSMakeRect(xCoordinate, 0.0, 560.0, xContentViewScrollerHeight)]; 

Can anyone suggest me some way or method to resolve this issue. By doing google search I found that in UITextView there is contentSize method which can get size of its content, I tried to find similar method in NSTextView but could not get any success :(

Pullman answered 16/4, 2010 at 16:26 Comment(1)
Solution suggested by Peter elaborated here cheersPerchance
S
13

Based on @Peter Hosey's answer, here is an extension to NSTextView in Swift 4.2:

extension NSTextView {

    var contentSize: CGSize {
        get {
            guard let layoutManager = layoutManager, let textContainer = textContainer else {
                print("textView no layoutManager or textContainer")
                return .zero
            }

            layoutManager.ensureLayout(for: textContainer)
            return layoutManager.usedRect(for: textContainer).size
        }
    }
}
Surtout answered 17/1, 2019 at 2:29 Comment(3)
This doesn't work for me. I am using Swift 5.0. Do I need to do something else apart from the above code?Fiery
Great answer, but this only gives you the height of one line of text if you use autolayout. I wounder how you find total height of all lines when using autolayout. 🤔Vigesimal
Thanks, I use this code with Auto Layout’s heightAnchor and it works well (Swift 5).Sigh
S
10
NSTextView *textView = [[NSTextView alloc] init];
textView.font = [NSFont systemFontOfSize:[NSFont systemFontSize]];
textView.string = @"Lorem ipsum";

[textView.layoutManager ensureLayoutForTextContainer:textView.textContainer];

textView.frame = [textView.layoutManager usedRectForTextContainer:textView.textContainer];
Shirty answered 3/4, 2017 at 11:3 Comment(0)
H
3
+ (float)heightForString:(NSString *)myString font:(NSFont *)myFont andWidth:(float)myWidth andPadding:(float)padding {
     NSTextStorage *textStorage = [[NSTextStorage alloc] initWithString:myString];
     NSTextContainer *textContainer = [[NSTextContainer alloc] initWithContainerSize:NSMakeSize(myWidth, FLT_MAX)];
     NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
     [layoutManager addTextContainer:textContainer];
     [textStorage addLayoutManager:layoutManager];
     [textStorage addAttribute:NSFontAttributeName value:myFont
                    range:NSMakeRange(0, textStorage.length)];
     textContainer.lineFragmentPadding = padding;

     (void) [layoutManager glyphRangeForTextContainer:textContainer];
     return [layoutManager usedRectForTextContainer:textContainer].size.height;
}

I did the function using this reference: Documentation

Example:

float width = textView.frame.size.width - 2 * textView.textContainerInset.width;
float proposedHeight = [Utils heightForString:textView.string font:textView.font andWidth:width
                                   andPadding:textView.textContainer.lineFragmentPadding];
Hestia answered 7/7, 2017 at 11:0 Comment(0)
S
1

Based on @hyouuu's answer, I have built a working version of a custom NSTextView in Swift 5.x:

import AppKit

class MyTextView: NSTextView {

    var heightConstraint: NSLayoutConstraint?

    var contentSize: CGSize {
        get {
            guard let layoutManager = layoutManager, let textContainer = textContainer else {
                return .zero
            }

            layoutManager.ensureLayout(for: textContainer)
            return layoutManager.usedRect(for: textContainer).size
        }
    }
    
    override init(frame frameRect: NSRect, textContainer container: NSTextContainer?) {
        super.init(frame: frameRect, textContainer: container)
    }

    override init(frame frameRect: NSRect) {
        super.init(frame: frameRect)
        
        self.translatesAutoresizingMaskIntoConstraints = false
        heightConstraint = self.heightAnchor.constraint(equalToConstant: 0)
        heightConstraint?.isActive = true
    }
    
    required init?(coder: NSCoder) {
        fatalError()
    }
    
    // Adjust height during layout resizing
    override func layout() {
        updateHeight()
        super.layout()
    }

    // Adjust height after modifying string attributes
    override var string: String {
        didSet {
            didChangeText()
        }
    }
    
    // Adjust height while typing/texting
    override func didChangeText() {
        updateHeight()
        super.didChangeText()
    }
    
    // Adjust the height constraint. You can call this method manually if you programmatically change the text.
    func updateHeight() {
        heightConstraint?.constant = self.contentSize.height + textContainerInset.height * 2
    }
    
}

Additional autolayout constraints should be added when adding it to a view. This works well even in NSTableView with tableView.usesAutomaticRowHeights = true

Sigh answered 18/5, 2023 at 7:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.