iOS Autolayout 垂直相等的空间来填充父视图

Posted

技术标签:

【中文标题】iOS Autolayout 垂直相等的空间来填充父视图【英文标题】:iOS Autolayout Vertically equal space to fill parent view 【发布时间】:2013-06-13 14:16:52 【问题描述】:

我有一个带有 12 个UITextFields 的视图控制器。

它非常适合 3.5" 显示器。

我需要为 iPhone 5(4 英寸显示屏)设置它,以便所有 UITextFields 覆盖整个 UIView,方法是在它们之间添加额外的空间。

我正在尝试通过自动布局来做到这一点,但它无法正常工作。

这是我的代码:

- (void) viewWillLayoutSubviews

    int h = txt1.bounds.size.height * 12;

    float unusedHorizontalSpace = self.view.bounds.size.height - h ;

    NSNumber* spaceBetweenEachButton=  [NSNumber numberWithFloat: unusedHorizontalSpace / 13 ] ;

    NSMutableArray *constraintsForButtons = [[NSMutableArray alloc] init];

    [constraintsForButtons addObjectsFromArray: [NSLayoutConstraint constraintsWithVisualFormat: @"V:|-50-[txt1(==30)]-(space)-[txt2(==txt1)]-(space)-[txt3(==txt1)]-(space)-[txt4(==txt1)]-(space)-[txt5(==txt1)]-(space)-[txt6(==txt1)]-(space)-[txt7(==txt1)]-(space)-[txt8(==txt1)]-(space)-[txt9(==txt1)]-(space)-[txt10(==txt1)]-(space)-[txt11(==txt1)]-(space)-[txt12]-(space)-|"
                                                                                        options: NSLayoutFormatAlignAllCenterX
                                                                                        metrics: @@"space":spaceBetweenEachButton
                                                                                          views: NSDictionaryOfVariableBindings(txt1,txt10,txt11,txt12,txt2,txt3,txt4,txt5,txt6, txt7,txt8,txt9)]];

    [self.view addConstraints:constraintsForButtons];

如果我使用[txt12(==txt1)],那么它会显示与 3.5" 屏幕相同的内容,并在下方留出空间。

我哪里出错了?

【问题讨论】:

在您的constraintsWithVisualFormat 字符串中,您的第一个“空格”是一个常量 50,并且您只使用了 12 个“空格”标识符,即使您计算了 13。将 50 更改为(space) 或将spaceBetweenEachButton 计算为unusedHorizontalSpace/12 - 50。不确定它是否会起作用,但值得一试。 您实际上从上面的代码中得到了什么? @Hari Karam Sing 查看第二张图片。 哦,我明白了。你的间距是计算出来的,然后作为一个固定值传递,不是吗?您是否检查过该值是否正确计算为 Retina4 尺寸较大? 我有一个很好的解决方案,它不使用垫片并使用 IB。 [这里][1] [1]:***.com/a/25898949/951349 【参考方案1】:

要使用自动布局执行此操作,您必须创建额外的视图来填充文本字段之间的空间。

回想一下,自动布局约束基本上是线性方程A = m * B + cA 是一个视图的属性(例如,viewA 的下边缘的 Y 坐标),B 是另一个视图的属性(例如,viewB 的上边缘的 Y 坐标)。 mc 是常量。因此,例如,要布置viewAviewB,以便在viewA 的底部和viewB 的顶部之间有30 个点,我们可以创建一个约束,其中m 是1 和@987654340 @ 是 -30。

您遇到的问题是您希望在 13 个不同的约束条件下为 c 使用相同的值,并且您希望自动布局为您计算 c 值。自动布局根本无法做到这一点。不是直接的。自动布局只能计算视图的属性;它无法计算 mc 常量。

有一种方法可以使自动布局将视图放置在您想要的位置:将文本字段之间的空间具体化为附加(不可见)视图。这是一个只有 3 个文本字段的示例:

我们将创建一个约束,将每个分隔符的上边缘固定到其上方文本字段的下边缘。我们还将创建一个约束,将每个分隔符的底部边缘固定到其下方文本字段的顶部边缘。最后,我们将创建一个约束来强制每个垫片与最上面的垫片具有相同的高度。

