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


152

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

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

[UIApplication sharedApplication].idleTimerDisabled;

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

NSTimeInterval timeElapsed = [UIApplication sharedApplication].idleTimeElapsed;

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

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


이것은 좋은 질문입니다. Windows에는 OnIdle 이벤트 개념이 있지만 현재 메시지 펌프에서 아무것도 처리하지 않는 앱과 iOS 장치의 잠금 시간에만 관련된 것으로 보이는 iOS idleTimerDisabled 속성에 관한 것입니다. iOS / MacOSX의 Windows 개념에 원격으로 가까운 것이 있는지 아는 사람이 있습니까?
stonedauwg

답변:


153

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

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

3
안녕하세요, 내 AppDelegate가 NSObject에서 상속되었습니다. UIApplication을 변경하고 사용자가 유휴 상태가되는 것을 감지하기 위해 위의 메소드를 구현했지만 오류가 발생했습니다. ".. 내가해야 할 일이 또 있습니까? ...?
Mihir Mehta

7
UIApplication 서브 클래스는 UIApplicationDelegate 서브 클래스와 분리되어야한다고
덧붙였습니다.

타이머가 작동을 멈출 때 장치가 비활성 상태로 들어가는 방법에 대해 잘 모르겠습니다.
anonmys 2016 년

시간 초과 이벤트에 popToRootViewController 함수를 사용하여 할당하면 제대로 작동하지 않습니다. 내가 UIAlertView, 다음 popToRootViewController을 보여줄 때, 나는 이미 튀어되어있는 UIViewController에서 선택과 UIAlertView에있는 버튼을 누르면 발생
Gargo

4
아주 좋아요! 그러나이 방법은 NSTimer터치가 많은 경우 많은 인스턴스를 만듭니다 .
Andreas Ley

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

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


1
아주 좋아요 이 대답은 흔들린다! 나는 그것이 훨씬 더 일찍 알고 있음에도 불구하고 정답으로 표시 된 답을 얻지 만 이것이 더 나은 해결책입니다.
Chintan Patel 2016 년

이것은 훌륭하지만 내가 찾은 한 가지 문제는 다음과 같습니다. UITableViews에서 스크롤해도 nextResponder가 호출되지 않습니다. 또한 touchesBegan : 및 touchesMoved :를 통해 추적을 시도했지만 개선되지 않았습니다. 어떤 아이디어?
Greg Maletic

3
@GregMaletic : 나는 같은 문제가 있었지만 마지막으로-(void) scrollViewWillBeginDragging : (UIScrollView *) scrollView {NSLog (@ "드래그 시작"); }-(void) scrollViewDidScroll : (UIScrollView *) scrollView {NSLog (@ "Did 스크롤"); [자체 resetIdleTimer]; } 당신이 이것을 시도 했습니까?
Akshay Aher

감사. 이것은 여전히 ​​도움이됩니다. 나는 그것을 스위프트로 포팅했고 훌륭하게 작동했다.
Mark.ewd

당신은 록 스타입니다. kudos
Pras

21

스위프트 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)
   }
} 

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

다른 클래스에서 알림 관찰

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

2
우리가 확인해야 할 이유를 나는 이해가 안 돼요 if idleTimer != nil에서 sendEvent()방법을?
Guangyu Wang

timeoutInSeconds웹 서비스 응답에서 가치를 어떻게 설정할 수 있습니까?
User_1191

12

이 스레드는 큰 도움이되었으며 알림을 보내는 UIWindow 하위 클래스로 묶었습니다. 실제로 느슨한 결합을 만들기 위해 알림을 선택했지만 대리자를 쉽게 추가 할 수 있습니다.

요점은 다음과 같습니다.

http://gist.github.com/365998

또한 UIApplication 서브 클래스 문제의 원인은 NIB가 애플리케이션과 델리게이트를 포함하므로 2 개의 UIApplication 오브젝트를 작성하도록 설정 되었기 때문입니다. UIWindow 서브 클래스는 훌륭하게 작동합니다.


1
코드 사용법을 알려줄 수 있습니까? 나는 그것을 부르는 방법을 이해하지 못한다
R. Dewi

