点击状态栏回到顶部的功能失效的解决办法

 

在我们IOS开发中,UIScrollView自带有点击顶部状态栏自动返回顶部的效果,不过这个效果是有约束条件的:

// When the user taps the status bar, the scroll view beneath the touch which is closest to the status bar will be scrolled to top, but only if its `scrollsToTop` property is YES, its delegate does not return NO from `shouldScrollViewScrollToTop`, and it is not already at the top.
// On iPhone, we execute this gesture only if there's one on-screen scroll view with `scrollsToTop` == YES. If more than one is found, none will be scrolled.
@property(nonatomic) BOOL  scrollsToTop __TVOS_PROHIBITED;          // default is YES.

从上面分析我们可以得出结论:我们必须保证窗口上scrollsToTop == YESScrollView(及其子类)同一时间内有且只有一个。这一样才能保证点击statusBar,该唯一存在的ScrollView能自动回到顶部。即这个手势只能作用在一个scrollView上,当发现多个时,手势将会失效。

在实际应用中,我们可能会有多个scrollView(包含UITableView/UICollectionView),如汽车之家、网易新闻、爱奇艺等等应用,这时候,系统默认的点击状态栏返回到顶部效果就会失效,

如何保证苹果自带的该功能一直好使呢?

解决方案一:

当前显示哪个tableView,哪个的scrollsToTop就设置为YES,其余的设置为NO;

解决方案二:自己实现

初级思路:在statusBar的区域添加一个遮盖,监听遮盖的点击事件 (用监听也可以就是有点low  下面有更好的方法用递归)

1.首先我们想到用UIView来做这个遮盖。但是,在这里我们使用UIView是着不住statusBar的,UIView会一直在statusBar的下面,所以不能接收点击事件。因为statusBar其实是一个UIWindow,且优先级高于下面的keyWindow。所以,添加的UIView会在statusBar的下面。

2.由于优先级的关系,我们可以用一个UIWindow来做遮盖,设置遮盖window的优先级高于statusBar即可。当然,设置最高优先级(UIWindowLevelAlert)肯定是可以的。然后给遮盖Window添加一个点击事件,背景色设置透明即可

代码如下

#import "AppDelegate.h"

@interface AppDelegate ()
@property(strong, nonatomic) UIWindow *coverStatusBarWindow; // 覆盖在statusBar上的透明窗口
@end

在代理方法中添加 coverStatusBarWindow 

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    //添加coverStatusBarWindow 并让其显示出来
    UIWindow * coverStatusBarWindow =[[UIWindow alloc]initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, 20)];
    coverStatusBarWindow.rootViewController = [[UIViewController alloc]init];
    coverStatusBarWindow.backgroundColor = [UIColor clearColor];
    coverStatusBarWindow.windowLevel = UIWindowLevelStatusBar+1;
    [coverStatusBarWindow makeKeyAndVisible];
    self.coverStatusBarWindow = coverStatusBarWindow;
    
    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(coverWindowOnClicked)];
    [self.coverStatusBarWindow addGestureRecognizer:tap];


    self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    self.window.backgroundColor = [UIColor grayColor];
    // 创建一个控制器
    UIViewController *vc = [[UIViewController alloc] init];
    self.window.rootViewController = vc;
    self.window.windowLevel = UIWindowLevelNormal;
    // 让UIwindow成为keyWindow(主窗口),并且可见
    [self.window1 makeKeyAndVisible];
    // 给UIwindow添加一个输入框
    UITextField *tf = [[UITextField alloc] init];
    tf.frame = CGRectMake(10, 64, 100, 20);
    tf.borderStyle = UITextBorderStyleRoundedRect;
    [self.window addSubview:tf];

    return YES;


}

//发通知可以让其他的页面监听到状态栏的点击
- (void)coverWindowOnClicked{
    [[NSNotificationCenter defaultCenter]postNotificationName:@"onClickedStatusBarNotification" object:self userInfo:nil];
}

在其他控制器里面监听

- (void)viewDidLoad {
    [super viewDidLoad];

    [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(onClickedStatusBar:) name:@"kOnClickedStatusBarNotification" object:nil];
    
}

