【问题标题】:How to detect touches in status bar如何检测状态栏中的触摸
【发布时间】:2011-04-14 18:20:03
【问题描述】:

我的应用程序中有自定义视图,可以由用户滚动。但是,此视图不继承自 UIScrollView。现在我希望用户能够将此视图滚动到顶部,就像任何其他可滚动视图允许的那样。我认为没有直接的方法可以这样做。

Google 提出了一种解决方案:http://cocoawithlove.com/2009/05/intercepting-status-bar-touches-on.html 这不再适用于 iOS 4.x。这是不行的。

我的想法是创建一个滚动视图并将其保存在某个地方,只是为了捕捉它的通知,然后将它们转发给我的控件。这不是解决我的问题的好方法,所以我正在寻找“更清洁”的解决方案。我喜欢上述链接到子类 UIApplication 的一般方法。但是什么 API 可以为我提供可靠的信息?

有什么想法、帮助等吗?

编辑:我不喜欢当前解决方案的另一件事是,它仅在当前视图没有任何滚动视图时才有效。滚动到顶部手势仅在只有一个滚动视图存在时才有效。一旦将虚拟对象添加到具有另一个滚动视图的视图中(有关详细信息,请参阅下面的答案),该手势将完全禁用。寻找更好解决方案的另一个原因...

【问题讨论】:

  • 我想知道为什么状态栏不做任何事情就拦截触摸,

标签: iphone ios


【解决方案1】:

最后,我从这里的答案中收集了可行的解决方案。谢谢你们。

在某处声明通知名称(例如 AppDelegate.h):

static NSString * const kStatusBarTappedNotification = @"statusBarTappedNotification";

在您的 AppDelegate.m 中添加以下行:

#pragma mark - Status bar touch tracking
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [super touchesBegan:touches withEvent:event];
    CGPoint location = [[[event allTouches] anyObject] locationInView:[self window]];
    CGRect statusBarFrame = [UIApplication sharedApplication].statusBarFrame;
    if (CGRectContainsPoint(statusBarFrame, location)) {
        [self statusBarTouchedAction];
    }
}

- (void)statusBarTouchedAction {
    [[NSNotificationCenter defaultCenter] postNotificationName:kStatusBarTappedNotification
                                                        object:nil];
}

在所需的控制器中观察通知(例如在 viewWillAppear 中):

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(statusBarTappedAction:)             
                                             name:kStatusBarTappedNotification
                                           object:nil];

正确移除观察者(例如在 viewDidDisappear 中):

[[NSNotificationCenter defaultCenter] removeObserver:self name:kStatusBarTappedNotification object:nil];

实现通知处理回调:

- (void)statusBarTappedAction:(NSNotification*)notification {
    NSLog(@"StatusBar tapped");
    //handle StatusBar tap here.
}

希望它会有所帮助。


Swift 3 更新

在 iOS 9+ 上测试并运行。

在某处声明通知名称:

let statusBarTappedNotification = Notification(name: Notification.Name(rawValue: "statusBarTappedNotification"))

跟踪状态栏的触摸和发布通知。将以下行添加到您的 AppDelegate.swift:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    super.touchesBegan(touches, with: event)

    let statusBarRect = UIApplication.shared.statusBarFrame
    guard let touchPoint = event?.allTouches?.first?.location(in: self.window) else { return }

    if statusBarRect.contains(touchPoint) {
        NotificationCenter.default.post(statusBarTappedNotification)
    }
}

必要时注意通知:

NotificationCenter.default.addObserver(forName: statusBarTappedNotification.name, object: .none, queue: .none) { _ in
    print("status bar tapped")
}

【讨论】:

  • 确保您的 AppDelegate 扩展了 UIResponder 以便调用 -touchesBegan:withEvent:(旧项目可能不会)。
  • 您可能需要添加 touch.window.windowLevel == UIWindowLevelStatusBar 否则它将检测到 UIAlertView 顶部的点击
  • @Vyacheslav 为什么不试试并告诉我们
  • 大家好。注意到还有一个活动正在进行:)。更新了我的答案,添加了 Swift 3 代码 sn-p 并测试了该方法在 iOS 9 和 10 中仍然有效。
  • @SevenSystems,不,它没有
【解决方案2】:

所以这是我目前的解决方案,效果非常好。但是请提出其他想法,因为我不太喜欢它...

  • 在视图的某处添加滚动视图。也许将其隐藏或放置在其他视图下方等。
  • 将其contentSize 设置为大于界限
  • 设置非零contentOffset
  • 在您的控制器中实现滚动视图的委托,如下所示。

通过始终返回NO,滚动视图永远不会向上滚动,并且每当用户点击状态栏时都会收到通知。然而,问题在于,这不适用于“真实”的内容滚动视图。 (见问题)

- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView
{
    // Do your action here
    return NO;
}

【讨论】:

【解决方案3】:

将它添加到您的 AppDelegate.swift 将满足您的需求:

override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
    super.touchesBegan(touches, withEvent: event)
    let events = event!.allTouches()
    let touch = events!.first
    let location = touch!.locationInView(self.window)
    let statusBarFrame = UIApplication.sharedApplication().statusBarFrame
    if CGRectContainsPoint(statusBarFrame, location) {
        NSNotificationCenter.defaultCenter().postNotificationName("statusBarSelected", object: nil)
    }
}

现在您可以随时随地订阅活动:

NSNotificationCenter.defaultCenter().addObserverForName("statusBarSelected", object: nil, queue: nil) { event in

    // scroll to top of a table view
    self.tableView!.setContentOffset(CGPointZero, animated: true)
}

【讨论】:

  • 谢谢!这正是我想要的。 SWIFT 真的很简洁。我希望更多的人投票赞成这个答案!
  • 我正在尝试将其更新到 Swift 4,但遇到了一些问题
  • iOS 13 的任何解决方案?
【解决方案4】:

在这里找到了一个与 iOS7 兼容的更好的解决方案:http://ruiaureliano.tumblr.com/post/37260346960/uitableview-tap-status-bar-to-scroll-up

将此方法添加到您的 AppDelegate:

- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [super touchesBegan:touches withEvent:event];
    CGPoint location = [[[event allTouches] anyObject] locationInView:[self window]];
    if (CGRectContainsPoint([UIApplication sharedApplication].statusBarFrame, location)) {
        NSLog(@"STATUS BAR TAPPED!");
    }
}

【讨论】:

  • 在状态栏是双倍高度或不再是 20 像素的情况下,您的代码将失败。试试这样的方法: if (CGRectContainsPoint([UIApplication sharedApplication].statusBarFrame, location)) { /* added */ }
  • 如果旋转,即使倒置也会失败。不过,使用 20 像素比 statusBarFrame 更好,因为 statusBarFrame 包含 statusBar 的“通话中”部分,我认为您可能想忽略它。
  • 谢谢你!好棒!我建议你使用“[UIApplication sharedApplication].statusBarFrame.size.height”代替“20”。 ;)
  • 我不同意使用 20px 比状态栏框架更好。使用 statusBarFrame 如果状态栏被隐藏,则不会发布通知。如果你真的想在状态栏中检测触摸,而不仅仅是在窗口顶部
  • @amleszk 如果隐藏状态栏,为什么根本不调用 toucesBegan?至少不应该调用它,但是状态栏框架的高度为零等......?为什么根本不调用它?
【解决方案5】:

感谢 Max,经过多年的研究,您的解决方案对我有用。

有关信息:

dummyScrollView = [[UIScrollView alloc] init];
dummyScrollView.delegate = self;
[view addSubview:dummyScrollView];
[view sendSubviewToBack:dummyScrollView];   

然后

  dummyScrollView.contentSize = CGSizeMake(view.frame.size.width, view.frame.size.height+200);
  // scroll it a bit, otherwise scrollViewShouldScrollToTop not called
  dummyScrollView.contentOffset = CGPointMake(0, 1);  

//delegate :
- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView
{
  // DETECTED! - do what you need to
  NSLog(@"scrollViewShouldScrollToTop");
  return NO;
}

请注意,我也有一个 UIWebView,我不得不用我在某处找到的解决方案来破解它:

- (void)webViewDidFinishLoad:(UIWebView *)wv
{  
  [super webViewDidFinishLoad:wv];  

  UIScrollView *scroller = (UIScrollView *)[[webView subviews] objectAtIndex:0];
  if ([scroller respondsToSelector:@selector(setScrollEnabled:)])
    scroller.scrollEnabled = NO; 
}

【讨论】:

  • 恐怕这似乎不适用于我的设置。我有一个主 UIView,我有一个包含应用程序所有不同页面的滚动视图。我已经在主视图和AppDelegate(对于UIWindow)中插入了代码,但它不起作用!
  • 如果屏幕上有表格视图,则不起作用。即使tableView.scrollsToTop = false
【解决方案6】:

我通过在状态栏上添加一个清晰的 UIView 然后拦截触摸事件来实现这一点

首先在您的 Application 委托应用程序中:didFinishLaunchingWithOptions:添加这两行代码:

self.window.backgroundColor = [UIColor clearColor];
self.window.windowLevel = UIWindowLevelStatusBar+1.f;

然后在您希望拦截状态栏点击的视图控制器中(或在应用程序委托中)添加以下代码

