// 15 august 2015 #import "uipriv_darwin.h" NSLayoutConstraint *mkConstraint(id view1, NSLayoutAttribute attr1, NSLayoutRelation relation, id view2, NSLayoutAttribute attr2, CGFloat multiplier, CGFloat c, NSString *desc) { NSLayoutConstraint *constraint; constraint = [NSLayoutConstraint constraintWithItem:view1 attribute:attr1 relatedBy:relation toItem:view2 attribute:attr2 multiplier:multiplier constant:c]; // apparently only added in 10.9 if ([constraint respondsToSelector:@selector(setIdentifier:)]) [((id) constraint) setIdentifier:desc]; return constraint; } // this is needed for NSSplitView to work properly; see http://stackoverflow.com/questions/34574478/how-can-i-set-the-position-of-a-nssplitview-nowadays-setpositionofdivideratind (stal in irc.freenode.net/#macdev came up with the exact combination) // turns out it also works on NSTabView and NSBox too, possibly others! // and for bonus points, it even seems to fix unsatisfiable-constraint-autoresizing-mask issues with NSTabView and NSBox too!!! this is nuts void jiggleViewLayout(NSView *view) { [view setNeedsLayout:YES]; [view layoutSubtreeIfNeeded]; } static CGFloat margins(int margined) { if (!margined) return 0.0; return 20.0; // TODO named constant } void singleChildConstraintsEstablish(struct singleChildConstraints *c, NSView *contentView, NSView *childView, BOOL hugsTrailing, BOOL hugsBottom, int margined, NSString *desc) { CGFloat margin; margin = margins(margined); c->leadingConstraint = mkConstraint(contentView, NSLayoutAttributeLeading, NSLayoutRelationEqual, childView, NSLayoutAttributeLeading, 1, -margin, [desc stringByAppendingString:@" leading constraint"]); [contentView addConstraint:c->leadingConstraint]; [c->leadingConstraint retain]; c->topConstraint = mkConstraint(contentView, NSLayoutAttributeTop, NSLayoutRelationEqual, childView, NSLayoutAttributeTop, 1, -margin, [desc stringByAppendingString:@" top constraint"]); [contentView addConstraint:c->topConstraint]; [c->topConstraint retain]; c->trailingConstraintGreater = mkConstraint(contentView, NSLayoutAttributeTrailing, NSLayoutRelationGreaterThanOrEqual, childView, NSLayoutAttributeTrailing, 1, margin, [desc stringByAppendingString:@" trailing >= constraint"]); if (hugsTrailing) [c->trailingConstraintGreater setPriority:NSLayoutPriorityDefaultLow]; [contentView addConstraint:c->trailingConstraintGreater]; [c->trailingConstraintGreater retain]; c->trailingConstraintEqual = mkConstraint(contentView, NSLayoutAttributeTrailing, NSLayoutRelationEqual, childView, NSLayoutAttributeTrailing, 1, margin, [desc stringByAppendingString:@" trailing == constraint"]); if (!hugsTrailing) [c->trailingConstraintEqual setPriority:NSLayoutPriorityDefaultLow]; [contentView addConstraint:c->trailingConstraintEqual]; [c->trailingConstraintEqual retain]; c->bottomConstraintGreater = mkConstraint(contentView, NSLayoutAttributeBottom, NSLayoutRelationGreaterThanOrEqual, childView, NSLayoutAttributeBottom, 1, margin, [desc stringByAppendingString:@" bottom >= constraint"]); if (hugsBottom) [c->bottomConstraintGreater setPriority:NSLayoutPriorityDefaultLow]; [contentView addConstraint:c->bottomConstraintGreater]; [c->bottomConstraintGreater retain]; c->bottomConstraintEqual = mkConstraint(contentView, NSLayoutAttributeBottom, NSLayoutRelationEqual, childView, NSLayoutAttributeBottom, 1, margin, [desc stringByAppendingString:@" bottom == constraint"]); if (!hugsBottom) [c->bottomConstraintEqual setPriority:NSLayoutPriorityDefaultLow]; [contentView addConstraint:c->bottomConstraintEqual]; [c->bottomConstraintEqual retain]; } void singleChildConstraintsRemove(struct singleChildConstraints *c, NSView *cv) { if (c->leadingConstraint != nil) { [cv removeConstraint:c->leadingConstraint]; [c->leadingConstraint release]; c->leadingConstraint = nil; } if (c->topConstraint != nil) { [cv removeConstraint:c->topConstraint]; [c->topConstraint release]; c->topConstraint = nil; } if (c->trailingConstraintGreater != nil) { [cv removeConstraint:c->trailingConstraintGreater]; [c->trailingConstraintGreater release]; c->trailingConstraintGreater = nil; } if (c->trailingConstraintEqual != nil) { [cv removeConstraint:c->trailingConstraintEqual]; [c->trailingConstraintEqual release]; c->trailingConstraintEqual = nil; } if (c->bottomConstraintGreater != nil) { [cv removeConstraint:c->bottomConstraintGreater]; [c->bottomConstraintGreater release]; c->bottomConstraintGreater = nil; } if (c->bottomConstraintEqual != nil) { [cv removeConstraint:c->bottomConstraintEqual]; [c->bottomConstraintEqual release]; c->bottomConstraintEqual = nil; } } void singleChildConstraintsSetMargined(struct singleChildConstraints *c, int margined) { CGFloat margin; margin = margins(margined); if (c->leadingConstraint != nil) [c->leadingConstraint setConstant:-margin]; if (c->topConstraint != nil) [c->topConstraint setConstant:-margin]; if (c->trailingConstraintGreater != nil) [c->trailingConstraintGreater setConstant:margin]; if (c->trailingConstraintEqual != nil) [c->trailingConstraintEqual setConstant:margin]; if (c->bottomConstraintGreater != nil) [c->bottomConstraintGreater setConstant:margin]; if (c->bottomConstraintEqual != nil) [c->bottomConstraintEqual setConstant:margin]; } // from http://blog.bjhomer.com/2014/08/nsscrollview-and-autolayout.html because (as pointed out there) Apple's official guide is really only for iOS // TODO this doesn't quite work with NSTextView; it *mostly* works void scrollViewConstraintsEstablish(struct scrollViewConstraints *c, NSScrollView *sv, NSString *desc) { NSView *cv, *dv; scrollViewConstraintsRemove(c, sv); cv = [sv contentView]; dv = [sv documentView]; c->documentLeading = mkConstraint(dv, NSLayoutAttributeLeading, NSLayoutRelationEqual, cv, NSLayoutAttributeLeading, 1, 0, [desc stringByAppendingString:@"document leading constraint"]); [sv addConstraint:c->documentLeading]; [c->documentLeading retain]; c->documentTop = mkConstraint(dv, NSLayoutAttributeTop, NSLayoutRelationEqual, cv, NSLayoutAttributeTop, 1, 0, [desc stringByAppendingString:@"document top constraint"]); [sv addConstraint:c->documentTop]; [c->documentTop retain]; c->documentTrailing = mkConstraint(dv, NSLayoutAttributeTrailing, NSLayoutRelationEqual, cv, NSLayoutAttributeTrailing, 1, 0, [desc stringByAppendingString:@"document trailing constraint"]); [sv addConstraint:c->documentTrailing]; [c->documentTrailing retain]; #if 0 c->documentBottom = mkConstraint(dv, NSLayoutAttributeBottom, NSLayoutRelationEqual, sv, NSLayoutAttributeBottom, 1, 0, [desc stringByAppendingString:@"document bottom constraint"]); [sv addConstraint:c->documentBottom]; [c->documentBottom retain]; #endif } void scrollViewConstraintsRemove(struct scrollViewConstraints *c, NSScrollView *sv) { if (c->documentLeading != nil) { [sv removeConstraint:c->documentLeading]; [c->documentLeading release]; c->documentLeading = nil; } if (c->documentTop != nil) { [sv removeConstraint:c->documentTop]; [c->documentTop release]; c->documentTop = nil; } if (c->documentTrailing != nil) { [sv removeConstraint:c->documentTrailing]; [c->documentTrailing release]; c->documentTrailing = nil; } if (c->documentBottom != nil) { [sv removeConstraint:c->documentBottom]; [c->documentBottom release]; c->documentBottom = nil; } if (c->documentWidth != nil) { [sv removeConstraint:c->documentWidth]; [c->documentWidth release]; c->documentWidth = nil; } if (c->documentHeight != nil) { [sv removeConstraint:c->documentHeight]; [c->documentHeight release]; c->documentHeight = nil; } }