developer tip

iOS는 일정 기간 동안 활동이 없으면 작업을 수행합니다 (사용자 상호 작용 없음).

copycodes 2020. 12. 28. 08:26
반응형

iOS는 일정 기간 동안 활동이 없으면 작업을 수행합니다 (사용자 상호 작용 없음).


사용자 상호 작용 (또는 부족)을 기반으로하는 내 iOS 앱에 타이머를 추가하려면 어떻게해야합니까? 즉, 2 분 동안 사용자 상호 작용이 없으면 앱이 작업을 수행하도록하고 싶습니다.이 경우 초기 뷰 컨트롤러로 이동합니다. 1:55에 누군가 화면을 터치하면 타이머가 재설정됩니다. 나는 이것이 글로벌 타이머가 필요하다고 생각하기 때문에 어떤 뷰를 사용하든 상호 작용이 없으면 타이머가 시작됩니다. 하지만 각보기에 고유 한 타이머를 만들 수 있습니다. 누구든지 이전에 이것이 수행 된 제안, 링크 또는 샘플 코드가 있습니까?


Anne이 제공 한 링크는 훌륭한 출발점 이었지만, 제가 n00b이기 때문에 기존 프로젝트로 번역하기가 어려웠습니다. 더 나은 단계별로 제공되는 블로그 [원래 블로그가 더 이상 존재하지 않음]를 찾았지만 XCode 4.2 용으로 작성되지 않았고 스토리 보드를 사용하지 않았습니다. 다음은 내 앱에서 작동하도록 비활성 타이머를 설정하는 방법에 대한 설명입니다.

  1. 새 파일 만들기-> Objective-C 클래스-> 이름 (제 경우에는 TIMERUIApplication)을 입력하고 하위 클래스를 UIApplication으로 변경합니다. 하위 클래스 필드에 수동으로 입력해야 할 수 있습니다. 이제 적절한 .h 및 .m 파일이 있어야합니다.

  2. .h 파일을 다음과 같이 변경하십시오.

    #import <Foundation/Foundation.h>
    
    //the length of time before your application "times out". This number actually represents seconds, so we'll have to multiple it by 60 in the .m file
    #define kApplicationTimeoutInMinutes 5
    
    //the notification your AppDelegate needs to watch for in order to know that it has indeed "timed out"
    #define kApplicationDidTimeoutNotification @"AppTimeOut"
    
    @interface TIMERUIApplication : UIApplication
    {
        NSTimer     *myidleTimer;
    }
    
    -(void)resetIdleTimer;
    
    @end
    
  3. .m 파일을 다음과 같이 변경하십시오.

    #import "TIMERUIApplication.h"
    
    @implementation TIMERUIApplication
    
    //here we are listening for any touch. If the screen receives touch, the timer is reset
    -(void)sendEvent:(UIEvent *)event
    {
        [super sendEvent:event];
    
        if (!myidleTimer)
        {
            [self resetIdleTimer];
        }
    
        NSSet *allTouches = [event allTouches];
        if ([allTouches count] > 0)
        {
            UITouchPhase phase = ((UITouch *)[allTouches anyObject]).phase;
            if (phase == UITouchPhaseBegan)
            {
                [self resetIdleTimer];
            }
    
        }
    }
    //as labeled...reset the timer
    -(void)resetIdleTimer
    {
        if (myidleTimer)
        {
            [myidleTimer invalidate];
        }
        //convert the wait period into minutes rather than seconds
        int timeout = kApplicationTimeoutInMinutes * 60;
        myidleTimer = [NSTimer scheduledTimerWithTimeInterval:timeout target:self selector:@selector(idleTimerExceeded) userInfo:nil repeats:NO];
    
    }
    //if the timer reaches the limit as defined in kApplicationTimeoutInMinutes, post this notification
    -(void)idleTimerExceeded
    {
        [[NSNotificationCenter defaultCenter] postNotificationName:kApplicationDidTimeoutNotification object:nil];
    }
    
    
    @end
    
  4. Supporting Files 폴더로 이동하여 main.m을 다음으로 변경하십시오 (이전 버전의 XCode와 다름).

    #import <UIKit/UIKit.h>
    
    #import "AppDelegate.h"
    #import "TIMERUIApplication.h"
    
    int main(int argc, char *argv[])
    {
        @autoreleasepool {
            return UIApplicationMain(argc, argv, NSStringFromClass([TIMERUIApplication class]), NSStringFromClass([AppDelegate class]));
        }
    }
    
  5. AppDelegate.m 파일에 나머지 코드를 작성하십시오. 이 프로세스와 관련이없는 코드는 생략했습니다. .h 파일에는 변경할 사항이 없습니다.

    #import "AppDelegate.h"
    #import "TIMERUIApplication.h"
    
    @implementation AppDelegate
    
    @synthesize window = _window;
    
    -(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 
    {      
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidTimeout:) name:kApplicationDidTimeoutNotification object:nil];
    
        return YES;
    }
    
    -(void)applicationDidTimeout:(NSNotification *) notif
    {
        NSLog (@"time exceeded!!");
    
    //This is where storyboarding vs xib files comes in. Whichever view controller you want to revert back to, on your storyboard, make sure it is given the identifier that matches the following code. In my case, "mainView". My storyboard file is called MainStoryboard.storyboard, so make sure your file name matches the storyboardWithName property.
        UIViewController *controller = [[UIStoryboard storyboardWithName:@"MainStoryboard" bundle:NULL] instantiateViewControllerWithIdentifier:@"mainView"];
    
        [(UINavigationController *)self.window.rootViewController pushViewController:controller animated:YES];
    }
    

