Before iOS7, there were 4 main switches for View Controller:
- Push/Pop,NavigationViewController
- Present and dismis Modal
- UITabBarController
- addChildViewController (usually used for custom container subclasses inherited from UIViewController)
iOS5, call - (void)transitionFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion NS_AVAILABLE_IOS(5_0);
(1)I won't talk about the first three methods here, it's a very common system method. As for the fourth method, I have already explained it in the previous article - Analyzing the effect of NetEase's tab bar, but the container transition animation it provides can only implement some simple UIView animations, but it is difficult to reuse and has high coupling.
(2) Key APIs:
A. Animation Controllers comply with the UIViewControllerAnimatedTransitioning protocol and are responsible for actually executing animations.
B. Interaction Controllers control interactive transitions by following the UIViewControllerInteractiveTransitioning protocol.
C. Transitioning Delegates (Transitioning Delegates) Provides the required animation controller and interaction controller according to different transition types.
Among them, 2 new methods are added to the UINavigationControllerDelegate delegate to NavigationController
UIViewControllerTransitioningDelegate Add transitioningDelegate to Modal Present and Dismiss
UITabBarControllerDelegate delegate
- (id <UIViewControllerInteractiveTransitioning>)tabBarController:(UITabBarController *)tabBarController
interactionControllerForAnimationController: (id <UIViewControllerAnimatedTransitioning>)animationController NS_AVAILABLE_IOS(7_0);
- (id <UIViewControllerAnimatedTransitioning>)tabBarController:(UITabBarController *)tabBarController
animationControllerForTransitionFromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC NS_AVAILABLE_IOS(7_0);
D. Transitioning Contexts define the metadata required during the transition, such as the view controller and related properties of the view that are involved in the transition process. Transition context objects comply with the UIViewControllerContextTransitioning protocol, and this is generated and provided by the system.
E. Transition Coordinators can run other animations in parallel when running transition animations. The transition coordinator complies with the UIViewControllerTransitionCoordinator protocol.
(3) The new API mainly provides two ways to switch VC:
A. Non-interactive switching, that is, define an animation effect from one VC to another, which will automatically play when switching.
B. Interactive switching. This method also requires defining animation effects, but this animation effect will switch VC according to the interactive gesture and play the animation effects at the same time. iOS7 provides a default percentage-based animation implementation UIPercentDrivenInteractiveTransition. According to WWDC's instructions, the easiest way to implement interactive animation is by inheriting UIPercentDrivenInteractiveTransition.
What Apple provides us developers with protocol interfaces, so we can propose them separately and write them into classes to achieve various custom effects in them.
(4) Let's take a look at the custom animation class that implements UIViewControllerAnimatedTransitioning
* Custom animation class
* Implementation protocol------>@protocol UIViewControllerAnimatedTransitioning
* This interface is responsible for the specific content of the switching, that is, "what should happen during the switching"
*/
@interface MTHCustomAnimator : NSObject <UIViewControllerAnimatedTransitioning>
@end
@implementation MTHCustomAnimator
// The system gives a switching context, and we return the time it takes to switch according to the context environment.
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
return 1.0;
}
// The main method to complete container transition animation, we complete the setting and animation of UIView during switching in this method.
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
// Can be regarded as destination ViewController
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
// Can be regarded as source ViewController
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
// Add toView to container
// If it is XCode6, you can use this section.
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0)
{
// iOS8 SDK new API
UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
//UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
[[transitionContext containerView] addSubview:toView];
}else{
// Add toView to the container
[[transitionContext containerView] addSubview:];
}
// If it is XCode5, use this section
[[transitionContext containerView] addSubview:];
= 0.0;
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
// There are many animation effects, here is a left offset
= CGAffineTransformMakeTranslation(-320, 0);
= 1.0;
} completion:^(BOOL finished) {
= CGAffineTransformIdentity;
// Declare the end of the transition --> Remember, don't forget to call completeTransition: this method at the end of the transition
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
PS: From the two methods in the protocol, it can be seen that the above two methods that must be implemented require a transition context parameter, which is an object that complies with the UIViewControllerContextTransitioning protocol. Normally, when we use the system's classes, the system framework provides us with Transitioning Delegates, which creates a transition context object for us and passes it to the animation controller.
// MainViewController
@interface MTHMainViewController () <UINavigationControllerDelegate,UIViewControllerTransitioningDelegate>
@property (nonatomic,strong) MTHCustomAnimator *customAnimator;
@property (nonatomic,strong) PDTransitionAnimator *minToMaxAnimator;
@property (nonatomic,strong) MTHNextViewController *nextVC;
// Interaction Controllers control interactive transitions by following the UIViewControllerInteractiveTransitioning protocol.
@property (strong, nonatomic) UIPercentDrivenInteractiveTransition* interactionController;
@end
@implementation MTHMainViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
= @"Demo";
= [UIColor yellowColor];
// Set up a proxy
= self;
// Set transition animation
= [[MTHCustomAnimator alloc] init];
= [PDTransitionAnimator new];
= [[MTHNextViewController alloc] init];
// Present's proxy and custom settings
_nextVC.transitioningDelegate = self;
_nextVC.modalPresentationStyle = UIModalPresentationCustom; (It seems that there is a bug) Change to modalTransitionStyle = UIModalPresentationCustom
// Push
UIButton *pushButton = [UIButton buttonWithType:UIButtonTypeSystem];
= CGRectMake(140, 200, 40, 40);
[pushButton setTitle:@"Push" forState:UIControlStateNormal];
[pushButton addTarget:self action:@selector(push) forControlEvents:UIControlEventTouchUpInside];
[ addSubview:pushButton];
// Present
UIButton *modalButton = [UIButton buttonWithType:UIButtonTypeSystem];
= CGRectMake(265, 500, 50, 50);
[modalButton setTitle:@"Modal" forState:UIControlStateNormal];
[modalButton addTarget:self action:@selector(modal) forControlEvents:UIControlEventTouchUpInside];
[ addSubview:modalButton];
// Gestures that implement interactive operations
UIPanGestureRecognizer *panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(didClickPanGestureRecognizer:)];
[ addGestureRecognizer:panRecognizer];
}
- (void)push
{
[ pushViewController:_nextVC animated:YES];
}
- (void)modal
{
[self presentViewController:_nextVC animated:YES completion:nil];
}
#pragma mark - UINavigationControllerDelegate 2 new methods added to iOS7
// Animation effects
- (id<UIViewControllerAnimatedTransitioning>) navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC
{
/**
* typedef NS_ENUM(NSInteger, UINavigationControllerOperation) {
* UINavigationControllerOperationNone,
* UINavigationControllerOperationPush,
* UINavigationControllerOperationPop,
* };
*/
if (operation == UINavigationControllerOperationPush) {
return ;
}else{
return nil;
}
}
// Interaction
- (id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController*)navigationController interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>)animationController
{
/**
* In non-interactive animation effects, this method returns nil
* Interactive transition, self-understanding means that the user can control it through his own actions (common: gestures), which is different from the system's default given push or pop (non-interactive)
*/
return _interactionController;
}
#pragma mark - Transitioning Delegate (Modal)
// The first 2 are used for animation
-(id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
{
= AnimationTypePresent;
return _minToMaxAnimator;
}
-(id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
= AnimationTypeDismiss;
return _minToMaxAnimator;
}
// The last 2 are used for interaction
- (id <UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id <UIViewControllerAnimatedTransitioning>)animator
{
return _interactionController;
}
- (id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator
{
return nil;
}
The above implementation is a non-interactive transition, which refers to the switching mechanism specified by the system. Users cannot cancel or control progress switching in the middle. So how to achieve interactive transition:
UIPercentDrivenInteractiveTransition implements the UIViewControllerInteractiveTransitioning interface class, which can use a percentage to control the interactive switching process. In gesture recognition, we only need to tell the current state percentage of the instance of this class. The system calculates the current UI rendering for us based on this percentage and the migration method we set before, which is very convenient. Several important specific methods:
-(void)updateInteractiveTransition:(CGFloat)percentComplete Update percentage, generally calculate a value through gesture recognition length and then update. You will see detailed usage in the following examples
-(void)cancelInteractiveTransition Report interaction cancellation, return to the status before switching
–(void)finishInteractiveTransition Report interaction is completed and updated to the status after switching
- (void)didClickPanGestureRecognizer:(UIPanGestureRecognizer*)recognizer
{
UIView* view = ;
if ( == UIGestureRecognizerStateBegan) {
// Get the touch point coordinates of the gesture
CGPoint location = [recognizer locationInView:view];
//Judgement: When the user slides from the right half, the next VC will be launched (whether it is promoted or launched according to actual needs)
if ( > CGRectGetMidX() && == 1){
= [[UIPercentDrivenInteractiveTransition alloc] init];
//
[self presentViewController:_nextVC animated:YES completion:nil];
}
} else if ( == UIGestureRecognizerStateChanged) {
// Get the coordinates of the offset gesture on the view
CGPoint translation = [recognizer translationInView:view];
// Calculate a percentage based on the distance dragged by the finger, and the animation effect of the switch will also follow this percentage.
CGFloat distance = fabs( / CGRectGetWidth());
// The interactive controller controls the progress of the animation
[ updateInteractiveTransition:distance];
} else if ( == UIGestureRecognizerStateEnded) {
CGPoint translation = [recognizer translationInView:view];
// Calculate a percentage based on the distance dragged by the finger, and the animation effect of the switch will also follow this percentage.
CGFloat distance = fabs( / CGRectGetWidth());
// Forced to complete if the movement exceeds half.
if (distance > 0.5) {
[ finishInteractiveTransition];
} else {
[ cancelInteractiveTransition];
}
// Make sure to set it to nil after the end
= nil;
}
}
Finally, I will share with you an animation special effect: Send ViewController switch similar to Feitu Cloud Transmission
#define Switch_Time 1.2
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext {
return Switch_Time;
}
#define Button_Width
#define Button_Space
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIView * toView = ;
UIView * fromView = ;
if ( == AnimationTypeDismiss) {
// This method can efficiently intercept the currently displayed view into a new view. You can use this intercepted view to display. For example, maybe you only want to use a screenshot to animation, after all, it is too expensive to use the original view to animation. Because it is to intercept the existing content, this method can only reflect the current status information of the intercepted view, but not the information to be displayed in the intercepted view. However, no matter what, calling this method will be more efficient than making the view into a screenshot to load.
UIView * snap = [toView snapshotViewAfterScreenUpdates:YES];
[ addSubview:snap];
[snap setFrame:CGRectMake([UIScreen mainScreen]. - Button_Width - Button_Space, [UIScreen mainScreen]. - Button_Width - Button_Space, Button_Width, Button_Width)];
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
[snap setFrame:[UIScreen mainScreen].bounds];
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.5 animations:^{
[[transitionContext containerView] addSubview:toView];
= 0;
} completion:^(BOOL finished) {
[snap removeFromSuperview];
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}];
} else {
UIView * snap2 = [toView snapshotViewAfterScreenUpdates:YES];
[ addSubview:snap2];
UIView * snap = [fromView snapshotViewAfterScreenUpdates:YES];
[ addSubview:snap];
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
[snap setFrame:CGRectMake([UIScreen mainScreen]. - Button_Width - Button_Space+ (Button_Width/2), [UIScreen mainScreen]. - Button_Width - Button_Space + (Button_Width/2), 0, 0)];
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.5 animations:^{
// = 0;
} completion:^(BOOL finished) {
[snap removeFromSuperview];
[snap2 removeFromSuperview];
[[transitionContext containerView] addSubview:toView];
// Remember not to forget
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}];
}
}
Among them, I don’t understand the explanation of snapshotViewAfterScreenUpdates method. Anyway, it’s fine to use it at the beginning. You can also refer to the following analysis:
Before iOS7, there were the following steps to obtain a snapshot of a UIView: First create a UIGraphics image context, then render the view's layer into that context, thereby obtaining an image, finally closing the image context, and displaying the image in the UIImageView. Now we only need one line of code to complete the above steps:
This method makes a copy of the UIView, which is especially convenient if we want the view to save its current appearance before the animation is executed for later use (the view may be obscured by a subview or some other changes occur).
The afterUpdates parameter indicates whether all effects are applied to the view and then snapshot is taken. For example, if the parameter is NO, you will immediately get a snapshot of the current state of the view, otherwise, the following code can only get a blank snapshot:
[view setAlpha:0.0];
Since we set the afterUpdates parameter to YES and the transparency value of the view is set to 0, the method will take a snapshot after the setting is applied to the view, so the screen is empty. In addition... you can take a snapshot again... Continue to snapshot...
Continuing the previous content, this chapter mainly introduces the switching of view VC on the custom ViewController container. Let’s first look at the container controllers UINavigationController and UITabBarController provided to us by the system. There are NSArray attribute viewControllers. Obviously, the VC that needs to be switched is stored. Similarly, we define a ContainerViewController, which is a direct subclass of UIViewController, used as a container support. For more information, please refer to the code for other attribute definitions. I won't talk about it here. (PS: Originally, the method of switching multiple custom view VCs was to place a UIScrollView, and then increment the X coordinates of the frame of the childViewController View according to this by incrementing it by 320. You can imagine it yourself. This is a bad thing. I feel that all VCs are all physicalized once loaded, and will not be released because they are switched and will not be displayed for the time being)
Be lazy, use storyboard to create 5 childVCs
// ContainerViewController
@interface FTContainerViewController ()
@property (strong, nonatomic) FTPhotoSenderViewController *photoSenderViewController;
@property (strong, nonatomic) FTVideoSenderViewController *videoSenderViewController;
@property (strong, nonatomic) FTFileSenderViewController *fileSenderViewController;
@property (strong, nonatomic) FTContactSenderViewController *contactSenderViewController;
@property (strong, nonatomic) FTClipboardSenderViewController *clipboardSenderViewController;
@property (strong, nonatomic) UIViewController
@property (strong, nonatomic) NSArray *viewControllers; // childVCArray
@property (assign, nonatomic) NSInteger �
@end
@implementation FTContainerViewController
#pragma mark - ViewLifecycle Methods
- (void)viewDidLoad
{
[super viewDidLoad];
// childVC
= [ instantiateViewControllerWithIdentifier:@"FTPhotoSenderViewController"];
= [ instantiateViewControllerWithIdentifier:@"FTVideoSenderViewController"];
= [ instantiateViewControllerWithIdentifier:@"FTFileSenderViewController"];
= [ instantiateViewControllerWithIdentifier:@"FTContactSenderViewController"];
= [ instantiateViewControllerWithIdentifier:@"FTClipboardSenderViewController"];
// Arrays that store childVC
= @[_photoSenderViewController,_videoSenderViewController,_fileSenderViewController,_contactSenderViewController,_clipboardSenderViewController];
// The default is VC with subscript 0
= ?: [0];
= 0;
}
Still, the Animator class that implements the UIViewControllerAnimatedTransitioning protocol is implemented, but it changes the animation effect and uses the new spring animation effect added by iOS7:
#import ""
@implementation FTMthTransitionAnimator
static CGFloat const kChildViewPadding = 16;
static CGFloat const kDamping = 0.5; // The damping parameter represents elastic damping. As the damping value becomes closer to 0.0, the elastic effect of the animation will become more and more obvious. If the damping value is set to 1.0, the view animation will not have elastic effect.
static CGFloat const kInitialSpringVelocity = 0.5; // Initialize spring rate
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
return 1.0;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
/**
* - viewControllerForKey: We can access two transitional ViewControllers through it.
* - containerView: containerView of two ViewControllers.
* - initialFrameForViewController and finalFrameForViewController are the frames of each ViewController at the beginning and end of the transition.
*/
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
[[transitionContext containerView] addSubview:];
= 0;
BOOL goingRight = ([transitionContext initialFrameForViewController:toViewController]. < [transitionContext finalFrameForViewController:toViewController].);
CGFloat transDistance = [transitionContext containerView]. + kChildViewPadding;
CGAffineTransform transform = CGAffineTransformMakeTranslation(goingRight ? transDistance : -transDistance, 0);
// CGAffineTransformInvert
= CGAffineTransformInvert(transform);
// = CGAffineTransformTranslate(, (goingRight ? transDistance : -transDistance), 0);
/**
* -----------------------------
* Use the timing curve `animations` described by the motion of the spring. When `dampingRatio` is 1, the animation will slow down smoothly to its final model value and will not oscillate. The damping ratio is less than 1 to stop completely, the oscillation will increase. The initial velocity of the spring may be used at a specified speed before the object at the end of the simulated spring is moved and attached. This is a unit coordinate system, where 1 refers to the animation of the total distance traveled in the second. So, if you change the position of an object by 200PT in this animation, and the animation you want to act as if the object is moving, before the 100PT/sec animation starts, you will pass 0.5. You usually want to pass the speed of 0.
*/
[UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0 usingSpringWithDamping:kDamping initialSpringVelocity:kInitialSpringVelocity options:0x00 animations:^{
= transform;
= 0;
// CGAffineTransformIdentity Reset, initialize
= CGAffineTransformIdentity;
= 1;
} completion:^(BOOL finished) {
= CGAffineTransformIdentity;
// Declare the end of the transition --> Remember, don't forget to call completeTransition: this method at the end of the transition.
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
@end
The next code is the key to implementing custom container switching. Usually, when we use the built-in class of the system, the system framework creates a transition context object for us and passes it to the animation controller. But in our case, we need to customize the transition animation, so we need to assume the responsibility of the system framework and create this transition context object ourselves.
@interface FTMthTransitionContext : NSObject <UIViewControllerContextTransitioning>
- (instancetype)initWithFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController goingRight:(BOOL)goingRight;
@property (nonatomic, copy) void (^completionBlock)(BOOL didComplete);
@property (nonatomic, assign, getter=isAnimated) BOOL animated;
@property (nonatomic, assign, getter=isInteractive) BOOL interactivity; // Is it interactive?
@property (nonatomic, strong) NSDictionary *privateViewControllers;
@property (nonatomic, assign) CGRect privateDisappearingFromRect;
@property (nonatomic, assign) CGRect privateAppearingFromRect;
@property (nonatomic, assign) CGRect privateDisappearingToRect;
@property (nonatomic, assign) CGRect privateAppearingToRect;
@property (nonatomic, weak) UIView *containerView;
@property (nonatomic, assign) UIModalPresentationStyle presentationStyle;
@end
@implementation FTMthTransitionContext
- (instancetype)initWithFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController goingRight:(BOOL)goingRight {
if ((self = [super init])) {
= UIModalPresentationCustom;
= ;
= @{
UITransitionContextFromViewControllerKey:fromViewController,
UITransitionContextToViewControllerKey:toViewController,
};
// Set the view frame properties which make sense in our specialized ContainerViewController context. Views appear from and disappear to the sides, corresponding to where the icon buttons are positioned. So tapping a button to the right of the currently selected, makes the view disappear to the left and the new view appear from the right. The animator object can choose to use this to determine whether the transition should be going left to right, or right to left, for example.
CGFloat travelDistance = (goingRight ? - : );
= = ;
= CGRectOffset (, travelDistance, 0);
= CGRectOffset (, -travelDistance, 0);
}
return self;
}
- (CGRect)initialFrameForViewController:(UIViewController *)viewController {
if (viewController == [self viewControllerForKey:UITransitionContextFromViewControllerKey]) {
return ;
} else {
return ;
}
}
- (CGRect)finalFrameForViewController:(UIViewController *)viewController {
if (viewController == [self viewControllerForKey:UITransitionContextFromViewControllerKey]) {
return ;
} else {
return ;
}
}
- (UIViewController *)viewControllerForKey:(NSString *)key {
return [key];
}
- (void)completeTransition:(BOOL)didComplete {
if () {
(didComplete);
}
}
// Non-interactive, directly return NO, because interaction is not allowed, of course, the operation progress cannot be cancelled.
- (BOOL)transitionWasCancelled { return NO; }
// Non-interactive, no operation is performed directly, only interaction is performed, the following three protocol methods are meaningful, you can refer to the interactive controller defined for us by referring to the system.
// @interface UIPercentDrivenInteractiveTransition : NSObject <UIViewControllerInteractiveTransitioning>
- (void)updateInteractiveTransition:(CGFloat)percentComplete {}
- (void)finishInteractiveTransition {}
- (void)cancelInteractiveTransition {}
@end
OK, the preparations are all done. In order to simulate the sliding switching of UIScrollView, but because the non-interactive ones are now displayed, we define a swip gesture.
UISwipeGestureRecognizer *leftGesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swapController:)];
[leftGesture setDirection:UISwipeGestureRecognizerDirectionLeft];
[ addGestureRecognizer:leftGesture];
UISwipeGestureRecognizer *rightGesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swapController:)];
[rightGesture setDirection:UISwipeGestureRecognizerDirectionRight];
[ addGestureRecognizer:rightGesture];
[objc] view plaincopy
// Methods to respond to gestures
- (void)swapViewControllers:(UISwipeGestureRecognizer *)swipeGestureRecognizer
{
if ( == UISwipeGestureRecognizerDirectionLeft) {
if (_currentControllerIndex < 4) {
_currentControllerIndex++;
}
NSLog(@"_currentControllerIndex = %ld",(long)_currentControllerIndex);
UIViewController *selectedViewController = [_currentControllerIndex];
NSLog(@"%s__%d__|%@",__FUNCTION__,__LINE__,@"right");
= selectedViewController;
} else if ( == UISwipeGestureRecognizerDirectionRight){
NSLog(@"%s__%d__|%@",__FUNCTION__,__LINE__,@"left");
if (_currentControllerIndex > 0) {
_currentControllerIndex--;
}
UIViewController *selectedViewController = [_currentControllerIndex];
= selectedViewController;
}
}
// Rewrite the selectedViewController's setter
- (void)setSelectedViewController:(UIViewController *)selectedViewController
{
NSParameterAssert (selectedViewController);
[self _transitionToChildViewController:selectedViewController];
_selectedViewController = selectedViewController;
}
// Switching operation (customized, Lenovo I used to switch the NetEase tab bar in the previous article. The transitionFromViewController given by the system is the same)
- (void)_transitionToChildViewController:(UIViewController *)toViewController
{
UIViewController *fromViewController = > 0 ? [0] : nil;
if (toViewController == fromViewController) {
return;
}
UIView *toView = ;
[toView setTranslatesAutoresizingMaskIntoConstraints:YES];
= UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
= ;
// AddChildViewController is the key to customizing the switch of containers, which ensures that the VC you want to display can be loaded into the container.
// The so-called animation and context are just for the animation effect of transition.
// Therefore, even if you use UIScrollView to switch, addChildViewController cannot be missing. Remember! Remember!
[fromViewController willMoveToParentViewController:nil];
[self addChildViewController:toViewController];
if (!fromViewController) {
[ addSubview:];
[toViewController didMoveToParentViewController:self];
return;
}
// Animator
FTMthTransitionAnimator *transitionAnimator = [[FTMthTransitionAnimator alloc] init];
NSUInteger fromIndex = [ indexOfObject:fromViewController];
NSUInteger toIndex = [ indexOfObject:toViewController];
// Context
FTMthTransitionContext *transitionContext = [[FTMthTransitionContext alloc] initWithFromViewController:fromViewController toViewController:toViewController goingRight:(toIndex > fromIndex)];
= YES;
= NO;
= ^(BOOL didComplete) {
// Because it is non-interactive, fromVC can directly remove its parent's children controllers array
[ removeFromSuperview];
[fromViewController removeFromParentViewController];
[toViewController didMoveToParentViewController:self];
if ([transitionAnimator respondsToSelector:@selector (animationEnded:)]) {
[transitionAnimator animationEnded:didComplete];
}
};
// Transition animation needs to be based on the transition context, because we are a custom Context, so we need to set it manually.
[transitionAnimator animateTransition:transitionContext];
}
The mission is done.
What is shown above is a non-interactive transition switch of a basic custom container. What about interactive? From the above I defined the gesture as swip instead of pan, it can be seen that non-interactive transitions cannot fully realize the pagination effect of UIScrollView. The switch between fromVC and toVC is performed in a similar percentage form because we lack an interactive controller. In the custom container, the system does not provide a protocol to return the interactive controller. After checking a lot of information, I did not find a clear method. I think that to implement the transition context, we should follow the system method and customize the interactive protocol method. We should think about how the system built this environment.