Как распознавать все касания в Swift 2

Я пытаюсь создать функцию тайм-аута для приложения, которое я разрабатываю с помощью 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];
    }
}

Приведенный выше код работает для каждого прикосновения, но быстрый эквивалент работает только тогда, когда представление не существует выше этой иерархии UIWindow?

Кто-нибудь знает способ обнаруживать каждое касание в приложении?


person FireDragonMule    schedule 26.07.2015    source источник
comment
Это как-то связано с iOS 9 вместо Swift 2. У меня есть аналогичное решение в моем пользовательском UIWindow, и оно тоже перестает работать. Вероятно, из-за нового разделенного представления приложений iOS 9 и т. Д. Поэтому они переделали его для поддержки этих новых функций - два открытых приложения, клавиатура, ... Не уверен на 100%, просто подумал вслух ...   -  person zrzka    schedule 28.07.2015


Ответы (3)


Так как у меня в приложении есть что-то похожее, я просто попытался исправить:

  • переопределить sendEvent в UIWindow - не работает
  • переопределить 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). Необработанный ключ - NSPrincipalClass.

введите здесь описание изображения

person zrzka    schedule 28.07.2015
comment
Имейте в виду одну вещь - это не улавливает события внешней клавиатуры. Если у вас есть клавиатура BT ... Только экранная / программная. - person zrzka; 29.07.2015
comment
Спасибо за внимание. Это приложение, которое я создаю, будет иметь внешние аксессуары для набора текста, поэтому я собираюсь изучить это. - person FireDragonMule; 29.07.2015
comment
не забудьте импортировать UIKit вверху. Это круто, именно то, что мне нужно. - person mondousage; 22.06.2016
comment
Что вы реализовали в appdelegate? Не могли бы вы поделиться своим кодом целиком? - person Jamshed Alam; 09.10.2019

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
}
person markiv    schedule 05.02.2018
comment
Хорошее решение, но, к сожалению: 'keyWindow' устарело в iOS 13.0: не должно использоваться для приложений, поддерживающих несколько сцен, поскольку оно возвращает ключевое окно для всех связанных сцен. - person wider-spider; 13.09.2019

Ответ @markiv работает как шарм, с двумя проблемами:

  1. keyWindow
    • "'keyWindow' was deprecated in iOS 13.0: Should not be used for applications that support multiple scenes as it returns a key window across all connected scenes"

Решается так:

let kw = UIApplication.shared.windows.filter {$0.isKeyWindow}.first найдено здесь

см. ответ @matt

  1. AppDelegate - я получаю такое сообщение:

    • The app delegate must implement the window property if it wants to use a main storyboard file

Можно создать подкласс UIWindow - ответ можно найти здесь. Объедините это с классом IdlingWindow.

Сообщение ушло.

var customWindow: IdlingWindow?    
var window: UIWindow? {
    get {
        customWindow = customWindow ?? IdlingWindow(frame: UIScreen.mainScreen().bounds)
        return customWindow
    }
    set { }
}
person wider-spider    schedule 13.09.2019