참고 : 터치가 감지 될 때마다 타이머가 시작됩니다. 즉, 사용자가 해당보기에서 벗어나지 않고도 기본 화면 (제 경우에는 "mainView")을 터치하면 할당 된 시간이 지나면 동일한보기가 자동으로 푸시됩니다. 내 앱에는 큰 문제가 아니지만 당신에게는 그럴 수도 있습니다. 타이머는 터치가 인식 된 후에 만 ​​재설정됩니다. 원하는 페이지로 돌아 오자마자 타이머를 재설정하려면 ... pushViewController : controller animated : YES] 뒤에이 코드를 포함합니다.

[(TIMERUIApplication *)[UIApplication sharedApplication] resetIdleTimer];

이렇게하면 뷰가 상호 작용없이 앉아있는 경우 x 분마다 푸시됩니다. 타이머는 터치를 인식 할 때마다 재설정되므로 여전히 작동합니다.

개선을 제안한 경우 특히 "mainView"가 현재 표시되는 경우 타이머를 비활성화하는 방법에 대해 의견을 보내주십시오. 현재 뷰를 등록하기 위해 if 문을 알아낼 수없는 것 같습니다. 그러나 나는 내가있는 곳에 만족한다. 아래는 if 문에 대한 나의 초기 시도이므로 내가 어디로 가고 있는지 알 수 있습니다.

-(void)applicationDidTimeout:(NSNotification *) notif
{
    NSLog (@"time exceeded!!");
    UIViewController *controller = [[UIStoryboard storyboardWithName:@"MainStoryboard" bundle:NULL] instantiateViewControllerWithIdentifier:@"mainView"];

    //I've tried a few varieties of the if statement to no avail. Always goes to else.
    if ([controller isViewLoaded]) {
        NSLog(@"Already there!");
    }
    else {
        NSLog(@"go home");
        [(UINavigationController *)self.window.rootViewController pushViewController:controller animated:YES];
        //[(TIMERUIApplication *)[UIApplication sharedApplication] resetIdleTimer];
    }
}

나는 여전히 n00b이고 모든 것을 최선의 방법으로하지 않았을 수도 있습니다. 제안은 언제나 환영합니다.


