Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Programmatically set up NSTextView inside NSScrollView

I'm trying to set up an NSTextView in an NSScrollView programmatically and am having trouble.

Specifically, I can't scroll the text view; the scroll view seems to think "this is all there is". When you try to scroll down to see the rest of the content (without releasing), this is the result:

Clipped

As soon as you release, the scroll view bounces back, so it's not a graphical glitch; it simply clips the text view at the frame height.

I've tried experimenting with the Apple 'Text In ScrollView' docs and I've experimented with setVerticallyResizable: etc.

The code:

- (void)configureValueTextView
{
    NSScrollView *vfContainer = [[NSScrollView alloc] initWithFrame:(NSRect){0,0,50,50}];
    vfContainer.hasVerticalScroller = YES;
    vfContainer.hasHorizontalScroller = NO;
    vfContainer.translatesAutoresizingMaskIntoConstraints = NO;
    _valueFieldContainer = vfContainer;
    vfContainer.borderType = NSLineBorder;

    NSTextView *valueField = _valueField = [[NSTextView alloc] initWithFrame:(NSRect){0,0,vfContainer.contentSize}];
    valueField.delegate = self;
    valueField.minSize = (NSSize){0,0};
    valueField.maxSize = (NSSize){FLT_MAX, FLT_MAX};
    [valueField setVerticallyResizable:YES];
    [valueField setHorizontallyResizable:YES];

    valueField.translatesAutoresizingMaskIntoConstraints = NO;
    valueField.string = _value ? _value : @"";
    valueField.editable = YES;
    valueField.textContainer.containerSize = (NSSize) { vfContainer.contentSize.width, FLT_MAX };
    valueField.textContainer.heightTracksTextView = NO;

    vfContainer.documentView = valueField;
    [vfContainer addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(0)-[_valueField]-(0)-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_valueField)]];
    [vfContainer addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(0)-[_valueField]-(0)-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_valueField)]];
}

The frames are later adjusted to their right values,

[_valueFieldContainer setFrame:(NSRect){kKEFormFieldViewKeyWidth + 10, 0, valueFieldWidth, h}];
[_valueField setFrame:(NSRect){0, 0, valueFieldWidth, h}];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(0)-[_valueFieldContainer]-(5)-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_valueFieldContainer)]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(0)-[_keyField]-(10)-[_valueFieldContainer]-(0)-|" options:0 metrics:nil views:@{@"_keyField": _keyField, @"_valueFieldContainer": _valueFieldContainer}]];

And that's it. Hints welcome!

like image 256
Kalle Avatar asked Jul 17 '14 12:07

Kalle


2 Answers

You've set a vertical constraint making the text view match the height of its superview (the clip view of the scroll view). I think that's what going wrong. If you just totally remove that, you've got complete ambiguity in the vertical direction and the text view can end up anywhere. I think you can get away with just pinning its top edge to the superview's top – i.e. @"V:|[_valueField]". That will work if the text view provides an intrinsic height, but I'm not sure it does. Setting up a constraint for the text view's height such that it matches what the text layout manager will produce is non-trivial.

Given that you don't really need anything special from the layout, you can just use the old-style autoresizing mask instead of auto layout.

like image 40
Ken Thomases Avatar answered Oct 25 '22 16:10

Ken Thomases


Swift 4 Example.

class ViewController: NSViewController {

   fileprivate lazy var textStorage = NSTextStorage()
   fileprivate lazy var layoutManager = NSLayoutManager()
   fileprivate lazy var textContainer = NSTextContainer()
   fileprivate lazy var textView: NSTextView = NSTextView(frame: CGRect(), textContainer: textContainer)
   fileprivate lazy var scrollview = NSScrollView()

   override func viewDidLoad() {
      super.viewDidLoad()
      view.addSubview(scrollview)
      // Set `true` to enable horizontal scrolling.
      setupUI(isHorizontalScrollingEnabled: false) 
      setupLayout()
      setupTextStack()
   }
}

extension ViewController {

   fileprivate func setupTextStack() {
      textStorage.addLayoutManager(layoutManager)
      layoutManager.addTextContainer(textContainer)
   }

   fileprivate func setupUI(isHorizontalScrollingEnabled: Bool) {

      let contentSize = scrollview.contentSize

      if isHorizontalScrollingEnabled {
         textContainer.containerSize = CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
         textContainer.widthTracksTextView = false
      } else {
         textContainer.containerSize = CGSize(width: contentSize.width, height: CGFloat.greatestFiniteMagnitude)
         textContainer.widthTracksTextView = true
      }

      textView.minSize = CGSize(width: 0, height: 0)
      textView.maxSize = CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
      textView.isVerticallyResizable = true
      textView.isHorizontallyResizable = isHorizontalScrollingEnabled
      textView.frame = CGRect(width: contentSize.width, height: contentSize.height)
      if isHorizontalScrollingEnabled {
         textView.autoresizingMask = [.width, .height]
      } else {
         textView.autoresizingMask = [.width]
      }

      scrollview.borderType = .noBorder
      scrollview.hasVerticalScroller = true
      scrollview.hasHorizontalScroller = isHorizontalScrollingEnabled
      scrollview.documentView = textView
   }

   fileprivate func setupLayout() {
      scrollview.translatesAutoresizingMaskIntoConstraints = false

      var customConstraints = [NSLayoutConstraint]()
      // `LayoutConstraint` - custom type with convenience methods. 
      customConstraints += LayoutConstraint.pinInSuperview(scrollview)
      view.addConstraints(customConstraints)
   }
}
like image 90
Vlad Avatar answered Oct 25 '22 18:10

Vlad