我正在尝试使用自动布局 for OS X 来实现容器视图,其运行方式与NSStackView类似,但有一些差异,NSStackView无法处理(我还需要10.7支持)。我的规则是:
我认为这可以通过一种简单的方式完成,将主要方向的视图一个接一个地连接起来,然后使用|[view]|
视觉格式在次要方向上连接。使用内部内容大小为0x0的NSView作为最后一个视图来处理缺乏弹性视图。
这大部分都有效。遗憾的是,模糊性出现在表格的水平方向堆栈的嵌套树中(出于说明目的使用HTML表示)
.box {
display: inline-block;
border: 1px solid black;
padding: 0.5em 0.5em 0.5em 0.5em;
}
.outermost-box {
display: inline-block;
border: 1px solid black;
padding: 0.5em 0.5em 0.5em 0.5em;
width: 100%;
}

<div class="outermost-box">
<div class="box">
<input type="button" value="These">
<input type="button" value="Buttons" disabled>
</div>
<div class="box">
<input type="button" value="are">
<div class="box">
<input type="button" value="in" disabled>
</div>
</div>
<div class="box">
<div class="box">
<input type="button" value="nested">
<div class="box">
<input type="button" value="boxes" disabled>
</div>
</div>
</div>
</div>
&#13;
其中所有堆栈都没有可伸缩的子视图。根据我的定义,最外面的盒子伸展,只有那个伸展。但是,自动布局会将额外空间随机分配给其中一个内框:
将额外视图更改为NSLayoutRelationLessThanOrEqual
关系(last view.trailing <= superview.trailing
)也无济于事。不过,我会为这篇文章的其余部分保留这个模型,因为我的下一次尝试是基于它的。
然后我决定尝试让容器询问它的superview是否应该扩展。这解决了上述问题,但引入了另一个问题,即容器的深链在水平和垂直之间交替:
标有&#34;右边距测试&#34的按钮; 应拉伸,但它们要么不伸展或伸展,要么剪掉两侧的视图(我现在没有截图;对不起)。
然后我决定同时在右边缘上同时使用<=
和替代==
约束,如果应该有额外的空间,则将==
设置为低优先级。这个新的主要有效,但现在有一个奇怪的问题。如果我在上面显示的窗口上调整窗口大小,那么切换到第4页,我得到
然后,如果我调整大小,我会
即使在所有条件下都应该有底部空间。有时可以看到按钮的底部,并且可视化其垂直约束显示它认为它想要与单选按钮矩阵一样高(现在是NSMatrix;将其更改为一堆NSButtons将等到我修复所有这些自动布局问题)。
我真的不确定发生了什么或如何解决这些问题。我尝试使我提到的==
约束具有可设置的&#34;真正的拥抱优先级&#34;它本身,但只是让事情以更加壮观的方式打破。&#39;
还有一些问题,标签视图的位置最初太低,需要几个布局周期来正确设置...
显示的所有内容都是使用这些容器完成的,NSBoxes包含一个子视图,NSTabs包含一个子视图。我将粘贴我的容器的代码,如下所示。
那么自动布局怎么样呢?我不明白我不能用明显的代码使它正常工作吗?或者NSStackView可以完成我想要的所有内容,我应该只使用它吗? (假设alignment
将Width
和Height
视为有效,而Interface Builder似乎并未将其视为有效。
谢谢!
// 15 august 2015
#import "uipriv_darwin.h"
// TODOs:
// - tab on page 2 is glitched initially and doesn't grow
// - page 3 doesn't work right; probably due to our shouldExpand logic being applied incorrectly
// TODOs to confirm
// - 10.8: if we switch to page 4, then switch back to page 1, check Spaced, and go back to page 4, some controls (progress bar, popup button) are clipped on the sides
@interface boxChild : NSObject
@property uiControl *c;
@property BOOL stretchy;
@property NSLayoutPriority oldHorzHuggingPri;
@property NSLayoutPriority oldVertHuggingPri;
- (NSView *)view;
@end
@interface boxView : NSView {
uiBox *b;
NSMutableArray *children;
BOOL vertical;
int padded;
NSLayoutConstraint *first;
NSMutableArray *inBetweens;
NSLayoutConstraint *last, *last2;
NSMutableArray *otherConstraints;
NSLayoutAttribute primaryStart;
NSLayoutAttribute primaryEnd;
NSLayoutAttribute secondaryStart;
NSLayoutAttribute secondaryEnd;
NSLayoutAttribute primarySize;
NSLayoutConstraintOrientation primaryOrientation;
NSLayoutConstraintOrientation secondaryOrientation;
}
- (id)initWithVertical:(BOOL)vert b:(uiBox *)bb;
- (void)onDestroy;
- (void)removeOurConstraints;
- (void)forAll:(void (^)(uintmax_t i, boxChild *b))closure;
- (boxChild *)child:(uintmax_t)i;
- (BOOL)isVertical;
- (void)append:(uiControl *)c stretchy:(int)stretchy;
- (void)delete:(uintmax_t)n;
- (int)isPadded;
- (void)setPadded:(int)p;
@end
struct uiBox {
uiDarwinControl c;
boxView *view;
};
@implementation boxChild
- (NSView *)view
{
return (NSView *) uiControlHandle(self.c);
}
@end
@implementation boxView
- (id)initWithVertical:(BOOL)vert b:(uiBox *)bb
{
self = [super initWithFrame:NSZeroRect];
if (self != nil) {
// the weird names vert and bb are to shut the compiler up about shadowing because implicit this/self is stupid
self->b = bb;
self->vertical = vert;
self->children = [NSMutableArray new];
self->inBetweens = [NSMutableArray new];
self->otherConstraints = [NSMutableArray new];
if (self->vertical) {
self->primaryStart = NSLayoutAttributeTop;
self->primaryEnd = NSLayoutAttributeBottom;
self->secondaryStart = NSLayoutAttributeLeading;
self->secondaryEnd = NSLayoutAttributeTrailing;
self->primarySize = NSLayoutAttributeHeight;
self->primaryOrientation = NSLayoutConstraintOrientationVertical;
self->secondaryOrientation = NSLayoutConstraintOrientationHorizontal;
} else {
self->primaryStart = NSLayoutAttributeLeading;
self->primaryEnd = NSLayoutAttributeTrailing;
self->secondaryStart = NSLayoutAttributeTop;
self->secondaryEnd = NSLayoutAttributeBottom;
self->primarySize = NSLayoutAttributeWidth;
self->primaryOrientation = NSLayoutConstraintOrientationHorizontal;
self->secondaryOrientation = NSLayoutConstraintOrientationVertical;
}
}
return self;
}
- (void)onDestroy
{
boxChild *bc;
uintmax_t i, n;
[self removeOurConstraints];
[self->first release];
[self->inBetweens release];
[self->last release];
[self->last2 release];
[self->otherConstraints release];
n = [self->children count];
for (i = 0; i < n; i++) {
bc = [self child:i];
uiControlSetParent(bc.c, NULL);
uiDarwinControlSetSuperview(uiDarwinControl(bc.c), nil);
uiControlDestroy(bc.c);
}
[self->children release];
}
- (void)removeOurConstraints
{
[self removeConstraint:self->first];
[self removeConstraints:self->inBetweens];
[self removeConstraint:self->last];
[self removeConstraint:self->last2];
[self removeConstraints:self->otherConstraints];
}
- (void)forAll:(void (^)(uintmax_t i, boxChild *b))closure
{
uintmax_t i, n;
n = [self->children count];
for (i = 0; i < n; i++)
closure(i, [self child:i]);
}
- (boxChild *)child:(uintmax_t)i
{
return (boxChild *) [self->children objectAtIndex:i];
}
- (BOOL)isVertical
{
return self->vertical;
}
// TODO something about spinbox hugging
- (void)updateConstraints
{
uintmax_t i, n;
BOOL hasStretchy;
NSView *firstStretchy = nil;
CGFloat padding;
NSView *prev, *next;
NSLayoutConstraint *c;
NSLayoutPriority priority;
[super updateConstraints];
[self removeOurConstraints];
n = [self->children count];
if (n == 0)
return;
padding = 0;
if (self->padded)
padding = 8.0; // TODO named constant
// first, attach the first view to the leading
prev = [[self child:0] view];
self->first = mkConstraint(prev, self->primaryStart,
NSLayoutRelationEqual,
self, self->primaryStart,
1, 0,
@"uiBox first primary constraint");
[self addConstraint:self->first];
[self->first retain];
// next, assemble the views in the primary direction
// they all go in a straight line
// also figure out whether we have stretchy controls, and which is the first
if ([self child:0].stretchy) {
hasStretchy = YES;
firstStretchy = prev;
} else
hasStretchy = NO;
for (i = 1; i < n; i++) {
next = [[self child:i] view];
if (!hasStretchy && [self child:i].stretchy) {
hasStretchy = YES;
firstStretchy = next;
}
c = mkConstraint(next, self->primaryStart,
NSLayoutRelationEqual,
prev, self->primaryEnd,
1, padding,
@"uiBox later primary constraint");
[self addConstraint:c];
[self->inBetweens addObject:c];
prev = next;
}
// and finally end the primary direction
self->last = mkConstraint(prev, self->primaryEnd,
NSLayoutRelationLessThanOrEqual,
self, self->primaryEnd,
1, 0,
@"uiBox last primary constraint");
[self addConstraint:self->last];
[self->last retain];
// if there is a stretchy control, add the no-stretchy view
self->last2 = mkConstraint(prev, self->primaryEnd,
NSLayoutRelationEqual,
self, self->primaryEnd,
1, 0,
@"uiBox last2 primary constraint");
priority = NSLayoutPriorityRequired;
if (!hasStretchy) {
BOOL shouldExpand = NO;
uiControl *parent;
parent = uiControlParent(uiControl(self->b));
if (parent != nil)
if (self->vertical)
shouldExpand = uiDarwinControlChildrenShouldAllowSpaceAtBottom(uiDarwinControl(parent));
else
shouldExpand = uiDarwinControlChildrenShouldAllowSpaceAtTrailingEdge(uiDarwinControl(parent));
if (shouldExpand)
priority = NSLayoutPriorityDefaultLow;
}
[self->last2 setPriority:priority];
[self addConstraint:self->last2];
[self->last2 retain];
// next: assemble the views in the secondary direction
// each of them will span the secondary direction
for (i = 0; i < n; i++) {
prev = [[self child:i] view];
c = mkConstraint(prev, self->secondaryStart,
NSLayoutRelationEqual,
self, self->secondaryStart,
1, 0,
@"uiBox start secondary constraint");
[self addConstraint:c];
[self->otherConstraints addObject:c];
c = mkConstraint(prev, self->secondaryEnd,
NSLayoutRelationEqual,
self, self->secondaryEnd,
1, 0,
@"uiBox end secondary constraint");
[self addConstraint:c];
[self->otherConstraints addObject:c];
}
// finally, set sizes for stretchy controls
if (hasStretchy)
for (i = 0; i < n; i++) {
if (![self child:i].stretchy)
continue;
prev = [[self child:i] view];
if (prev == firstStretchy)
continue;
c = mkConstraint(prev, self->primarySize,
NSLayoutRelationEqual,
firstStretchy, self->primarySize,
1, 0,
@"uiBox stretchy sizing");
[self addConstraint:c];
[self->otherConstraints addObject:c];
}
}
- (void)append:(uiControl *)c stretchy:(int)stretchy
{
boxChild *bc;
NSView *childView;
bc = [boxChild new];
bc.c = c;
bc.stretchy = stretchy;
childView = [bc view];
bc.oldHorzHuggingPri = horzHuggingPri(childView);
bc.oldVertHuggingPri = vertHuggingPri(childView);
uiControlSetParent(bc.c, uiControl(self->b));
uiDarwinControlSetSuperview(uiDarwinControl(bc.c), self);
uiDarwinControlSyncEnableState(uiDarwinControl(bc.c), uiControlEnabledToUser(uiControl(self->b)));
// if a control is stretchy, it should not hug in the primary direction
// otherwise, it should *forcibly* hug
if (stretchy)
setHuggingPri(childView, NSLayoutPriorityDefaultLow, self->primaryOrientation);
else
// TODO will default high work?
setHuggingPri(childView, NSLayoutPriorityRequired, self->primaryOrientation);
// make sure controls don't hug their secondary direction so they fill the width of the view
setHuggingPri(childView, NSLayoutPriorityDefaultLow, self->secondaryOrientation);
[self->children addObject:bc];
[bc release]; // we don't need the initial reference now
[self setNeedsUpdateConstraints:YES];
}
- (void)delete:(uintmax_t)n
{
boxChild *bc;
NSView *removedView;
bc = [self child:n];
removedView = [bc view];
uiControlSetParent(bc.c, NULL);
uiDarwinControlSetSuperview(uiDarwinControl(bc.c), nil);
setHorzHuggingPri(removedView, bc.oldHorzHuggingPri);
setVertHuggingPri(removedView, bc.oldVertHuggingPri);
[self->children removeObjectAtIndex:n];
[self setNeedsUpdateConstraints:YES];
}
- (int)isPadded
{
return self->padded;
}
- (void)setPadded:(int)p
{
CGFloat padding;
uintmax_t i, n;
NSLayoutConstraint *c;
self->padded = p;
// TODO split into method (using above code)
padding = 0;
if (self->padded)
padding = 8.0;
n = [self->inBetweens count];
for (i = 0; i < n; i++) {
c = (NSLayoutConstraint *) [self->inBetweens objectAtIndex:i];
[c setConstant:padding];
}
// TODO call anything?
}
@end
static void uiBoxDestroy(uiControl *c)
{
uiBox *b = uiBox(c);
[b->view onDestroy];
[b->view release];
uiFreeControl(uiControl(b));
}
uiDarwinControlDefaultHandle(uiBox, view)
uiDarwinControlDefaultParent(uiBox, view)
uiDarwinControlDefaultSetParent(uiBox, view)
uiDarwinControlDefaultToplevel(uiBox, view)
uiDarwinControlDefaultVisible(uiBox, view)
uiDarwinControlDefaultShow(uiBox, view)
uiDarwinControlDefaultHide(uiBox, view)
uiDarwinControlDefaultEnabled(uiBox, view)
uiDarwinControlDefaultEnable(uiBox, view)
uiDarwinControlDefaultDisable(uiBox, view)
static void uiBoxSyncEnableState(uiDarwinControl *c, int enabled)
{
uiBox *b = uiBox(c);
if (uiDarwinShouldStopSyncEnableState(uiDarwinControl(b), enabled))
return;
[b->view forAll:^(uintmax_t i, boxChild *bc) {
uiDarwinControlSyncEnableState(uiDarwinControl(bc.c), enabled);
}];
}
uiDarwinControlDefaultSetSuperview(uiBox, view)
static BOOL uiBoxChildrenShouldAllowSpaceAtTrailingEdge(uiDarwinControl *c)
{
uiBox *b = uiBox(c);
// return NO if this box is horizontal so nested horizontal boxes don't lead to ambiguity
return [b->view isVertical];
}
static BOOL uiBoxChildrenShouldAllowSpaceAtBottom(uiDarwinControl *c)
{
uiBox *b = uiBox(c);
// return NO if this box is vertical so nested vertical boxes don't lead to ambiguity
return ![b->view isVertical];
}
void uiBoxAppend(uiBox *b, uiControl *c, int stretchy)
{
[b->view append:c stretchy:stretchy];
}
void uiBoxDelete(uiBox *b, uintmax_t n)
{
[b->view delete:n];
}
int uiBoxPadded(uiBox *b)
{
return [b->view isPadded];
}
void uiBoxSetPadded(uiBox *b, int padded)
{
[b->view setPadded:padded];
}
static uiBox *finishNewBox(BOOL vertical)
{
uiBox *b;
uiDarwinNewControl(uiBox, b);
b->view = [[boxView alloc] initWithVertical:vertical b:b];
return b;
}
uiBox *uiNewHorizontalBox(void)
{
return finishNewBox(NO);
}
uiBox *uiNewVerticalBox(void)
{
return finishNewBox(YES);
}
答案 0 :(得分:0)
我自己解决了这个问题。
主要的变化是,不是通过对自我的约束而是在其超级视图上的有余空间之后。 superview询问self是否应该占用额外空间,如果是,则分配额外空间(使用&gt; =约束到superview边缘而不是==约束)。
各种其他小修正修复了边缘情况。特别是,我到处都是
relation = NSLayoutRelationSomething;
if (condition)
relation = NSLayoutRelationSomethingElse;
constraint = [NSLayoutConstraint constraintWithArg:arg arg:arg
relation:relation
...]
我改为使用两个约束,根据条件设置它们的优先级。 这应该是自动布局的最佳做法,因为它运作良好......
非常感谢!