2
터치에는 효과적이지만 키보드 입력은 처리하지 않는 것 같습니다. 이것은 사용자가 GUI 키보드에 물건을 입력하면 시간이 초과됨을 의미합니다.
Martin Wickman

2
나도 그것을 사용하는 방법을 이해할 수 없다 ... 뷰 컨트롤러에 옵저버를 추가하고 앱이 손대지 않거나 유휴 상태 일 때 알림이 발생할 것으로 예상됩니다. 120 초 후에 유휴 시간을 원하므로 120 초 후에 IdleNotification이 발생하지 않아야합니다.
Ans

5

실제로 서브 클래 싱 아이디어는 훌륭합니다. 델리게이트를 UIApplication서브 클래스로 만들지 마십시오 . 상속 된 다른 파일 UIApplication(예 : myApp)을 만듭니다. IB에서 fileOwner객체 의 클래스를 myAppmyApp.m으로 설정하고 sendEvent위와 같이 메소드를 구현하십시오 . main.m에서 :

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

et voilà!


1
그렇습니다. 독립형 UIApplication 서브 클래스를 생성하는 것이 좋습니다. 나는 두 번째 parm nil을 main에 두었다.
핫 릭

@ 로비, 내 쿼리 stackoverflow.com/questions/20088521/…을 살펴보십시오 .
Tirth

4

방금 동작에 의해 제어되는 게임, 즉 화면 잠금이 비활성화되었지만 메뉴 모드에있을 때 다시 활성화해야하는 게임에서이 문제가 발생했습니다. 타이머 대신 setIdleTimerDisabled다음 메소드를 제공하는 작은 클래스 내로의 모든 호출을 캡슐화했습니다 .

- (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];
}

disableIdleTimerenableIdleTimerDelayed메뉴로 들어갈 때 유휴 타이머를 비활성화 하거나 유휴 타이머가 활성화 된 상태에서 실행해야하는 모든 항목을 변경하고 모든 변경 사항이 시스템 기본 동작으로 올바르게 재설정되도록 enableIdleTimerAppDelegate의 applicationWillResignActive메소드 에서 호출됩니다 .
기사를 작성하고 iPhone 게임에서 싱글 톤 클래스 IdleTimerManager 유휴 타이머 처리에 대한 코드를 제공했습니다.


4

활동을 감지하는 다른 방법은 다음과 같습니다.

에 타이머가 추가되어 활동 UITrackingRunLoopMode이있는 경우에만 작동 할 수 있습니다 UITracking. 또한 모든 터치 이벤트에 대해 스팸 메일을 보내지 않기 때문에 지난 ACTIVITY_DETECT_TIMER_RESOLUTION몇 초 동안 활동이 있었는지 알려줍니다 . keepAlive적절한 사용 사례 인 것처럼 선택기 를 명명했습니다 . 물론 최근 활동이 있었다는 정보로 원하는 것을 할 수 있습니다.

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

어떻게 요? 나는 당신이 필요로하는 모든 것에 "keepAlive"선택기를 스스로 만들어야한다는 것이 확실하다고 믿습니다. 어쩌면 내가 당신의 관점을 잃어 버렸습니까?
Mihai Timar 2016 년

이것은 활동을 감지하는 또 다른 방법이지만, NSTimer 인 iVar 만 인스턴스화합니다. 이것이 OP의 질문에 어떻게 대답하는지 알 수 없습니다.
재스퍼

1
타이머는 UITrackingRunLoopMode에 추가되므로 UITracking 활동이있는 경우에만 실행될 수 있습니다. 또한 모든 터치 이벤트에 대해 스팸 메일을 보내지 않기 때문에 마지막 ACTIVITY_DETECT_TIMER_RESOLUTION 초 동안 활동이 있었음을 알려줍니다. 적절한 유스 케이스 인 것처럼 셀렉터 keepAlive를 명명했습니다. 물론 최근 활동이 있었다는 정보로 원하는 것을 할 수 있습니다.
Mihai Timar 2016 년

1
이 답변을 개선하고 싶습니다. 더 명확하게 만드는 데 도움이 될 수 있다면 큰 도움이 될 것입니다.
Mihai Timar 2016 년

