使用自动布局居中模态视图

Posted

技术标签:

【中文标题】使用自动布局居中模态视图【英文标题】:Centering Modal View with Autolayout 【发布时间】:2014-09-15 15:04:16 【问题描述】:

我正在使用 presentViewController 和自定义 modalPresentationStyle 呈现 UIViewController,以实现 Facebook POP 动画过渡。

模态视图本身是完全动态的,在代码中使用自动布局约束定义。没有 xib/storyboard 支持模态。

我无法让模态视图在屏幕上居中!自动布局是不够的,因为没有超级视图可以添加约束!

我的演示代码如下所示(取自 FB POP 代码示例):

- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext

    UIView *fromView = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey].view;
    fromView.tintAdjustmentMode = UIViewTintAdjustmentModeDimmed;
    fromView.userInteractionEnabled = NO;

    UIView *dimmingView = [[UIView alloc] initWithFrame:fromView.bounds];
    dimmingView.backgroundColor = [UIColor colorWithRed:(24/255.0) green:(42/255.0) blue:(15/255.0) alpha:1.0];
    dimmingView.layer.opacity = 0.0;

    UIView *toView = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey].view;
    toView.frame = CGRectMake(0,
                              0,
                              CGRectGetWidth(transitionContext.containerView.bounds) - 104.f,
                              CGRectGetHeight(transitionContext.containerView.bounds) - 320.f);
    toView.center = CGPointMake(transitionContext.containerView.center.x, -transitionContext.containerView.center.y);

    [transitionContext.containerView addSubview:dimmingView];
    [transitionContext.containerView addSubview:toView];

    POPSpringAnimation *positionAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerPositionY];
    positionAnimation.toValue = @(transitionContext.containerView.center.y);
    positionAnimation.springBounciness = 10;
    [positionAnimation setCompletionBlock:^(POPAnimation *anim, BOOL finished) 
        [transitionContext completeTransition:YES];
    ];

    POPSpringAnimation *scaleAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerScaleXY];
    scaleAnimation.springBounciness = 20;
    scaleAnimation.fromValue = [NSValue valueWithCGPoint:CGPointMake(1.2, 1.4)];

    POPBasicAnimation *opacityAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerOpacity];
    opacityAnimation.toValue = @(0.2);

    [toView.layer pop_addAnimation:positionAnimation forKey:@"positionAnimation"];
    [toView.layer pop_addAnimation:scaleAnimation forKey:@"scaleAnimation"];
    [dimmingView.layer pop_addAnimation:opacityAnimation forKey:@"opacityAnimation"];

这很好用,但我需要动态的实际视图大小(有时模态将有四行文本和两个按钮等)。为此,我需要在 VC 子类中设置 translatesAutoresizingMaskIntoConstraints=NO。这显然否定了我在演示动画师中所做的帧居中。

最终结果是一个模态框粘在屏幕的左边缘;奇怪的是,它垂直居中,而不是水平居中。从视觉上看,它看起来像这样(请原谅黑色方块,出于法律目的,我必须这样做):

显而易见的解决方案是添加一个使视图居中的视图约束。没问题吧?

但是我在哪里添加它? view.superview 为零;没有superview。我尝试创建一个自定义的“superview”属性并进行设置,但自动布局不知道如何处理其视图层次结构之外的视图(呈现的 vc)。这是我的视图层次结构的样子,带注释:

您显然不应该直接访问 UITransitionView。 UIWindow 上的约束无效。

有人有什么建议吗?遇到这种事,你们是怎么处理的?

【问题讨论】:

