在子视图布局约束中使用模态视图帧大小

Using modal view frame size in subview layout constraints

提问人:ennell 提问时间:8/15/2023 更新时间:8/16/2023 访问量:65

问:

问题:在调整模式视图控制器的子视图大小时,使用正确的帧值来模拟视口百分比时,我遇到了一些问题。我宁愿使用布局约束而不是显式框架值来调整子视图的大小。

未正确设置模态视图控制器的帧值,最高为 (包括 )。使用所需的帧值依次调用的两个方法是 和 。到目前为止,这些似乎是我唯一可以使用的“钩子”来访问正确的帧值。viewWillAppearviewWillLayoutSubviewsviewDidAppear

这些方法不容易使用,因为:

  • viewWillLayoutSubviews被多次调用,我假设是中间帧值。多次设置相同约束会引发“自动布局”约束错误。

    // Multiple frame values return from viewWillLayoutSubviews
    {{0, 0}, {393, 783}}
    {{0, 0}, {393, 283.66666666666663}}
    
  • viewDidAppear效果更好,因为它只使用最终的、所需的帧值调用一次。但是,此时视图将添加到视图层次结构中并显示在屏幕上。然后,在设置布局约束时,屏幕简介会先显示不受约束的布局,然后再显示受约束的布局。

解决方法解决方案:在激活新约束之前使用和停用旧约束,以避免约束冲突。这可以通过在第一次调用 时遍历或维护对原始约束的状态变量(强引用)来完成。这似乎不是一个干净的解决方案。viewWillLayoutSubviewsmodal.view.constraintsviewWillLayoutSubviews

// Utility function to activate constraints
void setConstraints (UIView* view, NSLayoutAnchor *leading, NSLayoutAnchor *trailing, NSLayoutAnchor *top, NSLayoutAnchor *bottom,
                   CGFloat cLeading, CGFloat cTrailing, CGFloat cTop, CGFloat cBottom, NSArray<NSLayoutConstraint*>* __strong *constraints) {
  NSArray *newConstraints = @[
      [view.leadingAnchor constraintEqualToAnchor:leading constant:cLeading],
      [view.trailingAnchor constraintEqualToAnchor:trailing constant:cTrailing],
      [view.topAnchor constraintEqualToAnchor:top constant:cTop],
      [view.bottomAnchor constraintEqualToAnchor:bottom constant:cBottom]
  ];
  [NSLayoutConstraint deactivateConstraints:*constraints];
  [NSLayoutConstraint activateConstraints:newConstraints];
  *constraints = newConstraints;
}

// UIViewController "hook" with eventually correct frame value
- (void)viewWillLayoutSubviews {
  [super viewWillLayoutSubviews];
  [self.recordingView setConstraints];
}

// Modal view-controller setting auto layout constraints based on layoutMarginsGuide and current modal frame values
- (void) setConstraints {
  UILayoutGuide *guide = self.layoutMarginsGuide;
  CGFloat width = self.frame.size.width;
  CGFloat height = self.frame.size.height;
  setConstraints(self.textField, guide.leadingAnchor, guide.trailingAnchor, guide.topAnchor, guide.topAnchor,
                 0.05*width, -0.05*width, 0.0, 0.2*height, &textFieldConstraints);
  setConstraints(self.progressBar, guide.leadingAnchor, guide.trailingAnchor, self.textField.bottomAnchor, self.textField.bottomAnchor,
                 0.05*width, -0.05*width, 0.08*height, 0.1*height, &progressBarConstraints);
}

问题:有没有更好的方法来做到这一点,而我没有看到?还是我真的不应该使用帧值作为视口宽度/高度百分比(类似于 CSS)?

iOS Objective-C UIVieviceController UIKition NSPalityConstraint

评论

0赞 DonMag 8/15/2023
非常令人困惑...实际上是一个?你希望它是控制器视图高度的 20%?并且是 ?这应该是高度的 10%?两者都应该是宽度的 90%,水平居中?textFieldUITextViewprogressBarUIProgressView
0赞 HangarRash 8/15/2023
最终目标是简单地将文本字段和进度条放置在录制视图中,还是应该根据文本字段和进度条的布局调整录制视图的大小?
0赞 HangarRash 8/15/2023
同样令人困惑的是,您希望文本字段和进度视图的前缘距离视图的前缘边距更远,为视图宽度的 5%。这真的是你的本意吗?或者您真的只想让文本字段和进度条的前缘是视图前缘宽度的 5%?
0赞 enell 8/15/2023
@DonMag 对不起。是的,确实是一个.基本上,我想根据您描述的视口宽度/高度百分比来定位子视图。具体百分比不是特别相关。textFieldUITextView
0赞 enell 8/15/2023
@HangarRash 布局的细节并不真正相关。我的问题更多地涉及如何在 UIKit 中为模态视图实现视口宽度/高度百分比。如果这是定位子视图的不正确方法,那么首选的方法/策略是什么?

答:

1赞 HangarRash 8/15/2023 #1

由于您的问题询问的是一般方法,因此下面只是一个示例,用于在其超级视图中设置文本视图的约束。

[NSLayoutConstraint activateConstraints:@[
    // Set the width as a percentage of the superview's width
    [self.textView.widthAnchor constraintEqualToAnchor:self.layoutMarginsGuide.widthAnchor multiplier:0.9],
    // Align in the center horizontally
    [self.textView.centerXAnchor constraintEqualToAnchor:self.layoutMarginsGuide.centerXAnchor],
    // Set the top
    [self.textView.topAnchor constraintEqualToAnchor:self.layoutMarginsGuide.topAnchor],
    // Rely on the intrinsic height of the text view or you can optionally set a specific height constraint if desired
]];

第一个约束是与您的问题最相关的约束。这显示了如何使文本视图的宽度为超视图边距布局宽度的 90%。根据需要进行调整。也许根据您的需要替换。self.layoutMarginsGuide.widthAnchorself.widthAnchor

设置一次,你就完成了。无需更新。随着上视图宽度的变化,文本视图的宽度也会发生变化。


如果要将 、 、 或设置为视图高度或宽度的百分比,则需要使用较长的形式来创建约束。topAnchorbottomAnchorleadingAnchortrailingAnchor

以下将文本视图设置为超级视图的 30%:topAnchorbottomAnchor

NSLayoutConstraint *top = [NSLayoutConstraint constraintWithItem:self.textView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeBottom multiplier:0.3 constant:0];

然后,您需要像往常一样激活它。

评论

0赞 enell 8/15/2023
这适用于调整子视图的大小。谢谢!作为后续,如果我想将子视图(例如从 topAnchor 定位为超视图高度的 30%),而不是仅在 topAnchor 上,该怎么办?似乎没有一种自适应布局方法可以使用类似的“乘数”参数来做到这一点,只有“常量”,这是一个固定值。
0赞 HangarRash 8/15/2023
@ennell我用那个案例的例子更新了我的答案。
0赞 enell 8/16/2023
谢谢。我试过了,不幸的是它会导致一个错误,这是因为它试图将 a 限制为 .来源:github.com/SnapKit/SnapKit/issues/534Invalid pairing of layout attributes.topAnchorheightAnchor
1赞 HangarRash 8/16/2023
尝试替换为 .NSLayoutAttributeHeightNSLayoutAttributeBottom
0赞 HangarRash 8/16/2023
@ennell我已经更新了答案以反映这一变化。很高兴它对你有用。