What is the proper way to remove iAd from the view hierarchy - ios

I am trying to remove an iAd view from the view hierarchy. My implementation successfully removes the iAd banner from the view, but I continue to receive the following error:
Unhandled error (no delegate or delegate does not implement didFailToReceiveAdWithError:)
According to Apple documentation, removing the iAd view is reasonable in cases where the user navigates away from a screen that displays an iAd and you don't expect them to return to that screen for a while:
If the user navigates from a screen of content with a banner view to a screen that does
not have a banner view, and you expect them to be on that screen for a long period of
time, remove the banner view from the view hierarchy, set its delegate to nil and
release it before transitioning to the new screen of content. More generally, avoid
keeping a banner view around when it is invisible to the user.
So, I removed the banner view and set the delegate to nil, and this results in the banner disappearing from the view. However, I then start receiving the above mentioned error. Its not really clear how to do what Apple suggests. Here is what I have done. My use of iAd is in a class that is not a UIViewController (i.e. this is a cocos2d project). Hence, I utilize the RootViewController. In the header file of my class, I have:
RootViewController *viewController;
AppDelegate *app;
BannerViewController *bannerViewController;
In my class implementation file, I initialize the bannerViewController as follows:
app = (AppDelegate *)[[UIApplication sharedApplication] delegate];
viewController = [(AppDelegate *)[[UIApplication sharedApplication] delegate] viewController];
bannerViewController = [[BannerViewController alloc] initWithContentViewController:viewController];
app.window.rootViewController = bannerViewController;
When I am ready to remove the iAd banner permanently, I attempt to remove the banner view from the view hierarchy and set its delegate to nil as follows:
if (bannerViewController) {
[viewController removeFromParentViewController];
bannerViewController._bannerView.delegate = nil;
[bannerViewController._bannerView removeFromSuperview];
[bannerViewController release];
bannerViewController = nil;
}
The bannerViewController is from Apples iAd Suite. The initialization and construction of the view hierarchy is as follows:
#interface BannerViewController () <ADBannerViewDelegate>
#end
#implementation BannerViewController {
ADBannerView *_bannerView;
UIViewController *_contentController; // RootViewController
}
- (instancetype)initWithContentViewController:(UIViewController *)contentController
{
self = [super init];
if (self != nil) {
// On iOS 6 ADBannerView introduces a new initializer, use it when available.
if ([ADBannerView instancesRespondToSelector:#selector(initWithAdType:)]) {
_bannerView = [[ADBannerView alloc] initWithAdType:ADAdTypeBanner];
}
else {
_bannerView = [[ADBannerView alloc] init];
}
_contentController = contentController;
_bannerView.delegate = self;
}
return self;
}
- (void)loadView
{
UIView *contentView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
[contentView addSubview:_bannerView];
// Setup containment of the _contentController.
[self addChildViewController:_contentController];
[contentView addSubview:_contentController.view];
[_contentController didMoveToParentViewController:self];
self.view = contentView;
}
My RootViewController handles the observers for iAd BannerViewActionNotification's.
What I have noticed is that even though I release the bannerViewController, the dealloc method is not called. Like I said, the iAd banner does disappear from the view, but the error messages keep coming. This suggests that I have not properly disconnected from iAd and continue to receive ad notifications.
So, what am I doing wrong? How should I be removing the banner view from the view hierarchy per Apple's recommendation.

Related

Modal viewcontroller UI not responsive after presentViewController:animated:completion:

My app has a root viewcontroller, which at the start of the app displays
login viewController view if the user is not logged in
main viewController view if the user is logged in
AppDelegate code:
- (BOOL) application: (UIApplication*) application
didFinishLaunchingWithOptions: (NSDictionary*) launchOptions
{
self.window = [[UIWindow alloc] initWithFrame: [[UIScreen mainScreen] bounds]];
self.window.rootViewController = [[RootViewController alloc] init];
[self.window makeKeyAndVisible];
return YES;
}
Here's the code used in RootViewController:
#implementation RootViewController
- (void) loadView
{
[super loadView];
// here mainViewController and loginNavigationController are initialized
}
...
- (UIView*) view
{
[super view]; // this invokes loadView
return self.isLoggedIn ? self.mainViewController.view :
self.loginNavigationController.view;
}
....
- (void) userDidLogin
{
[self.loginNavigationController presentViewController: self.mainViewController
animated: YES
completion: nil];
}
#end
If the user is not logged in and presses login button the main viewController is presented.
The problem is that after main viewController is presented, I'm not able to interact with any of the UI elements. For example, I have a tableView as a main viewController's subview and when I try to scroll it I get the following warning in debug panel:
<UITableView: 0x202a4000; frame = (0 0; 310 548); clipsToBounds = YES;
gestureRecognizers = <NSArray: 0x1fd9f570>; layer = <CALayer: 0x1fdccff0>;
contentOffset: {0, 0}>'s window
is not equal to <RootViewController: 0x1fd9f7d0>'s view's window!
Ok, so after looking at the updated code I see that you have a rootViewController and are dynamically giving the view you think should be presented. The thing is, the rootViewController is in charge of the root view while your other two view controllers manage their own views. You should not be passing a different view controller's view off.
So in the end it looks like you want to conditionally set your rootviewcontroller. So lets look at the app delegate. I think you should make your app delegate do something like this. Have it figure out at runtime which viewcontroller to present. Then make that the rootviewcontroller for the app.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UIViewController * resolvedRootViewController = [self someMethodThatCorrectlyGivesRootViewController];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.rootViewController = resolvedRootViewController;
[self.window makeKeyAndVisible];
return YES;
}