我实际上没有适合你的解决方案,但我告诉你在这种情况下我会怎么做:注释掉任何自动布局通过 CGRectMake( 。还有一件事:你提出这个问题的方式很难说问题出在哪里。祝你好运。 这是第 22 条规则。我需要使用自动布局,因为视图需要是完全动态的。 【参考方案1】:

您可以在 animateTranisition 方法中以编程方式将一个居中约束从 containerView 添加到 toView:

(在 Swift 中,但你应该能够理解...)

containerView.addSubview(toView)

let centerXLayoutConstraint = NSLayoutConstraint(item: toView, attribute: NSLayoutAttribute.CenterX, relatedBy: NSLayoutRelation.Equal, toItem: containerView, attribute: NSLayoutAttribute.CenterX, multiplier: 1, constant: 0)
let centerYLayoutConstraint = NSLayoutConstraint(item: toView, attribute: NSLayoutAttribute.CenterY, relatedBy: NSLayoutRelation.Equal, toItem: containerView, attribute: NSLayoutAttribute.CenterY, multiplier: 1, constant: 0)

containerView.addConstraint(centerXLayoutConstraint)
containerView.addConstraint(centerYLayoutConstraint)

当我尝试这样做时,我还向 toView 添加了宽度和高度约束,以使其相对于 containerView 调整大小。成功了——没问题。

我认为它也应该与自定大小的 toView 一起使用。您可能必须在 toView 类中覆盖 intrinsicSize 和/或强制它更新其约束。

【讨论】:

完美运行。我有点担心我不能直接访问 containerView ......但这是目前的解决方案。谢谢! 不客气!很高兴它对你有效!是的,我很好奇视图容器的限制。我的意思是,您至少需要能够添加子视图。也许限制更多是关于在 animateTransition 期间依赖其状态(大小、位置等)? 它有效,我将提交,看看会发生什么。如果被拒绝,我会在几个月后更新!【参考方案2】:

你能试试这个吗?

将您的模态窗口视图声明为UIView 的子类,并实现didMoveToSuperView

- (void)didMoveToSuperview 
    UIView *superView = self.superview;

    if(superView == nil) 
        return;
    

    [superView addConstraint:[NSLayoutConstraint constraintWithItem:self
                                                          attribute:NSLayoutAttributeCenterX
                                                          relatedBy:NSLayoutRelationEqual
                                                             toItem:superView
                                                          attribute:NSLayoutAttributeCenterX
                                                         multiplier:1.0
                                                           constant:0]];
    [superView addConstraint:[NSLayoutConstraint constraintWithItem:self
                                                          attribute:NSLayoutAttributeCenterY
                                                          relatedBy:NSLayoutRelationEqual
                                                             toItem:superView
                                                          attribute:NSLayoutAttributeCenterY
                                                         multiplier:1.0
                                                           constant:0]];

    // [superView layoutIfNeeded]; // If this does not work, try uncomment this.

当添加到任何超级视图时,它应该会自动居中。

需要说,您还需要translatesAutoresizingMaskIntoConstraints = NO 和任何宽度/高度限制。

不过,我还没有使用 UITransitionView 进行测试。也许这与您的positionAnimation 冲突。

编辑: 2014/09/22

我认为您应该使用systemLayoutSizeFittingSize: 方法不使用 translatesAutoresizingMaskIntoConstraints = NO,而不是对模态视图本身使用自动布局约束。

- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext

    // ...snip

    UIView *toView = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey].view;

    toView.translatesAutoresizingMaskIntoConstraints = YES; // To clarify. You don't need this line because this is the default.

    CGSize sysSize = [toView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
    toView.bounds = CGRectMake(0, 0, sysSize.width, sysSize.height);
    toView.center = CGPointMake(transitionContext.containerView.center.x, -transitionContext.containerView.center.y);
    toView.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleRightMargin;

    // ...snip


如果您想在 显示后调整模态视图的大小(作为修改其内容的副作用),请在模态视图UIViewController 子类中执行以下代码:

- (void)yourAppMethod 
    NSString *message = @""; // <- as u like
    UILabel *label = self.messageLabel;
    label.text = message;
    [self resizeViewIfNeeded]; // <- this will resize self.view


- (void)resizeViewIfNeeded 
    CGSize sysSize = [self.view systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
    if(!CGSizeEqualToSize(sysSize, self.view.bounds.size)) 
        self.view.bounds = CGRectMake(0, 0, sysSize.width, sysSize.height);
    

【讨论】:

不能改成 UIView 子类,需要是 VC。在 transitioncontext 容器视图上设置约束就可以了!感谢您的提示。【参考方案3】:

在 animateTransition 中:一旦将视图添加到层​​次结构中,您可以调用像 [self addConstraints] 这样的私有方法,然后执行以下操作:

 - (void)addConstraints
    
        [self.toView setTranslatesAutoresizingMaskIntoConstraints:NO];
        [self.dimmingView setTranslatesAutoresizingMaskIntoConstraints:NO];

        [self.containerView addConstraint:[NSLayoutConstraint constraintWithItem:self.toView
                                                                       attribute:NSLayoutAttributeCenterX
                                                                       relatedBy:NSLayoutRelationEqual
                                                                          toItem:self.containerView
                                                                       attribute:NSLayoutAttributeCenterX
                                                                      multiplier:1
                                                                        constant:0]];
        [self.containerView addConstraint:[NSLayoutConstraint constraintWithItem:self.toView
                                                                       attribute:NSLayoutAttributeCenterY
                                                                       relatedBy:NSLayoutRelationEqual
                                                                          toItem:self.containerView
                                                                       attribute:NSLayoutAttributeCenterY
                                                                      multiplier:1
                                                                        constant:0]];
        NSDictionary *views = @@"dimmingView" : self.dimmingView;
        [self.containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[dimmingView]|"
                                                                                   options:0
                                                                                   metrics:nil
                                                                                     views:views]];
        [self.containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[dimmingView]|"
                                                                                   options:0
                                                                                   metrics:nil
                                                                                     views:views]];

【讨论】:

以上是关于使用自动布局居中模态视图的主要内容,如果未能解决你的问题,请参考以下文章

在滚动视图中使用自动布局缩放图像如何居中?

使用自动布局将具有不同高度的多个视图居中

剩余空间中的自动布局居中视图(以编程方式)

使用自动布局居中或固定到顶部

自动布局视图 xib - 没有可编辑的约束?我需要居中的元素

为啥我的 superview-with-subview 在模态显示(使用自动布局)时会缩小?