81

I have a view controller that uses an NSTimer to execute some code.

What's the best way to detect when the app is going to the background so I can pause the timer?

Aaron Brager
  • 65,323
  • 19
  • 161
  • 287
jfisk
  • 6,125
  • 20
  • 77
  • 113

10 Answers10

175

You can have any class interested in when the app goes into the background receive notifications. This is a good alternative to coupling these classes with the AppDelegate.

When initializing said classes:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appWillResignActive:) name:UIApplicationWillResignActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appWillTerminate:) name:UIApplicationWillTerminateNotification object:nil];

Responding to the notifications

-(void)appWillResignActive:(NSNotification*)note
{

}
-(void)appWillTerminate:(NSNotification*)note
{
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillResignActiveNotification object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillTerminateNotification object:nil];

}
Jesse Black
  • 7,966
  • 3
  • 34
  • 45
  • 2
    I had to make a small fix for this code to work: by adding a colon to the method names inside the `@selector`, i.e. replacing `@selector(appWillResignActive)` with `@selector(appWillResignActive:)`(and same for `@selector(appWillTerminate:)`). – Piovezan Aug 07 '13 at 11:58
  • @Piovezan, the reason you need the ":" is because whatever you call your method it must still take "..one and only one argument (an instance of NSNotification)." - Just Alt + left click on the addObserver declaration to find out more. – serge-k May 25 '15 at 19:55
  • willResignActive does not mean it is going into the background -- it means it is going inactive. For example, an incoming call on top of your app will make you inactive as will system level dialog popup. UIApplicationDidEnterBackground is the notification for actually going into the background. – chadbag Sep 20 '19 at 14:33
26

In Swift 4.0

override func viewDidLoad() {
    super.viewDidLoad()

    let app = UIApplication.shared

    //Register for the applicationWillResignActive anywhere in your app.
    NotificationCenter.default.addObserver(self, selector: #selector(ViewController.applicationWillResignActive(notification:)), name: NSNotification.Name.UIApplicationWillResignActive, object: app)
}

@objc func applicationWillResignActive(notification: NSNotification) {

}
Bart van Kuik
  • 4,704
  • 1
  • 33
  • 57
Ashok R
  • 19,892
  • 8
  • 68
  • 68
  • 1
    Where are you un-registering the notification here? – thexande Jun 25 '18 at 19:47
  • 1
    @thexande if I'm correct Swift 4 does it for you and you do not have to un-register Notification anymore (unless you cannot wait ;-) – Stephane Paquet Oct 07 '18 at 22:34
  • 2
    @StephanePaquet from Apple's docs: "If your app targets iOS 9.0 and later or macOS 10.11 and later, you don't need to unregister an observer in its dealloc method." : ) – Rob VS Jan 04 '19 at 18:46
10

On your applications AppDelegate the (void)applicationDidEnterBackground:(UIApplication *)application method will be called by iOS. You can stop your timer in there.

Damien
  • 2,421
  • 22
  • 38
  • 3
    Your app can also register itself for UIApplicationDidEnterBackgroundNotification notifications. – Marco Jan 26 '12 at 11:26
8

For those looking to do this in Swift:

On init:

NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(applicationWillResignActive), name: UIApplicationWillResignActiveNotification, object: nil)

On deinit:

NSNotificationCenter.defaultCenter().removeObserver(self, name: UIApplicationWillResignActiveNotification, object: nil)

Responding to the notification:

dynamic private func applicationWillResignActive() {
    // Do things here
}

Apple encourages us to avoid dynamic dispatch and Objective-C selectors whenever possible in Swift, but this is still the most convenient way to do this.

Luke
  • 7,110
  • 6
  • 45
  • 74
2

In swift 4.1:

I use the closure version:

var observer: NSObjectProtocol!

// inside init or viewDidLoad:
observer = NotificationCenter.default.addObserver(forName: .UIApplicationWillResignActive, object: nil, queue: nil) { _ in
    print("willResignActive")
}