Changing the rootViewController of a UIWindow

When my app first loads, I set the rootViewController property of my UIWindow to controllerA.
Sometime during my app, I choose to change the rootViewController to controllerB.
The issue is that sometimes when I do a flip transition in controllerB, I see controllerA's view behind it. For some reason that view isn't getting removed. Whats even more worrying is that after setting the rootViewController to controllerB, controllerA's dealloc method never gets fired.
I've tried removing the subviews of UIWindow manually before switching to controllerB, that solves the issue of seeing controllerA's views in the background but controllerA's dealloc still never gets called. Whats going on here????
Apples docs say:
The root view controller provides the content view of the window. Assigning a view controller to this property (either programmatically or using Interface Builder) installs the view controller’s view as the content view of the window. If the window has an existing view hierarchy, the old views are removed before the new ones are installed.
UPDATE
Here's the code of my AppDelegate:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
[self showControllerA];
[self.window makeKeyAndVisible];
return YES;
}
- (void)showControllerA
{
ControllerA* a = [ControllerA new];
self.window.rootViewController = a;
}
- (void) showControllerB {
ControllerB* b = [ControllerB new];
self.window.rootViewController = b;
}
It turns out there are two separate issues. 1) I had a retain cycle in Controller A so it was never getting dealloc'd. Secondly, in order to change the root view controller you must remove the windows subviews first (even though the docs suggest otherwise)
The problem could be in your implementation of ControllerA or ControllerB, they may retain 'self' in the code so ARC cant automatically dealloc you ViewController. Can you post you ControllerA and ControllerB implementation.
var loginNavigationController: OnBoardViewController?{
willSet{
if newValue == nil {
loginNavigationController?.view.removeFromSuperview()
}
}
}
loginNavigationController = nil
It's apple's bug, we assume ViewControllerA as the current rootViewController:
// ViewControllerA.m
- (void)buttonClick {
[self dismissViewControllerAnimated:YES completion:^{
// [((AppDelegate *)[[UIApplication sharedApplication] delegate]) resetRoot]; // OK
}];
[((AppDelegate *)[[UIApplication sharedApplication] delegate]) resetRoot]; // ViewControllerA's view will not dealloc
}
// AppDelegate.m
- (void)resetRoot {
ViewControllerB *controller = [[ViewControllerB alloc] init];
self.window.rootViewController = controller;
}
If reset window's rootViewController as this code, the ViewControllerA's view will never dealloc.
An even simpler solution is to set the backgroundColor of your new window to .white or any color. The default is nil, which results in a transparent background. That is why the older window (on top of which the new one is made visible) is being seen through.

Why do appearance methods get called even when the view controller is not explicitly added as a child?

