神刀安全网

Creating Custom UIViewController Transitions

Creating Custom UIViewController Transitions

Time to master the transitioning API

Push, pop, cover vertically… you get some nice view controller transitions built in to iOS, but it’s great fun to make your own. Custom UIViewController transitions can add significantly to the user experience and set your app apart from the rest of the pack. If you’ve avoided making your own custom transitions based on what you’ve heard in the past, you’ll find it’s far less work than you might expect.

In this tutorial, you’ll add some custom UIViewController transitions to a small guessing game app. By the time you’re done, you’ll have picked up the following skills:

  • How the transitioning API is structured.
  • How to present and dismiss view controllers using custom transitions.
  • How to build interactive transitions.

Note: The transitions shown in this tutorial make use of UIView animations, so you’ll need to be familiar with the topic on at least a basic level. Check out our tutorial oniOS Animation for a quick introduction to the topic.

Getting Started

Download the starter project for this tutorial . Build and run the project; you’ll see the following:

Creating Custom UIViewController Transitions

Creating Custom UIViewController Transitions

Custom transitions – AND kittehs, all in the same tutorial?

The app presents several cards in a page view controller. Each card shows a description of a pet, and tapping a card reveals which pet it describes.

Your job is to guess the pet! Is it a cat, dog, or fish? Play with the app, and see how well you do.

Most of the navigation logic is already in place, but the app currently feels quite bland. You’re going to spice it up with custom transitions.

Exploring the Transitioning API

Instead of concrete objects, the transitioning API makes heavy use of protocols. By the end of this section, you’ll understand the responsibilities of each protocol and the connections between them. The diagram below shows you the main players in the API:

Creating Custom UIViewController Transitions

The Participants

Although the diagram looks complex, it will feel quite straightforward once you understand how the various parts work together.

Transitioning Delegate

Every view controller can have a transitioningDelegate that conforms to the UIViewControllerTransitioningDelegate protocol.

Whenever you present or dismiss a view controller, UIKit queries the transitioning delegate for the animation controller to use. Setting the view controller’s transitioningDelegate to an instance of your custom class lets you return your own, custom animation controllers instead of the default ones.

Animation Controller

Objects implementing the UIViewControllerAnimatedTransitioning protocol perform the transition animations.

Transitioning Context

The context object implements the UIViewControllerContextTransitioning procotol and plays a vital role in the transitioning process: it encapsulates information about the view controllers involved in the transition.

You don’t actually need to implement this protocol in your own code. Instead, your animation controller will receive a fully configured context object from UIKit whenever a transition occurs.

The Transitioning Process

Here are the steps involved in a presentation transition:

  1. You trigger the transition either programmatically or via a segue.
  2. UIKit asks the “to” view controller (the view controller to be shown) for its transitioning delegate. If it doesn’t have one, UIKIt uses the standard, built-in transition.
  3. UIKit then asks the transitioning delegate for an animation controller via animationControllerForPresentedController(_:presentingController:sourceController:) . If this returns nil , the transition will use the default animation.
  4. Once it has a valid animation controller, UIKit constructs the transitioning context.
  5. UIKit then queries the duration of the animation using transitionDuration(_:) from the animation controller.
  6. Then UIKit invokes animateTransition(_:) on the the animation controller to perform the transition.
  7. Finally, the animation controller calls the completeTransition(_:) method on the transitioning context to indicate the animation is complete.

Creating a Custom Presentation Transition

Time to put your new-found knowledge into practice!

Your goal is to implement the following animation:

  • When the user taps a card, it flips to reveal the second view scaled down to the size of the card.
  • Following the flip, the view scales to fill the whole screen.

Creating the Animator

You’ll start by creating the animation controller.

Go to File/New/File… , choose iOS/Source/Cocoa Touch Class , and click Next . Set the name to FlipPresentAnimationController , make it a subclass of NSObject and set the language to Swift . Click Next and set the Group to Animation Controllers . Click Create to make your new file.

Animation controllers need to conform to the UIViewControllerAnimatedTransitioning protocol. Open FlipPresentAnimationController.swift , and update the class declaration accordingly:

import UIKit   class FlipPresentAnimationController: NSObject, UIViewControllerAnimatedTransitioning {   }

