如何在Swift 2中检测所有触摸事件

5

我正在尝试为我正在使用Swift 2开发的应用程序创建一个超时函数,但在Swift 2中,您可以将此代码放入应用程序委托中并且它可以工作,但它无法检测到任何键盘按键、按钮按下、文本字段按下等:

override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
    super.touchesBegan(touches, withEvent: event);
    let allTouches = event!.allTouches();

    if(allTouches?.count > 0) {
        let phase = (allTouches!.first as UITouch!).phase;
        if(phase == UITouchPhase.Began || phase == UITouchPhase.Ended) {
            //Stuff
            timeoutModel.actionPerformed();
        }
    }
}

在 Swift 2 之前,我可以将 AppDelegate 子类化为 UIApplication 并像这样覆盖 sendEvent:

-(void)sendEvent:(UIEvent *)event
{
    [super sendEvent:event];

    // Only want to reset the timer on a Began touch or an Ended touch, to reduce the number of timer resets.
    NSSet *allTouches = [event allTouches];
    if ([allTouches count] > 0) {
        // allTouches count only ever seems to be 1, so anyObject works here.
        UITouchPhase phase = ((UITouch *)[allTouches anyObject]).phase;
        if (phase == UITouchPhaseBegan || phase == UITouchPhaseEnded)
            [[InactivityModel instance] actionPerformed];
    }
}

上述代码适用于每一个触摸事件,但是Swift的等效代码只在该UIWindow层次结构中不存在视图时才起作用?

有人知道一种检测应用程序中每一个触摸事件的方法吗?


这与Swift 2无关,而是与iOS 9有关。我在自定义的UIWindow中有类似的解决方案,但它也停止工作了。可能是因为新的iOS 9分割应用视图等功能。因此,他们对其进行了重新设计,以支持这些新功能-两个打开的应用程序、键盘等。不是100%确定,只是随口想想... - zrzka
3个回答

19

在我的应用程序中也有类似的问题,我尝试着去解决:

  • 覆盖UIWindow中的sendEvent - 无效
  • 覆盖代理中的sendEvent - 无效

所以唯一的方法是提供一个自定义的UIApplication子类。我到目前为止的代码(适用于iOS 9)如下:

@objc(MyApplication) class MyApplication: UIApplication {

  override func sendEvent(event: UIEvent) {
    //
    // Ignore .Motion and .RemoteControl event
    // simply everything else then .Touches
    //
    if event.type != .Touches {
      super.sendEvent(event)
      return
    }

    //
    // .Touches only
    //
    var restartTimer = true

    if let touches = event.allTouches() {
      //
      // At least one touch in progress?
      // Do not restart auto lock timer, just invalidate it
      //
      for touch in touches.enumerate() {
        if touch.element.phase != .Cancelled && touch.element.phase != .Ended {
          restartTimer = false
          break
        }
      }
    }

    if restartTimer {
      // Touches ended || cancelled, restart auto lock timer
      print("Restart auto lock timer")
    } else {
      // Touch in progress - !ended, !cancelled, just invalidate it
      print("Invalidate auto lock timer")
    }

    super.sendEvent(event)
  }

}
为什么有 @objc(MyApplication)。这是因为Swift与Objective-C在名称方面的处理方式不同,它只是说 - 我的Objective-C类名是MyApplication
要使它正常工作,请打开您的info.plist并添加一行带有Principal class键和MyApplication值的条目(MyApplication@objc(...)中的内容,而不是您的Swift类名)。原始键是NSPrincipalClassenter image description here

注意一件事情 - 这不会捕获外部键盘事件。如果您使用蓝牙键盘...只能捕获屏幕上/软件上的键盘事件。 - zrzka
谢谢提醒。我正在开发的这个应用程序将具有外部配件进行输入,因此我需要研究一下这方面的内容。 - FireDragonMule
不要忘记在顶部导入UIKit。这太棒了,正是我所需要的。 - mondousage
你在AppDelegate中实现了什么?你能分享整个代码吗? - Jamshed Alam

4

UIWindow还有一个可以被重写的sendEvent方法。这将允许您跟踪自上次屏幕触摸以来的时间。Swift 4:

class IdlingWindow: UIWindow {
    /// Tracks the last time this window was interacted with
    var lastInteraction = Date.distantPast

    override func sendEvent(_ event: UIEvent) {
        super.sendEvent(event)
        lastInteraction = Date()
    }
}

如果您正在使用故事板,您可以在didFinishLaunchingWithOptions中加载它:
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: IdlingWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        window = IdlingWindow(frame: UIScreen.main.bounds)
        window?.rootViewController = UIStoryboard.init(name: "Main", bundle: nil).instantiateInitialViewController()
        window?.makeKeyAndVisible()
        return true
    }
    :
}

extension UIApplication {
    /// Conveniently gets the last interaction time
    var lastInteraction: Date {
        return (keyWindow as? IdlingWindow)?.lastInteraction ?? .distantPast
    }
}

现在在您的应用程序中,您可以像这样检查不活动状态:

if UIApplication.shared.lastInteraction.timeIntervalSinceNow < -2 {
    // the window has been idle over 2 seconds
}

不错的解决方案,但不幸的是: “'keyWindow'在iOS 13.0中已被弃用:不应该用于支持多个场景的应用程序,因为它会返回所有连接场景中的关键窗口。” - wider-spider

0

@markiv的答案非常好,但有两个问题:

  1. keyWindow
    • "'keyWindow'在iOS 13.0中已被弃用:不应该用于支持多个场景的应用程序,因为它会返回所有连接场景中的关键窗口"

可以这样解决:

let kw = UIApplication.shared.windows.filter {$0.isKeyWindow}.first这里找到

参见@matt的答案

  1. AppDelegate - 我收到了这条消息:

    • 如果要使用主故事板文件,应用程序委托必须实现window属性

可以继承UIWindow - 在这里找到答案。结合IdlingWindow类使用。

消息已经消失了。

var customWindow: IdlingWindow?    
var window: UIWindow? {
    get {
        customWindow = customWindow ?? IdlingWindow(frame: UIScreen.mainScreen().bounds)
        return customWindow
    }
    set { }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接