답변에 설명을 추가했습니다. 이제 훨씬 더 이해가됩니다.
재스퍼

3

궁극적으로 유휴 상태 인 것으로 정의한 항목을 정의해야합니다. 사용자가 화면을 터치하지 않은 결과 유휴 상태입니까, 또는 컴퓨팅 리소스를 사용하지 않는 경우 시스템 상태입니까? 많은 응용 프로그램에서 사용자가 터치 스크린을 통해 장치와 적극적으로 상호 작용하지 않더라도 무언가를 수행하는 것이 가능합니다. 사용자는 기기가 절전 모드로 전환되는 개념과 화면 디밍을 통해 발생한다는 알림에 익숙 할 수 있지만 유휴 상태 일 때 무언가가 발생할 것으로 예상되는 것은 아닙니다.주의해야합니다. 당신이 할 일에 대해. 그러나 원래 진술로 돌아가서-첫 번째 사례를 정의라고 생각하면 실제로 이것을 수행하는 쉬운 방법은 없습니다. 각 터치 이벤트를 받아야합니다. 수신 된 시간을 기록하면서 필요에 따라 응답자 체인에 전달합니다. 유휴 계산을위한 몇 가지 기초를 제공합니다. 두 번째 사례를 정의한 것으로 간주하면 NSPostWhenIdle 알림을 사용하여 해당 시점에 논리를 시도하고 수행 할 수 있습니다.


1
명확히하기 위해 화면과의 상호 작용에 대해 이야기하고 있습니다. 이를 반영하여 질문을 업데이트하겠습니다.
Mike McMaster

1
그런 다음 터치가 발생할 때마다 유휴 타이머를 확인하거나 설정 (및 재설정)하는 값을 업데이트 할 수있는 무언가를 구현할 수 있지만 wisequark가 말했듯이 유휴를 구성하는 요소는 다른 앱.
루이스 Gerbarg

1
화면을 마지막으로 터치 한 후 시간으로 "유휴"를 엄격하게 정의하고 있습니다. 나는 그것을 직접 구현해야한다는 것을 얻었습니다. 스크린 터치를 가로 채거나 누군가가 이것을 결정하는 다른 방법을 알고 있다면 "가장 좋은"방법이 무엇인지 궁금합니다.
Mike McMaster

3

개별 컨트롤러 없이도이 앱을 광범위하게 수행 할 수있는 방법이 있습니다. 터치를 취소하지 않는 동작 인식기를 추가하기 만하면됩니다. 이런 식으로 타이머에 대한 모든 터치가 추적되며 다른 터치 및 제스처는 전혀 영향을받지 않으므로 다른 사람이 이에 대해 알 필요가 없습니다.

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

앱 델리게이트가 실행 방법을 완료했습니다. addGesture를 호출하면 모든 준비가 완료됩니다. 모든 접촉은 다른 사람의 기능을 방해하지 않고 CatchAllGesture의 방법을 거칩니다.


1
이 접근 방식이 마음에 들어 Xamarin과 비슷한 문제에 사용했습니다. stackoverflow.com/a/51727021/250164
Wolfgang Schreurs

1
잘 작동합니다.이 기술은 AVPlayerViewController ( private API ref ) 에서 UI 컨트롤의 가시성을 제어하는 ​​데 사용되는 것처럼 보입니다 . 앱을 재정의하는 -sendEvent:것은 과잉이며 UITrackingRunLoopMode많은 경우를 처리하지 않습니다.
로마 B.

@RomanB. 예 바로 그 거예요. iOS를 충분히 오래 사용했을 때는 항상 "올바른 방법"을 사용하는 것을 알고 있으며 이는 의도 된 developer.apple.com/documentation/uikit/uigesturerecognizer/…
Jlam

touchesEnded에서 상태를 .failed로 설정하는 것은 무엇입니까?
stonedauwg

나는이 접근법을 좋아하지만 시도했을 때 터치 로직에서 이동, 스 와이프 등과 같은 다른 제스처가 아닌 탭 만 잡는 것처럼 보입니다. 그 의도입니까?
stonedauwg 19
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.