UINavigationController的滑动返回作为iOS7的新特性被引入,但是只有在滑动视图的左边缘的时,才可以体验这样的交互。如果,想要滑动视图的任意位置,想要体验这样的交互,就需要自己动手处理了,后者使用第三方库。
另外,最近的程序中遇到这样的一个问题:push到一个钱包界面,这个界面请求钱包的内容。这个界面比较敏感,涉及钱财,采取了一些安全措施。其中的一个就是客户端距离上次请求验证的接口超过有效时间,就会要求用户再次登陆。那么对于这里,就是在进入钱包界面的时候,请求数据,服务器端如果认为登陆状态失效了,就需要引导用户再次登陆。如果用户不愿意登陆,点击返回到钱包界面,因为用户没有再次登陆,为了安全起见,需要立刻跳转到钱包的前一个界面。如此来回快速的push和pop很容易引起crash,(如can't add self as subview等问题)。
关于这个滑动返回还要涉及的一个问题就是navigationbar的颜色更改和隐藏与否。如果不能很好处理navigationbar的颜色更改,就有可能出现navigationbar的颜色是现在的颜色和下一个颜色的过渡色;当前界面不隐藏navigationbar,下一个界面隐藏,如果不能很好处理,就有可能出现下个界面因为要处理navigationbar而引起界面的跳动。
观察了一些已有的app的,有些处理的很好,于是,就想尝试自己写一个。
两个navigationbar的颜色不同,滑动返回也不会 滑动返回中,上一个navigationbar上的文本与
导致两个navigationbar串色。 当前的重叠。
1.分析滑动返回:
滑动是一个手势。滑动返回是利用手势来控制当前视图的移动,另外,上一个界面也会因为当前界面右移而右移显示出来。这里需要用到截屏,也就是说上一个界面其实是截取的一个“图片”。
2.分析连续的pop和push:
连续的pop和push之所以会引起问题,是因为动画需要时间,在动画没有结束之前,来回返回push和pop就会引起异常,解决的方式是在一个UIViewController或者其子类的viewDidAppear方法里面进行push和pop操作,也就是说要视图完全呈现出来,动画完全结束后,再进行操作。
解决方法:
使用一个数组来保存所有的push和pop操作,在确定了viewDidAppear方法被调用后,再一次执行数组里面保存的操作。
滑动返回则需要手势识别器UIPanGestureRecognizer,视图随着手势的移动而移动。
首先是滑动返回:
<span style="font-family:KaiTi_GB2312;font-size:18px;"> UIPanGestureRecognizer *panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)];
[self.view addGestureRecognizer:panRecognizer];</span>
处理手势:
<span style="font-family:KaiTi_GB2312;font-size:18px;">- (void)handlePanGesture:(UIPanGestureRecognizer *)pan {
if (!self.dragEnable || self.viewControllers.count < 2) {
return;
}
CGPoint touchPoint = [pan locationInView:KEY_WINDOW];
if (pan.state == UIGestureRecognizerStateBegan) {
self.startTouchPoint = touchPoint;
if (![self.view.superview.subviews containsObject:self.containerView]) {
[self.view.superview insertSubview:self.containerView belowSubview:self.view];
}
if (![self.containerView.subviews containsObject:self.lastScreenImageView]) {
[self.containerView addSubview:self.lastScreenImageView];
}
self.lastScreenImageView.image = [self.screenShots lastObject];
CGRect frame = self.lastScreenImageView.frame;
frame.origin.x = self.startX;
frame.size = [[UIScreen mainScreen] bounds].size;
self.lastScreenImageView.frame = frame;
self.containerView.hidden = NO;
}
else if (pan.state == UIGestureRecognizerStateEnded) {
if (touchPoint.x - self.startTouchPoint.x > CGRectGetWidth(self.view.frame) / 3.0) {
[UIView animateWithDuration:self.animationDuration animations:^{
[self __moveViewWithOffsetX:CGRectGetWidth(self.view.frame)];
}completion:^(BOOL finished) {
[self popViewControllerAnimated:NO];
CGRect frame = self.view.frame;
frame.origin = CGPointZero;
self.view.frame = frame;
self.containerView.hidden = YES;
}];
}
else {
[UIView animateWithDuration:self.animationDuration animations:^{
[self __moveViewWithOffsetX:0];
}completion:^(BOOL finished) {
self.containerView.hidden = YES;
}];
}
return;
}
else if (pan.state == UIGestureRecognizerStateCancelled) {
[self __resetOriginalState];
return;
}
[self __moveViewWithOffsetX:touchPoint.x - self.startTouchPoint.x];
}
</span>
移动视图界面:
<span style="font-family:KaiTi_GB2312;font-size:18px;">- (void)__moveViewWithOffsetX:(CGFloat)x {
x = MAX(MIN(CGRectGetWidth(self.view.frame), x), 0);
CGRect frame = self.view.frame;
frame.origin.x = x;
self.view.frame = frame;
CGFloat rate = fabsf(self.startX) / CGRectGetWidth(self.view.frame);
CGFloat distance = rate * x;
self.lastScreenImageView.frame = CGRectMake(self.startX + distance, 0, CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame));
}
</span>
当滑动界面没有达到滑动临界值时,所要把当前的界面设置为“原始”状态:
<span style="font-family:KaiTi_GB2312;font-size:18px;">- (void)__resetOriginalState {
[UIView animateWithDuration:self.animationDuration animations:^{
[self __moveViewWithOffsetX:0];
}completion:^(BOOL finished) {
self.containerView.hidden = YES;
}];
}</span>
在处理连续push和pop的时候,重写了pushViewController:animated: popViewControllerAnimated: popToViewControllerAnimated: popToRootViewControllerAnimated:这四个方法
<pre name="code" class="objc"><span style="font-family:KaiTi_GB2312;font-size:18px;">- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
__weak typeof(self) weakSelf = self;
[self __addTransitionBlock:^{
[weakSelf.screenShots addObject:[weakSelf __captureScreen]];
[super pushViewController:viewController animated:animated];
}];
}
- (UIViewController *)popViewControllerAnimated:(BOOL)animated {
__weak typeof(self)weakSelf = self;
[self __addTransitionBlock:^{
[weakSelf.screenShots removeLastObject];
UIViewController *viewController = [super popViewControllerAnimated:animated];
if (viewController == nil) {
weakSelf.transitionInProcess = NO;
}
}];
return nil;
}
- (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated {
__weak typeof(self)weakSelf = self;
[self __addTransitionBlock:^{
if ([weakSelf.viewControllers containsObject:viewController]) {
NSInteger index = [weakSelf.viewControllers indexOfObject:viewController];
if (weakSelf.viewControllers.count > index) {
[weakSelf.screenShots gf_removeObjectsFromIndex:index];
}
NSArray *viewControllers = [super popToViewController:viewController animated:animated];
if (viewControllers.count == 0) {
weakSelf.transitionInProcess = NO;
}
}
else {
weakSelf.transitionInProcess = NO;
}
}];
return nil;
}
- (NSArray *)popToRootViewControllerAnimated:(BOOL)animated {
__weak typeof(self) weakSelf = self;
[self __addTransitionBlock:^{
if (weakSelf.screenShots.count) {
[weakSelf.screenShots gf_removeObjectsFromIndex:0];
}
NSArray *viewControllers = [super popToRootViewControllerAnimated:animated];
if (viewControllers.count == 0) {
weakSelf.transitionInProcess = NO;
}
}];
return nil;
}</span>
这里需要注意的是:正式因为把对NavigationController的堆栈操作都写到block里面,pop的一系列方法将没有返回值。
pendingBlocks是一个数组,保存了对navigationController堆栈的操作
<span style="font-family:KaiTi_GB2312;font-size:18px;">- (void)__addTransitionBlock:(PendingBlock)block {
if (self.transitionInProcess) {
[self.pendingBlocks addObject:[block copy]];
}
else {
_transitionInProcess = YES;
block();
}
}
- (void)__runNextTransition {
if (self.pendingBlocks.count) {
PendingBlock block = [self.pendingBlocks lastObject];
[self.pendingBlocks removeLastObject];
block();
}
}</span>
另外使用transitionInProcess来标记当前是否有navigationcontroller堆栈的操作
<span style="font-family:KaiTi_GB2312;font-size:18px;">@property (nonatomic, assign, getter = isTransitionInProcess) BOOL transitionInProcess;
</span>
<span style="font-family:KaiTi_GB2312;font-size:18px;">- (void)setTransitionInProcess:(BOOL)transitionInProcess {
_transitionInProcess = transitionInProcess;
if (!_transitionInProcess && self.pendingBlocks.count) {
_transitionInProcess = YES;
[self __runNextTransition];
}
}</span>
需要注意的一点是:整个布尔值需要在自己项目的BaseViewController 的viewDidAppear方法中设置为No, 因为一个视图已经展示了,可以运行下个操作了。我的项目中既视图控制器是GFBaseViewController, 你可以使用你的,只要在下面代码加入;或者都继承GFBaseViewController,就不用写下面的代码了。另外,需要在BaseViewController里面将self.view的背景色设置成白色,我是设置成白色了,当然,任何颜色都可以。但是,不要默认。
<span style="font-family:KaiTi_GB2312;font-size:18px;">- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
((GFNavigationController *)self.navigationController).transitionInProcess = NO;
}</span>
这样就可以随心所欲在viewWillAppear等地方push,pop了
<span style="font-family:KaiTi_GB2312;">- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
//push
UIViewController *controller = [[UIViewController alloc] init];
[self.navigationController pushViewController:controller animated:YES];
}</span>
运行效果图:

1815

被折叠的 条评论
为什么被折叠?



