Home button press causes EXC_BAD_ACCESS code=1 in SpriteKit SKView - ios

SpriteKit is supposed to clean up and pause all timers when you press the home button.
However, we have found that if you single tap the home button while displaying an active SKView, the app crashes. This occurs even if that view is already paused by the user.
Strangely, if you double tap the home button and move to the multitasking view, everything works fine.
Follow Up Note: The simulator works perfectly in both situations, no crash
Is anyone else seeing this issue with SpriteKit?
Have you found the cause / solution?

I was having the same problem and I solved it manually pausing the root SKView in the ViewController before the app moving to the background:
- (void)viewDidLoad
{
[super viewDidLoad];
// Configure the view (already setup as SKView via storyboard)
SKView * skView = (SKView *)self.view;
// Create and configure the scene.
SKScene *scene = [Menu sceneWithSize:CGSizeMake(skView.bounds.size.height, skView.bounds.size.width)];
scene.scaleMode = SKSceneScaleModeAspectFill;
// Present the scene.
[skView presentScene:scene];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(appWillEnterBackground)
name:UIApplicationWillResignActiveNotification
object:NULL];
}
- (void)appWillEnterBackground
{
SKView *skView = (SKView *)self.view;
skView.paused = YES;
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(appWillEnterForeground)
name:UIApplicationWillEnterForegroundNotification
object:NULL];
}
- (void)appWillEnterForeground
{
SKView * skView = (SKView *)self.view;
skView.paused = NO;
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(appWillEnterBackground)
name:UIApplicationWillResignActiveNotification
object:NULL];
}

Run Xcode's analyzer (Project > Analyze) to determine if you have memory management issues in your code.
Run this scenario in the allocations instrument of the Instruments app (Project > Profile), which can report any detected misuse of memory.

We ran into some similar inconsistent behavior with UICollectionView. In that case, instantiating the object via a Storyboard (versus programmatically) resolved the issue.
On a hunch, I tried that this morning and Viola, success!
So, it appears that Storyboards are doing some magic with SKView on creation that is unknown to us and allows them to be properly terminated on Home Button press. Frustrating, but at least it works now.

The problem can be caused by audio, if so you can check the answer here https://stackoverflow.com/a/19283721/1278463
I solved this in a game which is not using audio. Th solution is to pause SKView when entering background:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
SKView *view = (SKView *)self.window.rootViewController.view;
if (view) {
view.paused = YES;
}
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
SKView *view = (SKView *)self.window.rootViewController.view;
if (view) {
view.paused = NO;
}
}

Related

Black view is comming while hiding a keyboard

Here is the some piece of code I written for moving textview when user start typing in textview.
Here is the code,
-(BOOL)textViewShouldBeginEditing:(UITextView *)textView{
if(textView == _textViewMsg || textView == _subjectView ){
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardDidShow:) name:UIKeyboardDidShowNotification object:nil];
// return YES;
}
return YES;
}
- (BOOL)textViewShouldEndEditing:(UITextView *)textView{
if(textView == _textViewMsg || textView == _subjectView ){
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardDidHide:) name:UIKeyboardDidHideNotification object:nil];
[self.view endEditing:YES];
}
return YES;
}
- (void)keyboardDidShow:(NSNotification *)notification
{
[self.view setFrame:CGRectMake(0,-50,320,460)];
}
-(void)keyboardDidHide:(NSNotification *)notification
{
[self.view setFrame:CGRectMake(0,60,320,520)];
}
When I click on textview (in Subject) view in moving and I am able to type in textview. It is working fine. (Image 1)
When I click on done button, while hiding i.e at the time of keyboard hide one black view is coming (check Image 2) for few seconds, and then again normal view come.
Edit:
Solution : #michael gives me solution just now
changing UIKeyboardDidHideNotification to UIKeyboardWillHideNotification it works for me.
New Problem Occurred :
When I start typing in first 2 textviews i.e Requester, firstname & lastname...I am not able to type in it, because it is moving up.
It happened because you changing frame of your view after keyboard did disappear, when system animation is completed. Quick fix: change UIKeyboardDidHideNotification to UIKeyboardWillHideNotification
But I want to point few other items in you code, if you don't mind.
1:
You are subscribing for notifications each time user begin editing. It means that when user start typing second time, you code will be triggered twice. It is much more more appropriate to subscribe for notifications on viewDidAppear and unsubscribe on viewWillDisappear:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear: animated];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardDidShow:) name:UIKeyboardDidShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillHide:) name:UIKeyboardDidHideNotification object:nil];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
2:
Then you changing your frame to constant value. But now apple support different keyboards with different size not to mention that keyboards size vary on different devices and in different possible modes. So it is much more wise to get the size of keyboard from notification:
- (void)keyboardDidShow:(NSNotification *)aNotification
{
CGRect keyboardFrame = [[aNotification.userInfo valueForKey: UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGRect overlap = CGRectIntersection( self.view.frame, keyboardFrame);
// Setup new frmae according to overlap, for example reduce size by half of overlap, and move up by another half
CGRect newFrame = self.view.frame;
newFrame.origin.y -= overlap.size.height / 2.0
newFrame.size.height -= overlap.size.height / 2.0
self.view.frame = newFrame;
}
3:
Animations: You can fetch keyboard animation duration from notification as well:
UIKeyboardAnimationDurationUserInfoKey:
NSTimeInterval duration = [[aNotification.userInfo valueForKey: UIKeyboardAnimationDurationUserInfoKey] doubleValue];
[UIView animateWithDuration:duration animations:^{
self.view.frame = newFrame;
}];
4
As it seems your working with scroll view (UITableView and UICollectioView are scroll view), you can instead of changing frame, change content inset (add empty space to bottom scroll view)
[self.view setContentInset:UIEdgeInsetsMake(0, 0, overlap.size.height, 0)];
5
Consider using AutoLayout.
adding observers should be there in viewdidload. try moving the code of adding observers for keyboard to viewdidload.
Edit
Instead of did... notification type check with will...
so replace UIKeyboardDidShowNotification with UIKeyboardWillShow and UIKeyboardDidHideNotification with UIKeyboardWillHide.
Use of IQKeyboardManager framework will help to handle keyboard hide and unhide issues,
Most efficient way for keyboard handling: IQKeyboardManager
Method 1:
You can use a Cocoa Pod file for IQKeyboardManager Library.
init the pod file.
pod 'IQKeyboardManager'
install Pod file.
Method 2:
Download IQKeyboardManger SDK from github and drag & drop inside your project file.
Swift code snippet:
import IQKeyboardManagerSwift
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
IQKeyboardManager.sharedManager().enable = true
return true
}
}
Objective-C Snippet:
#import <IQKeyboardManager/IQKeyboardManager.h>
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//ONE LINE OF CODE.
//Enabling keyboard manager(Use this line to enable managing distance between keyboard & textField/textView).
[[IQKeyboardManager sharedManager] setEnable:YES];
return true;
}
Hope this will help you to resolve your keyboard hide and unhide UI issues.

