I have a View Controller in which my value is 0 (label) and when I open that View Controller from another ViewController I have set viewDidAppear to set value 20 on label. It works fine but when I close my app and than again I open my app but the value doesn't change because viewDidLoad, viewDidAppear and viewWillAppear nothing get called. How can I call when I open my app. Do I have to do anything from applicationDidBecomeActive?
Curious about the exact sequence of events, I instrumented an app as follows: (#Zohaib, you can use the NSNotificationCenter code below to answer your question).
// AppDelegate.m
- (void)applicationWillEnterForeground:(UIApplication *)application
{
NSLog(#"app will enter foreground");
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
NSLog(#"app did become active");
}
// ViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"view did load");
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(appDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(appWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
}
- (void)appDidBecomeActive:(NSNotification *)notification {
NSLog(#"did become active notification");
}
- (void)appWillEnterForeground:(NSNotification *)notification {
NSLog(#"will enter foreground notification");
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSLog(#"view will appear");
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(#"view did appear");
}
At launch, the output looks like this:
2013-04-07 09:31:06.505 myapp[15459:11303] view did load
2013-04-07 09:31:06.507 myapp[15459:11303] view will appear
2013-04-07 09:31:06.511 myapp[15459:11303] app did become active
2013-04-07 09:31:06.512 myapp[15459:11303] did become active notification
2013-04-07 09:31:06.517 myapp[15459:11303] view did appear
Enter the background then reenter the foreground:
2013-04-07 09:32:05.923 myapp[15459:11303] app will enter foreground
2013-04-07 09:32:05.924 myapp[15459:11303] will enter foreground notification
2013-04-07 09:32:05.925 myapp[15459:11303] app did become active
2013-04-07 09:32:05.926 myapp[15459:11303] did become active notification
Using Objective-C
You should register a UIApplicationWillEnterForegroundNotification in your ViewController's viewDidLoad method and whenever app comes back from background you can do whatever you want to do in the method registered for notification. ViewController's viewWillAppear or viewDidAppear won't be called when app comes back from background to foreground.
-(void)viewDidLoad{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(doYourStuff)
name:UIApplicationWillEnterForegroundNotification object:nil];
}
-(void)doYourStuff{
// do whatever you want to do when app comes back from background.
}
Don't forget to unregister the notification you are registered for.
-(void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
Note if you register your viewController for UIApplicationDidBecomeActiveNotification then your method would be called every time your app becomes active, It is not recommended to register viewController for this notification .
Using Swift
For adding observer you can use the following code
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: "doYourStuff", name: UIApplication.willEnterForegroundNotification, object: nil)
}
func doYourStuff(){
// your code
}
To remove observer you can use deinit function of swift.
deinit {
NotificationCenter.default.removeObserver(self)
}
Swift 3.0 ++ version
In your viewDidLoad, register at notification center to listen to this opened from background action
NotificationCenter.default.addObserver(self, selector:#selector(doSomething), name: NSNotification.Name.UIApplicationWillEnterForeground, object: nil)
Then add this function and perform needed action
func doSomething(){
//...
}
Finally add this function to clean up the notification observer when your view controller is destroyed.
deinit {
NotificationCenter.default.removeObserver(self)
}
Swift 4.2. version
Register with the NotificationCenter in viewDidLoad to be notified when the app returns from background
NotificationCenter.default.addObserver(self, selector: #selector(doSomething), name: UIApplication.willEnterForegroundNotification, object: nil)
Implement the method that should be called.
#objc private func doSomething() {
// Do whatever you want, for example update your view.
}
You can remove the observer once the ViewController is destroyed. This is only required below iOS9 and macOS 10.11
deinit {
NotificationCenter.default.removeObserver(self)
}
Just have your view controller register for the UIApplicationWillEnterForegroundNotification notification and react accordingly.
I think registering for the UIApplicationWillEnterForegroundNotification is risky as you may end up with more than one controller reacting to that notification. Nothing garanties that these controllers are still visible when the notification is received.
Here is what I do: I force call viewDidAppear on the active controller directly from the App's delegate didBecomeActive method:
Add the code below to - (void)applicationDidBecomeActive:(UIApplication *)application
UIViewController *activeController = window.rootViewController;
if ([activeController isKindOfClass:[UINavigationController class]]) {
activeController = [(UINavigationController*)window.rootViewController topViewController];
}
[activeController viewDidAppear:NO];
try adding this in AppDelegate applicationWillEnterForeground.
func applicationWillEnterForeground(_ application: UIApplication) {
// makes viewWillAppear run
self.window?.rootViewController?.beginAppearanceTransition(true, animated: false)
self.window?.rootViewController?.endAppearanceTransition()
}
As per Apple's documentation:
(void)beginAppearanceTransition:(BOOL)isAppearing animated:(BOOL)animated;
Description:
Tells a child controller its appearance is about to change.
If you are implementing a custom container controller, use this method to tell the child that its views are about to appear or disappear. Do not invoke viewWillAppear:, viewWillDisappear:, viewDidAppear:, or viewDidDisappear: directly.
(void)endAppearanceTransition;
Description:
Tells a child controller its appearance has changed.
If you are implementing a custom container controller, use this method to tell the child that the view transition is complete.
Sample code:
(void)applicationDidEnterBackground:(UIApplication *)application
{
[self.window.rootViewController beginAppearanceTransition: NO animated: NO]; // I commented this line
[self.window.rootViewController endAppearanceTransition]; // I commented this line
}
Question: How I fixed?
Ans: I found this piece of lines in application. This lines made my app not recieving any ViewWillAppear notification's. When I commented these lines it's working fine.
Related
I am sending a notification from a class inherits from NSObject using NSNotificationCenter.
The notification should be sent to 2 viewController but it's being sent just to one of them.
My Code:
fetchFromParse:
-(void)sendAllStores
{
[[NSNotificationCenter defaultCenter]postNotificationName:#"getStoresArrays" object:nil userInfo:self.storesDict];
}
firstVC.m (working):
- (void)viewDidLoad {
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(getStoresArrays:) name:#"getStoresArrays" object:nil];
}
-(void)getStoresArrays:(NSNotification*)notification
{
NSLog(#“Working”); //Working
}
secondVC.m (not working):
-(void)prepareArrays
{
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(getStoresArrays:) name:#"getStoresArrays" object:nil];
}
-(void)getStoresArrays:(NSNotification*)notification
{
NSLog(#“Not Working”); //Not working
}
AppDelegate.m:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
secondVC *secVC=[[secondVC alloc] init];
[secVC prepareArrays];
fetchFromParse *fetchFromParseObj=[[fetchFromParse alloc] init];
[fetchFromParseObj getStoresFromParse];
Return YES;
}
Note: Xcode shows me an error message that "firstVC is not registered as an observer".
The firstVC is never used so it cannot add an observer then.
It adds the observer in the viewDidLoad, but if the view controller is never used it cannot load a view and therefore not add an observer.
The logic for your code is not correct. You are posting a notification and have made two classes as observer, so this will lead to unpredictable results. You should have one class as observer for a notification.
From what i see only one notification listener should be called here and that should be your SecondVC because your first view controller is not loaded yet so no observer is registered for first view controller.
I'm getting data off the server via JSON and displaying it on Labels.
I've added that method in viewDidLoad.
I want to refresh the data when the user opens the app again. Currently, even if I kill the app in the simulator and start the app again, it doesn't refresh.
I tried the viewDidAppear method, but it isn't being executed for some reason.
-(void)viewDidAppear{
NSLog(#"Called viewDidAppear");
}
This is never called. I tried to minimize the app but it didn't work.
You can listen for notifications and respond appropriately. Try using these and decide what works for your intended workflow.
UIApplicationDidBecomeActiveNotification
UIApplicationWillEnterForegroundNotification
You can use respond to the notification like this.
[[NSNotificationCenter defaultCenter] addObserverForName: UIApplicationDidBecomeActiveNotification object: nil queue: [NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
// LOAD JSON
}];
I followed this tutorial - http://leejon.es/notifying-a-viewcontroller-with-uiapplicationdidbecomeactivenotification/
First, attach to the notification in the viewWillAppear method of the target view controller:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector( appActivated: )
name: UIApplicationDidBecomeActiveNotification
object: nil];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self ];
}
- (void)appActivated:(NSNotification *)note
{
[self update];
}
The viewDidAppear: method takes a bool parameter wether the view was displayed with an animation which you are missing. Also you have to call the implementation of the superclass:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear: animated];
NSLog(#"Called viewDidAppear");
}
In your app delegate implementation, there is a method called:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions;
This method is called each time the app is launched, so I think it fits your needs. If you place your code here, it should work.
Also, be aware you should not perform a synchronous call here, because you will delay the app launch.
EDIT:
This method will be only called when the app launches. You could place your code inside a method, and call it from application didFinishLaunchingWithOptions, and then also call it from the method:
- (void)applicationWillEnterForeground:(UIApplication *)application;
This method will be called when the application enters the foreground, but not after the first launch, so beware.
I also think you should check the UIApplicationDelegate methods from apple developer page: http://developer.apple.com/library/ios/#documentation/uikit/reference/UIApplicationDelegate_Protocol/Reference/Reference.html
Also, check out the application state changes:
http://developer.apple.com/library/ios/#documentation/iphone/conceptual/iphoneosprogrammingguide/ManagingYourApplicationsFlow/ManagingYourApplicationsFlow.html
I want to show a full page image Ad every time a UIViewController is shown.
I think I have to call the method inside a viewDidAppear or ViewWillAppear, but they are being called once.
- (void) viewDidAppear:(BOOL)animated{
[self showAds];
}
- (void) showAds{
//Do Something
}
What should I do to call a method every time a uiviewcontroller is shown( even if its already created)?
ViewWillAppear will be called every time a UIViewController is shown,but won't be called when the app is back to foreground.
you can use Notification to achieve your goal by following code,
This scenario is specially when your app is in background and user press HOME button to active it.
Register for Notifcation when your application enterForground in viewDidLoad only.
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(handleEnteredBackground)
name: UIApplicationDidBecomeActiveNotification
object: nil];
write a method to invoke when application enterForground
-(void)handleEnteredBackground
{
NSLog(#"%s",__FUNCTION__);
// Your stuff here
}
Dont forget to Remove Observer in viewDidUnload method
[[NSNotificationCenter defaultCenter] removeObserver:self];
Post New Notification everytime your application enterForground
- (void)applicationWillEnterForeground:(UIApplication *)application
{
[[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationDidBecomeActiveNotification object:nil];
}
ViewWillAppear should be called every time. Use:
- (void) viewWillAppear:(BOOL)animated{
[self showAds];
}
When my app is first opened there is a long loading time so I can display a loading screen. When the user exits the app by clicking the home button then re-opens it (the viewDidLoad/viewDidAppear methods aren't called again) the app has another loading period, I guess while it's "waking up".
What method can I use to detect the user hitting the home button to send the app into the background and what method can I use to detect that the app has been revived from the background?
This should be sufficient enough to provide my loading screen properly but just in-case. Is there also a method for detecting when the loading has finished after a "revival"?
You can register a notification for UIApplicationWillEnterForegroundNotification. There you can do your stuff.
- (void)viewDidLoad
{
[super viewDidLoad];
// Register for the notifcation
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(refreshView) name:UIApplicationWillEnterForegroundNotification object:nil];
}
-(void)refreshView
{
/*
Invoked when application enters foreground. Do your stuff
*/
}
To remove observer
-(void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
Albert you can detect home button click on this two method declared in AppDelegate
- (void)applicationWillResignActive:(UIApplication *)application
- (void)applicationDidEnterBackground:(UIApplication *)application
and these are notification you can use
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(setFlag:)
name: UIApplicationWillResignActiveNotification
object: [UIApplication sharedApplication]];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(setFlag1:)
name: UIApplicationDidEnterBackgroundNotification
object: [UIApplication sharedApplication]];
I'm writing an app and I need to change the view if the user is looking at the app while talking on the phone.
I've implemented the following method:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSLog(#"viewWillAppear:");
_sv.frame = CGRectMake(0.0, 0.0, 320.0, self.view.bounds.size.height);
}
But it's not being called when the app returns to the foreground.
I know that I can implement:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(statusBarFrameChanged:) name:UIApplicationDidChangeStatusBarFrameNotification object:nil];
but I don't want to do this. I'd much rather put all my layout information in the viewWillAppear: method, and let that handle all possible scenarios.
I've even tried to call viewWillAppear: from applicationWillEnterForeground:, but I can't seem to pinpoint which is the current view controller at that point.
Does anybody know the proper way to deal with this? I'm sure I'm missing an obvious solution.
Swift
Short answer
Use a NotificationCenter observer rather than viewWillAppear.
override func viewDidLoad() {
super.viewDidLoad()
// set observer for UIApplication.willEnterForegroundNotification
NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
}
// my selector that was defined above
#objc func willEnterForeground() {
// do stuff
}
Long answer
To find out when an app comes back from the background, use a NotificationCenter observer rather than viewWillAppear. Here is a sample project that shows which events happen when. (This is an adaptation of this Objective-C answer.)
import UIKit
class ViewController: UIViewController {
// MARK: - Overrides
override func viewDidLoad() {
super.viewDidLoad()
print("view did load")
// add notification observers
NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
}
override func viewWillAppear(_ animated: Bool) {
print("view will appear")
}
override func viewDidAppear(_ animated: Bool) {
print("view did appear")
}
// MARK: - Notification oberserver methods
#objc func didBecomeActive() {
print("did become active")
}
#objc func willEnterForeground() {
print("will enter foreground")
}
}
On first starting the app, the output order is:
view did load
view will appear
did become active
view did appear
After pushing the home button and then bringing the app back to the foreground, the output order is:
will enter foreground
did become active
So if you were originally trying to use viewWillAppear then UIApplication.willEnterForegroundNotification is probably what you want.
Note
As of iOS 9 and later, you don't need to remove the observer. The documentation states:
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.
The method viewWillAppear should be taken in the context of what is going on in your own application, and not in the context of your application being placed in the foreground when you switch back to it from another app.
In other words, if someone looks at another application or takes a phone call, then switches back to your app which was earlier on backgrounded, your UIViewController which was already visible when you left your app 'doesn't care' so to speak -- as far as it is concerned, it's never disappeared and it's still visible -- and so viewWillAppear isn't called.
I recommend against calling the viewWillAppear yourself -- it has a specific meaning which you shouldn't subvert! A refactoring you can do to achieve the same effect might be as follows:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self doMyLayoutStuff:self];
}
- (void)doMyLayoutStuff:(id)sender {
// stuff
}
Then also you trigger doMyLayoutStuff from the appropriate notification:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(doMyLayoutStuff:) name:UIApplicationDidChangeStatusBarFrameNotification object:self];
There's no out of the box way to tell which is the 'current' UIViewController by the way. But you can find ways around that, e.g. there are delegate methods of UINavigationController for finding out when a UIViewController is presented therein. You could use such a thing to track the latest UIViewController which has been presented.
Update
If you layout out UIs with the appropriate autoresizing masks on the various bits, sometimes you don't even need to deal with the 'manual' laying out of your UI - it just gets dealt with...
Use Notification Center in the viewDidLoad: method of your ViewController to call a method and from there do what you were supposed to do in your viewWillAppear: method. Calling viewWillAppear: directly is not a good option.
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"view did load");
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationIsActive:)
name:UIApplicationDidBecomeActiveNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationEnteredForeground:)
name:UIApplicationWillEnterForegroundNotification
object:nil];
}
- (void)applicationIsActive:(NSNotification *)notification {
NSLog(#"Application Did Become Active");
}
- (void)applicationEnteredForeground:(NSNotification *)notification {
NSLog(#"Application Entered Foreground");
}
viewWillAppear:animated:, one of the most confusing methods in the iOS SDKs in my opinion, is never be invoked in such a situation, i.e., application switching. That method is only invoked according to the relationship between the view controller's view and the application's window, i.e., the message is sent to a view controller only if its view appears on the application's window, not on the screen.
When your application goes background, obviously the topmost views of the application window are no longer visible to the user. In your application window's perspective, however, they are still the topmost views and therefore they did not disappear from the window. Rather, those views disappeared because the application window disappeared. They did not disappeared because they disappeared from the window.
Therefore, when the user switches back to your application, they obviously seem to appear on the screen, because the window appears again. But from the window's perspective, they haven't disappeared at all. Therefore the view controllers never get the viewWillAppear:animated message.
Swift 4.2 / 5
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground),
name: Notification.Name.UIApplication.willEnterForegroundNotification,
object: nil)
}
#objc func willEnterForeground() {
// do what's needed
}
Just trying to make it as easy as possible see code below:
- (void)viewDidLoad
{
[self appWillEnterForeground]; //register For Application Will enterForeground
}
- (id)appWillEnterForeground{ //Application will enter foreground.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(allFunctions)
name:UIApplicationWillEnterForegroundNotification
object:nil];
return self;
}
-(void) allFunctions{ //call any functions that need to be run when application will enter foreground
NSLog(#"calling all functions...application just came back from foreground");
}
It's even easier with SwiftUI:
var body: some View {
Text("Hello World")
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification)) { _ in
print("Moving to background!")
}
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in
print("Moving back to foreground!")
}
}