3

If I create a label in interface builder and set a string through code that does not fit its current size, the label will grow vertically to fit its size, great!. Besides the x & y constraint, Xcode creates a NSContentSizeLayoutConstraint that contains hugging and compression resistance.

Here is a log output of the constraint

<NSContentSizeLayoutConstraint:0x6080000a11a0 V:[NSTextField:0x600000180c30(17)] Hug:750 CompressionResistance:750>

The question is, how to do this programmatically? I have not found a way yet. There is no way to create a NSContentSizeLayoutConstraint and setting the contentCompressionResistance of the NSTextField does not seem to have any effect.

Here is my code:

 self.label = [[NSTextField alloc]initWithFrame:NSMakeRect(0, 0, 100, 20)];
[self.label setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.label setBordered:YES];
[[self.label cell] setLineBreakMode:NSLineBreakByWordWrapping];
[self.window.contentView addSubview:self.label];
 self.label.stringValue = @"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";

NSDictionary *dict = @{@"label" : self.label};
[self.label.superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[label]"options:0 metrics:nil views:dict]];
[self.label.superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[label]" options:0 metrics:nil views:dict]];

NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:self.label attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:0 constant:40];
constraint.priority = 1;
[self.label addConstraint:constraint];
constraint = [NSLayoutConstraint constraintWithItem:self.label attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:0 constant:18];
constraint.priority = 1;
[self.label addConstraint:constraint];

[self.label setContentCompressionResistancePriority:1000 forOrientation:NSLayoutConstraintOrientationHorizontal];
[self.label setContentCompressionResistancePriority:1000 forOrientation:NSLayoutConstraintOrientationVertical];
the Reverend
  • 12,305
  • 10
  • 66
  • 121

2 Answers2

3

Set the text field's preferredMaxLayoutWidth. I think you then don't want or need the explicit width constraint.


In my testing, I find that this works:

self.label = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 100, 18)];
self.label.translatesAutoresizingMaskIntoConstraints = NO;
[self.label.cell setLineBreakMode:NSLineBreakByWordWrapping];
self.label.stringValue = @"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
self.label.preferredMaxLayoutWidth = 100;
self.label.editable = NO;

[self.window.contentView addSubview:self.label];

NSDictionary* views = @{ @"label" : self.label };
[self.label.superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[label]" options:0 metrics:nil views:views]];
[self.label.superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[label]" options:0 metrics:nil views:views]];

Basically, the main difference from yours is that I set editable to NO. I also leave out the width and height constraints and setting the compression resistance.

Ken Thomases
  • 88,520
  • 7
  • 116
  • 154
  • Thanks for the idea, but I get a layout is ambiguous message, when setting the prefferedMaxLayoutWidth and not setting the width constraint. I've also tried them both at the same time, but the compression resistance is not respected. – the Reverend Sep 19 '14 at 17:30
  • Try `[self.label setScrollable:NO]`. If that doesn't work, I suggest you create the setup that you say works in IB and then examine all relevant properties of the text field and its cell and make sure your match. Be sure to query not only the properties declared in `NSTextField` and `NSTextFieldCell`, but their superclasses as well. Also, have IB add the missing or suggested constraints on the text field. I suspect you'll find it has neither height nor width constraints and allows those to come from the text field's intrinsic size. – Ken Thomases Sep 20 '14 at 19:54
  • Thats just it, I don't know how IB adds this "NSContentSizeLayoutConstraint". Its a private class. I opened a technical support request with Apple, I will update the question when I get an answer. – the Reverend Sep 20 '14 at 23:11
  • I'm talking about the properties of the text field and its cell, not the constraints. I doubt the constraint is "added" by IB. If anything, it's a consequence of the text field's properties. – Ken Thomases Sep 21 '14 at 01:53
  • Thank you. As you where telling me, the editable property affects its layout constraints. – the Reverend Sep 21 '14 at 02:41
  • You're welcome. I'm glad I could help. To clarify: I don't think the editable property *directly* affects its layout constraints. When the text field is editable, it doesn't base its `intrinsicContentSize` on its string value. Editable content is not "intrinsic", it's "extrinsic". So, the text field's intrinsic width is `NSViewNoInstrinsicMetric` and its intrinsic height is enough for a single line. When the text field is not editable, its string value and `preferredMaxLayoutWidth` affects its intrinsic size. The layout system translates that intrinsic size into the constraint you logged. – Ken Thomases Sep 21 '14 at 03:19
  • Could you help a Swift learner understand how to set these constraints properly. Also looking to have a NSTextView expand its frame vertically upon new lines. It's already part of a larger loop of views in a SwiftUI ScrollView; I'm subclassing an NSView / NSViewRepresentable to get this view. I've tried translating the above into Swift, but am not making progress. – Beginner Jul 27 '20 at 22:43
2

I think a better way to do this, is to override NSTextField, and update the intrinsicContentSize.

public class DynamicTextField: NSTextField {
   public override var intrinsicContentSize: NSSize {
      if cell!.wraps {
         let fictionalBounds = NSRect(x: bounds.minX, y: bounds.minY, width: bounds.width, height: CGFloat.greatestFiniteMagnitude)
         return cell!.cellSize(forBounds: fictionalBounds)
      } else {
         return super.intrinsicContentSize
      }
   }

   public override func textDidChange(_ notification: Notification) {
      super.textDidChange(notification)

      if cell!.wraps {
         validateEditing()
         invalidateIntrinsicContentSize()
      }
   }
}

Afterwards the constraints works as expected. Note that it only works when you set the Layout to Wraps in Interface Builder or code.

radley
  • 3,212
  • 1
  • 22
  • 18
TimTwoToes
  • 695
  • 5
  • 10
  • This works on for the initial layout and will unwrap as I widen the window, but won't wrap when I narrow the window. – radley Sep 20 '19 at 06:48