- (void)onClickedStatusBar:(NSNotification *)noti{
    //让当前所显示的tableView 回到最顶部 (偏移量看情况设定哦)
    self.currentTableView.contentOffset = CGPointMake(0, 0);
}

- (void)dealloc{
    [[NSNotificationCenter defaultCenter]removeObserver:self];
}

想移除coverStatusBarWindow 将其赋值为空

    self.coverStatusBarWindow = nil;

这里面可以将  coverStatusBarWindow 抽出来封装一下  提供 创建sharedCoverStatusBarWindow的方法 和show  和 dismiss 的方法

自己可以去试一下 

最终思路:

1).创建一个UIWindow,背景颜色设置成透明色,frame设置成statusBar的frame,覆盖掉statusBar

2).给这个window添加一个点击的手势,点击这个window就遍历keyWindow中所有的子控件,取出当前显示在眼前的UIScrollView,将其滑动到顶部

代码如下(封装了一下)

.h文件

//
//  JHStatusBarScrollsToTopManager.h
//  TestQuestion
//
//  Created by  Mark on 2016/10/28.
//  Copyright © 2016年 Mark. All rights reserved.
//  点击头部的状态栏,当前显示在眼前的scrollView就会移动到最初的位置,用于解决有嵌套多个scrollView 系统scrollsToTop 禁用问题

#import <Foundation/Foundation.h>

@interface JHStatusBarScrollsToTopManager : NSObject


/**
 生效
 */
+ (void)becomeEffective;

/**
 失效
 */
+ (void)disabled;

@end

.m文件

//
//  JHStatusBarScrollsToTopManager.m
//  TestQuestion
//
//  Created by Mark on 2016/10/28.
//  Copyright © 2016年 淡蓝. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "JHStatusBarScrollsToTopManager.h"

@implementation JHStatusBarScrollsToTopManager

static UIWindow *statusBarWindow;

+ (void)becomeEffective {
    if (statusBarWindow == nil) {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            statusBarWindow = [[UIWindow alloc] init];
            statusBarWindow.frame = [UIApplication sharedApplication].statusBarFrame;
            statusBarWindow.backgroundColor = [UIColor clearColor];
            statusBarWindow.windowLevel = UIWindowLevelStatusBar+1;
            [statusBarWindow addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(topWindowClick)]];
            statusBarWindow.hidden = NO;
        });
    }
    
    else {
        statusBarWindow.hidden = NO;
    }
}

+ (void)disabled {
    statusBarWindow.hidden = YES;//不用在赋值为空了 为了下次不用再次创建
}

- (void)topWindowClick {
    // 用递归的思想找到所有的UIScrollView
    UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
    [self searchAllScrollViewsInView:keyWindow];
}

/**
 *  找到view里面的所有UIScrollView 并 取出当前显示在眼前的UIScrollView将其滑动到顶部
 */
- (void)searchAllScrollViewsInView:(UIView *)view {
    
    // 这个for循环可以保证所有子控件都能传进来
    for (UIView *subview in view.subviews) {
        [self searchAllScrollViewsInView:subview];
    }
    
    // 如果不是UIScrollView,直接返回
    if (![view isKindOfClass:[UIScrollView class]]) return;
    
    //如果是scrollView则进行如下的处理
    UIScrollView *scrollView = (UIScrollView *)view;
    
    CGRect scrollViewRect = [scrollView convertRect:scrollView.bounds toView:nil];
    CGRect keyWindowRect = [UIApplication sharedApplication].keyWindow.bounds;
    
    // 如果scrollView的矩形框 跟 keywindow 没有重叠,(表示不是显示在眼前的UIScrollView)直接返回
    if (!CGRectIntersectsRect(scrollViewRect, keyWindowRect)) return;
    
    // 若scrollView与keyWindow重叠(是显示在眼前的UIScrollView)让UIScrollView滚动到最前面
    [scrollView scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:YES];
    
}


@end
原文地址:https://www.cnblogs.com/junhuawang/p/6003191.html