Bobby가 제안한 것을 구현했지만 Swift로 구현했습니다. 코드는 다음과 같습니다.

  1. 새 파일 만들기-> Swift File-> 이름 (내 경우에는 TimerUIApplication)을 입력하고 하위 클래스를 UIApplication으로 변경합니다. TimerUIApplication.swift 파일을 다음과 같이 변경하십시오.

    class TimerUIApplication: UIApplication {
    
        static let ApplicationDidTimoutNotification = "AppTimout"
    
        // The timeout in seconds for when to fire the idle timer.
        let timeoutInSeconds: TimeInterval = 5 * 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 event.allTouches?.first(where: { $0.phase == .began }) != nil {
                resetIdleTimer()
            }
        }
    
        // Resent the timer because there was user interaction.
        func resetIdleTimer() {
            idleTimer?.invalidate()
            idleTimer = Timer.scheduledTimer(timeInterval: timeoutInSeconds, target: self, selector: #selector(AppDelegate.idleTimerExceeded), userInfo: nil, repeats: false)
        }
    
        // If the timer reaches the limit as defined in timeoutInSeconds, post this notification.
        func idleTimerExceeded() {
            Foundation.NotificationCenter.default.post(name: NSNotification.Name(rawValue: TimerUIApplication.ApplicationDidTimoutNotification), object: nil)
        }
    }
    
  2. 새 파일 만들기-> Swift File-> main.swift (이름이 중요합니다).

    import UIKit
    
    UIApplicationMain(Process.argc, Process.unsafeArgv, NSStringFromClass(TimerUIApplication), NSStringFromClass(AppDelegate))
    
  3. AppDelegate에서 : AppDelegate 위에서 제거하십시오 @UIApplicationMain.

    class AppDelegate: UIResponder, UIApplicationDelegate {
    
        func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
            NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(AppDelegate.applicationDidTimout(_:)), name: TimerUIApplication.ApplicationDidTimoutNotification, object: nil)
            return true
        }
    
        ...
    
        // The callback for when the timeout was fired.
        func applicationDidTimout(notification: NSNotification) {
            if let vc = self.window?.rootViewController as? UINavigationController {
                if let myTableViewController = vc.visibleViewController as? MyMainViewController {
                    // Call a function defined in your view controller.
                    myMainViewController.userIdle()
                } else {
                  // We are not on the main view controller. Here, you could segue to the desired class.
                  let storyboard = UIStoryboard(name: "MyStoryboard", bundle: nil)
                  let vc = storyboard.instantiateViewControllerWithIdentifier("myStoryboardIdentifier")
                }
            }
        }
    }
    

루트 뷰 컨트롤러에 따라 applicationDidTimout에서 다른 작업을 수행해야 할 수도 있습니다. 뷰 컨트롤러를 캐스팅하는 방법에 대한 자세한 내용 이 게시물 을 참조하십시오. 탐색 컨트롤러에 대한 모달 뷰가있는 경우 topViewController 대신 visibleViewController 를 사용할 수 있습니다 .


배경 [Swift 솔루션]

이 답변을 Swift로 업데이트하라는 요청이 있었으므로 아래에 스 니펫을 추가했습니다.

내 용도로 사양을 다소 수정했습니다. 기본적으로 UIEvents5 초 동안 작업이 없으면 작업을하고 싶습니다 . 들어오는 터치 UIEvent는 이전 타이머를 취소하고 새 타이머로 다시 시작합니다.

위 답변과의 차이점

  • 위의 답변 에서 일부 변경 : 첫 번째 이벤트에서 첫 번째 타이머를 설정하는 대신 타이머를 init()즉시 설정했습니다 . 또한 reset_idle_timer()이전 타이머를 취소하므로 한 번에 하나의 타이머 만 실행됩니다.

중요 : 조립 전 2 단계

SO에 대한 몇 가지 훌륭한 답변 덕분에 위의 코드를 Swift 코드로 조정할 수있었습니다.

  • Swift 에서 하위 클래스를 만드는 방법에 대한 요약은 이 답변따르십시오 UIApplication. Swift에 대해 이러한 단계를 따르지 않으면 아래의 스 니펫이 컴파일되지 않습니다. 연결된 답변이 단계를 너무 잘 설명했기 때문에 여기서 반복하지 않겠습니다. 제대로 읽고 설정하는 데 1 분도 걸리지 않습니다.

  • 나는 가져올 수 없습니다 NSTimer'들 cancelPreviousPerformRequestsWithTarget:나는이 발견, 그래서 일에 업데이트 된 GCD 솔루션 잘 작동합니다. 그냥 별도의 .swift 파일에 해당 코드를 삭제하고 (당신이 호출 할 수 있도록 당신은 GTG하는 delay()cancel_delay()및 사용 dispatch_cancelable_closure).

IMHO, 아래 코드는 누구나 이해할 수있을만큼 간단합니다. 이 답변에 대한 질문에 답변하지 않은 것에 대해 미리 사과드립니다 (작업 ATM으로 약간 넘침).

나는 내가 얻은 훌륭한 정보에 다시 기여하기 위해이 답변을 게시했습니다.

단편

import UIKit
import Foundation

private let g_secs = 5.0

class MYApplication: UIApplication
{
    var idle_timer : dispatch_cancelable_closure?

    override init()
    {
        super.init()
        reset_idle_timer()
    }

