My UI uses animation of a UILabel to indicate a particular state. I turn that state on and off by using a key in NSUserDefaults standardUserDefaults. The animation restarts correctly (in the simulator) when the app becomes active after simulating the Home button and then clicking on the app. But it doesn't restart correctly if I simulate the Lock button and then click Home. Both events show in the console and the method -(void)startFlashingButton is called in both cases. I can't figure out why it works in one case and not in the other. I would appreciate any help.
Edit 2/14/17: I read several posts on other boards relating to UINotification. It is apparently very difficult to get to work. My animation gets triggered when a notification comes in to the app delegate. I wanted to set up a kind of "do not disturb" system that would silence notifications at a particular time. My understanding is that this cannot be automated by UINotification because the app does not see a notification unless it is front and center OR it is in the background and the user taps on one of the action buttons. If the app is in background and the user ignores the alert, the app will not get triggered and thus has no way of knowing if the do not disturb time has been reached. Also, you can't do this reliably with an NSTimer because the timer won't be recognized when the app is suspended, which it would be shortly after going to background. So, to put it briefly, I have a much bigger problem than the simulation not running. But thanks to all of you for your replies.
Here is the startFlashingButton method:
-(void) startFlashingButton
{
NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
if([storage boolForKey:#"animation started"] == YES){
// Fade out the view right away
[UIView animateWithDuration:2.0
delay: 0.0
options: (UIViewAnimationOptionCurveEaseIn | UIViewAnimationOptionRepeat | UIViewAnimationOptionAllowUserInteraction)
animations:^{
self.timerLabel.alpha = 0.0;
}
completion:^(BOOL finished){
// Wait one second and then fade in the view
[UIView animateWithDuration:1.0
delay: 2.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
self.timerLabel.alpha = 1.0;
}
completion:nil];
}];
}
[storage synchronize];
}
In -viewDidLoad, I set up the app to be notified when it becomes active as follows:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(startFlashingButton) name:UIApplicationDidBecomeActiveNotification object:nil];
In the app delegate, I synchonize the user defaults when the app enters background as follows:
- (void)applicationDidEnterBackground:(UIApplication *)application {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
NSLog(#"application did enter background");
NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
[storage synchronize];
As Apple's documentation on background transition cycle states
When the user presses the Home button, presses the Sleep/Wake button, or the system launches another app, the foreground app transitions to the inactive state and then to the background state. These transitions result in calls to the app delegate’s applicationWillResignActive: and applicationDidEnterBackground: methods.
Hence The app might resign being active but not being in the background when you lock and then press the home button.
Try creating this helper method and call it in both applicationWillResignActive: and applicationDidEnterBackground:
-(void)saveUserDefaults {
NSLog(#"application did enter background");
NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
[storage synchronize];
}
Also, make sure that Application does not run in background in your info.plist is set to NO else your app will go to the suspended.
Related
I used the following code to create a blur effect and it worked fine on iOS 10, but it stopped working on iOS 11.I am not able to view the blur when app moves to background.When the app comes to foreground, some times,the blur stays alwaysand the application no longer works,only way to make it work is by relaunching the app.
Is there any work around for this for iOS 11 ?
-(void)addBlurEffect{
UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark];
UIVisualEffectView *blurEffectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
UIWindow * window = [[UIApplication sharedApplication] keyWindow];
blurEffectView.frame = window.frame;
blurEffectView.tag = 4444;
[window addSubview:blurEffectView];
}
-(void)removeBlurEffect{
NSArray *allWindows = [[UIApplication sharedApplication] windows];
for (UIWindow * aWindow in allWindows) {
UIView *blurEffectView = [aWindow viewWithTag:4444];
if (blurEffectView){
[blurEffectView removeFromSuperview];
}
}
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
[self addBlurEffect];
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
[self removeBlurEffect];
}
This happens because you are doing it in the WRONG delegates the entire time. You need to read the documentation or the comments in AppDelegate on what each method does..
- (void)applicationWillResignActive:(UIApplication *)application {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
[self addBlurEffect];
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
[self removeBlurEffect];
}
So think of the two methods above as "pausing" or "unpausing" a game.. When a game goes in the background or gets interrupted, typically you pause it for the user so they don't lose progress or die or something while it's in the background state or transitioning state (IE: The task manager state).
Then when they resume and the app is on screen, you unpause it. This would require refreshing and invalidation of graphics.. Which the two methods above handle..
However, you were doing it in:
- (void)applicationDidEnterBackground:(UIApplication *)application {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
This method is for saving states and cleaning up.. not modifying UI.. IE: Storing stuff in a database or syncing the user settings to disk or something or releasing memory.. User can potentially kill the app in this state or the system can kill it too.
This only gets called when the application is actually minimized (minimized/background state is NOT the same as task-switcher state/in-active state!).. iOS 11 has a different transition so now we actually see the difference I guess..
If you move your code into active state handlers, it works 100% fine..
I have an animating sine wave in my app that needs to be reset when the user hits the home button or locks their phone (turning off the screen). Once the user brings the app back to the foreground, the sine wave animation is triggered again. This works fine until I realized it was occurring when the microphone access permission pops up. So the microphone access pops up, causing my app to go into the background and the animation to turn off, but the app is still visible. Any way I can know that the mic access pop up is occurring, so I can make some kind of if statement?
-(void) appDidEnterForeground:(NSNotification *)notification
{
if( viewIsUp == NO)
{
[self.sineWave.layer removeAllAnimations];
}
else
{
[self.sineWave animateWave];
}
}
-(void) appDidEnterBackground:(NSNotification *)notification
{
if(viewIsUp)
{
[self.sineWave.layer removeAllAnimations];
}
}
I accidentally set UIApplicationWillResignActiveNotification as the notification to call appDidEnterBackground. When the microphone access pop up would display, the only notification that would get called was UIApplicationWillResignActiveNotification. So when the home button was tapped or the screen was locked, I would remove the animation when UIApplicationDidEnterBackgroundNotification was called and when the mic access pop up displayed, I would leave the animation as it is when UIApplicationWillResignActiveNotification gets called. Worked out nicely. Hope this helps someone.
I have registered for Calendar Change Notifications using the following:
- (void) registerForLocalCalendarChanges
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(localCalendarStoreChanged) name:EKEventStoreChangedNotification object:self.store ];
}
This should call the following when a change is made to the local calendar:
- (void) localCalendarStoreChanged
{
// This gets called when an event in store changes
// you have to go through the calendar to look for changes
// launch this in a thread of its own!
ashsysCalendarEventReporter *eventReport = [ashsysCalendarEventReporter new];
NSLog(#"Local Calendar Store Changed");
[NSThread detachNewThreadSelector:#selector(getCalendarEvents) toTarget:eventReport withObject:nil];
}
BUT...when I start the app, then send it to the background so I can change a calendar entry, nothing happens when I change the calendar entry. It DOES fire when I return to the app. But, of course that is not the objective.
store is defined in the header file with:
#property (strong,nonatomic) EKEventStore *store;
Update...forgot to show the stuff I have in the background fetch.
This is in didFinishLaunchingWithOptions
[application setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
This is in the app delegate:
- (void) application:(UIApplication*) application performFetchWithCompletionHandler:(void (^) (UIBackgroundFetchResult))completionHandler
{
// UIBackgroundTaskIdentifier uploadCalInfo = [application beginBackgroundTaskWithExpirationHandler:nil];
NSLog(#"A fetch got called");
// ashsysCalendarEventReporter *eventReport = [ashsysCalendarEventReporter new];
// [eventReport getCalendarEvents];
// // [NSThread detachNewThreadSelector:#selector(getCalendarEvents) toTarget:eventReport withObject:nil];
// [application endBackgroundTask:uploadCalInfo];
completionHandler(UIBackgroundFetchResultNewData);
The performFetch gets called at what seem like random times some not at all related to the calendar. Is there a way to find out what is firing the background fetch? Is it always the calendar? The actual execution is commented out -- is it correct?
What am I missing?
I've been trying to find this answer out myself and I don't think there's an actual way to accomplish this. As per Apple's documentation, we're not allowed access to system resources while in a background state:
Stop using shared system resources before being suspended. Apps that interact with shared system resources such as the Address Book or calendar databases should stop using those resources before being suspended. Priority for such resources always goes to the foreground app. When your app is suspended, if it is found to be using a shared resource, the app is killed.
I'm still looking for a better answer/work around but would love to hear this from anyone else.
According to the Apple Docs, in order to find out if a user tapped on your push notification you are supposed to check the applicationState in application:didReceiveRemoteNotification:
If the value is UIApplicationStateInactive, the user tapped the action button; if the value is UIApplicationStateActive, the application was frontmost when it received the notification.
I have found that this is not always true. For example:
Double-tap the home button to reveal the system tray and enter "fast app switching mode", your application slides up to reveal other running applications and your app is put into the inactive state (even though it's still mostyle visible). If you receive a push notification in this mode your app delegate will still receive the application:didReceiveRemoteNotification: and at this point your applicationState is UIApplicationStateActive. According to the docs you should treat it like the user tapped the alert... but in this case they didn't. Not only that, the user didn't even see the push notification (possibly because the top of your application is cut off in this mode).
Does anyone know of a way to detect being in 'fast app switching mode' or handle the notification correctly?
I was able to fix it myself with some nifty checks...
Essentially the key to this whole thing is
-(void)applicationDidEnterBackground:(UIApplication *)application;
This method isn't called when you enter fast app switching (or control center) so you need to setup a check based on it.
#property BOOL isInBackground;
#property (nonatomic, retain) NSMutableArray *queuedNotifications;
And when you receive a notification...
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
UIApplicationState appState = application.applicationState;
// Check if we're in this special state. If so, queue the message up
if (appState == UIApplicationStateInactive && !self.isInBackground) {
// This is a special case in which we're in fast app switching or control center
if (!self.queuedNotifications) {
self.queuedNotifications = [NSMutableArray array];
}
// Queue this to show when we come back
[self.queuedNotifications addObject:userInfo];
}
}
And then when we come back...
- (void)applicationDidBecomeActive:(UIApplication *)application {
application.applicationIconBadgeNumber = 0;
if (!self.isInBackground) {
// Show your notifications here
// Then make sure to reset your array of queued notifications
self.queuedNotifications = [NSMutableArray array];
}
}
One more thing you may want to do is check for this special case of going to fast app switching and the user going somewhere else. I do this just before setting the isInBackground BOOL. I choose to send them as local notifications
-(void)applicationDidEnterBackground:(UIApplication *)application {
for (NSDictionary *eachNotification in self.queuedNotifications) {
UILocalNotification *notification = [self convertUserInfoToLocalNotification:eachNotification];
[[UIApplication sharedApplication] scheduleLocalNotification:notification];
}
self.queuedNotifications = [NSMutableArray array];
self.isInBackground = YES;
}
So, here is my life story that I can't seem to figure out what's wrong!!!
I have an app that's already in the store and it has a weird issue which it gives out mixed results.
I have two switches in my main view both controls local notification with repeat intervals.
now I've set up the switches right and ready to go. (I guess).
However, when repeat intervals are scheduled they have to be cancelled again when the user decided not to receive notifications. So, I had my switches coded like that:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
BOOL switchOn = [userDefaults boolForKey:#"switchO"];
if (switchOn) {
[self Firstnotif];
[self Secondnotif];
[self Thirdnotif];
[self Lastnotif];
}
else { [[UIApplication sharedApplication] cancelAllLocalNotifications]; }
BOOL switchOn2 = [userDefaults boolForKey:#"switchO2"];
if (switchOn2) {
[self Firstnotif];
}
else { [[UIApplication sharedApplication] cancelAllLocalNotifications]; }
}
notice I have used the method Firstnotif twice in both switches.
My issues are:
I used to have only one switch and when they turn the first switch on to receive these four notification, doesn't work with all of the users. so I tell them to switch it off, press home button, open app again switch it on and home button again. It works!!!! why?
Now, since I've added another switch new mixed results are appearing.
the second switch is to only fire first notification with it's repeat interval. Some say it works, some say not. others say when I turn the other one it does and does not. What are they facing here??
My questions,
Am I canceling the repeat intervals in the right way?
What could be wrong with my app? and I'll provide you with more codes if needed.
I appreciate your inputs as I've spent weeks on these issues with no luck.
I solved it!
I replaced the cancellation request under applicationDidBecomeActive method and removed my else
guess what? It worked!!! :)