While researching container view controllers in an attempt to refactor some code, I came upon something that I do not understand.
The Apple documentation tells me that in order for child view controllers to get their appearance methods called they must be added as children to a parent view controller using addChildViewController:
This puzzles me as my code does not use any of the container view controller methods and yet all of my child view controllers are getting the viewWillAppear: message.
I've boiled the code down to this simple example, where you will see "ChildViewController:viewWillAppear:" in the debug log despite any calls to addChildViewController:
#interface ChildViewController : UIViewController
#end
#implementation ChildViewController
- (void)loadView {
self.view = [[UIView alloc] initWithFrame:CGRectMake(10.0f, 10.0f, 250.0f, 250.0f)];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSLog(#"ChildViewController:viewWillAppear:");
}
#end
#interface RootViewController : UIViewController
#property (strong) ChildViewController *cvc;
#end
#implementation RootViewController
#synthesize cvc;
- (void)loadView {
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(10.0f, 10.0f, 500.0f, 500.0f)];
cvc = [[ChildViewController alloc] init];
[view addSubview:[cvc view]];
self.view = view;
}
#end
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.rootViewController = [[RootViewController alloc] init];
[self.window makeKeyAndVisible];
return YES;
}
#end
Why does this work?
The process of calling addSubview is what will result in the view appearing and thus resulting in the loadView, viewDidLoad, viewWillAppear, viewDidAppear, etc., calls. The addChildViewController (and the call to didMoveToParentViewController that you should also do) does not affect this.
You call addChildViewController to make sure your controller hierarchy stays in sync with the view hierarchy. If you don't do this, you won't get certain events getting passed to your child controller (such as rotation events). Also, by doing addChildViewController, your controller will be retained for you, without you needing to maintain your own property to keep track of the child controllers.
If you see WWDC 2011 - #201 Implementing UIViewController Containment, it will talk about the importance of keeping the view hierarchy and the controller hierarchy synchronized.

ViewController programmatically?

I would like to know what steps are needed once a fresh "Single view" project was created in xcode, in order to achieve:
1. a viewController that initializes without a NIB, but rather programmatically loads it's own controls in its view.
2. How to get that viewcontroller's view to load and call viewDidLoad?
3. make the view for that controller visible on the screen with all of the controls.
How do I go about this from this function:
-(BOOL)application:(UIApplication*)application didFinishLoadingWithOptions:(NSDictionary *)launchOptions
I am trying to modify a new xcode project but all I get is a black screeen, viewDidLoad doesn't get called
That's your app delegate's application loading method.
In there, you would probably want to create an instance of your custom view controller and assign that as the rootViewController to your app delegate didFinishLoading. There should be a line like:
// app delegate .h file
#import "CustomViewController.h"
#interface
{
...
CustomViewController *myCustomVC;
...
}
#property (nonatomic, retain) CustomViewController *myCustomVC;
// app delegate .m file
#implementation AppDelegate
#synthesize myCustomVC;
-(BOOL)application:(UIApplication*)application didFinishLoadingWithOptions:(NSDictionary *)launchOptions
{
...
myCustomerVC = [[CustomViewController alloc] init];
[self.window setRootViewController:myCustomVC];
...
}
Then inside your custom view controller's viewDidLoad method, you can do this as a test:
// custom view controller .m file
-(void)viewDidLoad
{
self.view.backgroundColor = [UIColor redColor];
}
UIViewController *myViewController = [[UIViewController alloc] init];
[myViewController.view setFrame:self.view.bounds];
[self.view addSubview:myViewController.view]; // if you want to add it in another viewcontroller
// For testing, set the background color to something other than white (default)
[myViewController.view setBackgroundColor:[UIColor greenColor]];
And off you go !
You need to create a subclass of UIViewController, and setup your view hierarchy either in loadView, or viewDidLoad (depending on the level of customisation)
By subclassing UIViewController the loading method calls will be made for you so you don't have to worry about getting getting viewDidLoad etc.
To make it visible on the screen the simplest way is to set it as the rootViewController of the apps window
inside didFinishLaunchingWithOptions: in your app delegate
self.window.rootViewController = [[MyViewControllerSubclass alloc] init];
Try This :
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
HomeViewController *homeVC = [[HomeViewController alloc]init];
[self.window setRootViewController:homeVC];
[self.window makeKeyAndVisible];
return YES;
}
Remove Main(storyboard reference) from Main interface of general Setting :
Add Launch Image :
And select iOS-7 and later in your left corner setting

