【问题标题】:Use autolayout to set dynamic UIView to match container view使用自动布局设置动态 UIView 以匹配容器视图
【发布时间】:2013-04-15 23:28:19
【问题描述】:

我在 IB 中有一个 UIView,其中有另一个 UIView,我将其用作容器视图。在代码中,我创建了三个不同的视图,然后根据应用程序的状态将适当的视图动画化到容器的视图中。在任何给定时间,这三种不同观点中只有一种是有效的。问题是,当我在模拟器中的不同 iPhone 上运行时,我的新子视图没有缩放以匹配容器视图。我正在使用自动布局。出于测试目的,我将我的子视图设置为一个大按钮,它的所有边缘都限制在超级视图中。并且容器视图也有它的边缘限制到它的超级视图。我想要的是子视图与容器的视图相匹配。即按钮拉伸容器视图的整个大小。当在不同的 iPhone 上运行时,容器视图的大小和子视图应相对于不同 iPhone 的屏幕尺寸按比例缩放。

下面是我用来初始化子视图并设置它相对于容器视图的约束的代码。

UIView *containerView = self.subView;
UIView *newSubview = [[mySubview alloc] init];
[newSubview setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.containerView addSubview:newSubview];

[self.containerView addConstraint:[NSLayoutConstraint constraintWithItem:newSubview attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.containerView attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]];

[self.containerView addConstraint:[NSLayoutConstraint constraintWithItem:newSubview attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.containerView attribute:NSLayoutAttributeLeading multiplier:1.0 constant:0]];

[self.containerView addConstraint:[NSLayoutConstraint constraintWithItem:newSubview attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.containerView attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0]];

[self.containerView addConstraint:[NSLayoutConstraint constraintWithItem:newSubview attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.containerView attribute:NSLayoutAttributeHeight multiplier:1.0 constant:0]];  

我似乎无法让它工作。我对自动布局还很陌生,不确定我做错了什么,我不想再把头撞在墙上了。任何帮助都会很棒。 :)


****************** 附加信息 **************


抱歉,我没有尽可能清楚地说明我的问题。因此,这里有更多关于屏幕截图的信息。首先,我将描述我在代码方面所做的工作。

在 AppDelegate.m 的 didFinishLaunchingWithOptions 中,我像这样创建 MyViewController,

self.myViewController = [[MyViewController alloc] initWithNibName:@"MyViewController" bundle:nil];

在 MyViewController.m 的 viewDidLoad 中,我创建了 mySubview 并将其添加到我的 containerView 并像这样为其创建约束,

UIView *containerView = self.containerView;
UIView *mySubview = [[MySubview alloc] init];
[mySubview setTranslatesAutoresizingMaskIntoConstraints:NO];
[containerView addSubview:mySubview];

NSDictionary *views = NSDictionaryOfVariableBindings(mySubview);
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[mySubview]|"
                                                                 options:0
                                                                 metrics:nil
                                                                   views:views]];
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[mySubview]|"
                                                                  options:0
                                                                  metrics:nil
                                                                    views:views]];

最后在 MySubview.h 的 init 中,我将笔尖添加为这样的子视图,

- (id)init
{
    if(self = [super init])
    {
        NSArray *nibArray = [[NSBundle mainBundle]loadNibNamed:@"MySubview" owner:self options:nil];
        [self addSubview:[nibArray objectAtIndex:0]];
    }
    return self;
}

需要注意的几件事可能会有所帮助,

在 MyViewController.xib 中,我有一个 UIView 用作 containerView。它有一个到 UIView* containerView 的 IBOutlet,是上面提到的那个。我对 IB 中 containerView 的唯一限制是 Superview 的前导、尾随和底部空间以及 Superview = 44 的顶部空间。

对于 MySubview.xib,高度和宽度为 300,(高度或宽度没有使用约束)。我觉得这些尺寸应该无关紧要,因为 mySubview 应该受限于 containerView。

在 MySubview.xib 我有 3 个对象,topButton:height = 29、middleView:height = 242 和 bottomButton:height = 29。(见附图)TopButton 具有前导、尾随和顶部到 Superview 约束和底部到 middleView约束。 MiddleView 具有前导和尾随 Superview 约束以及从上到上按钮约束和从下到下按钮约束。最后,bottomButton 具有到 Superview 约束的前导、尾随和底部以及从顶部到中间视图的约束。

我想要发生的是让 mySubview 缩放以适应 containerView,因为已经创建并添加了约束,但是相反,mySubview 变得非常大并剪辑了 containerView。

以下是一些屏幕截图:

MyViewController.xib,标题下方的蓝色矩形是我的容器视图,并且有 IBOutlet containerView。

MySubview.xib

最后,结果是不正确的。

相反,我想要这个,我伪造它只是为了获取屏幕截图。

在 iPhone 4 上,

在 iPhone 5 上,

