label:
The developer of the anonymous social application Secret has developed an app called Ping, where users can push topics they are interested in.
Ping has a very cool thing, that is, the animation of switching between the main interface and it is very good. Every time I see a very dazzling animation, I can't help but think: "Do I have to implement the following by myself" Haha ~~~
In this tutorial, you will learn how to implement such cool animations with Swift. You will learn how to use shape layer, mask and use UIViewControllerAnimnatedTransitioning protocol and UIPercentDrivenInteractivetransition class to realize the animation of View Controller interface switch
However, it should be noted that it is assumed that you already have a certain Swift development foundation. If you are just a beginner, please check my other Swift tutorials.
Introduction
We mainly introduce the animation when jumping from one View Controller to another in Ping.
In iOS, you can put two View Controllers in UINavigationController and implement the UIViewControllerAnimatedTransitioning protocol to achieve the animation of interface switching. The specific details are:
Duration of animation
Create a container View to control the View of two View Controllers
Can achieve any animation you can think of
You can use the animation method of UIView to make these animations, or you can use the relatively low-level methods such as core animation. This tutorial will use the latter.
How to achieve
Now you already know where the code will probably be added. The following discusses how to realize the circle animation of the Ping. This animation is strictly described as:
The circle is generated from the button on the right. And from the circle you can see what the next layer is trying to do.
In other words, this circle is a mask. You can see all the circles, and all the outside are hidden.
You can use CALayer's mask to achieve this effect. Of course, you need to set alpha to 0 to hide the content of the next view. When the alpha value is set to 1, the content of the following view is displayed.
Now you understand masking. The next step is to decide which CAShapeLayer to use for this mask. You only need to modify the radius of the circle formed by these CAShapeLayer.
Start now
This is not a very detailed description, it is all about the steps to create and configure a project.
1. Create a new project. Select a single view application
2. Set the project name to CircleTransition. Language selection Swift. Devices choose iPhone
The project has been initially created. There is only one view controller in Main.stroyboard. But our animation requires at least two view controllers. But first you need to associate the current view controller with the UINavigationController. Select the only view controller, and then choose Editor-> Embed In-> Navigation Controller in the menu bar. After that, the navigation controller will become the initial controller, followed by the view controller that was initially generated. After that, select the navigation controller and tick "Shows navigation bar" in the fourth tab of the right menu bar. Because navigation bar is not needed in our app.
Next, add another view controller. Specify the view controller class for this view controller.
Then, for each view controller, except the navigation controller, add a button. Double-click the button to delete the text, and then set the background color of the button to black. The other button is handled in the same way. Set autolayout for these two buttons. Specify them in the upper right corner. Specify a width and height of 40 for these two buttons.
Finally, make the button round. Select "user defined runtime attributes" in the third tab of the right menu. Click the plus sign below to add the content. Set the corner radius of the button to 15.
In this way, the button is circular when it is running. After the setting is completed, this effect cannot be seen temporarily. After running:
Now we need to add some content to each view controller. First modify the background colors of these two view controllers.
Now this app is roughly formed. Different colors can represent a variety of content that you will display in the future. All it takes is to connect the two view controllers. Put down the mouse in the button of the orange controller. Press ctrl and drag the cursor to another controller. This is a pop-up menu. Connect the action of this menu to the controller again in the same way, and select show. In this way, when this button is selected, the navigation controller will be pushed to the next view controller. This is a segue. This segue will be needed in later tutorials, so I will give this segue an identifer called "PushSegue". Run the code and click the orange controller button to jump to the purple controller.
Because this is a cyclic process, from orange to purple, you need to go back from purple to orange. Complete this function now. First, add an action method to the ViewController class bound to the purple controller.
@IBAction func circleTapped (sender: UIButton) {
self.navigationController? .popViewControllerAnimated (true)
}
And add a reference to the button on the purple controller, this will be used later:
@IBOutlet weak var button: UIButton!
Then add the above @IBAction to the "touch up inside" event of the purple controller button.
Binding button properties:
Run it again and see. Orange to purple, purple to orange cycle back and forth!
Note: Both view controllers need to bind buttons and button events! Otherwise, the following animation can only be executed once!
Custom animation
The main processing here is the push and pop animation of the navigation controller. This requires us to implement the animationControllerForOperation method of the UINavigationControllerDelegate protocol. Add a new class directly in ViewController:
class NavigationControllerDelegate: NSObject, UINavigationControllerDelegate {
func navigationController (navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController)-> UIViewControllerAnimatedTransitioning? {
return nil
}
}
First, select the item Object in the menu on the right.
After that, drag this thing under the navigation controller secene.
Then select this Object and modify the class on the third tab of the right menu to the NavigationControllerDelegate we just defined.
Next, assign a delegate to the navigation controller. Select the navigation controller, and then connect the navigation controller's delegate option to the Object you just dragged in the last menu on the right:
At this time, no specific effect will appear. Because the method is still empty, it can only be regarded as a placeholder method.
func navigationController (navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController)-> UIViewControllerAnimatedTransitioning? {
return nil
}
This method accepts two controllers in the navigation controller. Two controllers that jump from one to the other. And return an object that implements UIViewControllerAnimatedTransitioning. Therefore, we need to create a class that implements the UIViewControllerAnimatedTransitioning protocol.
class CircleTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning
First add an attribute:
weak var transitionContext: UIViewControllerContextTransitioning?
This attribute will be used in the following code.
Add a method:
func transitionDuration (transitionContext: UIViewControllerContextTransitioning)-> NSTimeInterval {
return 0.5
}
This method returns the execution time of the animation.
Add animation method:
func animateTransition (transitionContext: UIViewControllerContextTransitioning) {
// 1
self.transitionContext = transitionContext
// 2
var containerView = transitionContext.containerView ()
var fromViewController = transitionContext.viewControllerForKey (UITransitionContextFromViewControllerKey) as ViewController
var toViewController = transitionContext.viewControllerForKey (UITransitionContextToViewControllerKey) as ViewController
var button = fromViewController.button
// 3
containerView.addSubview (toViewController.view)
// 4
var circleMaskPathInitial = UIBezierPath (ovalInRect: button.frame)
var extremePoint = CGPointMake (button.center.x, button.center.y-CGRectGetHeight (toViewController.view.bounds)) // need more research
var radius = sqrt (extremePoint.x * extremePoint.x + extremePoint.y * extremePoint.y)
var circleMaskPathFinal = UIBezierPath (ovalInRect: CGRectInset (button.frame, -radius, -radius))
// 5
var maskLayer = CAShapeLayer ()
maskLayer.path = circleMaskPathFinal.CGPath
toViewController.view.layer.mask = maskLayer
// 6
var maskLayerAnimation = CABasicAnimation (keyPath: "path")
maskLayerAnimation.fromValue = circleMaskPathInitial.CGPath
maskLayerAnimation.toValue = circleMaskPathFinal.CGPath
maskLayerAnimation.duration = self.transitionDuration (self.transitionContext!)
maskLayerAnimation.delegate = self
maskLayer.addAnimation (maskLayerAnimation, forKey: "CircleAnimation")
}
Step by step explanation:
The transitionContext property maintains a reference to a class member. This can be used in the following code.
Take out the button references on containerView and fromViewController and toViewController and controller. The animation mainly acts on the container view.
Add the view of toViewController to the container view.
Create two roads, one is the size of the button (the button is round after running), and the other is large enough to cover the entire screen. Animation is going back and forth on these two paths.
Create a CAShapeLayer as a mask. Assign the path value of this layer to circleMaskPathFinal, otherwise the animation may shrink back after the execution of the animation is completed.
Create a CABasicAnimation animation, the key path is "path", this animation acts on the path attribute of the layer. The animation is executed from circleMaskPathInitial to circleMaskPathFinal. And add a delegate to this animation to clean up the scene after the animation is completed.
The method of implementing animation agent:
override func animationDidStop (anim: CAAnimation !, finished flag: Bool) {
self.transitionContext? .completeTransition (! self.transitionContext! .transitionWasCancelled ())
self.transitionContext? .viewControllerForKey (UITransitionContextFromViewControllerKey) ?. view.layer.mask = nil
}
Now you can use CircleTransitionAnimator to achieve the effect of animation. Modify the code of NavigationControllerDelegate:
class NavigationControllerDelegate: NSObject, UINavigationControllerDelegate {
func navigationController (navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController)-> UIViewControllerAnimatedTransitioning? {
return CircleTransitionAnimator ()
}
}
Run it. Click the black button and the animation effect will appear.
It feels good, but this is not enough!
Add gesture response to animation
We will also add a transition to this animation that can respond to gestures. A method is needed to respond to gestures: navigationController-> interactionControllerForAnimationController. This is a method in UINavigationControllerDelegate. This method returns an object that implements the protocol UIViewControllerInteractiveTransitioning.
The iOS SDK provides a UIPercentDrivenInteractiveTransition class. This class implements the above protocol and provides many other gesture processing implementations.
Add the following properties and methods to the NavigationControllerDelegate class:
class NavigationControllerDelegate: NSObject, UINavigationControllerDelegate {
var interactionController: UIPercentDrivenInteractiveTransition?
func navigationController (navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController)-> UIViewControllerAnimatedTransitioning? {
return CircleTransitionAnimator ()
}
func navigationController (navigationController: UINavigationController, interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning)-> UIViewControllerInteractiveTransitioning? {
return self.interactionController
}
}
Since it is responsive to gestures, then a pan gesture is essential. But first, we must add some auxiliary things.
1. Add a reference to the navigation controller in NavigationControllerDelegate.
@IBOutlet weak var navigationController: UINavigationController?
Add a reference to the navigation controller to this reference,
Implement the awakeFromNib method:
override func awakeFromNib () {
super.awakeFromNib ()
var pan = UIPanGestureRecognizer (target: self, action: "panned:")
self.navigationController! .view.addGestureRecognizer (pan)
}
When the pan action occurs on the navigation controller's view, the panned callback method is triggered. Add the following code to this method:
func panned (gestureRecognizer: UIPanGestureRecognizer) {
switch gestureRecognizer.state {
case .Began:
self.interactionController = UIPercentDrivenInteractiveTransition ()
if self.navigationController? .viewControllers.count> 1 {
self.navigationController? .popViewControllerAnimated (true)
}
else {
self.navigationController? .topViewController.performSegueWithIdentifier ("PushSegue", sender: nil)
}
case .Changed:
var translation = gestureRecognizer.translationInView (self.navigationController! .view)
var completionProgress = translation.x / CGRectGetWidth (self.navigationController! .view.bounds)
self.interactionController? .updateInteractiveTransition (completionProgress)
case .Ended:
if gestureRecognizer.velocityInView (self.navigationController! .view) .x> 0 {
self.interactionController? .finishInteractiveTransition ()
}
else {
self.interactionController? .cancelInteractiveTransition ()
}
self.interactionController = nil
default:
self.interactionController? .cancelInteractiveTransition ()
self.interactionController = nil
}
}
In Begin, the pan gesture initializes the UIPercentDrivenInteractiveTransition object as soon as it is executed and assigns it as a value to the property self.interactionController.
If you set a push in the first view controller (a segue defined earlier), you set a pop in the second view controller.
When the navigation controller pushes or pops, the NavigationControllerDelegate triggers the method of returning the self.interactionController object.
Changed, in this method, the animation moves a different distance according to the distance the gesture moves. Here apple has done a lot for us.
Ended, here you will see the movement speed of the gesture. If it is positive, the transition ends, and if it is negative, it is cancelled. At the same time, set the interactionController value to nil.
default, if it is in another state, cancel trnasition directly and set the value of interactionController to nil.
Run the program and move your finger left and right on the screen to see the effect!
Swift: Superb View Controller switching animation