gpt4 book ai didi

objective-c - 在自动布局中,我设置了不应水平增长的标签和应水平增长的控件。为什么我会反其道而行之?

转载 作者:行者123 更新时间:2023-12-03 17:09:59 25 4
gpt4 key购买 nike

我正在尝试使用 OS X(不是 iOS!)上的自动布局构建一个类似表单的容器 View :它右对齐(在 LTR 布局中,根据 OS X 约定)标签位于控件旁边,并且控件应水平增长以适应 super View 分配给它的宽度(但不是标签)。以图形形式表示:

Example of what I want

(该图像试图解释正在发生的事情;这里有更详细的解释。)

到目前为止,我尝试做的是:

  1. 以明显的方式垂直排列控件
  2. 将每个控件的右边缘固定到表单 View 的右边缘
  3. 在每个控件及其标签之间放置适当的空间
  4. 确保所有控件具有相同的宽度
  5. 使用不等关系将标签附加到表单 View 的左边缘,以在左侧留出额外空间

在每种情况下,标签在两个维度上都以所需的优先级拥抱其内容,而控件在水平方向上拥抱较弱。

当产生以下结果时,我尝试了以下操作:

  • 将标签放在自己的 super View 中,固定到顶部、右侧和底部,并以不等式附加到左侧
  • 使所有标签具有相同的宽度
  • 将它们固定到表单 View 的左边缘
  • (用“前导”和“尾随”替换“左”和“右”;这也应该在 RTL 系统上正常工作。)

    但是,通过这两种方法,我得到了:

    Result

    请注意,当标签拉伸(stretch)时,控件如何保持最小尺寸(并且由于标签拥抱,额外的空间出现在窗口的左侧)。

    我做错了什么?我完全迷失在这里。

    这是在 OS X 10.11 上,但我的目标是 10.8。

    谢谢!

    // 7 june 2016
    #import <Cocoa/Cocoa.h>

    NSLayoutConstraint *mkConstraint(id view1, NSLayoutAttribute attr1, NSLayoutRelation relation, id view2, NSLayoutAttribute attr2, CGFloat multiplier, CGFloat c, NSString *desc);
    NSTextField *newLabel(NSString *str);

    @interface formChild : NSView
    @property (strong) NSView *view;
    @property (strong) NSTextField *label;
    @property BOOL stretchy;
    @property (strong) NSLayoutConstraint *baseline;
    @property (strong) NSLayoutConstraint *leading;
    @property (strong) NSLayoutConstraint *trailing;
    @property (strong) NSLayoutConstraint *top;
    @property (strong) NSLayoutConstraint *bottom;
    - (id)initWithLabel:(NSTextField *)l;
    - (void)onDestroy;
    @end

    @interface formView : NSView {
    NSMutableArray *children;
    BOOL padded;
    uintmax_t nStretchy;

    NSLayoutConstraint *first;
    NSMutableArray *inBetweens;
    NSLayoutConstraint *last;
    NSMutableArray *widths;
    NSMutableArray *leadings;
    NSMutableArray *middles;
    NSMutableArray *trailings;
    }
    - (id)init;
    - (void)onDestroy;
    - (void)removeOurConstraints;
    - (CGFloat)paddingAmount;
    - (void)establishOurConstraints;
    - (void)append:(NSString *)label c:(NSView *)c stretchy:(BOOL)stretchy;
    - (void)setPadded:(BOOL)p;
    @end

    @implementation formChild

    - (id)initWithLabel:(NSTextField *)l
    {
    self = [super initWithFrame:NSZeroRect];
    if (self) {
    self.label = l;
    [self.label setTranslatesAutoresizingMaskIntoConstraints:NO];
    [self.label setContentHuggingPriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationHorizontal];
    [self.label setContentHuggingPriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationVertical];
    [self addSubview:self.label];

    self.leading = mkConstraint(self.label, NSLayoutAttributeLeading,
    NSLayoutRelationGreaterThanOrEqual,
    self, NSLayoutAttributeLeading,
    1, 0,
    @"uiForm label leading");
    [self addConstraint:self.leading];
    self.trailing = mkConstraint(self.label, NSLayoutAttributeTrailing,
    NSLayoutRelationEqual,
    self, NSLayoutAttributeTrailing,
    1, 0,
    @"uiForm label trailing");
    [self addConstraint:self.trailing];
    self.top = mkConstraint(self.label, NSLayoutAttributeTop,
    NSLayoutRelationEqual,
    self, NSLayoutAttributeTop,
    1, 0,
    @"uiForm label top");
    [self addConstraint:self.top];
    self.bottom = mkConstraint(self.label, NSLayoutAttributeBottom,
    NSLayoutRelationEqual,
    self, NSLayoutAttributeBottom,
    1, 0,
    @"uiForm label bottom");
    [self addConstraint:self.bottom];
    }
    return self;
    }

    - (void)onDestroy
    {
    [self removeConstraint:self.trailing];
    self.trailing = nil;
    [self removeConstraint:self.top];
    self.top = nil;
    [self removeConstraint:self.bottom];
    self.bottom = nil;

    [self.label removeFromSuperview];
    self.label = nil;
    }

    @end

    @implementation formView

    - (id)init
    {
    self = [super initWithFrame:NSZeroRect];
    if (self != nil) {
    self->padded = NO;
    self->children = [NSMutableArray new];
    self->nStretchy = 0;

    self->inBetweens = [NSMutableArray new];
    self->widths = [NSMutableArray new];
    self->leadings = [NSMutableArray new];
    self->middles = [NSMutableArray new];
    self->trailings = [NSMutableArray new];
    }
    return self;
    }

    - (void)onDestroy
    {
    formChild *fc;

    [self removeOurConstraints];
    [self->inBetweens release];
    [self->widths release];
    [self->leadings release];
    [self->middles release];
    [self->trailings release];

    for (fc in self->children) {
    [self removeConstraint:fc.baseline];
    fc.baseline = nil;
    [fc.view removeFromSuperview];
    fc.view = nil;
    [fc onDestroy];
    [fc removeFromSuperview];
    }
    [self->children release];
    }

    - (void)removeOurConstraints
    {
    if (self->first != nil) {
    [self removeConstraint:self->first];
    [self->first release];
    self->first = nil;
    }
    if ([self->inBetweens count] != 0) {
    [self removeConstraints:self->inBetweens];
    [self->inBetweens removeAllObjects];
    }
    if (self->last != nil) {
    [self removeConstraint:self->last];
    [self->last release];
    self->last = nil;
    }
    if ([self->widths count] != 0) {
    [self removeConstraints:self->widths];
    [self->widths removeAllObjects];
    }
    if ([self->leadings count] != 0) {
    [self removeConstraints:self->leadings];
    [self->leadings removeAllObjects];
    }
    if ([self->middles count] != 0) {
    [self removeConstraints:self->middles];
    [self->middles removeAllObjects];
    }
    if ([self->trailings count] != 0) {
    [self removeConstraints:self->trailings];
    [self->trailings removeAllObjects];
    }
    }

    - (CGFloat)paddingAmount
    {
    if (!self->padded)
    return 0.0;
    return 8.0;
    }

    - (void)establishOurConstraints
    {
    formChild *fc;
    CGFloat padding;
    NSView *prev, *prevlabel;
    NSLayoutConstraint *c;
    NSLayoutRelation relation;

    [self removeOurConstraints];
    if ([self->children count] == 0)
    return;
    padding = [self paddingAmount];

    // first arrange the children vertically and make them the same width
    prev = nil;
    for (fc in self->children) {
    if (prev == nil) { // first view
    self->first = mkConstraint(self, NSLayoutAttributeTop,
    NSLayoutRelationEqual,
    fc.view, NSLayoutAttributeTop,
    1, 0,
    @"uiForm first vertical constraint");
    [self addConstraint:self->first];
    [self->first retain];
    prev = fc.view;
    prevlabel = fc;
    continue;
    }
    // not the first; link it
    c = mkConstraint(prev, NSLayoutAttributeBottom,
    NSLayoutRelationEqual,
    fc.view, NSLayoutAttributeTop,
    1, -padding,
    @"uiForm in-between vertical constraint");
    [self addConstraint:c];
    [self->inBetweens addObject:c];
    // and make the same width
    c = mkConstraint(prev, NSLayoutAttributeWidth,
    NSLayoutRelationEqual,
    fc.view, NSLayoutAttributeWidth,
    1, 0,
    @"uiForm width constraint");
    [self addConstraint:c];
    [self->widths addObject:c];
    c = mkConstraint(prevlabel, NSLayoutAttributeWidth,
    NSLayoutRelationEqual,
    fc, NSLayoutAttributeWidth,
    1, 0,
    @"uiForm label width constraint");
    [self addConstraint:c];
    [self->widths addObject:c];
    prev = fc.view;
    prevlabel = fc;
    }
    relation = NSLayoutRelationEqual;
    if (self->nStretchy != 0)
    relation = NSLayoutRelationLessThanOrEqual;
    self->last = mkConstraint(prev, NSLayoutAttributeBottom,
    NSLayoutRelationEqual,
    self, NSLayoutAttributeBottom,
    1, 0,
    @"uiForm last vertical constraint");
    [self addConstraint:self->last];
    [self->last retain];

    // now arrange the controls horizontally
    for (fc in self->children) {
    c = mkConstraint(self, NSLayoutAttributeLeading,
    NSLayoutRelationEqual,
    fc, NSLayoutAttributeLeading,
    1, 0,
    @"uiForm leading constraint");
    [self addConstraint:c];
    [self->leadings addObject:c];
    c = mkConstraint(fc, NSLayoutAttributeTrailing,
    NSLayoutRelationEqual,
    fc.view, NSLayoutAttributeLeading,
    1, -padding,
    @"uiForm middle constraint");
    [self addConstraint:c];
    [self->middles addObject:c];
    c = mkConstraint(fc.view, NSLayoutAttributeTrailing,
    NSLayoutRelationEqual,
    self, NSLayoutAttributeTrailing,
    1, 0,
    @"uiForm trailing constraint");
    [self addConstraint:c];
    [self->trailings addObject:c];
    }

    // we don't arrange the labels vertically; that's done when we add the control since those constraints don't need to change (they just need to be at their baseline)
    }

    - (void)append:(NSString *)label c:(NSView *)c stretchy:(BOOL)stretchy
    {
    formChild *fc;
    NSLayoutPriority priority;
    NSLayoutAttribute attribute;
    uintmax_t oldnStretchy;

    fc = [[formChild alloc] initWithLabel:newLabel(label)];
    fc.view = c;
    fc.stretchy = stretchy;
    [fc setTranslatesAutoresizingMaskIntoConstraints:NO];
    [self addSubview:fc];

    [self addSubview:fc.view];

    // if a control is stretchy, it should not hug vertically
    // otherwise, it should *forcibly* hug
    if (fc.stretchy)
    priority = NSLayoutPriorityDefaultLow;
    else
    // LONGTERM will default high work?
    priority = NSLayoutPriorityRequired;
    [fc.view setContentHuggingPriority:priority forOrientation:NSLayoutConstraintOrientationVertical];
    // make sure controls don't hug their horizontal direction so they fill the width of the view
    [fc.view setContentHuggingPriority:NSLayoutPriorityDefaultLow forOrientation:NSLayoutConstraintOrientationHorizontal];

    // and constrain the baselines to position the label vertically
    // if the view is a scroll view, align tops, not baselines
    // this is what Interface Builder does
    attribute = NSLayoutAttributeBaseline;
    if ([fc.view isKindOfClass:[NSScrollView class]])
    attribute = NSLayoutAttributeTop;
    fc.baseline = mkConstraint(fc.label, attribute,
    NSLayoutRelationEqual,
    fc.view, attribute,
    1, 0,
    @"uiForm baseline constraint");
    [self addConstraint:fc.baseline];

    [self->children addObject:fc];

    [self establishOurConstraints];
    if (fc.stretchy) {
    oldnStretchy = self->nStretchy;
    self->nStretchy++;
    if (oldnStretchy == 0)
    [self establishOurConstraints];
    }

    [fc release]; // we don't need the initial reference now
    }

    - (void)setPadded:(BOOL)p
    {
    CGFloat padding;
    NSLayoutConstraint *c;

    self->padded = p;
    padding = [self paddingAmount];
    for (c in self->inBetweens)
    [c setConstant:-padding];
    for (c in self->middles)
    [c setConstant:-padding];
    }

    @end

    // demo

    NSTextField *newPasswordField(void);
    NSTextField *newSearchField(void);
    NSButton *newCheckbox(NSString *label);

    @interface appDelegate : NSObject<NSApplicationDelegate>
    @property (strong) NSWindow *w;
    @property (strong) formView *form;
    @end

    @implementation appDelegate

    - (void)applicationDidFinishLaunching:(NSNotification *)note
    {
    NSView *contentView;
    formView *form;
    NSButton *cb;

    self.w = [[NSWindow alloc] initWithContentRect: NSMakeRect(0, 0, 200, 200)
    styleMask:(NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask)
    backing:NSBackingStoreBuffered
    defer:YES];
    contentView = [self.w contentView];

    self.form = [formView new];
    [self.form setTranslatesAutoresizingMaskIntoConstraints:NO];
    [contentView addSubview:self.form];
    [contentView addConstraint:mkConstraint(contentView, NSLayoutAttributeLeading,
    NSLayoutRelationEqual,
    self.form, NSLayoutAttributeLeading,
    1, -20,
    @"content view leading")];
    [contentView addConstraint:mkConstraint(contentView, NSLayoutAttributeTop,
    NSLayoutRelationEqual,
    self.form, NSLayoutAttributeTop,
    1, -20,
    @"content view top")];
    [contentView addConstraint:mkConstraint(contentView, NSLayoutAttributeTrailing,
    NSLayoutRelationEqual,
    self.form, NSLayoutAttributeTrailing,
    1, 20,
    @"content view trailing")];
    [contentView addConstraint:mkConstraint(contentView, NSLayoutAttributeBottom,
    NSLayoutRelationEqual,
    self.form, NSLayoutAttributeBottom,
    1, 20,
    @"content view bottom")];

    [self.form append:@"Password Field"
    c:newPasswordField()
    stretchy:NO];
    [self.form append:@"Search Box"
    c:newSearchField()
    stretchy:NO];
    cb = newCheckbox(@"Padded");
    [self.form append:@""
    c:cb
    stretchy:NO];
    [cb setTarget:self];
    [cb setAction:@selector(onToggled:)];

    [self.w visualizeConstraints:[self.form constraints]];
    [self.w makeKeyAndOrderFront:nil];
    }

    - (IBAction)onToggled:(id)sender
    {
    [self.form setPadded:([sender state] == NSOnState)];
    }

    - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)app
    {
    return YES;
    }

    @end

    int main(void)
    {
    NSApplication *a;

    a = [NSApplication sharedApplication];
    [a setActivationPolicy:NSApplicationActivationPolicyRegular];
    [a setDelegate:[appDelegate new]];
    [a run];
    return 0;
    }

    // boilerplate

    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;
    }

    NSTextField *finishNewTextField(NSTextField *t, BOOL isEntry)
    {
    [t setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSRegularControlSize]]];

    // THE ORDER OF THESE CALLS IS IMPORTANT; CHANGE IT AND THE BORDERS WILL DISAPPEAR
    [t setBordered:NO];
    [t setBezelStyle:NSTextFieldSquareBezel];
    [t setBezeled:isEntry];

    [[t cell] setLineBreakMode:NSLineBreakByClipping];
    [[t cell] setScrollable:YES];

    [t setTranslatesAutoresizingMaskIntoConstraints:NO];

    return t;
    }

    NSTextField *newPasswordField(void)
    {
    return finishNewTextField([[NSSecureTextField alloc] initWithFrame:NSZeroRect], YES);
    }

    NSTextField *newSearchField(void)
    {
    NSSearchField *s;

    s = (NSSearchField *) finishNewTextField([[NSSearchField alloc] initWithFrame:NSZeroRect], YES);
    [s setSendsSearchStringImmediately:NO];
    [s setSendsWholeSearchString:NO];
    [s setBordered:NO];
    [s setBezelStyle:NSTextFieldRoundedBezel];
    [s setBezeled:YES];
    return s;
    }

    NSTextField *newLabel(NSString *str)
    {
    NSTextField *tf;

    tf = [[NSTextField alloc] initWithFrame:NSZeroRect];
    [tf setStringValue:str];
    [tf setEditable:NO];
    [tf setSelectable:NO];
    [tf setDrawsBackground:NO];
    return finishNewTextField(tf, NO);
    }

    NSButton *newCheckbox(NSString *label)
    {
    NSButton *c;

    c = [[NSButton alloc] initWithFrame:NSZeroRect];
    [c setTitle:label];
    [c setButtonType:NSSwitchButton];
    // doesn't seem to have an associated bezel style
    [c setBordered:NO];
    [c setTransparent:NO];
    [c setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSRegularControlSize]]];
    [c setTranslatesAutoresizingMaskIntoConstraints:NO];
    return c;
    }

    最佳答案

    您之所以得到这样的结果,是因为考虑到您所描述的限制,文本字段没有理由比屏幕截图中的更大。在处理自动布局时,如果您得到了意想不到的结果,通常最好问问自己为什么它不应该是这样的。

    我经常从第一次学习自动布局的学生和同事那里看到一个错误,那就是不等式提供了一些指示,表明您实际上希望某物的大小。例如,简单地在前沿提供 >= 10 的不等式并不能使其尝试 == 10。

    要修复布局,您需要提供一个约束,使文本字段有理由达到您想要的宽度。显然有很多方法可以做到这一点,但一种直接的方法是添加约束,尝试使文本字段成为 super View 的整个宽度,优先级低于其他约束(因此,前缘等于前缘999 优先级的 superview 的宽度,或者宽度等于 999 优先级的 superview 的宽度)。

    这将使系统尝试使它们与 super View 一样大,但只能尽可能大,同时还要尊重您现有的其他更高优先级的约束(包括内容拥抱/压缩阻力)。

    关于objective-c - 在自动布局中,我设置了不应水平增长的标签和应水平增长的控件。为什么我会反其道而行之?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37710892/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com