Note that you may receive compiler errors due to missing methods; don’t panic – you’re about to fix these.

Creating Custom UIViewController Transitions

Compiler errors…don’t panic…

You’re going to use the frame of the tapped card as a starting point for the animation. Inside the body of the class, add the following new variable to hold this value:

var originFrame = CGRect.zeroRect

In order to meet the requirements for the UIViewControllerAnimatedTransitioning , you’ll need to add two methods to the class.

Add the method below inside the class body:

func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {   return 2.0 }

As the name suggests, this method specifies the duration of your transition. Setting it to two seconds will prove useful during development, as it leaves enough time to observe the animation.

Now add the following method stub to the class:

func animateTransition(transitionContext: UIViewControllerContextTransitioning) {   }

This protocol method is where you’ll implement the transition animation itself. Start off by inserting the following lines at the top of the method:

// 1 guard let fromVC = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey),     let containerView = transitionContext.containerView(),     let toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey) else {     return }   // 2 let initialFrame = originFrame let finalFrame = transitionContext.finalFrameForViewController(toVC)   // 3 let snapshot = toVC.view.snapshotViewAfterScreenUpdates(true) snapshot.frame = initialFrame snapshot.layer.cornerRadius = 25 snapshot.layer.masksToBounds = true

Here’s what’s going on above:

  1. The transitioning context will provide the view controllers and views participating in the transition. You use the appropriate keys to obtain them.
  2. You next specify the starting and final frames for the “to” view. In this case, the transition starts from the card’s frame and scales to fill the whole screen.
  3. UIView snapshotting captures the “to” view and renders it into a lightweight view; this lets you animate the view together with its hierarchy. The snapshot’s frame starts off as the card’s frame. You also modify the corner radius to match the card.

Continue by adding the following lines to the method body:

containerView.addSubview(toVC.view) containerView.addSubview(snapshot) toVC.view.hidden = true   AnimationHelper.perspectiveTransformForContainerView(containerView) snapshot.layer.transform = AnimationHelper.yRotation(M_PI_2)

A new player appears: the container view. Think of this as the dance floor upon which your transition shakes its stuff. The container view already contains the “from” view, but it’s your responsibility to add the “to” view.

You also add the snapshot view to the container and hide the real view for now. The completed animation will rotate the snapshot out of view and hide it from the user.

Note: Don’t let AnimationHelper confuse you. It’s a small utility class, responsible for adding perspective and rotation transforms to your views. Feel free to have a look at the implementation.

At this point, you have all the necessary parts in place to perform the animation. Add the final lines of code to the bottom of the method:

// 1 let duration = transitionDuration(transitionContext)   UIView.animateKeyframesWithDuration(   duration,   delay: 0,   options: .CalculationModeCubic,   animations: {     // 2     UIView.addKeyframeWithRelativeStartTime(0.0, relativeDuration: 1/3, animations: {       fromVC.view.layer.transform = AnimationHelper.yRotation(-M_PI_2)     })       // 3     UIView.addKeyframeWithRelativeStartTime(1/3, relativeDuration: 1/3, animations: {       snapshot.layer.transform = AnimationHelper.yRotation(0.0)     })       // 4     UIView.addKeyframeWithRelativeStartTime(2/3, relativeDuration: 1/3, animations: {       snapshot.frame = finalFrame     })   },   completion: { _ in     // 5     toVC.view.hidden = false     fromVC.view.layer.transform = AnimationHelper.yRotation(0.0)     snapshot.removeFromSuperview()     transitionContext.completeTransition(!transitionContext.transitionWasCancelled())   })

Taking each commented section in turn:

  1. First, you specify the duration of the animation. Notice the use of the transitionDuration(_:) method, implemented at the top of this class. You need the duration of your animations to match up with the duration you’ve declared for the whole transition so UIKit can keep things in sync.
  2. You start by rotating the “from” view halfway around its y-axis to hide it from view.
  3. Next, you reveal the snapshot using the same technique.
  4. Then you set the frame of the snapshot to fill the screen.
  5. Finally, it’s safe to reveal the real “to” view. You remove the snapshot since it’s no longer useful. Then you rotate the “from” view back in place; otherwise, it would hidden when transitioning back. Calling completeTransition informs the transitioning context that the animation is complete. UIKit will ensure the final state is consistent and remove the “from” view from the container.

