How to Implement iOS library animation-Part 1 (Part 2)
Status 2-open a book
Now that the animation of state 1 is complete, we can move it to the processing of state 2. Here we will convert a combined book into an opened book. Add the following method under setStartPositionForPush (_: toVC:
func setEndPositionForPush(fromVC: BooksViewController, toVC: BookViewController) { //1 for cell in fromVC.collectionView!.visibleCells() as! [BookCoverCell] { cell.alpha = 0 } //2 for cell in toVC.collectionView!.visibleCells() as! [BookPageCell] { cell.layer.transform = transforms[cell]! cell.updateShadowLayer(animated: true) }}
The above code is explained as follows:
Hide the cover of all the books, because the content of the selected books will be displayed next. Traverse every page in the book in BookViewController and read the transform previously saved in the Transform Array.
After navigating from BooksViewController to BookViewController, we still need to perform some cleanup work.
Add the following method after the preceding method:
func cleanupPush(fromVC: BooksViewController, toVC: BookViewController) { // Add background back to pushed view controller toVC.collectionView?.backgroundColor = toViewBackgroundColor}
When Push is complete, we set the background color of the Collection View of BookViewController back to the color originally saved to hide the content under it.
Animation of opening a book
Now we have implemented the Helper method. Next we need to implement the Push animation!
Add the following code to the null animateTransition (_ :) method:
//1let container = transitionContext.containerView()//2if isPush { //3 let fromVC = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey) as! BooksViewController let toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey) as! BookViewController //4 container.addSubview(toVC.view) // Perform transition //5 self.setStartPositionForPush(fromVC, toVC: toVC) UIView.animateWithDuration(self.transitionDuration(transitionContext), delay: 0.0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.7, options: nil, animations: { //6 self.setEndPositionForPush(fromVC, toVC: toVC) }, completion: { finished in //7 self.cleanupPush(fromVC, toVC: toVC) //8 transitionContext.completeTransition(finished) })} else { //POP}
The above code is explained as follows:
Obtain the Container View. The Container View acts as the parent View when two View controllers perform transition. Determine whether the current transfer action is a Push action. If yes, obtain fromVC (BooksViewController) and toVC (BookViewController) respectively ). Add toVC (BookViewController) to Container View. Set the start and end points of the Push action, that is, toVC and fromVC. Start animation. From the starting point (the State of book combination) to the ending point (the State of book opening ). Execute the cleanup action. Tells the system that the conversion is complete. Apply Push animation to Navigation Controller
Now that we have created a Push animation, we will apply it to the custom Navigation Controller.
Open BooksViewController. swift and add the following attributes to the class declaration:
var transition: BookOpeningTransition?
The transition attribute is used to save the Transition object. Through this attribute, we can know whether the current animation is a Push animation or a Pop animation.
Add an extension after the braces at the end of the file:
extension BooksViewController {func animationControllerForPresentController(vc: UIViewController) -> UIViewControllerAnimatedTransitioning? { // 1 var transition = BookOpeningTransition() // 2 transition.isPush = true // 3 self.transition = transition // 4 return transition }}
Through expansion, we separate part of the code. Here, we put the methods related to the conversion animation together. The above method creates and returns a Transition object.
The above code is explained as follows:
Create a new Transition. Because we want to bring up or Push a Controller, set isPush to true. Save the current Transition object. Returns the Transition object.
Now open CustomNavigationController. swift and replace the if Statement of Push:
if operation == .Push { if let vc = fromVC as? BooksViewController { return vc.animationControllerForPresentController(toVC) }}
The preceding statement checks whether the View Controller currently pushed is a BooksViewController. If yes, use the BookOpeningTransition we created to present the BookViewController.
Compile and run the program. Select a book and you will see the book slowly starting from the combined state:
Er... What about our animation effect?
The book jumps directly from the combination status to the open status, because we didn't load the cell (book page )!
The navigation controller switches from BooksViewController to BookViewController, both of which are UICollecitonViewController. UICollectionViewCell is not loaded in the main thread, so the number of cells is 0 at the beginning of the Code. Of course, no animation is generated.
We need to make the Collection View have enough time to load all cells.
Open BooksViewController. swift and replace the openBook (_ :) method:
func openBook(book: Book?) { let vc = storyboard?.instantiateViewControllerWithIdentifier(BookViewController) as! BookViewController vc.book = selectedCell()?.book //1 vc.view.snapshotViewAfterScreenUpdates(true) //2 dispatch_async(dispatch_get_main_queue(), { () -> Void in self.navigationController?.pushViewController(vc, animated: true) return })}
The above code is explained as follows:
Tell BookViewController to take screenshots before the animation starts. Place the action of pushing BookViewController in the main thread to load the cell.
Compile and run. This time you will see the correct Push Animation:
Does this look so good?
Now, the content of the Push animation ends here. Next, we will start to implement the Pop animation.
Pop animation assistant method
The Pop action of a View Controller is the opposite of Push. Status 1 indicates the opening status of the book, and status 2 indicates the combination status of the book:
Open up BookOpeningTransition. swift and add the following code:
Open BookOpeningTransition. swift and add the following methods:
// MARK: Pop methodsfunc setStartPositionForPop(fromVC: BookViewController, toVC: BooksViewController) { // Remove background from the pushed view controller toViewBackgroundColor = fromVC.collectionView?.backgroundColor fromVC.collectionView?.backgroundColor = nil}
The setStartPositionForPop (_: toVC) method only saves the background color of BookViewController and deletes the background color of the Collection View of BooksViewController. Note that you do not need to create any cell animation because the book is open at this time.
Next, add this method after the above method:
func setEndPositionForPop(fromVC: BookViewController, toVC: BooksViewController) { //1 let coverCell = toVC.selectedCell() //2 for cell in toVC.collectionView!.visibleCells() as! [BookCoverCell] { if cell != coverCell { cell.alpha = 1 } } //3 for cell in fromVC.collectionView!.visibleCells() as! [BookPageCell] { closePageCell(cell) }}
This method creates the start and end points of the Pop animation, that is, starting from opening to merging:
Obtain the album art of the selected book. In the starting state, traverse the cover of a private book in BooksViewController and perform an incremental effect on all objects. Traverse all pages of the current book in BookViewController, and convert all cells into a combined state.
Create the following method:
func cleanupPop(fromVC: BookViewController, toVC: BooksViewController) { // Add background back to pushed view controller fromVC.collectionView?.backgroundColor = self.toViewBackgroundColor // Unhide the original book cover toVC.selectedCell()?.alpha = 1}
This method is used to clean up the Pop Animation: set the background color of the Collection View of BooksViewController back to its start value and display the cover.
In the animateTransition (_ :) method, find the else statement block with the annotation "// POP" and add the following code:
//1let fromVC = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey) as! BookViewControllerlet toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey) as! BooksViewController//2container.insertSubview(toVC.view, belowSubview: fromVC.view)//3setStartPositionForPop(fromVC, toVC: toVC)UIView.animateWithDuration(self.transitionDuration(transitionContext), animations: { //4 self.setEndPositionForPop(fromVC, toVC: toVC)}, completion: { finished in //5 self.cleanupPop(fromVC, toVC: toVC) //6 transitionContext.completeTransition(finished)})
The above code is explained as follows:
Obtain the two viewcontrollers involved in the animation. Currently, fromVC is BookViewController (Enabled) and toVC is BooksViewController (Enabled ). Add BooksViewController to Container View (below bookviewcontorler ). The setStartPositionForPop (_: toVC) method saves the background color and then sets the background color to nil. Execute the animation, that is, switch from the open state to the starting state. After the animation is completed, clear the animation. Set the background color back to the original value to display the cover. Notification animation completed. Apply Pop animation to Navigation Controller
Now we need to create a Pop animation, just as we do in the Push animation.
Open BooksViewController. swift and add the following method after the animationControllerForPresentController (_ :) method:
func animationControllerForDismissController(vc: UIViewController) -> UIViewControllerAnimatedTransitioning? { var transition = BookOpeningTransition() transition.isPush = false self.transition = transition return transition}
Here, we create a new BookOpeningTransition object, but the difference is that isPush is set to false.
Open CustomNavigationController. swift and replace the if statement in the Pop section:
if operation == .Pop { if let vc = toVC as? BooksViewController { return vc.animationControllerForDismissController(vc) }}
The above code returns a Transition object and executes the Pop animation to combine books.
Compile, run the program, select a book, and view its opening and merging. As shown in:
Create an interactive Navigation Controller
Opening and starting the animation is done-but we can continue! Why don't we use a more intuitive gesture to open and contract books?
Open BookOpeningTransition. swift and add the following attribute definitions:
// MARK: Interaction Controllervar interactionController: UIPercentDrivenInteractiveTransition?
Then open CustomNavigationController. swift and add the following code:
func navigationController(navigationController: UINavigationController, interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { if let animationController = animationController as? BookOpeningTransition { return animationController.interactionController } return nil}
In this method, we obtain an interactionController from the BookOpeningTransition object. In this way, the navigation controller can track the animation process so that users can open and contract books with a pinch gesture.
Open BooksViewController. swift and add the following attributes under the trnasitoin variable:
//1var interactionController: UIPercentDrivenInteractiveTransition?//2var recognizer: UIGestureRecognizer? { didSet { if let recognizer = recognizer { collectionView?.addGestureRecognizer(recognizer) } }}
The functions of these two attributes are as follows:
InteractionController is a UIPercentDrivenInteractiveTransition class that manages custom animations for transition between View contorroller. InteractionController is generated by a Transition Animator, which is an object that implements the UIViewControllerAnimatorTransitioning protocol. We already have BookOpeningTransition-this is an object that implements UIViewControllerAnimatorTransitioning. InteractionController can control the progress between the Push animation and the Pop animation. For more information about this class, see the official Apple documentation. Recognizer is a UIGestureRecognizer. We use this gesture reader to open and close books with a pinch gesture.
In the animationControllerForPresentController (_ :) method extended by BooksViewController, add the code below a line of transition. isPush = true:
transition.interactionController = interactionController
This Code allows CustomNavigationController to know which interaction controller to use.
Add the same code to the transition. isPush = false line in the animationControllerForDismissController (_ :) method:
transition.interactionController = interactionController
Add the code in the viewDidLoad () method:
recognizer = UIPinchGestureRecognizer(target: self, action: handlePinch:)
Here we initialize a UIPinchGestureRecognizer, allowing users to call the handlePinch (_ :) method when making a pinch gesture.
This method is implemented below the viewDidLoad () method:
// MARK: Gesture recognizer actionfunc handlePinch(recognizer: UIPinchGestureRecognizer) { switch recognizer.state { case .Began: //1 interactionController = UIPercentDrivenInteractiveTransition() //2 if recognizer.scale >= 1 { //3 if recognizer.view == collectionView { //4 var book = self.selectedCell()?.book //5 self.openBook(book) } //6 } else { //7 navigationController?.popViewControllerAnimated(true) } case .Changed: //8 if transition!.isPush { //9 var progress = min(max(abs((recognizer.scale - 1)) / 5, 0), 1) //10 interactionController?.updateInteractiveTransition(progress) //11 } else { //12 var progress = min(max(abs((1 - recognizer.scale)), 0), 1) //13 interactionController?.updateInteractiveTransition(progress) } case .Ended: //14 interactionController?.finishInteractiveTransition() //15 interactionController = nil default: break }}
For UIPinchGestureRecognizer, we need to pay attention to these three states: starting state, which allows you to know when the pinch gesture starts; changing state, detecting changes in the pinch gesture; ending state, let you know when the pinch gesture ends.
The handlePinch (_ :) method code is described as follows:
Start status
1. Create a UIPercentDrivenInteractiveTransition object.
2. scale depends on the distance between the kneading points to determine whether the scale value is greater than or equal to 1.
3. If yes, determine whether the related View is a Collection View.
4. Obtain the book being merged.
5. Execute the Push BookViewController animation to display the book pages in the book.
6. If the scale is smaller than 1...
7 .... Execute the Pop BookViewController animation to display the cover
Changing status-during kneading
8. Determine whether the animation is a Push animation.
9. If a bookviewconroler is being pushed, calculate the progress of the pinch gesture. The progress must be a number between 0 and 1. We divide the original value by 5 to give users a better sense of control. Otherwise, when you open a book with a double finger, it will suddenly jump to the open state.
10. Update the animation progress based on the progress we calculated.
11. If it is not a Push animation, it should be a Pop animation.
12. When the dual fingers are combined to create a book, the scale value must be slowly changed from 1 to 0.
13. Finally, update the animation progress.
End status-ended with a gesture
14. Tell the system that the user's interactive animation is complete.
15. Set interaction controller to nil.
Finally, we need to achieve the state of "kneading to combine books. Of course, we must pass the gesture reader to BookViewController so that it can Pop.
Open BookViewController. swift and add an attribute under the book variable declaration:
var recognizer: UIGestureRecognizer? { didSet { if let recognizer = recognizer { collectionView?.addGestureRecognizer(recognizer) } }}
When we pass the gesture reader to BookViewController, it will be added to the Collection View, so we can track the user's "closing book" gesture.
Then, you need to pass the gesture identification between BooksViewController and BookViewController.
Open BookOpeningTransition. swift. In the cleanUpPush (_: toVC) method, add the following code after setting the background color:
// Pass the gesture recognizertoVC.recognizer = fromVC.recognizer
When we Push from BooksViewController to BookViewController, we pass the pinch gesture to BookViewController. This will cause the pinch gesture to be automatically added to the Collection View.
When we return the BooksViewController Pop to the BooksViewController, we must pass the pinch gesture back.
In the cleanUpPop (_: toVC) method, add the following code after setting the background color:
// Pass the gesture recognizertoVC.recognizer = fromVC.recognizer
Compile and run a program, select a book, and open and contract the book with a pinch gesture:
The pinch-and-put gesture is a natural gesture suitable for "switching" books; it makes our interface easier. We no longer need the Back button in the navigation bar-so we decided to remove it.
Open Main. storyboard, select Custom Navigation View Controller, and open the attribute panel. Under the Navigation Controller Bar, disable Bar Visibility, as shown below:
Re-compile and run the program:
What to do next
You can download the final project after all the preceding steps.
In this tutorial, we will learn how to customize the layout of Collection View to make the App user experience more natural and interesting. We also created custom animations and used smart interactions to enable users to open and close a book with a pinch gesture. While implementing all the basic functions, this App makes the program more humane and distinctive.
In comparison, the default "Fade-in/fade-out" animation is simpler. It can save some of your development time. However, outstanding applications should all have their own unique places to stand out.
You know, everyone will like to remember apps that are very interesting to use and can be excited on the UI without sacrificing functionality.
I hope you will like this tutorial. Thanks again for the example project provided by Attila Heged üs.