iPhone:检测自上次屏幕触摸后用户不活动/空闲时间

Mik*_*ter 152 iphone objective-c ios idle-timer

是否有人实施了一项功能,如果用户在一段时间内没有触摸屏幕,您会采取某种行动吗?我正试图找出最好的方法.

UIApplication中有一些与此有关的方法:

[UIApplication sharedApplication].idleTimerDisabled;
Run Code Online (Sandbox Code Playgroud)

如果你反而有这样的事情会很好:

NSTimeInterval timeElapsed = [UIApplication sharedApplication].idleTimeElapsed;
Run Code Online (Sandbox Code Playgroud)

然后我可以设置一个计时器并定期检查该值,并在超过阈值时采取一些措施.

希望这能解释我在寻找什么.有没有人已经解决过这个问题,或者对你将如何做到这一点有任何想法?谢谢.

Mik*_*ter 152

这是我一直在寻找的答案:

让您的应用程序委托子类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)
            [self resetIdleTimer];
    }
}

- (void)resetIdleTimer {
    if (idleTimer) {
        [idleTimer invalidate];
        [idleTimer release];
    }

    idleTimer = [[NSTimer scheduledTimerWithTimeInterval:maxIdleTime target:self selector:@selector(idleTimerExceeded) userInfo:nil repeats:NO] retain];
}

- (void)idleTimerExceeded {
    NSLog(@"idle time exceeded");
}
Run Code Online (Sandbox Code Playgroud)

其中maxIdleTime和idleTimer是实例变量.

为了使其工作,您还需要修改main.m以告诉UIApplicationMain使用您的委托类(在此示例中为AppDelegate)作为主要类:

int retVal = UIApplicationMain(argc, argv, @"AppDelegate", @"AppDelegate");
Run Code Online (Sandbox Code Playgroud)

  • 我想补充一点,UIApplication子类应该与UIApplicationDelegate子类分开 (7认同)
  • 非常好!但是,如果有很多触摸,这种方法会产生很多`NSTimer`实例. (4认同)
  • 嗨,迈克,我的AppDelegate是从NSObject开始的,所以改变它UIApplication并实现上面的方法来检测用户变得空闲但是我收到错误"由于未捕获的异常'终止应用'NSInternalInconsistencyException',原因:'只能有一个UIApplication实例'. "......还有什么我需要做的......? (3认同)

Chr*_*les 86

我有一个空闲计时器解决方案的变体,它不需要子类化UIApplication.它适用于特定的UIViewController子类,因此如果您只有一个视图控制器(如交互式应用程序或游戏可能有)或只想在特定视图控制器中处理空闲超时,则非常有用.

每次重置空闲计时器时,它也不会重新创建NSTimer对象.如果计时器触发,它只会创建一个新的.

您的代码可以调用resetIdleTimer可能需要使空闲计时器无效的任何其他事件(例如重要的加速度计输入).

@interface MainViewController : UIViewController
{
    NSTimer *idleTimer;
}
@end

#define kMaxIdleTimeSeconds 60.0

@implementation MainViewController

#pragma mark -
#pragma mark Handling idle timeout

- (void)resetIdleTimer {
    if (!idleTimer) {
        idleTimer = [[NSTimer scheduledTimerWithTimeInterval:kMaxIdleTimeSeconds
                                                      target:self
                                                    selector:@selector(idleTimerExceeded)
                                                    userInfo:nil
                                                     repeats:NO] retain];
    }
    else {
        if (fabs([idleTimer.fireDate timeIntervalSinceNow]) < kMaxIdleTimeSeconds-1.0) {
            [idleTimer setFireDate:[NSDate dateWithTimeIntervalSinceNow:kMaxIdleTimeSeconds]];
        }
    }
}

- (void)idleTimerExceeded {
    [idleTimer release]; idleTimer = nil;
    [self startScreenSaverOrSomethingInteresting];
    [self resetIdleTimer];
}

- (UIResponder *)nextResponder {
    [self resetIdleTimer];
    return [super nextResponder];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    [self resetIdleTimer];
}

@end
Run Code Online (Sandbox Code Playgroud)

