@继续前面的内容,这一章,主要介绍自定义ViewController容器上视图VC的切换.先来看看系统给我们提供的容器控制器 UINavigationController和UITabBarController 都有一个NSArray类型的属性viewControllers,很明显,存储的就是需要切换的视图VC.同理,我们定义一个ContainerViewController,是UIViewController的直接子类,用来作为容器依托,额,其他属性定义详见代码吧,这里不多说了.(PS:原先我进行多个自定义视图VC切换的方法,是放置一个UIScrollView,然后把所有childViewController的View的frame的X坐标,依此按320递增,大家可以自行想象下,这样不好的地方,我感觉就是所有的VC一经加载就全部实体化了,而且不会因为被切换变成暂不显示而释放掉)
@偷懒下,用storyboard创建的5个childVC
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
// 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 *selectedViewController;
// 当前选择的VC
@property
(strong, nonatomic) NSArray *viewControllers;
// childVC数组
@property
(assign, nonatomic) NSInteger currentControllerIndex;
// 当前选择的VC的数组下标号
@end
@implementation
FTContainerViewController
#pragma mark - ViewLifecycle Methods
- (
void
)viewDidLoad
{
[
super
viewDidLoad];
// childVC
self.photoSenderViewController = [self.storyboard instantiateViewControllerWithIdentifier:
@FTPhotoSenderViewController
];
self.videoSenderViewController = [self.storyboard instantiateViewControllerWithIdentifier:
@FTVideoSenderViewController
];
self.fileSenderViewController = [self.storyboard instantiateViewControllerWithIdentifier:
@FTFileSenderViewController
];
self.contactSenderViewController = [self.storyboard instantiateViewControllerWithIdentifier:
@FTContactSenderViewController
];
self.clipboardSenderViewController = [self.storyboard instantiateViewControllerWithIdentifier:
@FTClipboardSenderViewController
];
// 存储childVC的数组
self.viewControllers = @[_photoSenderViewController,_videoSenderViewController,_fileSenderViewController,_contactSenderViewController,_clipboardSenderViewController];
// 缺省为下标为0的VC
self.selectedViewController = self.selectedViewController ?: self.viewControllers[
0
];
self.currentControllerIndex =
0
;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
#
import
FTMthTransitionAnimator.h
@implementation
FTMthTransitionAnimator
static
CGFloat
const
kChildViewPadding =
16
;
static
CGFloat
const
kDamping =
0.5
;
// damping参数代表弹性阻尼,随着阻尼值越来越接近0.0,动画的弹性效果会越来越明显,而如果设置阻尼值为1.0,则视图动画不会有弹性效果
static
CGFloat
const
kInitialSpringVelocity =
0.5
;
// 初始化弹簧速率
- (NSTimeInterval)transitionDuration:(id<uiviewcontrollercontexttransitioning>)transitionContext
{
return
1.0
;
}
- (
void
)animateTransition:(id<uiviewcontrollercontexttransitioning>)transitionContext
{
/**
* - viewControllerForKey:我们可以通过他访问过渡的两个 ViewController。
* - containerView:两个 ViewController 的 containerView。
* - initialFrameForViewController 和 finalFrameForViewController 是过渡开始和结束时每个 ViewController 的 frame。
*/
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
[[transitionContext containerView] addSubview:toViewController.view];
toViewController.view.alpha =
0
;
BOOL goingRight = ([transitionContext initialFrameForViewController:toViewController].origin.x < [transitionContext finalFrameForViewController:toViewController].origin.x);
CGFloat transDistance = [transitionContext containerView].bounds.size.width + kChildViewPadding;
CGAffineTransform transform = CGAffineTransformMakeTranslation(goingRight ? transDistance : -transDistance,
0
);
// CGAffineTransformInvert 反转
toViewController.view.transform = CGAffineTransformInvert(transform);
// toViewController.view.transform = CGAffineTransformTranslate(toViewController.view.transform, (goingRight ? transDistance : -transDistance), 0);
/**
* ----------弹簧动画.....-------
* 使用由弹簧的运动描述的时序曲线` animations` 。当` dampingRatio`为1时,动画将平稳减速到其最终的模型值不会振荡。阻尼比小于1来完全停止前将振荡越来越多。可以使用弹簧的初始速度,以指定的速度在模拟弹簧的端部的物体被移动它附着之前。这是一个单元坐标系,其中1是指行驶总距离的动画在第二。所以,如果你改变一个物体的位置由200PT在这个动画,以及你想要的动画表现得好像物体在动,在100PT /秒的动画开始之前,你会通过0.5 。你通常会想通过0的速度。
*/
[UIView animateWithDuration:[self transitionDuration:transitionContext] delay:
0
usingSpringWithDamping:kDamping initialSpringVelocity:kInitialSpringVelocity options:
0x00
animations:^{
fromViewController.view.transform = transform;
fromViewController.view.alpha =
0
;
// CGAffineTransformIdentity 重置,初始化
toViewController.view.transform = CGAffineTransformIdentity;
toViewController.view.alpha =
1
;
} completion:^(BOOL finished) {
fromViewController.view.transform = CGAffineTransformIdentity;
// 声明过渡结束-->记住,一定别忘了在过渡结束时调用 completeTransition: 这个方法。
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
@end
</uiviewcontrollercontexttransitioning></uiviewcontrollercontexttransitioning>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
|
@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 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])) {
self.presentationStyle = UIModalPresentationCustom;
self.containerView = fromViewController.view.superview;
self.privateViewControllers = @{
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 ? -self.containerView.bounds.size.width : self.containerView.bounds.size.width);
self.privateDisappearingFromRect = self.privateAppearingToRect = self.containerView.bounds;
self.privateDisappearingToRect = CGRectOffset (self.containerView.bounds, travelDistance,
0
);
self.privateAppearingFromRect = CGRectOffset (self.containerView.bounds, -travelDistance,
0
);
}
return
self;
}
- (CGRect)initialFrameForViewController:(UIViewController *)viewController {
if
(viewController == [self viewControllerForKey:UITransitionContextFromViewControllerKey]) {
return
self.privateDisappearingFromRect;
}
else
{
return
self.privateAppearingFromRect;
}
}
- (CGRect)finalFrameForViewController:(UIViewController *)viewController {
if
(viewController == [self viewControllerForKey:UITransitionContextFromViewControllerKey]) {
return
self.privateDisappearingToRect;
}
else
{
return
self.privateAppearingToRect;
}
}
- (UIViewController *)viewControllerForKey:(NSString *)key {
return
self.privateViewControllers[key];
}
- (
void
)completeTransition:(BOOL)didComplete {
if
(self.completionBlock) {
self.completionBlock (didComplete);
}
}
// 非交互式,直接返回NO,因为不允许交互当然也就无法操作进度取消
- (BOOL)transitionWasCancelled {
return
NO; }
// 非交互式,直接不进行操作,只有进行交互,下面3个协议方法才有意义,可参照系统给我们定义好的交互控制器
// @interface UIPercentDrivenInteractiveTransition : NSObject <uiviewcontrollerinteractivetransitioning>
- (
void
)updateInteractiveTransition:(CGFloat)percentComplete {}
- (
void
)finishInteractiveTransition {}
- (
void
)cancelInteractiveTransition {}
@end
</uiviewcontrollerinteractivetransitioning></uiviewcontrollercontexttransitioning>
|
1
2
3
4
5
6
7
|
UISwipeGestureRecognizer *leftGesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:
@selector
(swapController:)];
[leftGesture setDirection:UISwipeGestureRecognizerDirectionLeft];
[self.view addGestureRecognizer:leftGesture];
UISwipeGestureRecognizer *rightGesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:
@selector
(swapController:)];
[rightGesture setDirection:UISwipeGestureRecognizerDirectionRight];
[self.view addGestureRecognizer:rightGesture];
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
// 响应手势的方法
- (
void
)swapViewControllers:(UISwipeGestureRecognizer *)swipeGestureRecognizer
{
if
(swipeGestureRecognizer.direction == UISwipeGestureRecognizerDirectionLeft) {
if
(_currentControllerIndex <
4
) {
_currentControllerIndex++;
}
NSLog(
@_currentControllerIndex
= %ld,(
long
)_currentControllerIndex);
UIViewController *selectedViewController = self.viewControllers[_currentControllerIndex];
NSLog(@%s__%d__|%@,__FUNCTION__,__LINE__,@右边);
self.selectedViewController = selectedViewController;
}
else
if
(swipeGestureRecognizer.direction == UISwipeGestureRecognizerDirectionRight){
NSLog(@%s__%d__|%@,__FUNCTION__,__LINE__,@左边);
if
(_currentControllerIndex >
0
) {
_currentControllerIndex--;
}
UIViewController *selectedViewController = self.viewControllers[_currentControllerIndex];
self.selectedViewController = selectedViewController;
}
}
// 重写selectedViewController的setter
- (
void
)setSelectedViewController:(UIViewController *)selectedViewController
{
NSParameterAssert (selectedViewController);
[self _transitionToChildViewController:selectedViewController];
_selectedViewController = selectedViewController;
}
// 切换操作(自定义的,联想我在前面文章网易标签栏切换中,系统给的transitionFromViewController,是一个道理)
- (
void
)_transitionToChildViewController:(UIViewController *)toViewController
{
UIViewController *fromViewController = self.childViewControllers.count >
0
? self.childViewControllers[
0
] : nil;
if
(toViewController == fromViewController) {
return
;
}
UIView *toView = toViewController.view;
[toView setTranslatesAutoresizingMaskIntoConstraints:YES];
toView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
toView.frame = self.view.bounds;
// 自定义容器的切换,addChildViewController是关键,它保证了你想要显示的VC能够加载到容器中
// 而所谓的动画和上下文,只是为了转场的动画效果
// 因此,就算用UIScrollView切换,也不能缺少addChildViewController,切记!切记!
[fromViewController willMoveToParentViewController:nil];
[self addChildViewController:toViewController];
if
(!fromViewController) {
[self.view addSubview:toViewController.view];
[toViewController didMoveToParentViewController:self];
return
;
}
// Animator
FTMthTransitionAnimator *transitionAnimator = [[FTMthTransitionAnimator alloc] init];
NSUInteger fromIndex = [self.viewControllers indexOfObject:fromViewController];
NSUInteger toIndex = [self.viewControllers indexOfObject:toViewController];
// Context
FTMthTransitionContext *transitionContext = [[FTMthTransitionContext alloc] initWithFromViewController:fromViewController toViewController:toViewController goingRight:(toIndex > fromIndex)];
transitionContext.animated = YES;
transitionContext.interactive = NO;
transitionContext.completionBlock = ^(BOOL didComplete) {
// 因为是非交互式,所以fromVC可以直接直接remove出its parent's children controllers array
[fromViewController.view removeFromSuperview];
[fromViewController removeFromParentViewController];
[toViewController didMoveToParentViewController:self];
if
([transitionAnimator respondsToSelector:
@selector
(animationEnded:)]) {
[transitionAnimator animationEnded:didComplete];
}
};
// 转场动画需要以转场上下文为依托,因为我们是自定义的Context,所以要手动设置
[transitionAnimator animateTransition:transitionContext];
}
|
上面展示的就是一个基本的自定义容器的非交互式的转场切换.那交互式的呢?从上面我定义手势定义为swip而不是pan也可以看出,非交互转场,并不能完全实现UIScrollView那种分页式的效果,按照类似百分比的形式来进行fromVC和toVC的切换,因为我们缺少交互控制器.在自定义的容器中,系统是没有提供返回交互控制器的协议给我们的,查了蛮多资料,也没找到给出明确的方法,我认为,要跟实现转场上下文一样,仿照系统方法,自定义的去实现交互式的协议方法.我们就要去思考,系统是如何搭建起这个环境的.
容后续给出响应的Demo,目前研究中.......