问题
I'm presenting a UIViewController using presentViewController and a custom modalPresentationStyle, in an effort to implement a Facebook POP animated transition.
The modal view itself is completely dynamic, defined using Autolayout constraints in code. There is no xib/storyboard to back the modal.
I can't get the modal view to center on screen! Autolayout isn't sufficient, because there is no superview to add constraints on!
My presenting code looks like this (taken from a FB POP code sample):
- (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"];
}
This works beautifully, but I need the actual view size to be dynamic (sometimes the modal will have four lines of text and two buttons, etc). To accomplish this, I need to set translatesAutoresizingMaskIntoConstraints=NO in the VC subclass. This obviously negates the frame centering I'm doing in the presentation animator.
The end result is a modal that's stuck to the left edge of the screen; curiously, it centers itself vertically, but not horizontally. Visually, it looks something like this (pardon the black squares, I had to do it for legal purposes):
![](https://www.eimg.top/images/2020/03/15/d82272c32e876d0213cbfd6873c24a4f.jpg)
The obvious solution would be to add a view constraint that centers the view. No problem, right?
But where do I add it? view.superview is nil; there is no superview. I tried creating a custom 'superview' property and setting it, but autolayout doesn't know how to handle a view that's outside of its view hierarchy (the presenting vc). This is what my view hierarchy looks like, annotated:
![](https://www.eimg.top/images/2020/03/15/8ca877f9bdc463a826e6e6958e19bb55.jpg)
You're apparently not supposed to access the UITransitionView directly. Constraints on the UIWindow have no effect.
Does anyone have any advice? How do you guys handle this sort of thing?
回答1:
You can programmatically add a centering constraint from containerView to toView in your animateTranisition method:
(in Swift, but you should be able to get the idea...)
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)
When I tried this, I also added width and height constraints to toView to size it relative to containerView. It worked -- no problem.
I think it should work with a self-sizing toView as well. You might have to override intrinsicSize in your toView class and/or play around with forcing it to update its constraints.
回答2:
Could you try this?
Declare your modal window view as subclass of UIView
, and implement 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.
}
This should automatically do centering itself when added to any superview.
Needles to say, you also need translatesAutoresizingMaskIntoConstraints = NO
and any width/height constraints.
I haven't tested with UITransitionView
, though. Maybe this conflict with your positionAnimation
.
EDIT: 2014/09/22
Instead of using Autolayout constraints to modal view itself, I think you should use systemLayoutSizeFittingSize: method without 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
}
and If you want resize modal view after display (as a side effect of modifying it's content), do like following code in your modal views UIViewController
subclass:
- (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);
}
}
回答3:
In animateTransition: once you add your views to the hierarchy you can call a private method like [self addConstraints] and then do something like this:
- (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]];
}
来源:https://stackoverflow.com/questions/25851112/centering-modal-view-with-autolayout