I've written a custom segue to get a fade effect, which i'm trying to achieve by inserting the destination view controller below the source view controller and animating the alpha of the source view controller to zero.
However, adding the destination view controller below the source seems to cancel the animation and the segue performs as if it is a regular present with animation disabled.
import UIKit
class FadeSegue: UIStoryboardSegue {
override func perform() {
// Get the view of the source
let sourceViewControllerView = self.sourceViewController.view
// Get the view of the destination
let destinationViewControllerView = self.destinationViewController.view
let screenWidth = UIScreen.mainScreen().bounds.size.width
let screenHeight = UIScreen.mainScreen().bounds.size.height
// Make the destination view the size of the screen
destinationViewControllerView.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight)
if let window = UIApplication.sharedApplication().keyWindow {
// Insert destination below the source
// Without this line the animation works but the transition is not smooth as it jumps from white to the new view controller
window.insertSubview(destinationViewControllerView, belowSubview: sourceViewControllerView)
// Animate the fade, remove the destination view on completion and present the full view controller
UIView.animateWithDuration(0.4, animations: {
sourceViewControllerView.alpha = 0.0
}, completion: { (finished) in
destinationViewControllerView.removeFromSuperview()
self.sourceViewController.presentViewController(self.destinationViewController, animated: false, completion: nil)
})
}
}
}
Hello I think that what you need is this
override func perform() {
// Get the view of the source
let sourceViewControllerView = self.sourceViewController.view
// Get the view of the destination
let destinationViewControllerView = self.destinationViewController.view
let screenWidth = UIScreen.mainScreen().bounds.size.width
let screenHeight = UIScreen.mainScreen().bounds.size.height
// Make the destination view the size of the screen
destinationViewControllerView.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight)
// Insert destination below the source
// Without this line the animation works but the transition is not smooth as it jumps from white to the new view controller
destinationViewControllerView.alpha = 0;
sourceViewControllerView.addSubview(destinationViewControllerView);
// Animate the fade, remove the destination view on completion and present the full view controller
UIView.animateWithDuration(10, animations: {
destinationViewControllerView.alpha = 1;
}, completion: { (finished) in
destinationViewControllerView.removeFromSuperview()
self.sourceViewController.presentViewController(self.destinationViewController, animated: false, completion: nil)
})
}
}
I Hope this help you
The slightly modified Swift 4.1 compatible solution based on the answer from #reinier-melian
/// Performs a simple fade between source and destination view controller.
class Fade: UIStoryboardSegue {
override func perform() {
guard let destinationView = self.destination.view else {
// Fallback to no fading
self.source.present(self.destination, animated: false, completion: nil)
return
}
destinationView.alpha = 0
self.source.view?.addSubview(destinationView)
UIView.animate(withDuration: CATransaction.animationDuration(), animations: {
destinationView.alpha = 1
}, completion: { _ in
self.source.present(self.destination, animated: false, completion: nil)
})
}
}
This is what I ended up doing for my purposes. It puts a placeholder view with basic ui elements over the top once all the on screen things have animated out and fades in the placeholder, then presents the destination view controller and removes the placeholder. The destination view controller then animates in all its elements, essentially making the transition invisible and looks like one single animation:
import UIKit
class FadeSegue: UIStoryboardSegue {
var placeholderView: UIViewController?
override func perform() {
let screenWidth = UIScreen.mainScreen().bounds.size.width
let screenHeight = UIScreen.mainScreen().bounds.size.height
if let placeholder = placeholderView {
placeholder.view.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight)
placeholder.view.alpha = 0
sourceViewController.view.addSubview(placeholder.view)
UIView.animateWithDuration(0.4, animations: {
placeholder.view.alpha = 1
}, completion: { (finished) in
self.sourceViewController.presentViewController(self.destinationViewController, animated: false, completion: {
placeholder.view.removeFromSuperview()
})
})
} else {
self.destinationViewController.view.alpha = 0.0
self.sourceViewController.presentViewController(self.destinationViewController, animated: false, completion: {
UIView.animateWithDuration(0.4, animations: {
self.destinationViewController.view.alpha = 1.0
})
})
}
}
}
and the unwind:
import UIKit
class FadeSegueUnwind: UIStoryboardSegue {
var placeholderView: UIViewController?
override func perform() {
let screenWidth = UIScreen.mainScreen().bounds.size.width
let screenHeight = UIScreen.mainScreen().bounds.size.height
if let placeholder = placeholderView {
placeholder.view.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight)
placeholder.view.alpha = 0
sourceViewController.view.addSubview(placeholder.view)
UIView.animateWithDuration(0.4, animations: {
placeholder.view.alpha = 1
}, completion: { (finished) in
self.sourceViewController.dismissViewControllerAnimated(false, completion: nil)
})
} else {
self.destinationViewController.view.alpha = 0.0
self.sourceViewController.dismissViewControllerAnimated(false, completion: {
UIView.animateWithDuration(0.4, animations: {
self.destinationViewController.view.alpha = 0.0
})
})
}
}
}
Related
I have got this custom segue which resets root view controller with a custom transition.
class ResetRootViewControllerSegue: UIStoryboardSegue {
override func perform() {
let src = source
let dst = destination
src.view.superview?.insertSubview(dst.view, aboveSubview: src.view)
dst.view.transform = CGAffineTransform(translationX: src.view.frame.size.width, y: 0)
UIView.animate(withDuration: 0.25,
delay: 0.0,
options: .curveEaseInOut,
animations: {
dst.view.transform = CGAffineTransform(translationX: 0, y: 0)
},
completion: { _ in
src.present(dst, animated: false, completion: {
UIApplication.shared.keyWindow?.rootViewController = dst
})
}
)
}
}
Root ViewController is a TabbarViewController which incapsulates navigation bars in each tab.
The problem.
When I execute this code only in the first navigation bar is shown. The other navigation bar titles are empty.
This code without animation doesn't work too
class ResetRootViewControllerSegue: UIStoryboardSegue {
override func perform() {
let src = source
let dst = destination
src.view.superview?.insertSubview(dst.view, aboveSubview: src.view)
dst.view.transform = CGAffineTransform(translationX: src.view.frame.size.width, y: 0)
src.present(dst, animated: false, completion: {
UIApplication.shared.keyWindow?.rootViewController = dst
})
}
}
But when I use this class without animation all works fine.
class ResetRootViewControllerSegue: UIStoryboardSegue {
override func perform() {
let src = source
let dst = destination
UIApplication.shared.keyWindow?.rootViewController = dst
}
}
this simple animation works too
class ResetRootViewControllerSegue: UIStoryboardSegue {
override func perform() {
let src = source
let dst = destination
UIApplication.shared.keyWindow?.rootViewController = dst
UIView.transition(with: (UIApplication.shared.delegate!).window!!,
duration: 4.0,
options: .transitionCrossDissolve,
animations: { UIApplication.shared.keyWindow?.rootViewController = dst },
completion: nil)
}
}
Any ideas how can I implement my custom transition animation?
I am attempting to segue by having the source view controller go from the top to bottom. I can get the transition to go from top to bottom, but as the transition is happening, there is a black rectangle that goes from top to bottom. And once that's finished, the destination view controller pops up. Anybody know how to solve this problem? I am using this tutorial: http://www.appcoda.com/custom-segue-animations/ and changing bits and piece of it to do what I want it to do.
import UIKit
class FirstCustomSegue: UIStoryboardSegue {
override func perform() {
var firstVCView = self.sourceViewController.view as UIView!
var secondVCView = self.destinationViewController.view as UIView!
// Get the screen width and height.
let screenWidth = UIScreen.mainScreen().bounds.size.width
let screenHeight = UIScreen.mainScreen().bounds.size.height
// Specify the initial position of the destination view.
secondVCView.frame = CGRectMake(0.0, 0, screenWidth, screenHeight)
// Access the app's key window and insert the destination view above the current (source) one.
let window = UIApplication.sharedApplication().keyWindow
window?.insertSubview(secondVCView, aboveSubview: firstVCView)
// Animate the transition.
UIView.animateWithDuration(2, animations: { () -> Void in
firstVCView.frame = CGRectOffset(firstVCView.frame, 0.0, screenHeight)
secondVCView.frame = CGRectOffset(secondVCView.frame, 0.0, screenHeight)
}) { (Finished) -> Void in
self.sourceViewController.presentViewController(self.destinationViewController as! UIViewController,
animated: false,
completion: nil)
}
}
}
Update your code like this, Is it working correctly now ?
secondVCView.frame = CGRectMake(0.0, -screenHeight, screenWidth, screenHeight)
For those who are looking for the full solution in Swift 4:
override func perform() {
let firstVCView = self.source.view
let secondVCView = self.destination.view
// Get the screen width and height.
let screenWidth = UIScreen.main.bounds.size.width
let screenHeight = UIScreen.main.bounds.size.height
// Specify the initial position of the destination view.
secondVCView!.frame = CGRect(x: 0, y: -screenHeight, width: screenWidth, height: screenHeight)
// Access the app's key window and insert the destination view above the current (source) one.
let window = UIApplication.shared.keyWindow
window?.insertSubview(secondVCView!, aboveSubview: firstVCView!)
// Animate the transition.
UIView.animate(withDuration: 1, animations: { () -> Void in
firstVCView?.frame = (firstVCView?.frame.offsetBy(dx: 0, dy: screenHeight))!
secondVCView?.frame = (secondVCView?.frame.offsetBy(dx: 0, dy: screenHeight))!
}) { (finished) -> Void in
self.source.present(self.destination, animated: false, completion: nil)
}
}
I want to "slide out" my view controller above the destinationViewController with a segue. This is my code:
override func perform()
{
let src = self.sourceViewController
let dst = self.destinationViewController
dst.view.superview?.insertSubview(src.view, aboveSubview: dst.view)
src.view.transform = CGAffineTransformMakeTranslation(0, 0)
UIView.animateWithDuration(0.25,
delay: 0.0,
options: UIViewAnimationOptions.CurveEaseInOut,
animations: {
src.view.transform = CGAffineTransformMakeTranslation(src.view.frame.size.width * -1, 0)
},
completion: { finished in
src.presentViewController(dst, animated: false, completion: nil)
}
)
}
The slide looks good, however the destinationViewController is all black during the animation, when the animation is completed the destinationViewController is shown correctly.
How can I make it display during segue animation?
EDIT:
solved it:
override func perform()
{
let src = self.sourceViewController
let dst = self.destinationViewController
src.view.superview?.insertSubview(dst.view, belowSubview: src.view)
dst.view.transform = CGAffineTransformMakeTranslation(0, 0)
UIView.animateWithDuration(0.25,
delay: 0.0,
options: UIViewAnimationOptions.CurveEaseInOut,
animations: {
src.view.transform = CGAffineTransformMakeTranslation(src.view.frame.size.width * -1, 0)
},
completion: { finished in
src.presentViewController(dst, animated: false, completion: nil)
}
)
}
Try this:
class FirstCustomSegue: UIStoryboardSegue {
override func perform() {
// Assign the source and destination views to local variables.
var firstVCView = self.sourceViewController.view as UIView!
var secondVCView = self.destinationViewController.view as UIView!
// Get the screen width and height.
let screenWidth = UIScreen.mainScreen().bounds.size.width
let screenHeight = UIScreen.mainScreen().bounds.size.height
// Specify the initial position of the destination view.
secondVCView.frame = CGRectMake(screenWidth, 0.0, screenWidth, screenHeight)
// Access the app's key window and insert the destination view above the current (source) one.
let window = UIApplication.sharedApplication().keyWindow
window?.insertSubview(secondVCView, aboveSubview: firstVCView)
// Animate the transition.
UIView.animateWithDuration(0.4, animations: { () -> Void in
firstVCView.frame = CGRectOffset(firstVCView.frame, -screenWidth, -0.0)
secondVCView.frame = CGRectOffset(secondVCView.frame, -screenWidth, -0.0)
}) { (Finished) -> Void in
self.sourceViewController.presentViewController(self.destinationViewController as! UIViewController,
animated: false,
completion: nil)
}
}
}
Try to add a Storyboard ID to your ViewController.
I have the following swift code and what i am trying to achieve is a segue that slides off the top. I want the secondVCView to be below the firstVCView and for the firstVCView to slide off displaying the secondVCView.
Currently there is no animation and it just flashes to the secondVCView.
Many Thanks
import UIKit
class TopToBottomSegue: UIStoryboardSegue {
override func perform() {
// Assign the source and destination views to local variables.
var firstVCView = self.sourceViewController.view as UIView!
var secondVCView = self.destinationViewController.view as UIView!
// Get the screen width and height.
let screenWidth = UIScreen.mainScreen().bounds.size.width
let screenHeight = UIScreen.mainScreen().bounds.size.height
// Specify the initial position of the destination view.
secondVCView.frame = CGRectMake(0, 0, screenWidth, screenHeight)
// Access the app's key window and insert the destination view below the current (source) one.
let window = UIApplication.sharedApplication().keyWindow
window?.insertSubview(secondVCView, belowSubview: firstVCView)
// Animate the transition.
UIView.animateWithDuration(0.4, animations: { () -> Void in
firstVCView.frame = CGRectOffset(firstVCView.frame, 0.0, screenHeight)
//secondVCView.frame = CGRectOffset(secondVCView.frame, -screenWidth, 0.0)
}) { (Finished) -> Void in
self.sourceViewController.presentViewController(self.destinationViewController as! UIViewController,
animated: false,
completion: nil)
}
}
}
As far as I know you need to call layoutIfNeed inside animateWithduration block in order to get a smooth animation.
Greetings
Im not sure if this is the correct way to do this but i managed to get it working by inserting both views.
L
--
import UIKit
class TopToBottomSegue: UIStoryboardSegue {
override func perform() {
// Assign the source and destination views to local variables.
let firstVCView = self.sourceViewController.view as UIView!
let secondVCView = self.destinationViewController.view as UIView!
// Get the screen width, height and status bar height.
let screenWidth = UIScreen.mainScreen().bounds.size.width
let screenHeight = UIScreen.mainScreen().bounds.size.height
let statusBarHeight = self.statusBarHeight()
// Specify the initial position of both views.
secondVCView.frame = CGRectMake(0.0, 0.0, screenWidth, screenHeight)
firstVCView.frame = CGRectMake(0.0, statusBarHeight, screenWidth, screenHeight)
// Access the app's key window and insert the destination view below the current (source) one.
let window = UIApplication.sharedApplication().keyWindow
window?.insertSubview(firstVCView, aboveSubview: secondVCView)
window?.insertSubview(secondVCView, belowSubview: firstVCView)
// Animate the transition.
UIView.animateWithDuration(0.3, animations: { () -> Void in
firstVCView.frame = CGRectOffset(firstVCView.frame, 0.0, screenHeight)
}) { (Finished) -> Void in
self.sourceViewController.presentViewController(self.destinationViewController as UIViewController,
animated: false,
completion: nil)
}
}
func statusBarHeight() -> CGFloat {
let statusBarSize = UIApplication.sharedApplication().statusBarFrame.size
return Swift.min(statusBarSize.width, statusBarSize.height)
}
}
I'm trying to make custom segue between two view controllers without any animation. Everything works as I expecting just console giving me following message:
2015-06-16 12:19:01.537 prototyp_2[42453:9048104] Unbalanced calls to
begin/end appearance transitions for .
Here is my code:
class Segue_forward: UIStoryboardSegue {
override func perform() {
// zdroj a destinace do lokálních proměnných
var firstVCView = self.sourceViewController.view as UIView!
var secondVCView = self.destinationViewController.view as UIView!
// rozměry displeje
let screenWidth = UIScreen.mainScreen().bounds.size.width
let screenHeight = UIScreen.mainScreen().bounds.size.height
// initial position of destination view
secondVCView.frame = CGRect(x: 0.0, y: 0.0, width: screenWidth, height: screenHeight)
// protože destination VC není subview okna aplikace tak to musime napravit
let window = UIApplication.sharedApplication().keyWindow
window?.insertSubview(secondVCView, aboveSubview: firstVCView)
// animace
UIView.animateWithDuration(0.0, animations: { () -> Void in
firstVCView.frame = CGRectOffset(firstVCView.frame, 0.0, 0.0)
secondVCView.frame = CGRectOffset(secondVCView.frame, 0.0, 0.0)
}) { (Finished) -> Void in
self.sourceViewController.presentViewController(self.destinationViewController as! UIViewController, animated: false, completion: nil)
}
}
}
And here is my action:
#IBAction func nextQuestion(sender: AnyObject) {
self.performSegueWithIdentifier("id_forwardSegue", sender: self)
}
Just do it without animation:
override func perform() {
self.sourceViewController.presentViewController(self.destinationViewController as! UIViewController, animated: false, completion: nil)
}
present the new UIViewController with the parentViewController:
self.sourceViewController.parentViewController?.presentViewController(YOUR_VIEW_CONTROLLER, animated: false, completion: nil)