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.
Related
I'm trying to make a segue in my app such that the current view slides away to reveal a view below it.
This is my code:
let window = UIApplication.shared.delegate?.window!
window?.insertSubview(destinationView, belowSubview: sourceView)
destinationView.center = CGPoint(x: sourceView.center.x, y: sourceView.center.y)
UIView.animate(withDuration: 0.6, animations: {
sourceView.center = CGPoint(x: sourceView.center.x, y: 0 - sourceView.center.y)
}, completion: { (value: Bool) in
self.source.navigationController?.pushViewController(self.destination, animated: false)
})
However, the view that I'm segueing to simply appears over the top of the view I'm currently on. Any ideas on what I'm doing wrong?
Thanks!
Well, I solved it. Here was my solution in case anyone else has this problem:
window?.insertSubview(destinationView, belowSubview: sourceView)
window?.insertSubview(sourceView, aboveSubview: destinationView)
I just added that second line of code and it worked.
I have a animation that I want to be infinite
var rotateRock: Bool = true
func rotateRock() {
UIView.animate(withDuration: 2.7, delay: 2.7, options: [UIViewAnimationOptions.repeat, UIViewAnimationOptions.curveLinear], animations:
{
self.rockImg.transform.ty += 155
if self.rotateRock {
self.rotateRock = false
self.rockImg.transform = CGAffineTransform(rotationAngle: (180.0 * CGFloat(M_PI)) / 180.0)
} else {
self.rotateRock = true
self.rockImg.transform = CGAffineTransform(rotationAngle: 0)
}
},completion: nil)
}
Then I call rotateRock() inside viewDidLoad() this works, the animation never stops which is what I want.
If I now enter another VC that is presented modally then exit it so I end up in my first VC the animation is still running which I want.
But if I change VC by clicking on another tab in my tab bar and then go back the animation has stopped. Is there any way to check if the animation has stopped so that I can restart it?
You could override viewDidAppear() and call rotateRock there. That should work.
Edit:
What Matt says below is true, viewDidAppear would work but maybe it would be better viewWillAppear to make sure you don't see it before starting.
I'm trying to show a custom view when I receive a notification from parse.
This view is showed with an animation and its hidden with another animation. This view also has a uitapgesturerecognizer that needs to be fired when the user taps the view.
My problem is that when the second animation gets fired the custom view's uitapgesture doesn't work :\
Any ideas? I paste the code.
Thanks!
func doManageObjectNotification(notification: COPushNotification){
mainView = UIApplication.sharedApplication().keyWindow!
customView = CustomNotificationView()
let height = customView.calculateViewHeight()
customView.frame = CGRectMake(0, -height, mainView.frame.width, height)
customView.targetMethod = notificationWasTapped
customView.setContent(notification)
customView.alpha = 0
mainView.addSubview(customView)
UIView.animateWithDuration(0.75, delay: 0, options: [UIViewAnimationOptions.CurveEaseInOut, UIViewAnimationOptions.AllowUserInteraction], animations: { () -> Void in
// Show the view
self.customView.frame.origin.y = 0
self.customView.alpha = 1
}) { (Bool) -> Void in
UIView.animateWithDuration(0.75, delay: 5, options: [UIViewAnimationOptions.CurveEaseInOut, UIViewAnimationOptions.AllowUserInteraction], animations: {
// Hide the view
self.customView.alpha = 0
}, completion: { finished in
self.customView.removeFromSuperview()
self.customView = nil
})
}
}
Im agree with Lion answer, but I want also focus your attention about customView.frame.origin.y = 0 during animation: if you use autolayout and you try to change frame dimensions or positions instead the correct constraints involved, you can disable you constraints effect causing warnings and unexpected view dimensions and movements. When you have this issue many times the UITapGestureRecognizer stop to responding.
The best way to do it is to create IBOutlets constraints and working with it (for example):
#IBOutlet weak var customViewTopConstraint: NSLayoutConstraint
...
UIView.animateWithDuration(0.75, delay: 0, options: [UIViewAnimationOptions.CurveEaseInOut, UIViewAnimationOptions.AllowUserInteraction], animations: { () -> Void in
// Show the view
self.customViewTopConstraint.constant = 0
For handling tap during animation you should implement touchesBegan method. by this you can detect touch and then check that touch is from your view,if yes then you can perform your desired task.
Hope this will help :)
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?
This is iPad app by using SWIFT. It having two views.
1. starting_View
2. login_View
These two views are in same ViewController.
starting_View will be first view. By Clicking, NEXT button in first View, Starting View will move to left side by using animateDuration and same time, login_View will come from right. If we click Username/password fields (Any TextField), it will navigates to previous view.
Same time,,
if login_View will be first screen means, textField is working, keyboard is appearing.
But in animateDuration, i couldn't type. Kindly help me. Am using XCODE 6.1.
Code (from comment):
#IBAction func getStart_button(sender: UIButton) {
UIView.animateWithDuration(0.7, delay: 0.25, options: .CurveEaseOut, animations: {
self.clt_login_vw.frame = CGRectMake(450, 56, 574, 660)
}, completion:nil)
}
In General try to never change frames directly when using Autolayout.
The best way to move views have been for me to use CGAffineTransform.
let move = CGAffineTransformMakeTranslation(translation_X , translation_Y)
and inside your animation closure:
self.clt_login_vw.transform = move
Then to return it back just do inside the animation Closure:
self.clt_login_vw.transform = CGAffineTransformIdentity
Resume:
let moveOut = CGAffineTransformMakeTranslation(translation_X , translation_Y)
#IBAction func getStart_button(sender: UIButton) {
UIView.animateWithDuration(0.7, delay: 0.25, options: .CurveEaseOut, animations: {
[weak self] // Remember no not create cycles
self!.clt_login_vw.transform = moveOut
}, completion:nil)
}
Edit:
Im gonna add Noah Witherspoon's option.
Another way to do this is getting the constraint that is holding your view horizontally, once you get it, you change its constant and call layoutIfNeeded() inside the closure:
#IBAction func getStart_button(sender: UIButton) {
constraint.constant = newConstant // enough to make it off the screen
UIView.animateWithDuration(0.7, delay: 0.25, options: .CurveEaseOut, animations: {
[weak self] // Remember no not create cycles
self!.view.layoutIfNeeded()
}, completion:nil)
}
The way to get the specific constraint, I'm more friend of making IBOutlets, so I get easily its reference in Code.
That is if you didn't make it by code.