正如您在假屏幕截图中所见,mySubview 会缩放以适应 containerView,即使 containerView 会稍微缩放以适应不同的手机屏幕尺寸。

希望我没有过度使用这些信息。任何帮助都会很棒。我觉得我很接近,但肯定错过了一个关键步骤。呸呸呸。

【问题讨论】:

    标签: ios uiview autolayout resize


    【解决方案1】:

    一些想法:

      1234563 @。您是否省略了设置 containerView 类属性的行?但是当我修复它时,你的代码对我来说很好。
    1. 确保您没有在设置约束后立即查看frame,因为更改尚未反映在frame 设置中。如果您想强制它根据约束重新布局所有内容,您可以执行[containerView layoutIfNeeded];。此外,如果您想确认frame 设置,最好推迟到viewDidAppear 之后再查看这些值(即viewDidLoad 在视图构建过程中为时过早)。

    2. 对您的代码稍作调整(与您的问题无关),但是当我在容器视图中设置约束时,我通常不仅会设置 NSLayoutAttributeTopNSLayoutAttributeLeading,就像您所做的那样,而且还会设置NSLayoutAttributeBottomNSLayoutAttributeTrailing(而不是 NSLayoutAttributeWidthNSLayoutAttributeHeight)。它完成与您的代码相同的事情,但是当使用非零值时,它更直观。

      无论如何,我只是做了以下代码,你的排列,它工作正常:

      - (IBAction)didTouchUpInsideAddView:(id)sender
      {
          UIView *containerView = self.containerView;
          UIView *newSubview = [[UIView alloc] initWithFrame:CGRectZero]; // initializing with CGRectZero so we can see it change
          newSubview.translatesAutoresizingMaskIntoConstraints = NO;
          newSubview.backgroundColor = [UIColor lightGrayColor];
          [containerView addSubview:newSubview];
      
          [containerView addConstraint:[NSLayoutConstraint constraintWithItem:newSubview
                                                                    attribute:NSLayoutAttributeTop
                                                                    relatedBy:NSLayoutRelationEqual
                                                                       toItem:containerView
                                                                    attribute:NSLayoutAttributeTop
                                                                   multiplier:1.0
                                                                     constant:0.0]];
      
          [containerView addConstraint:[NSLayoutConstraint constraintWithItem:newSubview
                                                                    attribute:NSLayoutAttributeLeading
                                                                    relatedBy:NSLayoutRelationEqual
                                                                       toItem:containerView
                                                                    attribute:NSLayoutAttributeLeading
                                                                   multiplier:1.0
                                                                     constant:0.0]];
      
          [containerView addConstraint:[NSLayoutConstraint constraintWithItem:newSubview
                                                                    attribute:NSLayoutAttributeBottom
                                                                    relatedBy:NSLayoutRelationEqual
                                                                       toItem:containerView
                                                                    attribute:NSLayoutAttributeBottom
                                                                   multiplier:1.0
                                                                     constant:0.0]];
      
          [containerView addConstraint:[NSLayoutConstraint constraintWithItem:newSubview
                                                                    attribute:NSLayoutAttributeTrailing
                                                                    relatedBy:NSLayoutRelationEqual
                                                                       toItem:containerView
                                                                    attribute:NSLayoutAttributeTrailing
                                                                   multiplier:1.0
                                                                     constant:0.0]];
      
          // the frame is still `CGRectZero` at this point
      
          NSLog(@"newSubview.frame before = %@", NSStringFromCGRect(newSubview.frame));
      
          // if not doing this in `viewDidLoad` (e.g. you're doing this in
          // `viewDidAppear` or later), you can force `layoutIfNeeded` if you want
          // to look at `frame` values. Generally you don't need to do this unless
          // manually inspecting `frame` values or when changing constraints in a
          // `animations` block of `animateWithDuration`.
      
          [containerView layoutIfNeeded];
      
          // everything is ok here
      
          NSLog(@"containerView.bounds after = %@", NSStringFromCGRect(containerView.bounds));
          NSLog(@"newSubview.frame after = %@", NSStringFromCGRect(newSubview.frame));
      }
      
    3. 您可以使用visual format language 稍微简化该代码,例如:

      NSDictionary *views = NSDictionaryOfVariableBindings(newSubview);
      
      [containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[newSubview]|"
                                                                            options:0
                                                                            metrics:nil
                                                                              views:views]];
      [containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[newSubview]|"
                                                                            options:0
                                                                            metrics:nil
                                                                              views:views]];
      

      我发现使用视觉格式语言更容易获得正确的约束。不太容易出错(至少对我来说)。但是,有些约束无法用可视化格式语言表示,在这种情况下,我会退回到您概述的语法。

    4. 在您修改后的问题中,您向我们展示了您的子视图的init 方法,该方法另一个 addSubview。你也必须在那里设置约束。底线,无论你在哪里addSubview,你都必须设置你的约束,例如

      - (id)init
      {
          if(self = [super init])
          {
              NSArray *nibArray = [[NSBundle mainBundle]loadNibNamed:@"MySubview" owner:self options:nil];
              UIView *subview = [nibArray objectAtIndex:0];
              subview.translatesAutoresizingMaskIntoConstraints = NO;
      
              [self addSubview:subview];
      
              NSDictionary *views = NSDictionaryOfVariableBindings(subview);
              [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[subview]|"
                                                                           options:0
                                                                           metrics:nil
                                                                             views:views]];
              [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[subview]|"
                                                                           options:0
                                                                           metrics:nil
                                                                             views:views]];
          }
          return self;
      }
      

    【讨论】:

    • 啊,是的,你是对的,我在写问题时确实弄乱了我的变量名。对困惑感到抱歉。视觉格式语言看起来不错。我想我会远离它,直到我可以让它工作,然后我会转而使用它。婴儿步。 :) 不幸的是,它仍然不适合我。我目前正在查看固定宽度和高度建议,然后在代码中修改出口约束。我只是不确定我应该将约束常量值更改为什么?
    • @Feta 究竟出了什么问题?我问是因为当我使用您的代码时,它工作正常。确保您没有在设置约束后立即尝试检查frame 设置,因为除非(a)您在viewDidAppear 或更高版本中执行此操作,否则这将不起作用;和/或 (b) 你强制layoutIfNeeded。最重要的是,您如何得出约束不起作用的结论?请参阅我修改后的答案以了解其他一些观察结果。
    • 我已经编辑了我的答案,以便更详细地描述我在做什么。以前,我在 viewDidLoad 中设置约束,我只是将其更改为 viewDidAppear 并且还调用了 layoutIfNeeded 但我仍然得到相同的错误结果。 :(
    • 所以我刚刚尝试了您的代码,它成功了,太棒了!有点。 :) 当我更改“UIView *newSubview = [[UIView alloc] initWithFrame:CGRectZero];”到“UIView *newSubview = [[MySubview alloc] initWithFrame:CGRectZero];”它不起作用并且行为与我在编辑的问题中概述的一样。 MySubview 加载了一个笔尖,我想我必须对此进行更多研究,因为它似乎可能会导致问题。
    • @Feta 是否在您的 NIB 中打开了自动布局标志?
    【解决方案2】:

    在问题中,您声明“我的新子视图未缩放以匹配容器视图” - 但从描述中我认为它们是 - 问题是您的容器视图没有固定大小。

    我认为,如果您将容器视图设置为具有应执行此操作的固定宽度和高度,您可能还需要调用

    [self.containerView layoutSubviews]
    

    在更新约束后强制调整大小。

    我还建议您改用基于文本的格式,您可以将上面的行替换为“H:|[newSubview]|”之类的内容和“V:|[newSubview]|”

    【讨论】:

    • 我认为这可能是正确的思路。如何更新约束以使其相对于设备的屏幕尺寸进行缩放?我是否将容器视图的高度和宽度固定在 IB 中,然后将它们连接到约束出口并在创建子视图时更改常量?如果是这样,假设在 IB 中,我的容器视图的高度是 400,并且该值对于 iPhone 5 是正确的,所以我不想修改它。当应用程序在 iPhone 4 上运行时,我将如何以及在哪里更改它以使其缩小一点以与其较小的屏幕成比例?
    • 那么,视图和屏幕大小有什么关系呢?是的,大小应该取决于屏幕,但目标是在约束中表达,而不是使用固定数字。如果说,containerView 高度应该是 superview 的 30%,您可以将高度约束从 IB 拖到您的控制器中,并使用它来设置加载时的缩放。您也可以尝试将高度从等式 (=) 更改为不等式 (>=)
    • 我想我正在这样做。 containerView 对 super 有约束,因为它是我在 IB 中设置的顶部和底部。你说我应该“在负载上缩放”这对我来说也很有意义,但是我从哪里得到那个比例因子。例如,iPhone 5 的高度是 1136,iPhone 4 的高度是 960,所以我想在某个地方我可以得到一个比例因子或 0.845 (960 / 1136) 我知道我可以在代码中通过将当前屏幕高度除以视图构建的屏幕高度,但这似乎不正确,似乎是自动布局应该处理的东西。我错了吗?
    • 就目前而言,当我创建子视图(已为 iPhone 5 构建)并将其约束到代码中的容器视图时,它对子视图的高度和底部没有影响在 iPhone 4 等较小的屏幕上显示时,视图会被剪裁。
    • 负载缩放是一个糟糕的词选择,而是应该根据现有的约束来推断高度以对负载进行超级视图。一口。能发个截图吗?您的子视图是否在较小的屏幕上超出了容器视图,或者在您添加子视图之前容器视图是否已经超出了底部?剪辑时日志中有任何错误或消息吗?
    猜你喜欢
    • 2016-11-13
    • 1970-01-01
    • 2015-04-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多