iPhone: अंतिम स्क्रीन टच के बाद से उपयोगकर्ता निष्क्रियता / निष्क्रिय समय का पता लगाना


152

क्या किसी ने एक सुविधा लागू की है जहाँ अगर उपयोगकर्ता ने एक निश्चित समयावधि के लिए स्क्रीन को नहीं छुआ है, तो आप एक निश्चित कार्यवाही करते हैं? मैं ऐसा करने का सबसे अच्छा तरीका जानने की कोशिश कर रहा हूं।

UIApplication में यह कुछ हद तक संबंधित विधि है:

[UIApplication sharedApplication].idleTimerDisabled;

यह अच्छा होगा यदि आप इसके बजाय कुछ इस तरह थे:

NSTimeInterval timeElapsed = [UIApplication sharedApplication].idleTimeElapsed;

तब मैं एक टाइमर सेट कर सकता था और समय-समय पर इस मूल्य की जांच कर सकता था, और जब यह सीमा से अधिक हो जाए तो कुछ कार्रवाई करें।

उम्मीद है कि बताते हैं कि मैं क्या देख रहा हूँ। क्या किसी ने पहले से ही इस मुद्दे से निपट लिया है, या आपके पास कोई विचार है कि आप इसे कैसे करेंगे? धन्यवाद।


यह एक बड़ा सवाल है। विंडोज में एक OnIdle इवेंट का कॉन्सेप्ट है, लेकिन मुझे लगता है कि यह ऐप के बारे में अधिक है जो वर्तमान में आईओएस idleTimerDisabled संपत्ति के संदेश पंप में कुछ भी नहीं संभाल रहा है जो केवल डिवाइस को लॉक करने से संबंधित है। किसी को पता है कि क्या आईओएस / मैकओएसएक्स में विंडोज अवधारणा के करीब भी कुछ है?
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 उदाहरण चर हैं।

इसे काम करने के लिए, आपको अपने मुख्य प्रतिनिधि को UIApplicationMain को अपने प्रतिनिधि वर्ग (उदाहरण के लिए, AppDelegate) में मुख्य वर्ग के रूप में बताने के लिए संशोधित करने की आवश्यकता है:

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

3
हाय माइक, मेरा AppDelegate NSObject से विरासत में मिला है इसलिए उपयोगकर्ता को निष्क्रिय होने का पता लगाने के लिए इसे UIApplication और Implement को बदल दिया है, लेकिन मुझे त्रुटि मिल रही है "बिना किसी अपवाद के अपवाद के कारण" समाप्त हो रहा ऐप "कारण: 'केवल एक UIApplication आवृत्ति हो सकती है।' “.. और भी कुछ करना है मुझे ...?
मिहिर मेहता

7
मुझे लगता है कि UIApplication उपवर्ग UIApplicationDelegate उपवर्ग से अलग होना चाहिए
boliva

मुझे यकीन नहीं है कि यह डिवाइस निष्क्रिय अवस्था में प्रवेश करने वाले डिवाइस के साथ कैसे काम करेगा जब टाइमर फायरिंग करना बंद कर देंगे?
अपराह्न

यदि मैं टाइमआउट इवेंट के लिए popToRootViewController फ़ंक्शन का उपयोग करके असाइन करता हूं, तो यह ठीक से काम नहीं करता है। यह तब होता है जब मैं UIAlertView, तो popToRootViewController दिखाने के लिए, तो मैं जो पहले से ही पॉपअप है UIViewController से एक चयनकर्ता के साथ UIAlertView पर कोई बटन दबाएँ
Gargo

4
बहुत अच्छा! हालांकि, यह दृष्टिकोण बहुत सारे NSTimerउदाहरण बनाता है यदि बहुत सारे स्पर्श हैं।
एंड्रियास ले

86

मेरे पास निष्क्रिय टाइमर समाधान की भिन्नता है जिसके लिए उप-उपकरण की आवश्यकता नहीं है। यह एक विशिष्ट 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
बहुत अच्छा। यह जवाब चट्टानों! जवाब को सही के रूप में चिह्नित करता है, हालांकि मुझे पता है कि यह बहुत पहले था लेकिन अब यह एक बेहतर समाधान है।
चिंतन पटेल

यह बहुत अच्छा है, लेकिन एक मुद्दा मुझे मिला: UITableViews में स्क्रॉल करने से नेक्स्टपॉन्डर को बुलाया नहीं जा सकता। मैंने भी टच के माध्यम से ट्रैकिंग की कोशिश की: कोई विचार?
ग्रेग मैलैटिक

3
@GregMaletic: मेरे पास एक ही मुद्दा था, लेकिन आखिरकार मैंने जोड़ दिया है - (शून्य) स्क्रॉलव्यूविलबिन मार्जिन: (UIScrollView *) स्क्रॉलव्यू {NSLog (@ "खींचना शुरू कर देंगे"); } - (शून्य) स्क्रॉलव्यूडिक्रोल: (यूआईएसक्रॉल व्यू *) स्क्रॉल दृश्य {एनएसलॉग (@ "स्क्रॉल किया"); [सेल्फ रिसेटइंडलीमर]; } क्या आपने यह कोशिश की है?
अक्षय अहिर