deinit {
    NotificationCenter.default.removeObserver(observer)
}

The addObserver method returns an opaque object that needs to be removed at some point.

juanjo
  • 3,737
  • 3
  • 39
  • 44
1

only a side note: If you register a controller A to be notified going background, be careful that it will be called even if you (for example..) push a second controller B and You are displaying B: If this behaviour is not correct, is better to register/unregister in

didAppear/WillDisappear.

ingconti
  • 10,876
  • 3
  • 61
  • 48
0

Swift 4:

init() {
    NotificationCenter.default.addObserver(self,
                                           selector: #selector(applicationWillResignActive),
                                           name: NSNotification.Name.UIApplicationWillResignActive,
                                           object: nil)
}

deinit {
    NotificationCenter.default.removeObserver(self,
                                              name: NSNotification.Name.UIApplicationWillResignActive,
                                              object: nil)
}

@objc private func applicationWillResignActive() {
    self.header.blur.effect = nil
}
thexande
  • 1,645
  • 16
  • 23
0

This is a better solution using closure

Declare observer

var backgroundObserver: NSObjectProtocol?

Initialize observer in viewDidLoad

backgroundObserver = NotificationCenter.default.addObserver(forName: UIApplication.willResignActiveNotification, object: nil, queue: .main) { [weak self] notification in
  // Do what you want to do when app would go to background/ resign active  
}

Don't forget to remove observer in deinit

deinit {
    if let observer = backgroundObserver {
        NotificationCenter.default.removeObserver(observer)
    } 
}
Mohammad Sadiq
  • 5,070
  • 28
  • 29
0

In Swift 5.1

    override func viewWillAppear(_ animated: Bool) {
         super.viewWillAppear(animated)

         NotificationCenter.default.addObserver(self, selector: #selector(applicationWillResignActive), name: UIApplication.willResignActiveNotification, object: nil)
    
         NotificationCenter.default.addObserver(self, selector: #selector(applicationDidBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil)
        }
    
    override func viewWillDisappear(_ animated: Bool) { 
        super.viewWillDisappear(animated)

        NotificationCenter.default.removeObserver(self, name: UIApplication.willResignActiveNotification, object: nil)

        NotificationCenter.default.removeObserver(self, name: UIApplication.didBecomeActiveNotification, object: nil)
    }


@objc private func applicationWillResignActive() {
    }

    @objc private func applicationDidBecomeActive() {
    }
Naveen Sharma
  • 311
  • 2
  • 8
0

- (void)applicationWillResignActive:(UIApplication *)application on your app delegate. You can also register for the UIApplicationWillResignActiveNotification notification on other objects.

You don't necessarily need to pause the timer, though. If you don't do anything, the app will get put to sleep anyway and won't execute any code. Presumably your timer will fire when you become active again (if you do). If you need to do something special there are 'did become active' delegate methods and notifications you can register for as well.

smparkes
  • 13,807
  • 4
  • 36
  • 61
  • As mentioned, there is a "background" version that's pretty similar to resign active if you're only targeting 4.0 and later. The "active" versions go back to 2.0. – smparkes Jan 25 '12 at 23:28
  • +1, didn't realize you had the notification portion when I put up my answer – Jesse Black Jan 26 '12 at 01:04
  • 1
    Strictly speaking an app resigning active state may not end up in background state (e.g. in the case of a temporary interruption such as a phone call or SMS). – Marco Jan 26 '12 at 11:31
  • 1
    @marco: agreed. I was being a little fast and loose. A lot of people (can't be sure about the OP) don't really differentiate between inactive and background. I thought, given the way the question was phrased, that inactive was more what he was looking for but maybe I went to far there. FWIW, certain modal pop ups from the OS (e.g., for network and location requirements) will also trigger resign active. – smparkes Jan 26 '12 at 16:14