UIView* statusBarInterceptView = [[[UIView alloc] initWithFrame:[UIApplication sharedApplication].statusBarFrame] autorelease];
statusBarInterceptView.backgroundColor = [UIColor clearColor];
UITapGestureRecognizer* tapRecognizer = [[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(statusBarClicked)] autorelease];
[statusBarInterceptView addGestureRecognizer:tapRecognizer];
[[[UIApplication sharedApplication].delegate window] addSubview:statusBarInterceptView];

在 statusBarClicked 选择器中,做你需要做的事情,在我的例子中,我向通知中心发布了一个通知,以便其他视图控制器可以响应状​​态栏点击。

【讨论】:

  • 我尝试了其中的几个,这是最可靠的解决方案。
  • 副作用!!!刚刚注意到 - 如果您弄乱了 UIWindowLevel,Facebook 共享视图控制器 (SLComposeViewController) 不会显示键盘。这个解决方案实际上并没有想象的那么好。
  • 雷达和苹果错误报告已提交。 openradar.appspot.com/radar?id=2887402
  • 这种 hack 看起来很冒险,他们会在没有警告的情况下破坏。
  • 仅供参考:这将与 iOS8 中的状态栏样式混淆,因为它似乎跟随最顶部的窗口。
【解决方案7】:

使用不可见的UIScrollView。在 iOS 10 和 Swift 3 上测试。

override func viewDidLoad() {
    let scrollView = UIScrollView()
    scrollView.bounds = view.bounds
    scrollView.contentOffset.y = 1
    scrollView.contentSize.height = view.bounds.height + 1
    scrollView.delegate = self
    view.addSubview(scrollView)
}

func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool {
    debugPrint("status bar tapped")
    return false
}

【讨论】:

  • 这是一个出色、简单且万无一失的策略。 scrollView 甚至不必是最顶层的视图。我把我的放在我的 webView 后面并调整大小以填充父视图,效果很好。
  • 这太棒了
  • iOS 13 的任何解决方案?
  • @swordray 如何在 AppDelegate 中添加它>
  • @JigarTarsariya 将这些功能添加到您的视图控制器中。
【解决方案8】:

您可以使用以下代码跟踪状态栏点击:

[[NSNotificationCenter defaultCenter] addObserverForName:@"_UIApplicationSystemGestureStateChangedNotification"
                                                  object:nil
                                                   queue:nil
                                              usingBlock:^(NSNotification *note) {
        NSLog(@"Status bar pressed!");
}];

【讨论】:

  • 这对我有用,但是通知不仅适用于 touchUpInside,还适用于任何状态更改。因此,我在这里要求的任何操作都会被调用两次,在触摸和释放时。
  • 是否有可能观察到“_UIApplicationSystemGestureStateChangedNotification”的应用在应用商店中被拒绝?前面带 _ 的通知名称对我来说有点私密。
  • 非常感谢!这刚刚解决了我的 UIWebView 的问题,该 UIWebView 包含一个跨越其整个高度的 div 并具有溢出:滚动集。
  • 这依赖于系统的内部实现细节。它不仅等同于使用私有 API,而且还可能因任意系统更新而中断。
  • 我在我的应用程序中使用了它。 App Store 审核顺利,没有提出任何问题。
【解决方案9】:

一种方法,可能不是最好的,可能是在状态栏顶部放置一个清晰的 UIView 并拦截 UIView 的触摸,如果没有其他问题可能会帮助你......

【讨论】:

  • 是的,这是一个想法。但这比在某处添加任意滚动视图并跟踪它的行为更骇人听闻,不是吗?
【解决方案10】:

如果您只是想在点击状态栏时让UIScrollView 滚动到顶部,请注意这是默认行为,如果 您的视图控制器中恰好有一个 UIScrollViewscrollsToTop 设置为YES 的子视图。

所以我只需要找到任何其他UIScrollView(或子类:UITableViewUICollectionView,并将scrollsToTop 设置为NO

公平地说,我在原始问题的post that was linked to 中找到了此信息,但它也因不再有效而被驳回,因此我跳过了它,仅在随后的搜索中找到了相关文章。

【讨论】:

    【解决方案11】:

    对于 iOS 13,这对我有用,UIStatusBarManager 的 Objective-C 类别

    @implementation UIStatusBarManager (CAPHandleTapAction)
    -(void)handleTapAction:(id)arg1 {
        // Your code here
    }
    @end
    

    【讨论】:

    • 你能告诉我如何在 Swift 5.0 和 iOS 13 中使用这段代码吗?
    • 你不能,这是一个objective-c类别,swift中没有类别
    • 好的,iOS 13 有什么解决方案吗?
    • 哦,但是我的代码在 Swift 5 中,所以有什么选择吗?
    • 你可以混合objective-c和swift
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-05
    相关资源
    最近更新 更多