You’re now ready to use your animation controller!

Wiring Up the Animator

Open CardViewController.swift and declare the following property for the class:

private let flipPresentAnimationController = FlipPresentAnimationController()

UIKit expects a transitioning delegate to vend the animation controller for a transition. To do this, you must first provide an object which conforms to UIViewControllerTransitioningDelegate .

In this example, CardViewController will act as a transitioning delegate. Add the following class extension to the bottom of the source file to make this class conform to UIViewControllerTransitioningDelegate :

extension CardViewController: UIViewControllerTransitioningDelegate {   }

Next, add the following method just inside the class extension:

func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {     flipPresentAnimationController.originFrame = cardView.frame   return flipPresentAnimationController }

Here you return your custom animation controller instance. The method will also ensure the transition starts from the correct frame.

Your final step is to mark CardViewController as the transitioning delegate. View controllers have a transitioningDelegate property, which UIKit will query to see if it shoulld use a custom transition.

Add the following to prepareForSeque(_:sender:) , just below the card assignment section:

var originFrame = CGRect.zeroRect

0

It’s important to note that it is the view controller being presented that needs a transitioning delegate, not the view controller doing the presenting!

Build and run your project; tap on a card and you should see the following:

Creating Custom UIViewController Transitions

There you have it – your first custom transition. But presenting your new view is only half the solution: you need to dismiss it in an equally showy manner!

Creating Custom UIViewController Transitions

Showy? I got yer showy right here!

Dismissing The View Controller

Go to File/New/File… , choose iOS/Source/Cocoa Touch Class , and click Next . Set the name to FlipDismissAnimationController , and make sure to subclass NSObject and set the language to Swift . Click Next and make sure the Animation Controllers group is still selected from last time. Click Create to create the file.

Replace the contents of the new file with the following:

var originFrame = CGRect.zeroRect

1

The goal of this class is essentially the reverse of the presenting animation:

  • Shrink the displayed view to the size of the card; destinationFrame will hold this value.
  • Flip the view around and reveal the original card.

Add the following lines to the top of animateTransition(_:) :

var originFrame = CGRect.zeroRect

2

Here’s what you’re doing in each commented section:

  1. Since this animation shrinks the view, you’ll need to flip the initial and final frames.
  2. This time you’re manipulating the “from” view so you take a snapshot of that.
  3. Just as before, you add the “to” view and the snapshot to the container view, then hide the “from” view, so that it doesn’t conflict with the snapshot.
  4. Finally, you hide the “to” view via the same rotation technique.

All that’s left to do is add the animation itself.

Add the following code directly below the code you just added to animateTransition(_:) :

var originFrame = CGRect.zeroRect

3

This is pretty much the inverse of the presenting animation:

  1. You scale the view first, then hide the snapshot with the rotation. Next you reveal the “to” view by rotating it halfway around the y-axis but in the opposite direction.
  2. Finally, you remove the snapshot and inform the context that the transition is complete. This allows UIKit to update the view controller hierarchy and tidy up the views it created to run the transition.

Open CardViewController.swift and declare the following property right below the previous animation controller:

var originFrame = CGRect.zeroRect

4

Next, add the following delegate method to the class extension:

var originFrame = CGRect.zeroRect

5

This passes the correct frame to the dismissing animation controller and returns it.

As a final step, modify transitionDuration in FlipPresentAnimationController to make the animation a bit more snappy:

var originFrame = CGRect.zeroRect

6

Build and run your app; tap the card to see your presenting and dismissing animations:

Creating Custom UIViewController Transitions

Your custom animation looks really sharp. But you can improve it even further by adding user interaction to the animation.

Making It Interactive

The Settings app in iOS has a great example of an interactive transition animation:

Creating Custom UIViewController Transitions

Your task in this section is to navigate back to the face-down state of the card with swipe from the left edge of the screen; the progress of the transition will follow the user’s finger.

How Interactive Transitions Work

An interaction controller responds to either touch events or programmatic input by speeding up, slowing down, or even reversing the progress of a transition. In order to enable interactive transitions, the transitioning delegate must also provide an interaction controller. This can be any object that implements the UIViewControllerInteractiveTransitioning protocol. You’ve already made the transition animation; the interaction controller moves this animation in response to gestures rather than letting it play like a video.

