I followed this guide Implementing a Container View Controller to make a container that would handle login/logout in the app.
Children view controllers are: UINavigationController for login, and UITabBarController for the rest of the app:
My problem is that UINavigationBar animates strangely, and I want to prevent its animation:
The animation code is basically this (full project code here):
let current = childViewControllers.first
current?.willMoveToParentViewController(nil)
child.securityContainer = self
addChildViewController(child)
child.view.frame = newChildOriginFrame
UIView.transitionWithView(view, duration: 0.3, options: [], animations: {
child.view.frame = newChildTargetFrame
current?.view.frame = oldChildTargetFrame
self.view.addSubview(child.view)
}, completion: { _ in
child.didMoveToParentViewController(self)
current?.view.removeFromSuperview()
current?.removeFromParentViewController()
current?.securityContainer = nil
})
How can I prevent animation of UINavigationBar?
Fixed it by moving addSubview outside animations block:
let current = childViewControllers.first
current?.willMoveToParentViewController(nil)
child.securityContainer = self
addChildViewController(child)
child.view.frame = newChildOriginFrame
view.addSubview(child.view)
UIView.transitionWithView(view, duration: 0.3, options: [], animations: {
child.view.frame = newChildTargetFrame
current?.view.frame = oldChildTargetFrame
}, completion: { _ in
child.didMoveToParentViewController(self)
current?.view.removeFromSuperview()
current?.removeFromParentViewController()
current?.securityContainer = nil
})
(full project source code)
Related
I'm using a custom UIPresentationController to present a view modally. After presenting the view, the first textfield in the presented view becomes the first responder and the keyboard shows up. To ensure that the view is still visible, I move it up. However, when I do this the frameOfPresentedViewInContainerView is not matching the actual frame of the view anymore. Because of this, when I tap on the view it's being dismissed, because there's a tapGestureRecogziner on the backgroundView which is on top of the presentingView. How to notify the presentingController that the frame/position of the presentedView has changed?
In the UIPresentationController:
override var frameOfPresentedViewInContainerView: CGRect {
var frame = CGRect.zero
let safeAreaBottom = self.presentingViewController.view.safeAreaInsets.bottom
guard let height = presentedView?.frame.height else { return frame }
if let containerBounds = containerView?.bounds {
frame = CGRect(x: 0,
y: containerBounds.height - height - safeAreaBottom,
width: containerBounds.width,
height: height + safeAreaBottom)
}
return frame
}
override func presentationTransitionWillBegin() {
if let containerView = self.containerView, let coordinator = presentingViewController.transitionCoordinator {
containerView.addSubview(self.dimmedBackgroundView)
self.dimmedBackgroundView.backgroundColor = .black
self.dimmedBackgroundView.frame = containerView.bounds
self.dimmedBackgroundView.alpha = 0
coordinator.animate(alongsideTransition: { _ in
self.dimmedBackgroundView.alpha = 0.5
}, completion: nil)
}
}
Presenting the view modally:
let overlayVC = CreateEventViewController()
overlayVC.transitioningDelegate = self.transitioningDelegate
overlayVC.modalPresentationStyle = .custom
self.present(overlayVC, animated: true, completion: nil)
Animation when keyboard appears (in the presented view):
#objc func animateWithKeyboard(notification: NSNotification) {
let userInfo = notification.userInfo!
guard let keyboardHeight = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.height,
let duration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double,
let curve = userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? UInt else {
return
}
// bottomContraint is the constraint that pins content to the bottom of the superview.
let moveUp = (notification.name == UIResponder.keyboardWillShowNotification)
bottomConstraint.constant = moveUp ? (keyboardHeight) : originalBottomValue
let options = UIView.AnimationOptions(rawValue: curve << 16)
UIView.animate(withDuration: duration, delay: 0,
options: options,
animations: {
self.view.layoutIfNeeded()
}, completion: nil)
}
From the Apple documentation:
UIKit calls this method multiple times during the course of a
presentation, so your implementation should return the same frame
rectangle each time. Do not use this method to make changes to your
view hierarchy or perform other one-time tasks.
AFAIK, if you specify frame through this variable, it's advised not to change it throughout the course of presentation. If you plan to play around with the frames, don't specify this variable and handle all the changes manually in your animator
How do I make subviews of stack interchange with animation
UIView.animateWithDuration(0.5, animations:
{
self.stackview.exchangeSubviewAtIndex(3, withSubviewAtIndex: 4)
})
This works when views are not in stack view :
let lastXcoVar = lastView.center.x
UIView.animateWithDuration(0.5, animations:
{
lastView.center.x = nextView.center.x
nextView.center.x = lastXcoVar
})
Animation is just a visualization. You can do the same animation as your views not in stack view, after the animation is finished we use exchangeSubviewAtIndex to update the views on stack view. For example:
let lastXcoVar = lastView.center.x
UIView.animateWithDuration(0.5, animations:
{
lastView.center.x = nextView.center.x
nextView.center.x = lastXcoVar
}) { _ in
self.stackview.exchangeSubviewAtIndex(<index of lastView>, withSubviewAtIndex: <index of nextView>)
}
I am unable to remove from superview with the following code?
Why is this?I have tried everything but seems that it is not working at all.I am adding more details so I don't get this very annoying alert that tells me that my post is mostly code....
let controller = storyboard!.instantiateViewControllerWithIdentifier("OrderViewController")
controller.view.frame = CGRectMake(self.view.frame.size.width/2 - 100, self.view.frame.size.height/2 - 100, 200, 200)
if sender.selected {
sender.selected = false
controller.view.transform = CGAffineTransformIdentity
[UIView .animateWithDuration(0.2, delay: 0.0, options: UIViewAnimationOptions.CurveEaseOut, animations: {
controller.view.transform = CGAffineTransformMakeScale(0.01, 0.01);
}, completion: { finished in
controller.willMoveToParentViewController(nil)
controller.view .removeFromSuperview()
controller.removeFromParentViewController()
})]
print("close")
}
else {
sender.selected = true
addChildViewController(controller)
view.addSubview(controller.view)
controller.didMoveToParentViewController(self)
controller.view.transform = CGAffineTransformMakeScale(0.01, 0.01);
[UIView .animateWithDuration(0.2, delay: 0.0, options: UIViewAnimationOptions.CurveEaseOut, animations: {
controller.view.transform = CGAffineTransformIdentity
}, completion: nil)]
print("present")
}
There's a weird mix of objective-c syntax (the square brackets around your UIView animation block) and swift. I'm surprised if this is even compiling without errors!
You're almost there, the main issue is that each time this block of code is called you're instantiating a new instance of an 'OrderViewController'.
You only want to create this if it doesn't exist (when you want to show it). When you're ready to hide it you want to grab a reference to the existing controller and do the animations etc to hide it.
To do that you'll need to keep a reference to it outside of the local scope of that code block.
Here's an example showing how you might do that:
import UIKit
class ViewController: UIViewController {
// keep a reference to the OrderViewController after it's been created
var controller:UIViewController?
#IBAction func buttonTapped(sender: AnyObject) {
// instead of using button selected state, just check to see if
// self.controller is nil or not
if let existingController = self.controller {
// controller already exists, lets hide it
existingController.view.transform = CGAffineTransformIdentity
UIView.animateWithDuration(0.2, delay: 0.0, options: UIViewAnimationOptions.CurveEaseOut, animations: {
existingController.view.transform = CGAffineTransformMakeScale(0.01, 0.01);
}, completion: { _ in
existingController.willMoveToParentViewController(nil)
existingController.view.removeFromSuperview()
existingController.removeFromParentViewController()
// make this nil, so that next time the button is
// tapped we'll go though the process of creating it again
self.controller = nil
})
print("close")
}
else {
// controller doesn't exist, lets instanstiate and show it
let newController = storyboard!.instantiateViewControllerWithIdentifier("OrderViewController")
newController.view.frame = CGRectMake(self.view.frame.size.width/2 - 100, self.view.frame.size.height/2 - 100, 200, 200)
addChildViewController(newController)
view.addSubview(newController.view)
newController.didMoveToParentViewController(self)
newController.view.transform = CGAffineTransformMakeScale(0.01, 0.01)
UIView.animateWithDuration(0.2, delay: 0.0, options: UIViewAnimationOptions.CurveEaseOut, animations: {
newController.view.transform = CGAffineTransformIdentity
}, completion: { _ in
// keep a reference to this controller you've just created,
// so that next time the button is tapped you can close it
self.controller = newController
})
print("present")
}
}
}
If you're working on a really simple app (just a couple of screens) then this method is okay.
But.... if you're planning on something more complex you might want to investigate using either a custom UISegue, or a mix of UIPresentationController and UIViewControllerAnimatedTransitioning so that all of this animation logic doesn't get locked into your view controller code.
Following is the code excerpts for the custom segue animation in Swift, which works fine if I make the source viewController as Initial Window in storyboard, only.
class CustomSegue: UIStoryboardSegue
{
override func perform()
{
var fromView = self.sourceViewController.view as UIView!
var toView = self.destinationViewController.view as UIView!
let offScreenHorizontalStart = CGAffineTransformMakeRotation(CGFloat(M_PI / 2))
let offScreenHorizontalEnd = CGAffineTransformMakeRotation(CGFloat(-M_PI / 2))
fromView.layer.anchorPoint = CGPoint(x:0, y:0)
fromView.layer.position = CGPoint(x:0, y:0)
UIApplication.sharedApplication().keyWindow?.insertSubview(toView, belowSubview: fromView)
UIView.animateWithDuration(0.7, delay: 0.0, usingSpringWithDamping: 1.6, initialSpringVelocity: 0.0, options: nil, animations:
{
fromView.transform = offScreenHorizontalEnd
}, completion: {
finished in
self.sourceViewController.presentViewController(self.destinationViewController as! UIViewController, animated: false, completion: nil)
})
}
}
By this segue animation, the source view expects to disappear with an upside rotation from x/y = 0/0.
Thanks.
As far as you are using explicitly the keyWindow property of the UIApplication object here:
UIApplication.sharedApplication().keyWindow?.insertSubview(toView, belowSubview: fromView)
I think it is no surprise that it won't work for other windows.
You could use the windows property of UIApplication instead. It holds all UIWindow objects currently open in the app in an ordered NSArray (the last object in the array is the topmost window).
Or you pick the current window from the fromView.window property?
I'm using custom transitions to display a fullscreen modal game view. When the user starts a game, the root view controller scales down "into" the screen, while the fullscreen game view controller zooms from a larger scale down onto the display while transitioning from 0% opacity to 100% opacity. Simple!
The transition looks great and works fine, also correctly reversing the animation when dismissing the game view controller.
The issue I'm having is that if the device is rotated while the fullscreen game view controller is displayed, on returning to the root view controller, the layout is all screwy. And further rotation doesn't fix the problem, the layout is screwy and stays screwy.
If I disable the use of a custom transition, this problem is gone. Also, if I keep the custom transition, but disable calls setting a CATransform3D on my source and destination views in the transition animation, the problem goes away again.
Here's my transitioning delegate:
class FullscreenModalTransitionManager: NSObject, UIViewControllerAnimatedTransitioning, UIViewControllerTransitioningDelegate {
private var presenting:Bool = true
// MARK: UIViewControllerAnimatedTransitioning protocol methods
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
// get reference to our fromView, toView and the container view that we should perform the transition in
let container = transitionContext.containerView()
let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey)!
let toView = transitionContext.viewForKey(UITransitionContextToViewKey)!
let scale:CGFloat = 1.075
//let bigScale = CGAffineTransformMakeScale(scale, scale)
//let smallScale = CGAffineTransformMakeScale(1/scale,1/scale)
let bigScale = CATransform3DMakeScale(scale, scale, 1)
let smallScale = CATransform3DMakeScale(1/scale, 1/scale, 1)
let smallOpacity:CGFloat = 0.5
let presenting = self.presenting
if presenting {
// when presenting, incoming view must be on top
container.addSubview(fromView)
container.addSubview(toView)
toView.layer.transform = bigScale
toView.opaque = false
toView.alpha = 0
fromView.layer.transform = CATransform3DIdentity
fromView.opaque = false
fromView.alpha = 1
} else {
// when !presenting, outgoing view must be on top
container.addSubview(toView)
container.addSubview(fromView)
toView.layer.transform = smallScale
toView.opaque = false
toView.alpha = smallOpacity
fromView.layer.transform = CATransform3DIdentity
fromView.opaque = false
fromView.alpha = 1
}
let duration = self.transitionDuration(transitionContext)
UIView.animateWithDuration(duration,
delay: 0.0,
usingSpringWithDamping: 0.7,
initialSpringVelocity: 0,
options: nil,
animations: {
if presenting {
fromView.layer.transform = smallScale
fromView.alpha = smallOpacity
} else {
fromView.layer.transform = bigScale
fromView.alpha = 0
}
},
completion: nil )
UIView.animateWithDuration(duration,
delay: duration/6,
usingSpringWithDamping: 0.7,
initialSpringVelocity: 0.5,
options: nil,
animations: {
toView.layer.transform = CATransform3DIdentity
toView.alpha = 1
},
completion: { finished in
transitionContext.completeTransition(true)
})
}
func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {
return 0.5
}
// MARK: UIViewControllerTransitioningDelegate protocol methods
func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
self.presenting = true
return self
}
func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
self.presenting = false
return self
}
}
And, for visual reference, here's what my root view controller normally looks like:
And, for visual reference, here's what my root view controller looks like if I rotate my device (at least once) while in the game view, and return to the root view controller.
And a final note - just in case it rings any bells - I'm using autolayout and size classes to lay out my root view controller.
Thanks,
I had a similar problem, I had written a custom transition for push and pop on a navigation controller. If you rotated the device after you did a push, when you popped back to the root view controller it's frame would be unchanged.
Problem
Solution
Objective C
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
// ViewController Reference
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
// Fix layout bug in iOS 9+
toViewController.view.frame = [transitionContext finalFrameForViewController:toViewController];
// The rest of your code ...
}
Swift 3.0
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
// ViewController reference
let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
// Fix layout bug in iOS 9+
toViewController.view.frame = transitionContext.finalFrame(for: toViewController)
// The rest of your code ...
}
I accidentally figured this out, while doing some manual layout code on views with non-identity transforms. Turns out if your view has a non-identity transform, normal layout code fails.
The fix, in my transitioning delegate was to take the transited-out view, and in the animation complete callback set its transform to identity (since that view's invisible at the end of the animation, and behind the new view, this has no effect on the appearance)
UIView.animateWithDuration(duration,
delay: 0.0,
usingSpringWithDamping: 0.7,
initialSpringVelocity: 0,
options: nil,
animations: {
if presenting {
fromView.transform = smallScale
fromView.alpha = smallOpacity
} else {
fromView.transform = bigScale
fromView.alpha = 0
}
},
completion: { completed in
// set transform of now hidden view to identity to prevent breakage during rotation
fromView.transform = CGAffineTransformIdentity
})
Finally I found solution for this issue. You need to improve #agilityvision code. You need to add BOOL value, something like closeVCNow that indicates you want to close VC, and in animateTransition:, in animation block do that:
if (self.closeVCNow) {
toVC.view.transform = CGAffineTransformIdentity;
toVC.view.frame = [transitionContext finalFrameForViewController:toVC];
self.closeVCNow = NO;
}