I find that tapping on iAds can freeze the screen on any Sprite Kit game. It's not just my specific project, since the stock Sprite Kit example project also freezes with iAd. This does not happen in the simulator though! I cannot decide whether if it's because the simulator runs iOS 8 and my actual testing device is on 7.1, or because of the fact that the simulator is just a simulator so it does things differently.
So if you tap on the iAd then click on the link in the iAd to go to safari (or manually switch over to any app at this point), then switch back to the Sprite Kit app, the app is frozen. The iAd banner is still live, and it loads ads just how it's supposed to. But the rest of the app is frozen. Or to be specific, It still receives touches and stuff (I can see from NSLogs) but the rendering of the nodes is frozen. If you open up iAd again by tapping on it, and close the iAd, then the app resumes somehow and it works again just fine.
If you're curious, here are the ONLY modifications that I did to the stock Sprite Kit example project:
// in GameViewController.h
#import <iAd/iAd.h>
#interface GameViewController : UIViewController <ADBannerViewDelegate>
#end
// in GameViewController.m
#interface GameViewController() {
ADBannerView* iAdBanner;
NSLayoutConstraint* centerAd;
}
#end
#implementation GameViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Configure iAd
iAdBanner = [[ADBannerView alloc] initWithFrame:CGRectZero];
iAdBanner.alpha = 0.0;
iAdBanner.delegate = self;
centerAd = [NSLayoutConstraint constraintWithItem:iAdBanner
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0.0];
[self.view addSubview:iAdBanner];
[self.view addConstraint:centerAd];
// The rest of viewDidLoad is the stock code. I'm not pasting that in...
}
// iAd delegate methods
-(void) bannerViewDidLoadAd:(ADBannerView *)banner {
// fade in iAd banner
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.5];
[banner setAlpha:1.0];
[UIView commitAnimations];
}
-(void) bannerView:(ADBannerView *)banner didFailToReceiveAdWithError:(NSError *)error {
NSLog(#"iAd error: %#", error.localizedDescription);
banner.alpha = 0.0;
}
Plus I set the deployment target to 7.1 (and obviously include the iAd framework in the project).
As you can see, I'm not doing anything to the app that could cause this crash. I simply add the iAd banner and that's it. So the problem is not in my code. This must be a bug in Apple's frameworks.
Does anyone know a workaround? I have found several topics on this on the internet, but nobody could suggest a working solution.
EDIT:
I found out that iOS 7.1 on my device calls bannerViewActionDidFinish: when returning to the app. iOS 8 in the simulator however, calls that method before leaving the app (exactly when you leave the app to be exact). That doesn't directly affect the code posted above, since it doesn't implement this method. But it does indicate that the iAd implementations iOS 7.1 and iOS 8 do things differently.
--- Final answer in EDIT 2 ---
Well I did read about this before, and I did try it, but seems like I failed when I tried it earlier. I tried it again and it works now.
So what you want to do is set the canDisplayBannerAds property of your view controller to YES, then use the originalContentView property instead of the normal view one to set up your SKView. Or if you want to access your view from inside a scene, that would be: self.view.window.rootViewController.originalContentView. So basically any time you need to do something with your view that is specific to SKView (like transition to a scene) and what cannot be done with a regular UIView then use scene.view.window.rootViewController.originalContentView instead of just scene.view. The reason for this is that iAd somehow explicitly casts your view as UIView when you set the canDisplayBannerAds property in your view controller, so you cannot use the SKView methods on your view anymore. The originalContentView is a workaround for this.
This totally solved my problem, the app returns to its normal state now.
EDIT:
Nope, it didn't solve my problem. :(
Switching to another app and back while an ad is open now doesn't freeze the app. Good. But simply opening and closing an ad now freezes it the same way. This makes no sense...
So if I don't set canDisplayBannerAds in my view controller, then leaving the app and coming back is the case when the app freezes. But just opening and closing an ad works fine. But if I do set canDisplayBannerAds, then it's the other way around.
This really makes no sense. I'm very confused now...
EDIT 2 (actual solution):
Finally I solved it! But it wasn't easy god damn it...
So the problem was in the view hierarchy. If I add the ADBannerView as a subview of my main view, then it freezes the app. This is strange, since that's the suggested method to add a banner to the screen.
If I don't add the banner as as subview manually, it sill appears on the screen though! I guess iAd is implemented in a way that once initialized, it will display itself on the screen no matter what. It appears a bit differently though. If it's not a subview of any view, then it comes in from the bottom with a "slide up" animation, resizing everything to a smaller box that's above it. This is a default behavior of an iAd banner I guess. So this way it doesn't freeze the screen, it works just fine! But since I wanted a bit more control over the banner (like animating it myself, instead of leaving that to the banner itself) I did the following:
In my view controller, I created an instance of SKView (NOT by casting self.view as SKView as usual, but a completely new instance). Then I added that as a subview of self.view. After that, I initialize the ADBannerView, and add it as a subview of self.view as well. Note that self.view is not directly displaying anything, I use it as a "container" to hold the other two. The view hierarchy then looks something like this:
self.view
/ \
SKView ADBannerView
This way the ADBannerView is not the direct subview or superview of my SKView (which is the main view my game uses) so it cannot screw it up because it doesn't have a concept of that view since they are both subviews of the same view. At this point I have no idea whether you need to set canDisplayBannerAds in your view controller or not. I have it set, and it works. I didn't try leaving that out.
In a nutshell, I have full control of the banner view as well as my game view, and they can't mess with each other since they are not in a subview/superview relationship.
This was very tough to figure out, but here you go. If you also have problems with iAd and Sprite Kit, just organize your views like this.
Related
I'm not for sure if my title responds what I want to ask but let me explain;
I'm using storyboard, I have created "HomeViewController" and set it as custom class.
Also, I want to create a view with programmatically and add this view to "HomeViewController" 's view.
These TabBar, GreenView and Logout Button are necessary. Because I added them via interface builder to see how quickly they appear on the screen and whenever I build and run my app, these components are load very quickly. They are not blinking or appear after a sec. They are stable!
Here is a simple code;
- (void)viewDidLoad {
[super viewDidLoad];
UIView *dummyView = [UIView new];
[dummyView setFrame:CGRectMake(0, 0, 200, 200)];
[dummyView setBackgroundColor:[UIColor yellowColor]];
[self.view addSubview:dummyView];}
When I run my code, the dummyView with yellow color appears on my view, yes, but with a delay of few seconds. It is really annoying!
Could you give me any idea please? When I use .xib I don't have any issues like this.
What should I do to show my view while my application starts without
any delay?
Is viewDidLoad method good enough for this? How about loadView method?
What is the reason I had this issue on storyboard?
Updated
I'm going to upload a .gif:
Thank you!
I found the solution, its really silly thing.
On my project there wasn't any SplashScreen. So it was immediately launch my main and it looks like views appears after some delay.
I have added SplashScreen and everything is as it should be!
I am trying to create an application with a feature similar to facebook's chat bubbles.
When the user navigates to a certain page (InCallViewController), they can connect to another person via video chat. When they navigate out of this page after connecting, I would like the video view to stay floating on the screen, but allow them to do what ever they want to do in the app.
In order to do this, I have made an InCallViewController class, which will allow the user to connect with the other person. Once connected, the video is displayed in a view. This view is movable (similar to facebook's chat bubbles) and displays the video chat perfectly, however when I exit the page and go to another page (AccountViewController) in the app, I am unable to keep this view on the screen. I have tried many things, including setting this view as a subview in the later pages. However when I do this, the subview is not displayed.
MyAccountView.m
- (void)viewDidLoad
{
[super viewDidLoad];
InCallViewController *inCallViewController = [[InCallViewController alloc] initWithNibName:#"InCallViewController" bundle:nil];
[self.view addSubview:inCallViewController.previewView];
[self.view bringSubviewToFront:inCallViewController.previewView];
(Do some other set up stuff)
}
InCallViewController.h
#interface InCallViewController : UIViewController <UIAlertViewDelegate>
{
CGPoint currentTouch;
NSArray *viewArray;
AVAudioPlayer *audioPlayer;
}
#property (weak, nonatomic) IBOutlet UIView *previewVideoView;
The previewView is a UIView in the InCallViewController class. This is hooked up in the IB, and works perfectly when in the InCallController class. The problem is, it won't show up when adding it as a subview in another class. I am wondering what I am doing wrong, or if there is a better way to keep the "previewView" remaining on the screen after I exit InCallViewController.
Thanks
You should consider implementing a container viewController. Since iOS6 and xcode 4.5 this has been made pretty straightforward.
The containing viewController can be handling your previewViews which are overlayed over whatever viewController is currently contained in it.
You can compare what you want to achieve with what Apple has achieved with a UInavigationController (also a container view controller): it contains viewController that are happily showing their content, but the navigationController makes sure the navigationBar is always present, for all viewControllers, even during animations.
Apple has some good documentation and even a WWDC session on this.
Hacking your way into [[UIApplication sharedApplication] keyWindow] is extremely poor design, and a blatant violation of the MVC pattern. It works, but it is a hack nonetheless and might give you headaches in the future.
You can add previewView to [[UIApplication sharedApplication] keyWindow] as a subview so that it appears on all your views and above each of them.
I recently made a menu screen to my cocos2d app where I have attached two pickerviews to the UIWindow. Everything works good, but when I go to select which "game" I want to play and the scene changes, the pickerviews remain. How can I remove them?
Thanks!
More info:
The button is a CCMenuItem (if that matters)
Edit: And this is how Im changing the scene once a button is pressed.
[[CCDirector sharedDirector] replaceScene:menuScene];
If a CCNode object (like CCMenuItem) remains on screen after changing scenes, you have a memory leak.
You're saying that you attach the views (CCMenuItem?) to the UIWindow. I can't imagine how that would work since CCNode objects are not UIView objects.
One of those two issues is likely to be your problem, but hard to give you a concrete fix without seeing the relevant code sections.
I figured it out, all I did was give each pickerview a unique tag, and then when my button is pressed, I used
[UIView removeFromSuperview: tag];
for each picker view.
As has been reported in other questions here on SO, iOS 5 changes how rotation callbacks for split view controllers are sent as per this release note. This is not a dupe (I think), as I can't find another question on SO that deals with how to adjust split view controller usage in iOS 5 to cope with the change:
Rotation callbacks in iOS 5 are not applied to view controllers that
are presented over a full screen. What this means is that if your code
presents a view controller over another view controller, and then the
user subsequently rotates the device to a different orientation, upon
dismissal, the underlying controller (i.e. presenting controller) will
not receive any rotation callbacks. Note however that the presenting
controller will receive a viewWillLayoutSubviews call when it is
redisplayed, and the interfaceOrientation property can be queried from
this method and used to lay out the controller correctly.
I'm having trouble configuring the popover button in my root split view controller (the one that is supposed to show the left pane view in a popover when you're in portrait). Here's how my app startup sequence used to work in iOS 4.x when the device is in landscape mode:
Install split view controller into window with [window addSubview:splitViewController.view]; [window makeKeyAndVisible];. This results in splitViewController:willHideViewController:withBarButtonItem:forPopoverController: being called on the delegate (i.e. simulating a landscape -> portrait rotation) even though the device is already in landscape mode.
Present a fullscreen modal (my loading screen) which completely covers the split view underneath.
Finish loading and dismiss the loading screen modal. Since the device is in landscape mode, as the split view controller is revealed, this causes splitViewController:willShowViewController:invalidatingBarButtonItem: to be called on the delegate (i.e. simulating a portrait -> landscape rotation), thereby invalidating the bar button item, removing it from the right-side of the split view, and leaving us where we want to be. Hooray!
So, the problem is that because of the change described in that release note, whatever happens internally in iOS 4.3 that results in splitViewController:willShowViewController:invalidatingBarButtonItem: being called no longer happens in iOS 5. I tried subclassing UISplitViewController so I could provide a custom implementation of viewWillLayoutSubviews as suggested by the release note, but I don't know how to reproduce the desired sequence of internal events that iOS 4 triggers. I tried this:
- (void) viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
UINavigationController *rightStack = [[self viewControllers] objectAtIndex:1];
UIViewController *rightRoot = [[rightStack viewControllers] objectAtIndex:0];
BOOL rightRootHasButton = ... // determine if bar button item for portrait mode is there
// iOS 4 never goes inside this 'if' branch
if (UIInterfaceOrientationIsLandscape( [self interfaceOrientation] ) &&
rightRootHasButton)
{
// Manually invoke the delegate method to hide the popover bar button item
[self.delegate splitViewController:self
willShowViewController:[[self viewControllers] objectAtIndex:0]
invalidatingBarButtonItem:rightRoot.navigationItem.leftBarButtonItem];
}
}
This mostly works, but not 100%. The problem is that invoking the delegate method yourself doesn't actually invalidate the bar button item, so the first time you rotate to portrait, the system thinks the bar button item is still installed properly and doesn't try to reinstall it. It's only after you rotate again to landscape and then back to portrait has the system got back into the right state and will actually install the popover bar button item in portrait mode.
Based on this question, I also tried invoking all the rotation callbacks manually instead of firing the delegate method, e.g.:
// iOS 4 never goes inside this 'if' branch
if (UIInterfaceOrientationIsLandscape( [self interfaceOrientation] ) &&
rightRootHasButton)
{
[self willRotateToInterfaceOrientation:self.interfaceOrientation duration:0];
[self willAnimateRotationToInterfaceOrientation:self.interfaceOrientation duration:0];
[self didRotateFromInterfaceOrientation:self.interfaceOrientation];
}
However this just seems to cause an infinite loop back into viewWillLayoutSubviews :(
Does anyone know what the correct way to simulate the iOS4-style rotation events is for a split view controller that appears from behind a full-screen modal? Or should you not simulate them at all and is there another best-practices approach that has become the standard for iOS5?
Any help really appreciated as this issue is holding us up from submitting our iOS5 bugfix release to the App Store.
I don't know the right way to handle this situation. However, the following seems to be working for me in iOS 5.
In splitViewController:willHideViewController:withBarButtonItem:forPopoverController:, store a reference to the barButtonItem in something like self.barButtonItem. Move the code for showing the button into a separate method, say ShowRootPopoverButtonItem.
In splitViewController:willShowViewController:invalidatingBarButtonItem:, clear that self.barButtonItem reference out. Move the code for showing the button into a separate method, say InvalidateRootPopoverButtonItem.
In viewWillLayoutSubviews, manually show or hide the button, depending on the interface orientation
Here's my implementation of viewWillLayoutSubviews. Note that calling self.interfaceOrientation always returned portrait, hence my use of statusBarOrientation.
- (void)viewWillLayoutSubviews
{
if (UIInterfaceOrientationIsPortrait(
[UIApplication sharedApplication].statusBarOrientation))
{
[self ShowRootPopoverButtonItem:self.barButtonItem];
}
else
{
[self InvalidateRootPopoverButtonItem:self.barButtonItem];
}
}
Short version:
I'm alloc/init/retaining a new UIViewController in one UIViewControllers viewDidLoad method, adding the new View to self.view. This usually works, but it seems to mess up orientation change handling of my iPad app.
Longer version:
I'm building a fairly complex iPad application, involving a lot of views and viewcontrollers. After running into some difficulties adjusting to the device orientation, I made a simple XCode project to figure out what the problem is.
Firstly, I have read the Apple Docs on this subject (a small document called "Why won't my UIViewController rotate with the device?"), and while I do believe it has something to do with one of the reasons listed there, I'm not really sure how to fix it.
In my test project I have an appDelegate, a rootViewController, and a UISplitViewController with two custom viewControllers. I use a button on the rootViewController to switch to the splitViewController, and from there I can use a button to switch back to the rootViewController. So far everything is great, i.e. all views adjust to the device orientation.
However, in the right viewController of the splitViewController, I use the viewDidLoad method to initialize some other viewControllers, and add their views to its own view:
self.newViewController = [[UIViewController new] autorelease];
[newViewController.view setBackgroundColor:[UIColor yellowColor]];
[self.view addSubview:newViewController.view];
This is where things go wrong. Somehow, after adding this view, adjusting to device orientation is messy. On startup everything is fine, after I switch to the splitViewController everything is still fine, but as soon as I switch back to the rootViewController it's all over. I have tried (almost) everything regarding retaining and releasing the viewcontroller, but nothing seems to fix it.
As you can see from the code above, I have declared the newViewController as a property, but the same happens if I don't.
Shouldn't I be adding a ViewController's view to my own view at all? That would really mess up my project, as I have a lot of viewControllers doing all sorts of things.
Any help on this would be greatly appreciated...
I had the same problem, seams resolved by removing from the parent view the "autoresize subview" option in IB.
Select the view a then: Inspector -> Attributes (first tab) -> drawing.
What I think is happening is that your new viewcontroller is getting the autorotation calls and probably not handling them. Meanwhile your old viewcontrollers and its views won't get the autorotation calls and will be stuck in whatever orientation they were in.
I can't tell, but I think what you want to do is to make a UIView and add it to your old viewcontroller, instead of making a UIVIewController and adding its view.