Determine frame/bounds in viewDidLoad

Hello fellow programmers,
First, sorry for the long post. My question is rather simple, but I want to make sure you know what I'm doing and I really don't want to change the basic idea of my approach.
(the following is all done programmatically, no storyboards, no nibs, no navigationcontroller)
I have a RootViewController without an own view. All he does is instantiate other ViewControllers, manage their transitions and push (add) their views into the main window. To position these views correctly, I want to get bounds/frame for one of the RootViewCOntrollers SubControllers. This is how I create the RootViewController from the appDelegate (I started with a empty project)
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
self.window.backgroundColor = [UIColor grayColor];
self.window.rootViewController = [[UCRootViewController alloc]init];
[self.window makeKeyAndVisible];
return YES;
}
After his initialization, the rootviewController creates a MapViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
NSLog(#"RootviewController initialized");
self.mapviewController = [[UCMapviewController alloc] initWithDelegate:self];
self.view = self.mapviewController.view;
[self.mapviewController.view setOpaque:YES];
[self.view bringSubviewToFront:self.mapviewController.view];
[self presentModalViewController:self.mapviewController animated:YES];
self.currentlyActiveController = self.mapviewController;
}
The MapViewController creates a navigationBar and a MKMapView. Right now I set the frames hardcoded, because I'm not able to get the bounds/frame of the window in the viewDidLoad() of the MapViewController When I try to get any infos about bounds/frame, I get 0 returned.
- (void)viewDidLoad
{
NSLog(#"MapviewController initialized");
[super viewDidLoad];
self.isMapViewPushedAside = NO;
// Custom initizialation for navigationBar
[self setupNavigationBar];
// Custom initialization for mapview
[self setUpMapview];
[self trackUserLocation];
// Custom initialization for popupActionsButton
[self setUpPopupButtons];
// Custom tests
[self test];
[self focusLocationOnMap:self.locationManager.location.coordinate];
[self.locationManager stopUpdatingLocation];
}
I've implemented two delegate methods that return frame/bounds (same) for the window. The problem is, I must get those values at the start, not after everything has been initialized. when I call the delegate methods from a button after everything is up, they work as expected.
CGRect frame = [self.mapDelegate frameData];
NSLog(#"width by frame: %f", frame.size.width);
NSLog(#"height by frame: %f", frame.size.height);
CGRect bounds = [self.mapDelegate boundsData];
NSLog(#"width by bounds: %f", bounds.size.width);
NSLog(#"height by bounds: %f", bounds.size.height);
How do I obtain the frame/bounds at the start, that is, before calling my custom "setup" methods..?!
I have a RootViewController without an own view.
You can't have a UIViewController without a view. If you have the app will crash. When you initialize a UIViewController it automatically creates a UIView for you.
- (void)viewDidLoad
{
[super viewDidLoad];
..
self.mapviewController = [[UCMapviewController alloc] initWithDelegate:self];
self.view = self.mapviewController.view;
..
}
From what can I see here, you're actually setting the RootviewController's view to be the map view. This should be done by overriding the -(void)loadView method of your controller and there you need to set the view:
-(void)loadView
{
self.mapviewController = [[UCMapviewController alloc] initWithDelegate:self]; //if you're not using ARC this should be autoreleased;
self.view = self.mapviewController.view;
}
When viewDidLoad method is called there is no geometry set in any of the views of your controller. They are only initialized (implicitly or explicitly by -(void)loadView) and viewDidLoad is called just right after that. Geometry is setup at earliest in viewWillAppear: method and the consecutive viewDidAppear: method, so viewWillAppear: is the earliest point you can have your actual frame/bounds of your views and in viewWillAppear: method you should only execute some lightweight operations (like setting geometry, starting timers, subscribe observers, etc..).
You said you don't want to change your approach, but you need to design according to these rules.

Resources