开发中由于系统版本以及sdk,第三方库等原因,都会影响成出现一些奇怪的问题。当时是引用YYTextview控件解决消息cell长文本的光标选择范围内容复制的小功能。由于YYTextview光标弹出层自定义在另外一个新的Windown上,然后左侧侧滑出的view是新增在另一个新的Windown中。当侧边栏dismiss的时候,可能由于windown层级的影响,当在聊天输入框再次输入文字长按时候,系统出现的UIMenu层的Button不见了。当时想从根本问题去排查为啥系统的UIMenu当长按的时候操作功能的button为啥不见去解决,花费了将近2个多小时通过Xcode调试查看层级,阅读YYTextview的光标自定义层的实现以及添加到windown的层级,查看左侧侧边栏添加动画实现方式等都未解决。然后想到如果不通过新创建window来添加view 实现动画,而是通过present一个控制器实现侧边栏动画。最终问题得到了解决。

旧Slide侧滑方案

旧有的侧滑方案是创建一个新window,然后设置其windowLevel 为正常的+1,这样就在最顶层了,然后设置其rootViewController为侧滑的控制器。很有可能是新创建的window与YYtextView的光标选择层创建的window有冲突,然后造成UIMenu按钮消失了。

改进方案,Present一个控制器 SlideVC

实则我们可以通过拿到最顶层控制器topVC,用导航控制器把SlideVC包裹好,设置它的 modalPresentationStyle 模式为UIModalPresentationOverCurrentContext,然后present出来 .这里要注意的是,系统的present方法是否有被runtime method swizled进行方法替换来公共统一处理modelPresentStyle问题,因为有些App交互不想使用系统的抽屉式present效果, 如果有需要做相关的过滤业务控制器逻辑判断。 侧边动画的实现:在SlideVC中定义一个半透明层背景bgView,添加tap手势,点击即关闭。往bgView添加一个左侧内容视图leftView 75%宽,右侧rightView。self.view添加一个Pan手势,用来计算leftView拖拽时候X的值位移变化。将内容控制器交由SlideVC管理:

1
2
[self addChildViewController:self.leftVC];
[self.leftVC didMoveToParentViewController:self];

当slideVC控制器ViewDidApper显示的时候,我们即做动画(可以加一个开关状态判断其是否打开)。根据侧滑手指移动距离是否使内容View到屏幕的一半,否则复位。然后可以适当做一点弹性动画。通过UIview的animateWithDuration: usingSpringWithDamping:方法进行设置。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (void)showAnimation {
self.view.userInteractionEnabled = NO;
self.menuOpenFlag = YES;
if(!self.hasMoreThenHalf){
CGAffineTransform translate = CGAffineTransformMakeTranslation(-300, 0);
CGAffineTransform scale = CGAffineTransformMakeScale(3.0, 1.0);
self.modalView.transform = CGAffineTransformConcat(translate, scale);
}
CGFloat time = fabs(self.modalView.frame.origin.x / self.modalView.frame.size.width) * animationTime;

[UIView animateWithDuration:time delay:0.0 usingSpringWithDamping:0.85 initialSpringVelocity:0.7 options:UIViewAnimationOptionCurveLinear animations:^{
if(self.hasMoreThenHalf){
self.modalView.frame = CGRectMake(0, 0, MenuWidth, [UIScreen mainScreen].bounds.size.height);
}else{
self.modalView.transform = CGAffineTransformIdentity;
}
} completion:^(BOOL finished) {
self.view.userInteractionEnabled = YES;
self.menuOpenFlag = YES;
}];
}

当关闭的时候:进行视图的复位动画,然后dismiss控制器。这个是主动关闭。另外需要预留好被动其他业务情况下需要关闭侧边栏的情况,注册好相关的通知,当收到通知时,就自动关闭了侧边栏。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (void)closeAnimation {
self.view.userInteractionEnabled = NO;
self.hasMoreThenHalf = NO;
// 根据当前x,计算隐藏时间
CGFloat time = (1 - fabs(self.modalView.frame.origin.x / self.modalView.frame.size.width)) * animationTime;
[UIView animateWithDuration:time animations:^{
self.modalView.frame = CGRectMake(-self.modalView.frame.size.width, 0, self.modalView.frame.size.width, [UIScreen mainScreen].bounds.size.height);
self.bgView.alpha = 0.0;
} completion:^(BOOL finished) {
self.menuOpenFlag = NO;
[self dismissViewControllerAnimated:false completion:nil];
}];

}

Pan手势的事件控制LeftView X坐标位移

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
- (void)moveViewWithGesture:(UIPanGestureRecognizer *)panGes {
// 开始位置
static CGFloat startX;
// 结束位置
static CGFloat lastX;
// 改变多少
static CGFloat durationX;

CGPoint touchPoint = [panGes locationInView:self.view];
// 手势开始
if (panGes.state == UIGestureRecognizerStateBegan) {
startX = touchPoint.x;
lastX = touchPoint.x;
}

if (panGes.state == UIGestureRecognizerStateChanged) {
CGFloat currentX = touchPoint.x;
durationX = currentX - lastX;
lastX = currentX;
self.hasMoreThenHalf = NO;
CGFloat leftVC_X = durationX + self.modalView.x;
if (leftVC_X <= -self.modalView.width) {
leftVC_X = - self.modalView.width;
}

//如果是向右滑动
if (leftVC_X > 0) {
leftVC_X = 0;
}

self.bgView.alpha = (1 + leftVC_X / self.modalView.frame.size.width) * 0.5;

[self.modalView setFrame:CGRectMake(leftVC_X, 0, _modalView.frame.size.width, _modalView.frame.size.height)];

}

if (panGes.state == UIGestureRecognizerStateEnded) {
// 超过一半屏幕
if (_modalView.x > - _modalView.frame.size.width + [UIScreen mainScreen].bounds.size.width / 2) {
self.hasMoreThenHalf = YES;
[self showAnimation];
}else{
[self closeAnimation];
}
}
}

H5页面奇怪的UIDocumentMenuViewController

在边栏控制器点击菜单进入进入意见反馈模块,通过input标签打开原生图片选择器出现闪退。此时系统的present会经过两层。第一层是UIDocumentMenuViewController,第二层才到系统选择相册那个控制器。所以我们可以在我们的导航控制器中重写dismiss方法,根据presentedViewController是否为UIDocumentMenuViewController做一个标记documentFlag, 需要手动控制当第二次系统调用dismiss时候就无需回调父类的dismiss方法。

1
2
3
4
5
6
7
8
9
10
11
12
-(void) dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion{
if(self.presentedViewController != nil && [self.presentedViewController isKindOfClass:[NSClassFromString(@"UIDocumentMenuViewController") class]]){
self.documentFlag = YES;
[super dismissViewControllerAnimated:flag completion:completion];
}else{
//第二次dismiss阻止掉。
if(!self.documentFlag){
[super dismissViewControllerAnimated:flag completion:completion];
}
self.documentFlag = NO;
}
}

总结:问题本身不可怕,可怕的是不愿意尝试从影响该问题点去绕开该点所导致的问题。这本身也是一种解决问题的捷径方案。当产生新的问题点时候,我们只需耐心调试,步步细心,发现造成问题点的直接原因。

评论