我们需要两个实例变量来设置:一个文本字段数组(按从上到下的顺序),以及对最上面的间隔视图的引用:

@implementation ViewController 
    NSMutableArray *textFields;
    UIView *topSpacer;

我们将在代码中创建文本字段和分隔符,因为在 *** 答案中很难显示 xib。我们在viewDidLoad 开始:

- (void)viewDidLoad 
    [super viewDidLoad];
    self.view.translatesAutoresizingMaskIntoConstraints = NO;
    [self addTextFields];
    [self addSpacers];

由于我们要使用自动布局,我们需要关闭translatesAutoresizingMaskIntoConstraints,以防止系统创建额外的约束。

我们创建每个文本字段,给它一些虚拟文本,并为其水平位置和大小设置约束:

- (void)addTextFields 
    textFields = [NSMutableArray array];
    for (int i = 0; i < 12; ++i) 
        [self addTextField];
    


- (void)addTextField 
    UITextField *field = [[UITextField alloc] init];
    field.backgroundColor = [UIColor colorWithHue:0.8 saturation:0.1 brightness:0.9 alpha:1];
    field.translatesAutoresizingMaskIntoConstraints = NO;
    field.text = [field description];
    [self.view addSubview:field];
    [field setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
    [field setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|-[field]-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(field)]];
    [textFields addObject:field];

我们也将使用循环来创建垫片,但我们创建顶部和底部垫片与中间垫片不同,因为我们需要将顶部和底部垫片固定到超级视图:

- (void)addSpacers 
    [self addTopSpacer];
    for (int i = 1, count = textFields.count; i < count; ++i) 
        [self addSpacerFromBottomOfView:textFields[i - 1]
            toTopOfView:textFields[i]];
    
    [self addBottomSpacer];

以下是我们如何创建顶部垫片并设置其约束。它的顶边被固定到超级视图,它的底边被固定到第一个(最上面的)文本字段。我们将顶部垫片存储在实例变量topSpacer 中,因此我们可以约束其他垫片与顶部垫片具有相同的高度。

- (void)addTopSpacer 
    UIView *spacer = [self newSpacer];
    UITextField *field = textFields[0];
    [self.view addConstraints:[NSLayoutConstraint
        constraintsWithVisualFormat:@"V:|[spacer][field]" options:0 metrics:nil
        views:NSDictionaryOfVariableBindings(spacer, field)]];
    topSpacer = spacer;

以下是我们实际创建间隔视图的方法。这只是一个隐藏的视图。由于我们不关心它的水平大小或位置,我们只是将它固定在超级视图的左右边缘。

- (UIView *)newSpacer 
    UIView *spacer = [[UIView alloc] init];
    spacer.hidden = YES; // Views participate in layout even when hidden.
    spacer.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:spacer];
    [self.view addConstraints:[NSLayoutConstraint
        constraintsWithVisualFormat:@"|[spacer]|" options:0 metrics:nil
        views:NSDictionaryOfVariableBindings(spacer)]];
    return spacer;

为了在两个文本视图之间创建一个“中间”间隔,我们将它固定到文本的底部边缘 上方的字段和下方文本字段的上边缘。我们还将其高度限制为与顶部隔板的高度相同。

- (void)addSpacerFromBottomOfView:(UIView *)overView toTopOfView:(UIView *)underView 
    UIView *spacer = [self newSpacer];
    [self.view addConstraints:[NSLayoutConstraint
        constraintsWithVisualFormat:@"V:[overView][spacer(==topSpacer)][underView]" options:0 metrics:nil
        views:NSDictionaryOfVariableBindings(spacer, overView, underView, topSpacer)]];

为了创建底部间隔,我们将它固定到最后一个文本字段和超级视图。我们还将其高度限制为与顶部隔板的高度相同。

- (void)addBottomSpacer 
    UIView *spacer = [self newSpacer];
    UITextField *field = textFields.lastObject;
    [self.view addConstraints:[NSLayoutConstraint
        constraintsWithVisualFormat:@"V:[field][spacer(==topSpacer)]|" options:0 metrics:nil
        views:NSDictionaryOfVariableBindings(spacer, field, topSpacer)]];