    override func sendEvent( event: UIEvent )
    {
        super.sendEvent( event )

        if let all_touches = event.allTouches() {
            if ( all_touches.count > 0 ) {
                let phase = (all_touches.anyObject() as UITouch).phase
                if phase == UITouchPhase.Began {
                    reset_idle_timer()
                }
            }
        }
    }

    private func reset_idle_timer()
    {
        cancel_delay( idle_timer )
        idle_timer = delay( g_secs ) { self.idle_timer_exceeded() }
    }

    func idle_timer_exceeded()
    {
        println( "Ring ----------------------- Do some Idle Work!" )
        reset_idle_timer()
    }
}

참고 : 터치가 감지 될 때마다 타이머가 시작됩니다. 즉, 사용자가 해당보기에서 벗어나지 않고도 기본 화면 (제 경우에는 "mainView")을 터치하면 할당 된 시간이 지나면 동일한보기가 자동으로 푸시됩니다. 내 앱에는 큰 문제가 아니지만 당신에게는 그럴 수도 있습니다. 타이머는 터치가 인식 된 후에 만 ​​재설정됩니다. 원하는 페이지로 돌아 오자마자 타이머를 재설정하려면 ... pushViewController : controller animated : YES] 뒤에이 코드를 포함합니다.

동일한 뷰가 다시 표시되는이 문제에 대한 한 가지 해결책은 appdelegate에 BOOL을두고 사용자가 유휴 상태인지 확인하려는 경우이를 true로 설정하고 유휴 뷰로 이동 한 경우 false로 설정하는 것입니다. 그런 다음 idleTimerExceeded 메서드의 TIMERUIApplication에 아래와 같은 if 문이 있습니다. 유휴 상태로 시작하는 사용자를 확인하려는 모든보기의 viewDidload보기에서 appdelegate.idle을 true로 설정하고, 사용자가 유휴 상태인지 확인할 필요가없는 다른보기가있는 경우이를 false로 설정할 수 있습니다. .

-(void)idleTimerExceeded{
          AppDelegate *appdelegate = [[UIApplication sharedApplication] delegate];

          if(appdelegate.idle){
            [[NSNotificationCenter defaultCenter] postNotificationName: kApplicationDidTimeOutNotification object:nil]; 
          }
}

여기에 Swift 3 예제

  1. 같은 클래스를 만듭니다.

     import Foundation
     import UIKit
    
     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//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)
       // print("3")
      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 {
        // print("1")
         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() {
          print("Time Out")
    
       NotificationCenter.default.post(name:Notification.Name.TimeOutUserInteraction, object: nil)
    
         //Go Main page after 15 second
    
        let appDelegate = UIApplication.shared.delegate as! AppDelegate
       appDelegate.window = UIWindow(frame: UIScreen.main.bounds)
        let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
       let yourVC = mainStoryboard.instantiateViewController(withIdentifier: "ViewController") as! ViewController
      appDelegate.window?.rootViewController = yourVC
      appDelegate.window?.makeKeyAndVisible()
    
    
       }
    }
    
  2. 라는 이름의 또 다른 클래스 생성 main.swift 를 넣고 코드를 붙여 넣를

    import Foundation
       import UIKit
    
       CommandLine.unsafeArgv.withMemoryRebound(to: UnsafeMutablePointer<Int8>.self, capacity: Int(CommandLine.argc))
        {    argv in
                _ = UIApplicationMain(CommandLine.argc, argv, NSStringFromClass(InterractionUIApplication.self), NSStringFromClass(AppDelegate.self))
            }
    
  3. AppDelegate에서 @UIApplicationMain 을 제거하는 것을 잊지 마십시오

  4. Swift 3 complete source code is given to GitHub. GitHub link:https://github.com/enamul95/UserInactivity


Swift 3.0 Conversion of the subclassed UIApplication in Vanessa's Answer

class TimerUIApplication: UIApplication {
static let ApplicationDidTimoutNotification = "AppTimout"

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

    var idleTimer: Timer?

    // 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(TimerUIApplication.idleTimerExceeded), userInfo: nil, repeats: false)
    }

    // If the timer reaches the limit as defined in timeoutInSeconds, post this notification.
    func idleTimerExceeded() {
        NotificationCenter.default.post(name: NSNotification.Name(rawValue: TimerUIApplication.ApplicationDidTimoutNotification), object: nil)
    }


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

    }
}

ReferenceURL : https://stackoverflow.com/questions/8085188/ios-perform-action-after-period-of-inactivity-no-user-interaction

반응형