Apple provides the ready-made UIPercentDrivenInteractiveTransition class, which is a concrete interaction controller implementation. You’ll use this class to make your transition interactive.

Creating an Interactive Transition

Your first job is to create the interaction controller. Go to File/New/File… , and then choose iOS/Source/Cocoa Touch Class . Call it SwipeInteractionController , and make it a subclass of UIPercentDrivenInteractiveTransition . Make sure the Language is Swift then click Next . Choose the Interaction Controllers group and click Create .

Open SwipeInteractionController.swift , and define the following properties at the top of the class:

var originFrame = CGRect.zeroRect

7

The code above is fairly straightforward:

  • As the name suggests, interactionInProgress indicates whether an interaction in already in progress.
  • You will use shouldCompleteTransition internally to control the transition – you’ll see how later on.
  • The interaction controller directly presents and dismisses view controllers, so you hold onto the current view controller in viewController .

Add the following method to the class body:

var originFrame = CGRect.zeroRect

8

Your implementation will rely on gesture detection to control the transition. In the above method, you obtain a reference to the view controller, and set up a gesture recognizer in its view.

Implement prepareGestureRecognizerInView(_:) as shown below:

var originFrame = CGRect.zeroRect

9

Here you declare a gesture recognizer, which will be triggered by a left edge swipe, and add it to the view.

The final piece of the puzzle is to add handleGesture(_:) as shown below:

func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {   return 2.0 }

0

Breaking down the implementation:

  1. You start by declaring local variables to track the progress. You’ll record the translation in the view and calculate the progress. A Swipe of 200 points will lead to 100% completion, so you use this number to measure the transition’s progress.
  2. When the gesture starts, you adjust interactionInProgress accordingly and trigger the dismissal of the view controller.
  3. While the gesture is moving, you continuously call updateInteractiveTransition with the progress amount. This is a method on UIPercentDrivenInteractiveTransition which moves the transition along by the percentage amount you pass in.
  4. If the gesture is cancelled, you update interactionInProgress and roll back the transition.
  5. Once the gesture has ended, you use the current progress of the transition to decide whether to cancel it or finish it for the user.

All that’s left is to wire it up.

Creating Custom UIViewController Transitions

SO CLOSE…

Open CardViewController.swift and declare the following property for the interaction controller:

func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {   return 2.0 }

1

UIKit queries the transitioning delegate for an interaction controller in interactionControllerForDismissal(_:) .

Add the following implementation to the class extension at the bottom of the file:

func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {   return 2.0 }

2

This implementation checks whether the view is currently detecting a gesture, which means there’s an interaction in progress. It then returns the appropriate interaction controller.

Now go to prepareForSegue(_:sender:) , and add the following line just below the transitioningDelegate assignment:

func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {   return 2.0 }

3

This gives the interaction controller a reference to the presented view controller.

Build and run your project; tap a card, then swipe from the left edge of the screen to see the final result:

Creating Custom UIViewController Transitions

Congratulations – you’ve created a sharp-looking interactive, animated transition!

Where to Go From Here?

You can download the completed project for this tutorialhere.

To learn more, check out Chapter 3, “Custom View Controller Transitions” iniOS 7 by Tutorials as well as Chapter 19, “Custom Presentation Controller & Device Orientation Animations” in iOS Animations by Tutorials .

This tutorial focuses on modal presentation and dismissal transitions. It’s important to point out that custom UIViewController transitions can also be used when using container view controllers:

  • When using a navigation controller, vending the animation controllers is the responsibility of its delegate, which is an object conforming to UINavigationControllerDelegate . The delegate can provide an animation controller in the navigationController(_:animationControllerForOperation:fromViewController:toViewController:) method.
  • A tab bar controller relies on an object implementing the UITabBarControllerDelegate protocol to return the animation controller in the tabBarController(_:animationControllerForTransitionFromViewController:toViewController:) method.

I hope you enjoyed this tutorial; if you have any questions or comments, please join the forum discussion below!

原文  http://www.raywenderlich.com/110536/custom-uiviewcontroller-transitions

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » Creating Custom UIViewController Transitions

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
分享按钮