धन्यवाद। यह अभी भी मददगार है। मैंने इसे स्विफ्ट में पोर्ट किया और इसने बहुत अच्छा काम किया।
Mark.ewd

आप एक रॉकस्टार हैं। kudos
प्रस

21

स्विफ्ट वी 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()विधि?
वांग वांग

हम timeoutInSecondsवेब सेवा प्रतिक्रिया से मूल्य कैसे निर्धारित कर सकते हैं ?
यूजर_1191

12

यह धागा एक बड़ी मदद थी, और मैंने इसे UIWindow उपवर्ग में लपेट दिया, जो सूचनाएँ भेजता है। मैंने इसे वास्तविक ढीली युग्मन बनाने के लिए अधिसूचनाएँ चुनीं, लेकिन आप एक प्रतिनिधि को आसानी से जोड़ सकते हैं।

यहाँ है जिस्ट:

http://gist.github.com/365998

इसके अलावा, UIApplication उपवर्ग समस्या का कारण यह है कि NIB तब 2 UIApplication ऑब्जेक्ट बनाता है क्योंकि इसमें एप्लिकेशन और प्रतिनिधि शामिल हैं। UIWindow उपवर्ग हालांकि महान काम करता है।


1
क्या आप मुझे अपने कोड का उपयोग करने का तरीका बता सकते हैं? मुझे समझ में नहीं आता कि इसे कैसे कहा जाए
R. Dewi

2
यह स्पर्श के लिए बहुत अच्छा काम करता है, लेकिन कीबोर्ड से इनपुट को संभालता नहीं है। इसका मतलब यह है कि उपयोगकर्ता कीबोर्ड पर सामान टाइप कर रहा है या नहीं।
मार्टिन विकमैन

2
मुझे यह भी समझ में नहीं आ रहा है कि इसका उपयोग कैसे किया जाए ... मैं अपने विचार नियंत्रक में पर्यवेक्षकों को जोड़ता हूं और जब एप्लिकेशन अछूता / निष्क्रिय हो तो निकाल दिए गए नोटिफिकेशन की अपेक्षा करता है .. लेकिन कुछ भी नहीं हुआ ... साथ ही हम निष्क्रिय समय को कहां से नियंत्रित कर सकते हैं? जैसे मैं 120 सेकंड का बेकार समय चाहता हूं ताकि 120 सेकंड के बाद IdleNotification को आग लग जाए, इससे पहले नहीं।
Ans

5

वास्तव में उपवर्ग विचार महान काम करता है। बस अपने प्रतिनिधि को UIApplicationउपवर्ग मत बनाओ । एक और फ़ाइल बनाएँ, जो विरासत में मिली है UIApplication(जैसे myApp)। IB में fileOwnerऑब्जेक्ट के वर्ग को myAppऔर myApp.m sendEventमें ऊपर की तरह विधि लागू करें । मुख्य रूप से करते हैं:

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

एट वॉयला!


1
हां, स्टैंड-अलोन UIApplication उपवर्ग बनाने से ठीक काम लगता है। मैंने मुख्य में दूसरा पैर्म निल छोड़ा।
हॉट लक्स

@ रॉबी, एक नज़र है कि मेरी क्वेरी stackoverflow.com/questions/20088521/…
तीर्थ

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मेनू में प्रवेश करते समय निष्क्रिय टाइमर को निष्क्रिय कर देता है, या जो भी निष्क्रिय टाइमर के साथ चलना चाहिए सक्रिय है और enableIdleTimerआपके applicationWillResignActiveसभी परिवर्तनों को सिस्टम डिफ़ॉल्ट व्यवहार पर ठीक से रीसेट करने के लिए आपके AppDelegate की विधि से कॉल किया जाता है।
मैंने एक लेख लिखा और आईफोन गेम्स में सिंगलटन क्लास आइडिलटीमरमैन आइडल टाइमर हैंडलिंग के लिए कोड प्रदान किया


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" का चयन करना चाहिए। शायद मुझे आपकी बात याद आ रही है?
मिहाई तिमार

आप कहते हैं कि यह गतिविधि का पता लगाने का एक और तरीका है, हालांकि, यह केवल एक iVar को रोकता है जो एक NSTimer है। मैं यह नहीं देखता कि यह ओपी के सवाल का जवाब कैसे देता है।
जैस्पर

1
टाइमर को UITrackingRunLoopMode में जोड़ा गया है, इसलिए यह केवल तभी फायर कर सकता है जब UITracking गतिविधि हो। यह आपको सभी स्पर्श घटनाओं के लिए स्पैमिंग नहीं करने का भी अच्छा लाभ है, इस प्रकार यह सूचित करता है कि यदि पिछले ACTIVITY_DETECT_TIMER_RESOLUTION सेकंड में गतिविधि थी। मैंने चयनकर्ता को नाम दिया क्योंकि यह इसके लिए एक उपयुक्त उपयोग मामला लगता है। आप निश्चित रूप से उस जानकारी के साथ जो कुछ भी आप चाहते हैं, कर सकते हैं जो हाल ही में गतिविधि थी।
मिहाई तिमार

