How To Customize Animation In Tabbarcontroller With Animationcontroller
When a view controller container shows a new child view controller onto the screen, it sometimes makes sense to animate that introduction.
Brand sure to check out the previous installments of this series on view controller containers here, here and here.
For example, a UINavigationController will slide the former child view controller off the screen to the left while sliding the new child view controller onto the screen from the right.
This outcome creates a visual cue for the user that indicates that they take moved deeper into a workflow. It besides informs the user that the previous view controller is 'somewhere off to the left', leading them to look for a back push button somewhere on the left side of the screen. Couple this blitheness with the consistent visibility of the navigation bar, and the user is drawn to the left side of the navigation bar when they desire to return to a previous view controller. Not-and so-coincidentally, that is where the back push is.
We take stuff similar that for granted, only information technology those kinds of simple brilliances that allow my grandmother and my three old nephew to use the same reckoner that I employ.
And then, the visual cue is important. It informs the user of some functionality that otherwise wouldn't be obvious or leads them to that functionality more rapidly than doing a full scan of the screen. In saying that, I've effectively issued the standard disclaimer about costless animations.
Now, it may make sense for your awarding to override the default animation for an existing container view controller or allow an application to customize your own container view controller's animation. There are a number of classes and protocols that exist to provide this 'custom transitioning' behavior in a safe and standardized way.
Custom Transitions
Some container view controllers have a 'default' blitheness for swapping view controllers – like UINavigationController's sliding animation. Before custom transitions, that was the blitheness you got when yous used a navigation controller. If you wanted to change that animation, y'all could either subclass UINavigationController and hack at its likely-to-change internals or y'all would rewrite UINavigationController yourself. Both situations are time-consuming and full of dangers.
(Or, you could download a 'solution' as a pod or whatever the absurd kids are doing these days, which reduces the time-consuming role down to cipher, just doubles down on the dangerous part.)
With the custom transition pattern, we have a condom way to do this. UINavigationController, UITabBarController, and modal view controllers all utilize the custom transition pattern. Each of these classes has a default animation, simply go out in hooks for letting another object to take over.
It is reasonable, then, that offering custom transitions in your own custom container view controllers would also use this same pattern. Information technology is but as reasonable, that, before we implement custom transitions in our own container view controller, we should kickoff learn how the congenital-in containers work with custom transitions.
In fact, this is full general advice that should always be followed: if you are replacing, creating an alternative to, or improving something that already exists in the iOS SDK, you must make sure it behaves the aforementioned way its predecessor does.
There are expectations that certain types of components – especially view controllers or views – volition work together in a sure way. These components are part of a greater system that works rather flawlessly if you lot play by the rules. Ignoring those rules is like replacing your iPhone's ability string with a cheap knock-off: y'all will eventually start a burn down.
For example, y'all could write a container view controller that somehow managed to omit sending viewDidAppear: to its children in certain circumstances. By doing this, yous have broken the contract that every view controller has with the system: "When your view appears, you lot will know about it."
In one case a contract of that magnitude is broken, your whole application spirals out of control. Every line of code you write is one line further from a stable awarding, because its cadre is rotten. Your objects can't trust each other, so they make up for each other's shortcomings, which makes them co-dependent – i.e., one tightly-coupled, spaghettied mess. (Take y'all ever noticed that every blueprint blueprint we have can exist expressed every bit a relationship?)
A Testable Application
With that tirade out of the way, let's create a quick projection to implement custom transitioning for a UITabBarController. Create an empty project, name it any you lot want. Create a UIViewController subclass, STKViewController. Give information technology a new designated initializer in STKViewController.h:
@interface STKViewController : UIViewController - (id)initWithColor:(UIColor *)color; @property (nonatomic, stiff) UIColor *color; @cease
And so, in STKViewController.m, write the post-obit lawmaking:
@implementation STKViewController - (id)initWithColor:(UIColor *)color { cocky = [super initWithNibName:nil bundle:nil]; if(self) { [self setColor:color]; float r, thou, b; [[self color] getRed:&r green:&g blue:&b blastoff:naught]; NSString *championship = [NSString stringWithFormat:@"%d %d %d", (int)r * 255, (int)one thousand * 255, (int)b * 255]; [[self tabBarItem] setTitle:title]; } return self; } - (id)initWithNibName:(NSString *)nibName parcel:(NSBundle *)bundle { return [self initWithColor:[UIColor whiteColor]]; } - (void)viewDidLoad { [super viewDidLoad]; [[self view] setBackgroundColor:[self color]]; } @finish
This gives us an piece of cake way of creating multiple view controllers with different colored views to see the swap animation. It is important that when you create something, you lot have a reliable and constructive way of ensuring it works and that it fails predictably. For creating container view controllers, shoving in a few multi-colored view controllers into the container is a groovy test harness.
In the app delegate, set up a tab bar controller and populate it with a few STKViewControllers.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { cocky.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; UITabBarController *tbc = [[UITabBarController alloc] init]; [tbc setViewControllers:@[ [[STKViewController alloc] initWithColor:[UIColor redColor]], [[STKViewController alloc] initWithColor:[UIColor greenColor]], [[STKViewController alloc] initWithColor:[UIColor blueColor]] ]]; [[self window] setRootViewController:tbc]; self.window.backgroundColor = [UIColor whiteColor]; [cocky.window makeKeyAndVisible]; return Yep; }
You tin can run the application and see that the tab bar controller works every bit you would expect and tapping on each tab takes you to the associated view controller. Now, nosotros tin customize that transition. Allow'south wait at the high-level process by which a tab bar controller'southward transition gets animated.
Following this flowchart, when the tab bar controller is transitioning between view controllers, information technology first asks its delegate if the transition should be customized. If the delegate thinks the transition should be customized, it offers up an object that volition facilitate that transition – the 'Blitheness Controller' in the figure.
The delegate and then steps aside, and the tab bar controller starts talking to the animation controller object. The tab bar controller will pass information to the animation controller object – like which view controllers are moving on/off the screen, where those view controller'south views are currently located on the screen, and where those views demand to stop upwards when the animation completes. The controller object will then perform the animation.
Once the animation has finished, the animation controller will inform the tab bar controller. At that point, the tab bar controller will finalize its state and fire off messages like viewDidAppear: and viewDidDisappear:. The transition is complete.
Let'south make our sample application handle that same menses. Outset things first, nosotros must plant which object is the delegate for the tab bar controller. That leads me into my adjacent preachy section… (did you think y'all were just going to re-create and paste some code to get custom transitions?)
The Principles of Delegation
An application is a tree of objects. The root of that tree is the UIApplication object itself. In order for an object to survive for longer than the scope of a method, information technology must be connected to that tree somehow. The beginning attachments to the tree are the awarding delegate, the window and the root view controller of that window. These are the foundations of the tree – the trunk, if y'all will. They are anchored to the tree and won't exist going abroad – that is, there exists a strong reference to them from either the root or other objects in the trunk.
Then come up the major arterial branches of the tree. These are the view controllers that are contained in the root container view controller of the window. They are sturdy, they aren't going anywhere, simply they don't know much about the branches heading off in other directions, they're focused on themselves. They spawn smaller branches and leaves – more than view controllers, model objects and view objects every bit necessary. They are all nevertheless fastened to one tree, but it is articulate that some objects are more 'important' than others.
So, an application looks kind of like this:
And yeah, I spent 10 minutes drawing a tree in UML, but it is to illustrate a betoken: the closer an object is to the root, the more information technology knows near the goals and structure of the application that it belongs to. The further abroad, the less it knows virtually the goals and structure of the application, but the more information technology knows near a specific detail the application utilizes.
For example, in the Phone awarding, information technology is the application consul that decides there will be a tab bar controller with view controllers that the user will exist able to switch between. These view controllers show a list of favorites, contacts, the keypad, recent calls and voicemail. This application delegate and its tab bar controller can change the cardinal features of the Phone application by simply irresolute the view controllers employed.
On the other side of that, a model object that represents a 'Contact' in the Telephone awarding doesn't take whatever idea what is going on in the awarding. Sure, it tin can hang onto names and phone numbers, merely it doesn't know why. That's for more important objects – objects closer to the trunk – to decide. In fact, this is why well-written model and (especially) view objects tin often be reused in other applications – they are decoupled from the bigger picture of a specific application.
Now, with this tree in mind, call back virtually the delegate pattern blueprint. Think about where you use information technology: UITableView, UIApplication, CLLocationManager, UIGestureRecognizer… what do all of these things accept in common? They all know how to practise a general task, merely they don't know how to translate that general chore into something specific to the application. For example, a location director knows how to observe a location, only information technology doesn't know if that location should wind upward equally a pin on a map, sent via a spider web service, or stored in a model object.
Thus, an object'southward delegate is responsible for contextualizing data with respect to the application's greater purpose.
This concept is inscribed into our tree: objects closer to the root know more almost the purpose of the application. To determine where an object should delegate to, we wait for the closest, more informed object in the tree – i.due east., the object one step closer to the root. (This explains why delegate properties are not strong references: this same tree construction manages the lifecycle of objects, and objects closer to the root own the objects that they spawn.)
In our sample application, the object that spawned the UITabBarController is the app delegate. Thus, the app consul should also be the tab bar controller'southward consul.
Dorsum to Custom Transitions
And then, we must ready the tab bar controller's delegate to the app consul so implement the method that answers the question, "Which object will handle the custom transition for this tab bar controller?" This is the 'Animation Controller' object we talked about previously. That code goes in your STKAppDelegate.thousand file:
// Having us conform to the delegate hither @interface STKAppDelegate () <UITabBarControllerDelegate> @end @implementation STKAppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { ... // Setting the consul here [tbc setDelegate:self]; ... return Yeah; } // Implementing the UITabBarControllerDelegate method - (id <UIViewControllerAnimatedTransitioning>)tabBarController:(UITabBarController *)tabBarController animationControllerForTransitionFromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC { return cocky; }
The message tabBarController:animationControllerForTransitionFromViewController:toViewController: is sent to the tab bar controller'due south delegate when the tab bar controller is about to transition betwixt view controllers. The object returned from this method volition become responsible for the animation by implementing methods from the UIViewControllerAnimatedTransitioning protocol. Thus, an 'Animation Controller' is some object that conforms to UIViewControllerAnimatedTransitioning. It will be sent letters from this protocol on behalf of the tab bar controller.
Now, as you can encounter in this case, we've returned self from this method. That means we're going to allow the app consul handle the animation. This flies in the face of my normal advice of "Don't you dare put anything in the app consul!", simply in this instance, it is actually okay. Why? The reason you avoid putting a lot of 'globally accessible' stuff in the app delegate is considering information technology creates far-reaching dependencies in your awarding – if the leaves of the application tree are reaching back to the torso, yous have a mess on your hands. The less dependencies between objects (and therefore the less distinct responsibilities one object has), the easier it is to maintain and add features to that application. A bad tree looks like this:
But in the instance of the app delegate'southward human relationship with the tab bar controller, that is already a tight relationship. Later all, the app delegate fabricated the decision to create the tab bar controller, if information technology makes the decision to change out that tab bar controller, this animation dependency is right in that location in the same file. At present, this doesn't mean that your custom transition code always goes in the app consul, it just ways that in this construction we have currently defined, it works. (We'll discuss another arroyo to this afterwards.)
An object responsible for handling a custom transition must conform to the UIViewControllerAnimatedTransitioning protocol. This protocol has ii required methods, transitionDuration: and animateTransition:. The first method the tab bar controller will telephone call simply returns the time (in seconds) that the animation will occur over. Add this method to the app delegate and declare that STKAppDelegate conforms to UIViewControllerAnimatedTransitioning.
@interface STKAppDelegate () <UITabBarControllerDelegate, UIViewControllerAnimatedTransitioning> @end @implementation STKAppDelegate - (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext { return 0.v; }
Next, the tab bar controller volition tell the Animation Controller to perform the blitheness. Information technology is the responsibility of this method to do the following:
one. Add the incoming view to the view hierarchy. two. Animate the erstwhile view off the screen. 3. Animate the new view on the screen. 4. Report back to the tab bar controller when the blitheness finishes.
For fun, we'll brand this animation slide the new view in from the peak while the old view slides off the screen downward. Permit's implement this method step-past-stride to figure out what is happening. Outset, a stub:
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext { }
Detect that this method takes an object that conforms to UIViewControllerContextTransitioning. The context object will contain but enough information for the Animation Controller to perform the animation. Annihilation more, and the tab bar controller would allow too much access to its internal structure; annihilation less, the animation controller wouldn't be able to practice its job. In other words, the context object is a safe communication proxy between the tab bar controller and the animation controller.
The first items the animation controller volition need to grab from the context object are the two view controllers that are participating in the transition.
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext { UIViewController *incomingVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; UIViewController *outgingVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; }
Now, we have pointers to the incoming view controller (incomingVC) and the outgoing view controller (outgoingVC). Next, we need to add the new view controller'south view to the view bureaucracy of the tab bar controller.
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext { UIViewController *incomingVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; UIViewController *outgoingVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; [[transitionContext containerView] addSubview:[incomingVC view]]; }
The tab bar controller has prepared the context object with a containerView belongings. This containerView is a view the tab bar controller manages that will directly contain whatever of its child view controller'south views. Note the containerView doesn't accept to be (and in fact, is non) the view of the tab bar controller. The tab bar controller is not giving access to its whole view bureaucracy to the blitheness controller, but just the parts that the blitheness controller needs to know about.
Side by side, the terminal positioning of the view that will exist swapped in has already been determined past the tab bar controller and added to the context. It wouldn't make sense for the tab bar controller to cede layout responsibility to the animation controller – but the tab bar controller knows where the view should go. Nosotros can grab the rectangle that volition guide our blitheness.
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext { UIViewController *incomingVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; UIViewController *outgoingVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; [[transitionContext containerView] addSubview:[incomingVC view]]; CGRect incomingDestinationFrame = [transitionContext finalFrameForViewController:incomingVC]; }
Finally, we can animate this transition:
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext { UIViewController *incomingVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; UIViewController *outgoingVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; [[transitionContext containerView] addSubview:[incomingVC view]]; // The last destination of the incoming view is where the tab bar controller wants it CGRect incomingDestinationFrame = [transitionContext finalFrameForViewController:incomingVC]; // The initial location of the incoming view is above the screen CGRect incomingInitialFrame = incomingDestinationFrame; incomingInitialFrame.origin.y -= incomingDestinationFrame.size.height; // The concluding destination of the outgoing view is underneath the screen CGRect outgoingDestinationFrame = [[fromVC view] frame]; outgoingDestinationFrame.origin.y = outgoingDestinationFrame.origin.y + outgoingDestinationFrame.size.height; [[toVC view] setFrame:incomingInitialFrame]; [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{ [[toVC view] setFrame:incomingDestinationFrame]; [[fromVC view] setFrame:outgoingDestinationFrame]; } completion:^(BOOL finished) { [transitionContext completeTransition:finished]; }]; }
Notice we compute the initial placement of the incoming view controller'southward view and the final position of the outgoing view controller to suit our animation desires. Then, we perform the animation. The important chip here is that the completion cake of the blitheness sends a message back to the context that the transition has completed. The context object relays this completion to the tab bar controller then it tin finalize its ain state and send viewDidAppear/viewDidDisappear messages to its children.
The finished value is of import, since an blitheness could potentially be interrupted and therefore not completed. This would indicate to the tab bar controller that the transition was not fully completed. For example, if during the animation between tabs, the user were able to tap on another tab, the tab bar controller would need to know. (A tab bar controller specifically restricts this, but other container view controllers may non.)
You can run this application and animate between unlike views in your tab bar controller. In the next installment, we'll add the ability to customize transitions to the view controller container nosotros wrote in previous installments. (I hope it won't take 5 months this time.)
That Other Approach
As a terminal note, I mentioned that in that location is some other approach for designating that Animation Controller object. In this instance, we let the app consul handle the blitheness. In some situations, it may make sense to create an object for the express purpose of existence an Animation Controller, i.e., conforming to UIViewControllerAnimatedTransitioning. For example:
@interface STKVerticalTransitioner : NSObject <UIViewControllerAnimatedTransitioning> @end @implementation STKVerticalTransitioner - (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext { ... } - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext { ... } @end
And then the delegate for the tab bar controller would do something like:
- (id <UIViewControllerAnimatedTransitioning>)tabBarController:(UITabBarController *)tabBarController animationControllerForTransitionFromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC { return [[STKVerticalTransitioner alloc] init]; }
This ends up beingness okay, because the tab bar controller will actually retain its animation controller for the duration of the blitheness. There are two situations where you would want to accept this approach.
First, if in that location are multiple animations that could occur depending on some other context (like the state of the application or which view controllers are being swapped), it makes more sense to split up out different animation logic into their ain course instead of sprinkling if statements into a monolithic blitheness controller.
The other reason to suspension off an animation controller into its own course is if you programme on reusing that blitheness in many places, or beyond projects.
Source: https://stablekernel.com/article/view-controller-containers-part-iv-custom-transitions/
Posted by: smithbanke1953.blogspot.com
0 Response to "How To Customize Animation In Tabbarcontroller With Animationcontroller"
Post a Comment