如果你做对了,你会得到这样的结果:

您可以在this github repository 中找到完整的示例项目。

【讨论】:

感谢您的出色回答。 我正在尝试转向自动布局,因为它认为它很容易实现。但是看起来很支持只支持两种视图。添加额外的视图也会占用内存和处理能力。但我认为没有其他解决方案。所以无论如何都要感谢这个。它帮助我学习一些东西。 我很困惑:“您希望自动布局为您计算 c 值。自动布局根本无法做到这一点”。如果c 是间距,那么他会在他的代码中明确指定它... 这个答案(不幸的是)是错误的。您可以在不使用间隔视图的情况下获得所需的效果。在我创建的UIView+AutoLayout 类别上的方法autoDistributeSubviews:alongAxis:withFixedSize:alignment: 的实现中查看此问题的一般案例解决方案。 我的答案不需要固定的文本字段大小。如果用户可以更改字体,或者如果应用程序在 ios 7 下支持动态类型,那么我的答案将继续正确布局视图,无需额外代码。即使每个文本字段的大小不同,我的回答也有效。【参考方案2】:

查看PureLayout。它旨在成为在代码中创建自动布局约束的最简单且对程序员最友好的 API。

针对您的具体问题,PureLayout 提供了两个用于分发视图的主要 API,一个是每个视图之间的间距是固定的(视图大小根据需要而变化),另一个是每个视图的大小是固定的(间距视需要而定)。后者将完成您正在寻找的,而无需使用任何“间隔视图”。

// NSArray+PureLayout.h

// ...

/** Distributes the views in this array equally along the selected axis in their superview. Views will be the same size (variable) in the dimension along the axis and will have spacing (fixed) between them. */
- (NSArray *)autoDistributeViewsAlongAxis:(ALAxis)axis
                                alignedTo:(ALAttribute)alignment
                         withFixedSpacing:(CGFloat)spacing;

/** Distributes the views in this array equally along the selected axis in their superview. Views will be the same size (fixed) in the dimension along the axis and will have spacing (variable) between them. */
- (NSArray *)autoDistributeViewsAlongAxis:(ALAxis)axis
                                alignedTo:(ALAttribute)alignment
                            withFixedSize:(CGFloat)size;

// ...

【讨论】:

我会试试的。看起来你做得很好。试着放到 www.cocoacontrols.com 和 cocoapods.org 上,让更多人看到。 谢谢!我一定会这样做的。如果您有任何问题、反馈、建议或发现错误,请随时告诉我!【参考方案3】:

查看 developer.apple' 文档,对解决方案有很好的描述,https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/AutoLayoutbyExample/AutoLayoutbyExample.html 查看该页面中的间距和翘曲,我认为这是很好的描述,所以不需要在这里解释同样的事情

编辑

上面的链接现在被苹果禁用了,从 iOS 9 开始,他们引入了 Stackview,这是所有这些的解决方案。

之前在上面的链接中,答案与@rob 提供的答案相同

【讨论】:

是的,我知道...Apple 现在已经更新了该文档。现在,为了做到这一点,他们引入了 UIStackview 等间距。所以,现在不需要放置 spacerViews(在该链接中描述).. 您现在可以使用 UIStackviews 轻松完成相同的任务 该链接上的解释与抢劫者的答案几乎相同。所以如果你想支持比 iOS 9 更早的 ios 版本。那么你只需要走那条路

以上是关于iOS Autolayout 垂直相等的空间来填充父视图的主要内容,如果未能解决你的问题,请参考以下文章

在代码中使两个 AutoLayout 约束相等?

AutoLayout Circular UIButton - 等效垂直和水平尺寸

iOS 嵌套 UIScrollViews 使用 AutoLayout

AutoLayout正确定位按钮

IOS / Storyboard / Autolayout:使Textview垂直扩展以适应文本

Xamarin iOS Autolayout:自动调整各种设备的宽度和垂直滚动,同时保持水平滚动禁用演示链接