I'm developing a game with Sprite Kit but I am having a multitasking issue. When I press the home button, during the game execution, my SKScene.paused becomes true, and I make the proper changes to the app in my applicationDidEnterBackground method at AppDelegate.m, such as saving stuff with NSUserDefaults. Anyway, if I opened my app again, it should resume from where it left off, but what happens is that my app terminates and starts again. This only happens in my iPhone (in the iOS Simulator it works fine). Since I am new at creating games with Sprite Kit and creating apps at all, I was hoping for some clue of what could the problem be...
PS: I think the problem is something about the app not being "suspended" correctly, because if I press the home button and immediately reopen the app, it works fine.
Here is my code:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
if(self.scene == nil) return;
[self.scene saveUserDefaults];
[self.scene pausar];
self.scene.paused = true;
}
In MyScene.m, inside the initWithSize method:
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
/* Setup your scene here */
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
appDelegate.scene = self;
And in the AppDelegate.h file: #property (weak, nonatomic) MyScene * scene;
Now, the saveUserDefaults method, which is inside the MyScene.m file:
-(void) saveUserDefaults
{
[userDefaults setBool:true forKey:#"active"];
[userDefaults setInteger:highScore forKey:#"highScore"];
[userDefaults setBool:soundOn forKey:#"soundOn"];
[userDefaults setBool:musicOn forKey:#"musicOn"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
Well, without seeing more of your code, it is impossible to know what is causing your issue.
First, read this question. Similarly, you should read up on the UIAppDelegate class since it is something you must know very well when programming on a phone where frequent interruptions like phone calls, etc can happen while someone is using your app.
More than likely, you have a problem with how you are responding to one of the protocol methods in the delegate.
Related
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.
I’m new in iOS development. My question is, I’ve two view controllers.
viewController - A viewController - B
Now, if i killed the app from the viewController - A and than relaunch the app. than app must be open the viewController - A. and if i killed the app from the viewController - B and than relaunch the app. than app must be open the viewController - B.
Can anyone help me, I’ve done the RND but can not find the proper solution.
Thanks
Create a sharedDelegate in AppDelegate.m file
+(AppDelegate *)sharedDelegate {
return (AppDelegate *) [UIApplication sharedApplication].delegate;
}
in AppDelegate.h
+ (AppDelegate *)sharedDelegate;
#property (nonatomic, strong) NSString *currentViewContoller;
when push to any contoller then set AppDelegate's currentViewContoller to new VC
YourViewController *vc=[[YourViewController alloc] init];
[self.navigationController pushViewController:vc animated:YES];
[AppDelegate sharedDelegate].currentViewContoller = NSStringFromClass([YourViewController class]);
now when app is terminated
- (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
[[NSUserDefaults standardUserDefaults]setObject:[AppDelegate sharedDelegate].currentViewContoller forKey:#"currentVC"];
}
now when app launched first time check previous controller when app terminated
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSString *string=[[NSUserDefaults standardUserDefaults] valueForKey:#"currentVC"];
and push this class
UIViewController *object = [[NSClassFromString(string) alloc] init...];
}
applicationWillTerminate you can use but it will only get called if user quit app from forground If your app is in background and then user quit app then applicationWillTerminate will not get called.
So, you have to take care of applicationDidEnterBackground.
So, when app enter in background (i.e call applicationDidEnterBackground ) or call applicationWillTerminate save state(your current VC) in your user defaults.
Now in your didFinishLaunchingWithOptions set that view controller as rootviewcontroller or whatever way that you want to manage it.
reference : Apple documentation for applicationWillTerminate
PS : You should not manage app like this. It is horrible way!! If possible then run your app as normal flow!
If you're using Storyboards you can use the Restoration Identifier to communicate the App which controller to launch as first
https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/PreservingandRestoringState.html
I'm making a game in Cocos2D iOS and would like play a short audio clip (in .m4a format) when the launch image (Default) is displayed.
I tried adding this line:
[[OALSimpleAudio sharedInstance] playBg:#"clip.m4a"];
in application didFinishLaunchingWithOptions but doesn't work.
I also tried playing a .caf file instead of .m4a, but it also doesn't play.
Can you guys please help me in this regard?
Thanks a lot!
Are you talking about the default loading screen that is defined in your project settings as 'default.png'? If yes, I don't think it's possible to play a sound at the same moment the app is launched, because it is currently loading it. What you could do however is a IntroScene that would only implement the onEnter method and immediately apply a transition to, example, your Main Menu. This is what I am doing right now in my game (using cocos2D 2.1, but I guess this isn't very different from a version to another) :
-(void) onEnter
{
[super onEnter];
CCScene *scene;
//Check if app has already launched once
if ([[NSUserDefaults standardUserDefaults] boolForKey:#"HasLaunchedOnce"]) {
scene = [MainMenuScene sceneWithParticles:nil];
}else{
scene = [Tutorial sceneWithParticle:nil];
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:#"HasLaunchedOnce"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
//PLAY YOUR SOUND HERE
//Transition
[[CCDirector sharedDirector] replaceScene:[CCTransitionFade transitionWithDuration:1.0 scene:scene]];
}
Hope this helps! :)
What is the best way to track App usage time and time a user spends viewing a Screen (or interacting with a UIView) for use within the App itself? Google Analytics seems to do a wonderful job, but the numbers I want to use inside the App itself to unlock items and areas of the App.
You could probably roll your own solution based on Core Data, or if your data is small you could even think of using NSDefaults.
Here's a good start. It involves having a base view controller which you should inherit from in each view controller you want to measure the time spent:
#interface BaseViewController : UIViewController
- (NSString *)screenKey;
+ (NSInteger)secondsInScreen:(NSString *)screenKey;
#end
The implementation simply measures the seconds between the appearance of the screen until it disappears. It's very important to notice the appDidEnterForeground and appDidEnterBackground notifications. When you send your app to the background or it comes back to the foreground, viewDidAppear and viewDidDisappear are not called.
#import "BaseViewController.h"
#implementation BaseViewController {
NSDate *_startDate;
}
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(appDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(appDidEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self startMeasuring];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[self stopMeasuring];
}
- (void)appDidEnterBackground:(NSNotification *)not {
[self stopMeasuring];
}
- (void)appDidEnterForeground:(NSNotification *)not {
[self startMeasuring];
}
- (void)startMeasuring {
_startDate = [NSDate date];
}
- (void)stopMeasuring {
NSInteger secondsInScreen = ABS([_startDate timeIntervalSinceNow]);
[self addSecondsToScreen:secondsInScreen];
}
- (NSString *)screenKey {
// Subclasses must override this method
return #"";
}
- (void)addSecondsToScreen:(NSInteger)seconds {
NSString *key = [self screenKey];
if (key.length > 0) {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSNumber *s = [defaults objectForKey:key];
NSInteger addedSeconds = s.integerValue + seconds;
[defaults setObject:[NSNumber numberWithInteger:addedSeconds] forKey:[self screenKey]];
[defaults synchronize];
}
}
+ (NSInteger)secondsInScreen:(NSString *)screenKey {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSNumber *s = [defaults objectForKey:screenKey];
return s.integerValue;
}
#end
Your subclasses must override screenKey retuning a unique identifier for each screen you want to measure. There's a static method in BaseViewController that allows you to get the sum of seconds spent in each screen.
This is a simple way of doing it. From a design point of view, it would be better to store this information in Core Data and to separate the view controller logic from the storage of that data (the static method secondsInScreen should probably be in another class). But it's a good, working start.
I think you would like to get app usage time to enable new features in the app or give some gifts for your users right?
To reach this, don't use any SDK to track audience like Google Analytics and Flurry for example. Both are for a different purpose you want to do.
A very very simple approach is to store locally using NSUserDefaults the session time of some user, or you can store more detailed information about this using CoreData or SQLite.
The iOS provide a lot of options to you do that, for example, each time the user start the session or open some screen, you can store some NSDate or mach_abosulte_time to save the time of user started the session/screen and you can get the offset time when the app goes to background or when the user closes the screen.
And if you want to store this remotely (server), you can create a service to send this time while the app is visible.
I hope this first insight can help you.
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!!! :)