IT story

iPhone : 마지막 화면 터치 이후의 사용자 비 활동 / 유휴 시간 감지

hot-time 2020. 6. 10. 08:02
반응형

iPhone : 마지막 화면 터치 이후의 사용자 비 활동 / 유휴 시간 감지


사용자가 특정 시간 동안 화면을 터치하지 않은 경우 특정 작업을 수행하는 기능을 구현 한 사람이 있습니까? 최선의 방법을 찾으려고 노력하고 있습니다.

UIApplication에는 다소 관련이있는 메소드가 있습니다.

[UIApplication sharedApplication].idleTimerDisabled;

대신 다음과 같은 것이 있으면 좋을 것입니다.

NSTimeInterval timeElapsed = [UIApplication sharedApplication].idleTimeElapsed;

그런 다음 타이머를 설정 하고이 값을 주기적으로 확인하고 임계 값을 초과하면 조치를 취할 수 있습니다.

바라건대 내가 찾고있는 것을 설명합니다. 이미이 문제를 해결 한 사람이 있습니까, 아니면 어떻게 할 것인지에 대한 생각이 있습니까? 감사.


내가 찾은 답변은 다음과 같습니다.

응용 프로그램에 하위 클래스 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");
}

여기서 maxIdleTime 및 idleTimer는 인스턴스 변수입니다.

이 기능을 사용하려면 main.m을 수정하여 UIApplicationMain에게 위임 클래스 (이 예제에서는 AppDelegate)를 주 클래스로 사용하도록 지시해야합니다.

int retVal = UIApplicationMain(argc, argv, @"AppDelegate", @"AppDelegate");

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

(간결성을 위해 메모리 정리 코드는 제외되었습니다.)


스위프트 v 3.1

dont't forget comment this line in 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)
   }
} 

create main.swif file and add this (name is important)

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

Observing notification in an any other class

NotificationCenter.default.addObserver(self, selector: #selector(someFuncitonName), name: Notification.Name.TimeOutUserInteraction, object: nil)

This thread was a great help, and I wrapped it up into a UIWindow subclass that sends out notifications. I chose notifications to make it a real loose coupling, but you can add a delegate easily enough.

Here's the gist:

http://gist.github.com/365998

Also, the reason for the UIApplication subclass issue is that the NIB is setup to then create 2 UIApplication objects since it contains the application and the delegate. UIWindow subclass works great though.


I just ran into this problem with a game that is controlled by motions i.e. has screen lock disabled but should enable it again when in menu mode. Instead of a timer I encapsulated all calls to setIdleTimerDisabled within a small class providing the following methods:

- (void) enableIdleTimerDelayed {
    [self performSelector:@selector (enableIdleTimer) withObject:nil afterDelay:60];
}

- (void) enableIdleTimer {
    [NSObject cancelPreviousPerformRequestsWithTarget:self];
    [[UIApplication sharedApplication] setIdleTimerDisabled:NO];
}

- (void) disableIdleTimer {
    [NSObject cancelPreviousPerformRequestsWithTarget:self];
    [[UIApplication sharedApplication] setIdleTimerDisabled:YES];
}

disableIdleTimer deactivates idle timer, enableIdleTimerDelayed when entering the menu or whatever should run with idle timer active and enableIdleTimer is called from your AppDelegate's applicationWillResignActive method to ensure all your changes are reset properly to the system default behaviour.
I wrote an article and provided the code for the singleton class IdleTimerManager Idle Timer Handling in iPhone Games


Here is another way to detect activity:

The timer is added in UITrackingRunLoopMode, so it can only fire if there is UITracking activity. It also has the nice advantage of not spamming you for all touch events, thus informing if there was activity in the last ACTIVITY_DETECT_TIMER_RESOLUTION seconds. I named the selector keepAlive as it seems an appropriate use case for this. You can of course do whatever you desire with the information that there was activity recently.

_touchesTimer = [NSTimer timerWithTimeInterval:ACTIVITY_DETECT_TIMER_RESOLUTION
                                        target:self
                                      selector:@selector(keepAlive)
                                      userInfo:nil
                                       repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:_touchesTimer forMode:UITrackingRunLoopMode];

Actually the subclassing idea works great. Just don't make your delegate the UIApplication subclass. Create another file that inherits from UIApplication (e.g. myApp). In IB set the class of the fileOwner object to myApp and in myApp.m implement the sendEvent method as above. In main.m do:

int retVal = UIApplicationMain(argc,argv,@"myApp.m",@"myApp.m")

et voilà!


Ultimately you need to define what you consider to be idle - is idle the result of the user not touching the screen or is it the state of the system if no computing resources are being used? It is possible, in many applications, for the user to be doing something even if not actively interacting with the device through the touch screen. While the user is probably familiar with the concept of the device going to sleep and the notice that it will happen via screen dimming, it is not necessarily the case that they'll expect something to happen if they are idle - you need to be careful about what you would do. But going back to the original statement - if you consider the 1st case to be your definition, there is no really easy way to do this. You'd need to receive each touch event, passing it along on the responder chain as needed while noting the time it was received. That will give you some basis for making the idle calculation. If you consider the second case to be your definition, you can play with an NSPostWhenIdle notification to try and perform your logic at that time.


There's a way to do this app wide without individual controllers having to do anything. Just add a gesture recognizer that doesn't cancel touches. This way, all touches will be tracked for the timer, and other touches and gestures aren't affected at all so no one else has to know about it.

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)
    }
}

In your app delegate's did finish launch method, just call addGesture and you're all set. All touches will go through the CatchAllGesture's methods without it preventing the functionality of others.

참고URL : https://stackoverflow.com/questions/273450/iphone-detecting-user-inactivity-idle-time-since-last-screen-touch

반응형