Custom Flip Segue in Swift
As of iOS 7, we generally don't animate transitions using a custom segue. We'd either use a standard modal presentation, specifying a modalTransitionStyle
(i.e. a fixed list of a few animations we can pick for our modal transitions), or you'd implement custom animation transitions. Both of those are described below:
If you are simply presenting another view controller's view, the simple solution for changing the animation to a flip is by setting the
modalTransitionStyle
in the destination view controller. You can do this entirely in Interface Builder under the segue's properties.If you want to do it programmatically, in the destination controller you could do the following in Swift 3:
override func viewDidLoad() {
super.viewDidLoad()
modalTransitionStyle = .flipHorizontal // use `.FlipHorizontal` in Swift 2
}Then, when you call
show
/showViewController
orpresent
/presentViewController
, and your presentation will be performed with horizontal flip. And, when you dismiss the view controller, the animation is reversed automatically for you.If you need more control, in iOS 7 and later, you would use custom animation transitions, in which you'd specify a
modalPresentationStyle
of.custom
. For example, in Swift 3:class SecondViewController: UIViewController {
let customTransitionDelegate = TransitioningDelegate()
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
modalPresentationStyle = .custom // use `.Custom` in Swift 2
transitioningDelegate = customTransitionDelegate
}
...
}That specifies the
UIViewControllerTransitioningDelegate
that would instantiate the animation controller. For example, in Swift 3:class TransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return AnimationController(transitionType: .presenting)
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return AnimationController(transitionType: .dismissing)
}
}And the animation controller would simply do
.transitionFlipFromRight
is a presentation, or.transitionFlipFromLeft
if dismissing in Swift 3:class AnimationController: NSObject, UIViewControllerAnimatedTransitioning {
enum TransitionType {
case presenting
case dismissing
}
var animationTransitionOptions: UIViewAnimationOptions
init(transitionType: TransitionType) {
switch transitionType {
case .presenting:
animationTransitionOptions = .transitionFlipFromRight
case .dismissing:
animationTransitionOptions = .transitionFlipFromLeft
}
super.init()
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
//let inView = transitionContext.containerView
let toView = transitionContext.viewController(forKey: .to)?.view
let fromView = transitionContext.viewController(forKey: .from)?.view
UIView.transition(from: fromView!, to: toView!, duration: transitionDuration(using: transitionContext), options: animationTransitionOptions.union(.curveEaseInOut)) { finished in
transitionContext.completeTransition(true)
}
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 1.0
}
}For more information on the custom transitions introduced in iOS 7, see WWDC 2013 video Custom Transitions Using View Controllers.
If should be acknowledged that the above
AnimationController
is actually a over-simplification, because we're usingtransform(from:to:...)
. That results in an animation that isn't cancelable (in case you're using interactive transition). It's also removing the "from" view itself, and as of iOS 8, that's now really the job of the presentation controller.So, you really want to do the flip animation using
UIView.animate
API. I apologize because the following involves using some unintuitiveCATransform3D
in key frame animations, but it results in a flip animation that can then be subjected to cancelable interactive transitions.So, in Swift 3:
class AnimationController: NSObject, UIViewControllerAnimatedTransitioning {
enum TransitionType {
case presenting
case dismissing
}
let transitionType: TransitionType
init(transitionType: TransitionType) {
self.transitionType = transitionType
super.init()
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let inView = transitionContext.containerView
let toView = transitionContext.view(forKey: .to)!
let fromView = transitionContext.view(forKey: .from)!
var frame = inView.bounds
func flipTransform(angle: CGFloat, offset: CGFloat = 0) -> CATransform3D {
var transform = CATransform3DMakeTranslation(offset, 0, 0)
transform.m34 = -1.0 / 1600
transform = CATransform3DRotate(transform, angle, 0, 1, 0)
return transform
}
toView.frame = inView.bounds
toView.alpha = 0
let transformFromStart: CATransform3D
let transformFromEnd: CATransform3D
let transformFromMiddle: CATransform3D
let transformToStart: CATransform3D
let transformToMiddle: CATransform3D
let transformToEnd: CATransform3D
switch transitionType {
case .presenting:
transformFromStart = flipTransform(angle: 0, offset: inView.bounds.size.width / 2)
transformFromEnd = flipTransform(angle: -.pi, offset: inView.bounds.size.width / 2)
transformFromMiddle = flipTransform(angle: -.pi / 2)
transformToStart = flipTransform(angle: .pi, offset: -inView.bounds.size.width / 2)
transformToMiddle = flipTransform(angle: .pi / 2)
transformToEnd = flipTransform(angle: 0, offset: -inView.bounds.size.width / 2)
toView.layer.anchorPoint = CGPoint(x: 0, y: 0.5)
fromView.layer.anchorPoint = CGPoint(x: 1, y: 0.5)
case .dismissing:
transformFromStart = flipTransform(angle: 0, offset: -inView.bounds.size.width / 2)
transformFromEnd = flipTransform(angle: .pi, offset: -inView.bounds.size.width / 2)
transformFromMiddle = flipTransform(angle: .pi / 2)
transformToStart = flipTransform(angle: -.pi, offset: inView.bounds.size.width / 2)
transformToMiddle = flipTransform(angle: -.pi / 2)
transformToEnd = flipTransform(angle: 0, offset: inView.bounds.size.width / 2)
toView.layer.anchorPoint = CGPoint(x: 1, y: 0.5)
fromView.layer.anchorPoint = CGPoint(x: 0, y: 0.5)
}
toView.layer.transform = transformToStart
fromView.layer.transform = transformFromStart
inView.addSubview(toView)
UIView.animateKeyframes(withDuration: self.transitionDuration(using: transitionContext), delay: 0, options: [], animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.0) {
toView.alpha = 0
fromView.alpha = 1
}
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.5) {
toView.layer.transform = transformToMiddle
fromView.layer.transform = transformFromMiddle
}
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.0) {
toView.alpha = 1
fromView.alpha = 0
}
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.5) {
toView.layer.transform = transformToEnd
fromView.layer.transform = transformFromEnd
}
}, completion: { finished in
toView.layer.transform = CATransform3DIdentity
fromView.layer.transform = CATransform3DIdentity
toView.layer.anchorPoint = CGPoint(x: 0.5, y: 0.5)
fromView.layer.anchorPoint = CGPoint(x: 0.5, y: 0.5)
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 1.0
}
}FYI, iOS 8 extends the custom transition model with presentation controllers. For more information, see WWDC 2014 video A Look Inside Presentation Controllers.
Anyway, if, at the end of the transition, the "from" view is no longer visible, you'd instruct your presentation controller to remove it from the view hierarchy, e.g.:
class PresentationController: UIPresentationController {
override var shouldRemovePresentersView: Bool { return true }
}And, obviously, you have to inform your
TransitioningDelegate
of this presentation controller:class TransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate {
...
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
return PresentationController(presentedViewController: presented, presenting: presenting)
}
}
This answer has been updated for Swift 3. Please refer to the previous revision of this answer if you want to see the Swift 2 implementation.
How to create a custom flip horizontally push segue like the one that's used for modal segues?
I have done this several month ago.
1.Customize your transition. For example this is Push(so as Pop):
class BWFlipTransionPush: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
return 0.5
}
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let fromVC = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)
let toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)! // as! UBPasswordLoginViewController
let container = transitionContext.containerView()
container!.addSubview(toVC.view)
container!.bringSubviewToFront(fromVC!.view)
//改变m34
var transfrom = CATransform3DIdentity
transfrom.m34 = -0.002
container!.layer.sublayerTransform = transfrom
//设置anrchPoint 和 position
let initalFrame = transitionContext.initialFrameForViewController(fromVC!)
toVC.view.frame = initalFrame
fromVC!.view.frame = initalFrame
toVC.view.layer.transform = CATransform3DMakeRotation(CGFloat(M_PI_2), 0, 1, 0)
//动画
UIView.animateWithDuration(transitionDuration(transitionContext), delay: 0, options: UIViewAnimationOptions.CurveEaseOut, animations: { () -> Void in
fromVC!.view.layer.transform = CATransform3DMakeRotation(CGFloat(-M_PI_2), 0, 1, 0)
}) { (finished: Bool) -> Void in
container?.bringSubviewToFront(toVC.view)
UIView.animateWithDuration(self.transitionDuration(transitionContext), delay: 0, options: UIViewAnimationOptions.CurveEaseOut, animations: { () -> Void in
toVC.view.layer.transform = CATransform3DIdentity
}) { (finished: Bool) -> Void in
fromVC!.view.layer.transform = CATransform3DIdentity
transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
}
}
}
}
2.Set delegate for your navigation controller like this:
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.delegate = self
}
3.Implement delegate function:
func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
switch operation {
case .Pop:
return nil // you should return customized pop
case .Push:
return BWFlipTransionPush()
default:
return nil
}
}
Update
Here is flip pop:
class BWFlipTransionPop: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
return 0.5
}
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let fromVC = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)! // as! UBPasswordLoginViewController
let toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)
let container = transitionContext.containerView()
container!.addSubview(toVC!.view)
//改变m34
var transfrom = CATransform3DIdentity
transfrom.m34 = -0.002
container!.layer.sublayerTransform = transfrom
//设置anrchPoint 和 position
let initalFrame = transitionContext.initialFrameForViewController(fromVC)
toVC!.view.frame = initalFrame
toVC!.view.layer.transform = CATransform3DMakeRotation(CGFloat(-M_PI_2), 0, 1, 0)
//动画
UIView.animateWithDuration(transitionDuration(transitionContext), delay: 0, options: UIViewAnimationOptions.CurveEaseOut, animations: { () -> Void in
fromVC.view.layer.transform = CATransform3DMakeRotation(CGFloat(M_PI_2), 0, 1, 0)
}) { (finished: Bool) -> Void in
container?.bringSubviewToFront(toVC!.view)
UIView.animateWithDuration(self.transitionDuration(transitionContext), delay: 0, options: UIViewAnimationOptions.CurveEaseOut, animations: { () -> Void in
toVC!.view.layer.transform = CATransform3DIdentity
}) { (finished: Bool) -> Void in
fromVC.view.layer.transform = CATransform3DIdentity
transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
}
}
}
}
How to perform custom animation using segues?
Ok, I finally found all the answers here:
http://www.appcoda.com/custom-segue-animations/
Creating a custom segue is not difficult at all, as it consists of a standard programming technique in iOS. What is actually only required, is to subclass the UIStoryboardSegue class and override just one method, named perform. In this method the custom animation logic must be implemented. Triggering the transition from a view controller to another and back must also be programmatically performed by the developers, but this is a totally standard procedure.
Horizontal flip direction in iOS Storyboards
I encountered the same problem, and hopefully found a solution.
I used a custom segue to flip the way back. Here is the code of my FlipLeftSegue
@implementation FlipLeftSegue
- (void)perform
{
UIViewController *src = (UIViewController *) self.sourceViewController;
UIViewController *dst = (UIViewController *) self.destinationViewController;
[UIView beginAnimations:@"LeftFlip" context:nil];
[UIView setAnimationDuration:0.8];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:src.view.superview cache:YES];
[UIView commitAnimations];
[src presentViewController:dst animated:NO completion:nil];
}
@end
The only problem I found to this solution, is that the viewDidAppear
method is called immediatly when the animation starts. Whereas with normal segue, it is called after the animation.
Slide Segue in Swift
I test with the following modification, and it work well.
func valForDirection() -> Int {
return direction == Direction.Left ? 1 : -1
}
If sliding to left, the destView's originX should be it's width. else -width.
Related Topics
How to Link a .Sks File to a .Swift File in Spritekit
Firebase Storage Downloadurl()' Is Deprecated: Use 'Storagereference.Downloadurlwithcompletion()
Swiftui - Two Buttons in a List
How to Copy a Struct and Modify One of Its Properties at the Same Time
Protocol Extension on an Objc Protocol
How to Remove Spaces from a String in Swift
How to Line Break Long Large Title in iOS 11
Swift - Seeding Arc4Random_Uniform? or Alternative
Rotate an Object in Its Direction of Motion
Method' Is Ambiguous for Type Lookup in This Context, Error in Alamofire
Passing Data from Tableview to Viewcontroller in Swift
Moving Node on Top of a Moving Platform
Swift Setting Badge Value for Uitabbaritem
What's the Difference Between If Nil != Optional … and If Let _ = Optional …
How to Create a Window with Transparent Background with Swift on Osx