1
मैं इस उत्तर को सुधारना चाहता हूं। यदि आप इसे स्पष्ट करने के लिए संकेत के साथ मदद कर सकते हैं तो यह एक बड़ी मदद होगी।
मिहाई तिमार

मैंने आपके स्पष्टीकरण को आपके उत्तर में जोड़ दिया। यह अब और अधिक समझ में आता है।
जैस्पर

3

अंतत: आपको यह परिभाषित करने की आवश्यकता है कि आप किस चीज को निष्क्रिय मानते हैं - उपयोगकर्ता द्वारा स्क्रीन को नहीं छूने के परिणामस्वरूप निष्क्रिय है या क्या यह सिस्टम की स्थिति है यदि कोई कंप्यूटिंग संसाधनों का उपयोग नहीं किया जा रहा है? यह संभव है, कई अनुप्रयोगों में, उपयोगकर्ता को टच स्क्रीन के माध्यम से डिवाइस के साथ सक्रिय रूप से बातचीत नहीं करने पर भी कुछ करने के लिए। जबकि उपयोगकर्ता संभवतः डिवाइस के सो जाने की अवधारणा से परिचित है और यह नोटिस कि यह स्क्रीन डिमिंग के माध्यम से होगा, यह जरूरी नहीं है कि वे निष्क्रिय होने पर कुछ होने की उम्मीद करेंगे - आपको सावधान रहने की आवश्यकता है आप क्या करेंगे। लेकिन मूल कथन पर वापस जा रहे हैं - यदि आप 1 मामले को अपनी परिभाषा मानते हैं, तो ऐसा करने का कोई आसान तरीका नहीं है। आपको प्रत्येक टच ईवेंट प्राप्त करना होगा, इसे प्राप्त करने के समय के रूप में आवश्यकतानुसार रिस्पोंडर श्रृंखला पर इसे पास करना। यह आपको निष्क्रिय गणना करने के लिए कुछ आधार देगा। यदि आप दूसरे मामले को अपनी परिभाषा मानते हैं, तो आप उस समय अपने तर्क को आजमाने और प्रदर्शन करने के लिए एक NSPostWhenIdle अधिसूचना के साथ खेल सकते हैं।


1
बस स्पष्ट करने के लिए, मैं स्क्रीन के साथ बातचीत के बारे में बात कर रहा हूं। मैं उस प्रश्न को प्रतिबिंबित करने के लिए अपडेट करूंगा।
माइक मैकमास्टर

1
फिर आप कुछ ऐसा लागू कर सकते हैं जहां किसी भी समय एक स्पर्श होने पर आप एक मूल्य को अपडेट करते हैं जिसे आप चेक करते हैं, या यहां तक ​​कि आग लगाने के लिए एक निष्क्रिय टाइमर सेट (और रीसेट) करते हैं, लेकिन आपको इसे स्वयं लागू करने की आवश्यकता है, क्योंकि जैसा कि ज्ञानी ने कहा, निष्क्रिय का गठन क्या होता है विभिन्न एप्लिकेशन।
लुइस गेरबर्ग 21

1
मैं "निष्क्रिय" को सख्ती से परिभाषित कर रहा हूं क्योंकि स्क्रीन को अंतिम रूप से छू रहा हूं। मुझे लगता है कि मुझे इसे स्वयं लागू करने की आवश्यकता होगी, मैं बस सोच रहा था कि "सर्वश्रेष्ठ" तरीका क्या होगा, कहने के लिए, अवरोधन स्क्रीन छूता है, या यदि कोई व्यक्ति इसे निर्धारित करने के लिए एक वैकल्पिक विधि जानता है।
माइक मैकमास्टर

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 को कॉल करें और आप सभी सेट हो जाएं। सभी टच कैचअलीगचर के तरीकों से गुजरेंगे, इसके बिना दूसरों की कार्यक्षमता को रोक नहीं पाएंगे।


1
मुझे यह दृष्टिकोण पसंद है, इसे
ज़मैरीन के

1
महान काम करता है, यह भी लगता है कि इस तकनीक का उपयोग AVPlayerViewController ( निजी एपीआई रेफरी ) में यूआई नियंत्रण की दृश्यता को नियंत्रित करने के लिए किया जाता है । ओवरराइडिंग ऐप -sendEvent:ओवरकिल है, और UITrackingRunLoopModeकई मामलों को संभालती नहीं है।
रोमन बी।

@RomanB। हां, ठीक यही। जब आप आईओएस के साथ काम किया काफी देर तक आप हमेशा "सही तरीके" का उपयोग करने को जानते हैं और यह इरादा रूप में एक कस्टम इशारा लागू करने के लिए सरल तरीका है developer.apple.com/documentation/uikit/uigesturerecognizer/...
Jlam

राज्य को स्पर्श में प्राप्त की गई।
stonedauwg

मुझे यह दृष्टिकोण पसंद है लेकिन जब यह केवल नल को पकड़ने की कोशिश करता है, तो किसी अन्य इशारे जैसे पैन, स्वाइप आदि को नहीं छूता है। क्या यही इरादा था?
पत्थरचट्टा
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.