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

11

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

模态视图本身是完全动态的,使用代码中的Autolayout约束定义。没有xib/storyboard来支持这个模态视图。

我无法让模态视图在屏幕中间居中! Autolayout不够用,因为没有superview可以添加约束!

我的presenting代码看起来像这样(取自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"属性并设置它,但是自动布局不知道如何处理在其视图层次结构之外(呈现vc)的视图。这就是我的视图层次结构的样子,注释如下:
显然不能直接访问UITransitionView。在UIWindow上设置约束无效。
有人有什么建议吗?你们如何处理这种情况?

我实际上没有你需要的解决方案,但是我可以告诉你在这种情况下我会怎么做:注释掉任何自动布局通过CGRectMake设置位置。还有一件事:你提出问题的方式有点难以确定问题所在。祝你好运。 - carlodurso
这就是一个进退两难的情况。我需要使用自动布局,因为视图需要完全动态化。 - chris stamper
3个回答

4
您可以在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...但这是现在的解决方案。谢谢! - chris stamper
不客气!很高兴它对你有用!是的,我对视图容器的限制很好奇。我的意思是,你至少需要能够添加子视图。也许限制更多地依赖于其状态(大小、位置等)在animateTransition期间? - Anna Dickinson
它可以工作,我要提交并看看会发生什么。如果被拒绝了,我会在几个月后更新! - chris stamper

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

与其使用Autolayout约束来模态视图本身,我认为您应该使用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容器视图上设置约束就解决了问题!谢谢你的建议。 - chris stamper

0
在 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]];
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接