(为简洁起见,不包括内存清理代码.)

  • @GregMaletic:我有同样的问题,但最后我添加了 - (void)scrollViewWillBeginDragging:(UIScrollView*)scrollView {NSLog(@"将开始拖动"); } - (void)scrollViewDidScroll:(UIScrollView*)scrollView {NSLog(@"Did Scroll"); [self resetIdleTimer]; 你试过这个吗? (3认同)

Ser*_*nik 20

对于swift v 3.1

别忘了在AppDelegate // @ UIApplicationMain中注释这一行

extension NSNotification.Name {
   public static let TimeOutUserInteraction: NSNotification.Name = NSNotification.Name(rawValue: "TimeOutUserInteraction")
}


class InterractionUIApplication: UIApplication {

static let ApplicationDidTimoutNotification = "AppTimout"

// The timeout in seconds for when to fire the idle timer.
let timeoutInSeconds: TimeInterval = 15 * 60

var idleTimer: Timer?

// Listen for any touch. If the screen receives a touch, the timer is reset.
override func sendEvent(_ event: UIEvent) {
    super.sendEvent(event)

    if idleTimer != nil {
        self.resetIdleTimer()
    }

    if let touches = event.allTouches {
        for touch in touches {
            if touch.phase == UITouchPhase.began {
                self.resetIdleTimer()
            }
        }
    }
}

// Resent the timer because there was user interaction.
func resetIdleTimer() {
    if let idleTimer = idleTimer {
        idleTimer.invalidate()
    }

    idleTimer = Timer.scheduledTimer(timeInterval: timeoutInSeconds, target: self, selector: #selector(self.idleTimerExceeded), userInfo: nil, repeats: false)
}

// If the timer reaches the limit as defined in timeoutInSeconds, post this notification.
func idleTimerExceeded() {
    NotificationCenter.default.post(name:Notification.Name.TimeOutUserInteraction, object: nil)
   }
} 
Run Code Online (Sandbox Code Playgroud)

创建main.swif文件并添加它(名称很重要)

CommandLine.unsafeArgv.withMemoryRebound(to: UnsafeMutablePointer<Int8>.self, capacity: Int(CommandLine.argc)) {argv in
_ = UIApplicationMain(CommandLine.argc, argv, NSStringFromClass(InterractionUIApplication.self), NSStringFromClass(AppDelegate.self))
}
Run Code Online (Sandbox Code Playgroud)

观察任何其他类的通知

NotificationCenter.default.addObserver(self, selector: #selector(someFuncitonName), name: Notification.Name.TimeOutUserInteraction, object: nil)
Run Code Online (Sandbox Code Playgroud)

  • 我不明白为什么我们需要在`sendEvent()`方法中检查`ifidTimer!= nil`? (2认同)

Bri*_*ing 12

这个线程是一个很好的帮助,我把它包装成一个发送通知的UIWindow子类.我选择通知使它成为一个真正的松耦合,但你可以很容易地添加一个委托.

这是要点:

http://gist.github.com/365998

此外,UIApplication子类问题的原因是NIB被设置为创建2个UIApplication对象,因为它包含应用程序和委托.UIWindow子类虽然很好用.

  • 它非常适合触摸,但似乎无法处理键盘输入。这意味着如果用户在gui键盘上输入内容,它将超时。 (2认同)
  • 我也无法理解如何使用它......我在我的视图控制器中添加观察者并期望在应用程序未受影响/闲置时触发通知...但没有发生任何事情......加上我们可以控制空闲时间?就像我想要120秒的空闲时间一样,120秒后IdleNotification应该开火,而不是之前. (2认同)

Jla*_*lam 6

有一种方法可以在整个应用程序范围内执行此操作,而无需单个控制器执行任何操作。只需添加一个不会取消触摸的手势识别器即可。这样,计时器将跟踪所有触摸,并且其他触摸和手势根本不受影响,因此其他人不必知道这一点。

fileprivate var timer ... //timer logic here

@objc public class CatchAllGesture : UIGestureRecognizer {
    override public func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
        super.touchesBegan(touches, with: event)
    }
    override public func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
        //reset your timer here
        state = .failed
        super.touchesEnded(touches, with: event)
    }
    override public func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
        super.touchesMoved(touches, with: event)
    }
}

@objc extension YOURAPPAppDelegate {

    func addGesture () {
        let aGesture = CatchAllGesture(target: nil, action: nil)
        aGesture.cancelsTouchesInView = false
        self.window.addGestureRecognizer(aGesture)
    }
}
Run Code Online (Sandbox Code Playgroud)

在您的应用程序委托的 did finish launch 方法中,只需调用 addGesture 即可完成设置。所有触摸都将通过 CatchAllGesture 的方法,而不会妨碍其他功能。


小智 5

实际上,子类化的想法很有效。只是不要让您的委托成为UIApplication子类。创建另一个继承自的文件UIApplication(例如 myApp)。在 IB 中将fileOwner对象的类设置为myApp并在 myApp.m 中实现上述sendEvent方法。在 main.m 中:

int retVal = UIApplicationMain(argc,argv,@"myApp.m",@"myApp.m")
Run Code Online (Sandbox Code Playgroud)

等等!