Ram Patra
Ram Patra

Reputation: 16664

How to resize a NSTextView automatically as per its content?

I am making an app where a user can click anywhere on the window and a NSTextView is added programmatically at the mouse location. I have got it working with the below code but I want this NSTextView to horizontally expand until it reaches the edge of the screen and then grow vertically. It currently has a fixed width and when I add more characters, the text view grows vertically (as expected) but I also want it to grow horizontally. How can I achieve this?

I have tried setting isHorizontallyResizable and isVerticallyResizable to true but this doesn't work. After researching for a while, I came across this https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/TextStorageLayer/Tasks/TrackingSize.html but this didn't work for me either.

Code in my ViewController to add the NSTextView to its view:

private func addText(at point: NSPoint) {     
    let textView = MyTextView(frame: NSRect(origin: point, size: CGSize(width: 150.0, height: 40.0)))
    view.addSubview(textView)       
}

And, MyTextView class looks like below:

class MyTextView: NSTextView {

    override func viewWillDraw() {
        
        isHorizontallyResizable = true
        isVerticallyResizable = true
        
        isRichText = false
    }
}

I have also seen this answer https://stackoverflow.com/a/54228147/1385441 but I am not fully sure how to implement it. I have added this code snippet in MyTextView and used it like:

override func didChangeText() {
    frame.size = contentSize
}

However, I think I am using it incorrectly. Ergo, any help would be much appreciated.

Upvotes: 3

Views: 1627

Answers (2)

Ram Patra
Ram Patra

Reputation: 16664

I finally got it to work (except for one minor thing). The link from Apple was the key here but they haven't described the code completely, unfortunately.

The below code work for me:

class MyTextView: NSTextView {
    override func viewWillDraw() {
        // for making the text view expand horizontally
        textContainer?.heightTracksTextView = false
        textContainer?.widthTracksTextView = false
        textContainer?.size.width = 10000.0
        maxSize = NSSize(width: 10000.0, height: 10000.0)
        isHorizontallyResizable = true
        isVerticallyResizable = true

        isRichText = false
    }
}

That one minor thing which I haven't been able to figure out yet is to limit expanding horizontally until the edge of the screen is reached. Right now it keeps on expanding even beyond the screen width and, in turn, the text is hidden after the screen width.

I think if I can somehow get the screen window width then I can replace 10000.0 with the screen width (minus the distance of text view from left edge) and I can limit the horizontal expansion until the edge of the screen. Having said that, keeping it 10000.0 won't impact performance as described in the Apple docs.

Upvotes: 0

zrzka
zrzka

Reputation: 21219

I'm a bit puzzled, because you're adding NSTextView to a NSView which is part of the NSViewController and then you're talking about the screen width. Is this part of your Presentify - Screen Annotation application? If yes, you have a full screen overlay window and you can get the size from it (or from the view controller's view).

view.bounds.size                // view controller's view size
view.window?.frame.size         // window size

If not and you really need to know the screen size, check the NSWindow & NSScreen.

view.window?.screen?.frame.size // screen size

Growing NSTextView

There's no any window/view controller's view resizing behavior specified.

import Cocoa

class BorderedTextView: NSTextView {
    override func draw(_ dirtyRect: NSRect) {
        super.draw(dirtyRect)
        
        let path = NSBezierPath(rect: bounds)
        NSColor.red.setStroke()
        path.stroke()
    }
}

class ViewController: NSViewController {
    override func mouseUp(with event: NSEvent) {
        // Convert point to the view coordinates
        let point = view.convert(event.locationInWindow, from: nil)
        
        // Initial size
        let size = CGSize(width: 100, height: 25)
        
        // Maximum text view width
        let maxWidth = view.bounds.size.width - point.x             // <----
                
        let textView = BorderedTextView(frame: NSRect(origin: point, size: size))
        textView.insertionPointColor = .orange
        textView.drawsBackground = false
        textView.textColor = .white
        textView.isRichText = false
        textView.allowsUndo = false
        textView.font = NSFont.systemFont(ofSize: 20.0)
        textView.isVerticallyResizable = true
        textView.isHorizontallyResizable = true
                
        textView.textContainer?.widthTracksTextView = false
        textView.textContainer?.heightTracksTextView = false
        textView.textContainer?.size.width = maxWidth               // <----
        textView.maxSize = NSSize(width: maxWidth, height: 10000)   // <----
        
        view.addSubview(textView)
        
        view.window?.makeFirstResponder(textView)
    }
}

enter image description here

Upvotes: 6

Related Questions