didBecomeActive un-pauses game

I am pausing my game with a willResignActive notification, and it seems to pause the game, but when didBecomeActive is called, it seems to un-pause on its own.
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(applicationWillResign)
name:UIApplicationWillResignActiveNotification
object:NULL];
- (void) applicationWillResign {
self.scene.view.paused = TRUE;
NSLog(#"About to lose focus");
}
How do I get it to stay paused? Do I actually need to pause it in my AppDelegate?
Here's a way to keep the view paused after returning from background mode. It's a bit of a hack, but it does work.
1) Define an SKView subclass with a boolean named stayPaused...
#interface MyView : SKView
#property BOOL stayPaused;
#end
#implementation MyView
// Override the paused setter to conditionally un-pause the view
- (void) setPaused:(BOOL)paused
{
if (!_stayPaused || paused) {
// Call the superclass's paused setter
[super setPaused:paused];
}
_stayPaused = NO;
}
- (void) setStayPaused
{
_stayPaused = YES;
}
#end
2) In the storyboard, change the class of the view to MyView
3) In the view controller, define the view as MyView
4) Add a notifier to set the stayPaused flag
#implementation GameViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Define view using the subclass
MyView * skView = (MyView *)self.view;
// Add an observer for a method that sets the stay pause flag when notified
[[NSNotificationCenter defaultCenter] addObserver:skView selector:#selector(setStayPaused)
name:#"stayPausedNotification" object:nil];
...
5) In AppDelegate.m, post a notification to set the stay paused flag when the app becomes active
- (void)applicationDidBecomeActive:(UIApplication *)application {
[[NSNotificationCenter defaultCenter] postNotificationName:#"stayPausedNotification" object:nil];
}

Sprite Kit pause/resume scene isn't working after restart

I have a game where I've successfully implemented a pause feature when the home button is pressed. In my View Controller that has the main scene, I pause using:
- (void)appWillEnterBackground{
SKView *skView = (SKView *)self.view;
skView.paused = YES;
bubbleOn=NO; //turns bubble spawn off
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(appWillEnterForeground)
name:UIApplicationDidBecomeActiveNotification
object:NULL];
}
To unpause,
- (void)appWillEnterForeground{
SKView * skView = (SKView *)self.view;
skView.paused=NO;
bubbleOn=YES; Allows recursive method to run until bubbleOn = YES
[NSTimer scheduledTimerWithTimeInterval:slowMo target:scene selector:#selector(spawnNew) userInfo:nil repeats:NO]; //Recursive spawn method
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(appWillEnterBackground)
name:UIApplicationWillResignActiveNotification
object:NULL];}
This works well until my the game ends and a new scene (End Scene) is presented. After the End Scene shows a score, the user can tap again to start again and main scene is presented. The main scene's initWithSize method begins the recursive method spawnNew. If the app goes to the background, the scene pauses and spawnNew stops.
But when the app goes to foreground, the scene does resume, but the spawnNew method does not work. It gets called and outputs a correct NSLog message, but the the method doesn't spawn bubble nodes.
The spawnNew method is in my main scene's implementation:
-(void) spawnNew{
if (bubbleOn==YES){
bubble = [SKSpriteNode spriteNodeWithImageNamed:ballName];
bubble.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:bubble.size.width/2];
...
//bubble properties
...
[self addChild:bubble];
NSLog(#"Spawn!");
[NSTimer scheduledTimerWithTimeInterval:slowMo target:self selector:#selector(spawnNew) userInfo:nil repeats:NO];
return;
} else{
return;
}
I'm out of ideas at this point! Any suggestions?
The easiest way is to get rid of NSTimer. You can use SKAction instead.
In SKScene's initWithSize method:
- (instancetype)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
....
__weak GameScene *weakSelf = self;
SKAction *spawnBubble = [SKAction runBlock:^{
[weakSelf spawnNew];
}];
SKAction *wait = [SKAction waitForDuration:slowMo];
[self runAction:[SKAction repeatActionForever:
[SKAction sequence:#[spawnBubble,wait]]]];
}
return self;
}
In this case you don't even need to use bubbleOn variable, as scene stops executing any actions as soon as it's SKView is paused.

SpriteKit - how to correctly pause and resume app

I have huge issue with my newest game for iPhone made with Games by Tutorials book's help.
NOTE : method from SpriteKit- the right way to multitask doesn't work.
So, in my ViewController.m file, I'm defining private variable SKView *_skView.
Then, I do something like this :
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
if(!_skView)
{
_skView = [[SKView alloc] initWithFrame:self.view.bounds];
_skView.showsFPS = NO;
_skView.showsNodeCount = NO;
// Create and configure the scene.
SKScene * scene = [MainMenuScene sceneWithSize:_skView.bounds.size];
scene.scaleMode = SKSceneScaleModeAspectFill;
// Present the scene.
[_skView presentScene:scene];
[self.view addSubview:_skView];
}
}
And I have my _skView defined, and everything works just fine.
But, when I interrupt game, it resets its state to initial, so, for example, if I'm currently playing, and somebody calls me, game switches back to main menu. It can't be like this.
Based on the site I mentioned above, I created this :
- (void)applicationWillResignActive:(UIApplication *)application
{
SKView *view = (SKView *)self.window.rootViewController.view;
view.paused = YES;
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
SKView *view = (SKView *)self.window.rootViewController.view;
view.paused = NO;
}
But game crashes as soon as it is launched, because second method is called and SKView* view is nil.
Getting it from self.window.rootViewController.view doesn't work.
I also tried to get it from self.window.rootViewController.view.subviews, but it doesn't work either.
I can't define my SKView (in ViewController.m) like this :
SKView * skView = (SKView *)self.view;
because then I have errors with my GameCenterController.
Can someone help me how correctly get actual skView and pause it properly??
You could subscribe to these notifications yourself within the ViewController that manages the SKView. Then you don't have to navigate some weird hierarchy to obtain it.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(willEnterBackground)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(willEnterForeground)
name:UIApplicationWillEnterForegroundNotification
object:nil];
- (void)willEnterForeground {
self.skView.paused = NO;
}
- (void)willEnterBackground {
// pause it and remember to resume the animation when we enter the foreground
self.skView.paused = YES;
}

Issue with UIView loaded from nib file

I am developing an application with multiple views. I have a template for an image gallery which I have created in a xlib file. This view will be loaded as a single page in a scroll view. I am able to get the view loaded multiple time from xlib with the following:
- (void)registerForKeyboardNotifications
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWasShown:)
name:UIKeyboardDidShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillBeHidden:)
name:UIKeyboardWillHideNotification object:nil];
}
- (id)initWithFrame:(CGRect)frame
{
self = [[[NSBundle mainBundle] loadNibNamed:#"GSEstimateView" owner:self options:NULL] lastObject];
self.commentText.delegate = self;
self.scrollView.delegate = self;
self.commentText.delegate =self;
[self registerForKeyboardNotifications];
return self;
}
The first issue I am facing is, when the key board is shown the keyboardWasShown: method is getting called for as many UIViews I have created. If I try to load the keyboard from the second UIView, I get an exception for invalid selector being called. Is the UIView loaded from a nib or xlib Singleton? How can I have my UIView instance notified if I load it from nib file?
(^.^)"Hi sorry for my English is not good if someone like correct my redaction I would appreciate this"
Hi first I don't recommend to use NSNotification prefer to use protocols like this.
#protocol KeyBoardDelegate <NSObject>
- (void)KeyBoardVisible:(BOOL)op;
#end
And if you have multiples views and if you want to now the view control like this:
*viewDidLoad, viewDidUnload, viewWillDisappear, viewWillAppear, and others *
I recommend use the view of the UIViewController like this.
UIViewControllerCustom *example = [[UIViewControllerCustom alloc] initWithNibName:#"exampleNIB" bundle:[NSBundle mainBundle]];
[self.view addSubview:example.view];
Using this you can take the control of the view of example viewcontroller and use the methods
- (void)viewDidLoad{
[super viewDidLoad];
//When the nib has been loaded.
}
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
//When the view is show.
}
- (void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
//The view is hidden
}
- (void)viewDidUnload{
[super viewDidUnload];
// Release any retained subviews of the main view.
}
And some more methods. :)

Resources