【问题标题】:Autolayout constraints and child view controller自动布局约束和子视图控制器
【发布时间】:2020-09-19 23:14:36
【问题描述】:

我有两个视图控制器,父级和子级。

所以在viewDidLoad 方法中,我执行以下操作:

ChildViewController* childViewController = [[ChildViewController alloc] init];

[self addChildViewController:childViewController];

// ChildViewController sets his own constraints in viewDidLoad
[self.view addSubview:childViewController.view];

[childViewController didMoveToParentViewController:self];

//
// setup constraints to expand childViewController.view to 
// fill the size of parent view controller
//

所以基本上发生的情况是在父控制器约束应用之前在 ChildViewController 上调用了updateViewConstraints,所以实际上self.view.frame == CGRectZero,与我在ChildViewController 中的自定义loadView 方法中指定的完全相同。

对于所有视图,translatesAutoresizingMaskIntoConstraints 全部设置为 NO

在这种情况下设置约束的正确方法是什么,以便ChildViewController 在父级之后更新他的约束?

来自两个控制器的当前日志非常令人沮丧,我不明白如何在 viewWillLayoutSubviews 之前调用 updateViewConstraints:

App[47933:c07] ChildViewController::updateViewConstraints. RECT: {{0, 0}, {0, 0}}
App[47933:c07] ParentViewController::updateViewConstraints
App[47933:c07] ChildViewController:viewWillLayoutSubviews. RECT: {{0, 0}, {984, 454}}
App[47933:c07] ChildViewController:viewDidLayoutSubviews. RECT: {{0, 0}, {984, 454}}

【问题讨论】:

  • 你需要显示你实际添加约束的代码,否则很难说哪里出了问题。
  • @Maarten 我解决了这个问题并在下面发布了答案,不确定它是否是我正在寻找的解决方案,它似乎错误,但它有效。对不起,我不能发布代码,代码太多了......

标签: objective-c autolayout


【解决方案1】:

您可以在调用 addSubview: 后立即添加约束,不要忘记将 translatesAutoresizingMaskIntoConstraints 设置为 false

这是添加和隐藏带有约束的子视图控制器的代码sn-p(灵感来自apple guide

显示

private func display(contentController content : UIViewController)
    {
        self.addChildViewController(content)
        content.view.translatesAutoresizingMaskIntoConstraints = false
        self.containerView.addSubview(content.view)
        content.didMove(toParentViewController: self)

        containerView.addConstraints([
            NSLayoutConstraint(item: content.view, attribute: .top, relatedBy: .equal, toItem: containerView, attribute: .top, multiplier: 1, constant: 0),
            NSLayoutConstraint(item: content.view, attribute: .bottom, relatedBy: .equal, toItem: containerView, attribute: .bottom, multiplier: 1, constant: 0),
            NSLayoutConstraint(item: content.view, attribute: .leading, relatedBy: .equal, toItem: containerView, attribute: .leading, multiplier: 1, constant: 0),
            NSLayoutConstraint(item: content.view, attribute: .trailing, relatedBy: .equal, toItem: containerView, attribute: .trailing, multiplier: 1, constant: 0)
            ])
    }

隐藏

private func hide(contentController content : UIViewController)
    {
        content.willMove(toParentViewController: nil)
        content.view.removeFromSuperview()
        content.removeFromParentViewController()
    }

【讨论】:

    【解决方案2】:

    我有类似的情况,我将子控制器添加到可见控制器(弹出窗口)。

    我在界面生成器中定义了子视图控制器。它的 viewDidLoad 方法只是调用 setTranslatesAutoresizingMaskIntoConstraints:NO

    然后我在子控制器上定义这个方法,它接受一个 UIViewController 参数,它是父级。 此方法将自身添加到给定的父视图控制器并定义自己的约束:

    - (void) addPopupToController:(UIViewController *)parent {
        UIView *view = [self view];
        [self willMoveToParentViewController:parent];
        [parent addChildViewController:self];
        [parent.view addSubview:view];
        [self didMoveToParentViewController:parent];
    
        NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[view]-0-|"
                                                                                 options:0
                                                                                 metrics:nil
                                                                                   views:NSDictionaryOfVariableBindings(view)];
        [parent.view addConstraints:horizontalConstraints];
    
        NSArray *verticalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[view]-0-|"
                                                                               options:0
                                                                               metrics:nil
                                                                                 views:NSDictionaryOfVariableBindings(view)];
        [parent.view addConstraints:verticalConstraints];
    }
    

    然后在父 UIViewController(已经显示的那个)内,当我想显示我的子弹出视图控制器时,它调用:

    PopUpNotificationViewController *popup = [[self storyboard] instantiateViewControllerWithIdentifier:@"NotificationPopup"];
    [popup addPopupToController:self];
    

    您可以在将子控制器的视图添加到父控制器的视图时定义您想要的任何约束。

    【讨论】:

      【解决方案3】:
      1. 我认为layoutIfNeeded 方法只有在您之前调用过setNeedsLayout 时才有效。如果您改用setNeedsLayout,系统将知道在适当的时间进行更新。尝试在您的代码中更改它。

      2. 当您向视图添加约束时,该视图应自动重新布置其子视图以考虑新约束。除非在添加约束后发生了某些变化,否则无需致电 setNeedsLayout。您是否将约束添加到视图,如果是:您是否将它们添加到正确的视图?

      3. 您可以尝试将UIView 子类化,以便在ChildViewController.viewParentViewController.view 执行新布局时看到日志消息:

        -(void) layoutSubviews {
            [super layoutSubviews];
            NSLog(@"layoutSubviews was called on the following view: %@", [view description]);
        }
        

      也许这会揭示您的视图何时是(或不是)它们的子视图的布局。

      【讨论】:

      • 对,如果之前未使用 setNeedsUpdateConstraints,layoutIfNeeded 并不会真正改变任何东西。那是我目前的日志:ChildViewController::updateViewConstraints。 RECT: {{0, 0}, {0, 0}} ParentViewController::updateViewConstraints ChildViewController_view::layoutSubviews
      • 所以子控制器首先更新,然后是父控制器。在 viewDidLoad 中创建的所有约束,所以我不完全理解它是如何实现的。
      【解决方案4】:

      根据the following link,我将约束创建移动到viewWillLayoutSubviews,这是正确设置视图边界的地方。我觉得这个答案没有解释为什么子视图控制器的 updateViewConstraints 在父视图控制器之前被调用,或者它可能只是我的代码中的一些错误,但是这个解决方法解决了这个问题......

      【讨论】:

        【解决方案5】:

        将添加和删除子控制器的代码放在扩展中很方便。使用示例:

        override func viewDidLoad() {
           super.viewDidLoad()
           let childController = YourCustomController()
           add(childController)
        }
        

        代码本身:

        extension UIViewController {
           
           func add(_ controller: UIViewController) {
              addChild(controller)
              view.addSubview(controller.view)
              
              controller.view.translatesAutoresizingMaskIntoConstraints = false
              NSLayoutConstraint.activate([
                 controller.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                 controller.view.topAnchor.constraint(equalTo: view.topAnchor),
                 controller.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
                 controller.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
              ])
              
              controller.didMove(toParent: self)
           }
           
           func remove() {
              guard parent != nil else {
                 return
              }
        
              willMove(toParent: nil)
              view.removeFromSuperview()
              removeFromParent()
           }
           
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-12-04
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-03-31
          • 1970-01-01
          相关资源
          最近更新 更多