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.
Related
I coded a custom segue emulating a "Push Left". I takes screenshots of the 2 views, animates them from right to left, then present the destination view and remove the overlaying screenshots. I'm afraid that this ultimately results in stacking views on top of one another, which should be avoided in this case. I can't figure out how to properly dismiss the underlaying view once the animation is completed. I tried to use navigationController?.pushViewController instead of present but my attempts were not successful. How could I solve this issue ?
My custom segue :
class SeguePushLeft: UIStoryboardSegue
{
override func perform()
{
self.source.view.isUserInteractionEnabled = false
self.destination.view.isUserInteractionEnabled = false
let slideViewOut = UIImageView(image: source.view.capture()!)
let slideViewIn = UIImageView(image: destination.view.capture()!)
let screenWidth = source.view.frame.size.width
self.source.view.addSubview(slideViewIn)
self.source.view.addSubview(slideViewOut)
slideViewIn.transform = CGAffineTransform(translationX: screenWidth, y: 0)
UIView.animate(withDuration: 0.4,
delay: 0.0,
usingSpringWithDamping: 1,
initialSpringVelocity: 0,
options: UIViewAnimationOptions.curveEaseOut,
animations: {
slideViewIn.transform = CGAffineTransform.identity
slideViewOut.transform = CGAffineTransform(translationX: -screenWidth, y: 0)
}, completion: { finished in
DispatchQueue.main.async{
(self.source as UIViewController).present((self.destination as UIViewController), animated: false, completion: {
self.source.view.isUserInteractionEnabled = true
self.destination.view.isUserInteractionEnabled = true
slideViewIn.removeFromSuperview()
slideViewOut.removeFromSuperview()
})
}
})
}
}
[Please forgive my previous too-hasty answer. I'm entering another rather than editing the first, because of all the comments that now blot the first.]
The problem is simple: you're doing this wrong. What you wish to do is a present transition, but with a custom animation. That's perfectly viable, and you can certainly do it with a custom segue, but the way you're doing it is not how you do that. You do it with a custom transition animation. There is a fixed way of implementing that, and it isn't how you're going about things. You need to read up on how to write a custom transition animation for a presentation transition and you'll be all set.
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)
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;
}
I am writing a camera app, and have trouble with showing the focus square when user tap on the screen.
My code is (in swift):
self.focusView.center = sender.locationInView(self.cameraWrapper)
self.focusView.transform = CGAffineTransformMakeScale(2, 2)
self.focusView.hidden = false
UIView.animateWithDuration(0.5, animations: { [unowned self] () -> Void in
self.focusView.transform = CGAffineTransformIdentity
}, completion: { (finished) -> Void in
UIView.animateWithDuration(0.5, delay: 1.0, options: nil, animations: { () -> Void in
self.focusView.alpha = 0.0
}, completion: { (finished) -> Void in
self.focusView.hidden = true
self.focusView.alpha = 1.0
})
})
However, if use tap the screen consecutively when the previous animation does not finish, the old and new animation will mix up and the focus view will behave strangely, for example it will disappear very quick.
Could anyone tell me how to cancel previous animation, especially the previous completion block?
You can user method removeAllAnimations to stop animation
Replace your code with below
self.focusView.center = sender.locationInView(self.cameraWrapper)
self.focusView.transform = CGAffineTransformMakeScale(2, 2)
self.focusView.hidden = false
self.focusView.layer.removeAllAnimations() // <<==== Solution
UIView.animateWithDuration(0.5, animations: { [unowned self] () -> Void in
self.focusView.transform = CGAffineTransformIdentity
}, completion: { (finished) -> Void in
UIView.animateWithDuration(0.5, delay: 1.0, options: nil, animations: { () -> Void in
self.focusView.alpha = 0.0
}, completion: { (finished) -> Void in
self.focusView.hidden = true
self.focusView.alpha = 1.0
})
})
Reference : link
#Jageen solution is great, but I worked with UIStackView animation and there I needed additional steps. I have stackView with view1 and view2 inside, and one view should be visible and one hidden:
public func configureStackView(hideView1: Bool, hideView2: Bool) {
let oldHideView1 = view1.isHidden
let oldHideView2 = view2.isHidden
view1.layer.removeAllAnimations()
view2.layer.removeAllAnimations()
view.layer.removeAllAnimations()
stackView.layer.removeAllAnimations()
// after stopping animation the values are unpredictable, so set values to old
view1.isHidden = oldHideView1 // <- Solution is here
view2.isHidden = oldHideView2 // <- Solution is here
UIView.animate(withDuration: 0.3,
delay: 0.0,
usingSpringWithDamping: 0.9,
initialSpringVelocity: 1,
options: [],
animations: {
view1.isHidden = hideView1
view2.isHidden = hideView2
stackView.layoutIfNeeded()
},
completion: nil)
}
In my case, I add the focusing indicator by using addSubview().
self.view.addSubview(square)
square is the UIView I defined in the function. So when the user tap the screen to focus, this function will add a square on subview.
And to cancel this animation when the next tap happen, I just simply use the removeFromSuperview() function, when this function called it removes the view from its superview which is the focusing square here.
filterView.subviews.forEach({ $0.removeFromSuperview() })
It's different from the method above to remove the animation, but remove the subview directly.
In my case, i just needed to set the value, i animated to concrete value.
For e.g.
if ([indexPath isEqual:self.currentPlayerOrderIndexPath])
{
[UIView animateWithDuration:0.5
delay:0.0
options:UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat | UIViewAnimationOptionCurveEaseInOut
animations:^{
cell.avatarImageV.transform = CGAffineTransformMakeScale(1.15,1.15);
}
completion:NULL];
}
else
{
cell.avatarImageV.transform = CGAffineTransformMakeScale(1.0, 1.0);