AdMob Refresh Requests - ios
I have several questions that I'm confused with:
Who is best responsible for ad refreshing? After I load the request bannerView.load(GADRequest()) is it best practice to let the developer or Google monitor it?
Should the refresh times be as little as 30 seconds or a minimum of 60 seconds? It seems odd that they recommend 60 seconds but give you the option to choose 30 seconds. I've read on different threads that you can get penalized for all sorts of things and your AdMob account will get closed no questions asked. If that's the case if you pick 30 seconds inside the console and they recommend 60, if they feel anything is "funny" they can terminate your account based on that alone. It's a very oblique process.
It says If your app is automatically refreshing ads, make sure ad requests are not made when the screen is off. If the answer to my first question is to let Google monitor/automatically refresh (without using my code below) how would Google know whether the screen is off or not? For example, the ad is shown in viewControllerA but the user presses a button and viewControllerB is pushed on. Since viewControllerA is still on the stack the ad will constantly get refreshed, I don't see how Google would know the screen is off until viewControllerA is deallocated. Also, if the user went to the background how would Google know? I'm assuming off means the user can no longer see the screen which means they can't see the ad so there's no need to refresh because they either switched tabs, pushed on another VC, or went to the background (all cases handled in the below code)
Where is the link to the refresh console page? Before you could go to https://apps.admob.com > Monetize and you would end up on the page with the refresh options. When I log in I no longer see a Monetize option but get a sidebar with a bunch of icons. None of the icons bring me to the refresh page. FYI I just started using AdMob yesterday and I only saw the refresh page in videos and online tutorials so I never used it before.
It says here (emphasis mine):
Refreshing ads
We recommend that you have ads persist for 60 seconds
or longer, depending on the functionality of your app. Our internal
tests have shown that this ensures users have enough time to engage
with ads, providing the best performance for both advertisers and
publishers. Furthermore, these tests have shown that refreshing ads
more often can hurt fill rate for our publishers.
If your app is automatically refreshing ads, make sure ad requests are
not made when the screen is off. Also, if users navigate to and from
pages with ads in an app over a short period of time, a new ad request
should not be made sooner than the recommended 60 second rate.
But it also says here:
Banner ads
We recommend using the Google-optimized automatic refresh
rate. The optimized rate is calculated using AdMob historical data to
ensure the ads shown in your ad units are being refreshed at the best
rate for banner ads.
You may also set a custom refresh rate of 30-120 seconds or disable
refresh rate completely.
Here's how I would manage the requests myself every 30 secs taking into account when the user goes to the background, switches tabs, or pushes on another VC. If I don't do all of this and left it up to them ("We recommend using the Google-optimized automatic refresh rate"), how would Google know the screen is off or not?
var timer: Timer?
override viewDidLoad() {
super.viewDidLoad()
// instantiate bannerView ...
NotificationCenter.default.addObserver(self, selector: #selector(startAdRefreshTimer), name: UIApplication.willEnterForegroundNotification, object: nil)
// *** the screen is OFF
NotificationCenter.default.addObserver(self, selector: #selector(stopAdRefreshTimer), name: UIApplication.willResignActiveNotification, object: nil)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
startAdRefreshTimer()
}
// *** the screen is OFF
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
stopAdRefreshTimer()
}
#objc func startAdRefreshTimer() {
timer?.invalidate()
timer = Timer.scheduledTimer(withTimeInterval: 30,
repeats: true,
block: { [weak self](timer) in
self?.refreshAd()
})
}
func refreshAd() {
bannerView.load(GADRequest())
}
#objc func stopAdRefreshTimer() {
if timer != nil {
timer?.invalidate()
timer = nil
}
}
// this really isn't necessary because of what's in viewWillDisappear but I added it anyway
#objc func buttonToPushNextVCTapped() {
let nextVC = NextVC()
navigationController?.pushViewController(viewController: nextVC, animated: true, completion: { [weak self] in
// *** the screen is OFF
self?.stopAdRefreshTimer()
})
}
This isn't the perfect solution because if the user switched tabs back and forth quickly and repeatedly the requests would happen much sooner then 30 seconds which would probably get your account banned. This is just for example purposes.
Answer to my 1st, 2nd, and 3rd question:
1st this is my setup, I have the bannerView inside a headerView which seems to be totally fine. I'm also using a TabBarController. Btw the vc the collectionView is in handles the bannerView and load(GADRequest()). The headerView only displays the ad so I don't have to worry about it constantly getting called as the user scrolls and the headerView is recycled. You can read the link in my set up for more details.
2nd AdMob's BannerView has a delegate method that gets called when a new ad is received:
MyController: GADBannerViewDelegate {
/// Tells the delegate an ad request loaded an ad.
func adViewDidReceiveAd(_ bannerView: GADBannerView) {
print("adViewDidReceiveAd")
}
}
3rd. I placed a break point on that print statement to see what happens and here are the results.
When I left the headerView visible (didn't scroll it off of the scene) the delegate method fired every 60 secs. I could be off by a few seconds because I looked at the time on my computer and didn't use a timer. Anyway pretty much every 60 secs the break point was hit and the test ad was changed.
As soon as I scrolled the headerView off of the scene and waited the breakpoint never got hit which means that the delegate method was never called. I waited 10 minutes and nothing happened -no ads were served.
As soon as I scrolled the headerView back on scene, I waited a several seconds and the breakpoint was eventually hit which means the delegate was called. Like in the first scenario it got hit every 60 seconds after that.
I switched to a different tab, left for a few minutes and the same exact thing in #2 happened, break point wasn't hit because the delegate wasn't called.
As soon as I came back to the tab with the headerView, the same thing in #3 happened, breakpoint was hit every 60secs because the delegate was called.
I went to the background and like #2 again the break point wasn't hit because the delegate wasn't called.
As soon as I came back from the background like #3 the breakpoint was hit every 60secs because the delegate was called.
My conclusion is that the bannerView DEFINITELY knows when it's not present on the scene. Wether a tab is changed, the app goes to the background (probably uses a notification), and most mysteriously it even knows when it's a subView of another view and that view is scrolled on/off the scene.
I'm in the Bronx, NY, it's 8am (I started testing around 7am) and the ad changed basically every 60 seconds. Maybe in other parts of the world, different times of the day, or different times of the year the duration might be longer or shorter?
Knowing what I know now I think that it's best that I let Google monitor the load(GADRequest()) and change the ads when it's ready. An ad changing every 60s is fine by me. If the scene can hold the user's attention for longer than a minute that's 2 ads served. It's more beneficial because I can spend more time trying to keep their attention than focus on serving them an ad.
As far as the code from my question to handle all the off cases it seems the bannerView is capable of handling all of that on it's own. It's less code for me to maintain and deal with and more importantly it's 1 less reason to worry about getting banned for handling something improperly.
In summary (this works best for me)
let Google handle/monitor the bannerView.load(GADRequest()) to serve news ads
don't customize the refresh, it seems to change every 60 secs on it's own
when using a bannerView (and possibly letting Google monitor the load(GADRequest())) ads aren't requested when the screen is off so no need to waste time worrying about it
Answer to my 4th question:
The Monetization page to open App Refresh doesn't seem to exist anymore. After you login into AdMob follow these 5 steps (a new screen will appear for steps 3, 4, and 5 after your click your blue ad unit name in step 2).
I think you will find many confusing things in AdMob banners. To determine what was going on with network calls for ads, I had to use Charles proxy to monitor the network stream. I could not find any evidence that the banner, on any view that wasn't on the top of the stack, was continuing to request ads. But, I didn't dig into the framework to find out how/why because I was trying to solve another issue. I would recommend just allowing Admob to serve the ads with their timing using their stock code stubs:
bannerView = GADBannerView(adSize: kGADAdSizeBanner)
addBannerViewToView(bannerView)
I also added some debug code to the delegate function adViewDidReceiveAd(_ bannerView: GADBannerView) to make sure my app was not serving ads when the view was not on top of the stack. Couldn't find any evidence.
Last, would highly recommend never testing with production ads and to follow their best practices relentlessly on ad placement in your VCs as to not touch or otherwise be directly adjacent to other actionable objects. Very possible you could be put in AdMob jail and not know it for weeks or longer (only that ads will stop serving and you will get the dreaded code 1 "No Ad to Show" debug message).
Related
How to display lock sreen in task switcher and resume correctly
for the app I'm developing, I implemented a lock screen that allows the user to unlock the App by manual-pin or touch/Face-ID. Everything is working OK during the normal use. However, I need to show the lock screen when the app is resumed from background and even in the task switcher to avoid "peeking" at the content without having unlocked properly. As recommended by Apple in this (old) article, I present the lock view controller in applicationDidEnterBackground: func applicationDidEnterBackground(_ application: UIApplication) { let lockVC = LoginViewController() lockVC.loginType = LoginViewController.LoginType.resumeApp if let topViewController = UIApplication.topViewController() { topViewController.present(lockVC, animated: false, completion: nil) } } where topViewControler is a useful extension to determnine the topmost view controller: Get top most UIViewController . The lockVC.loginType = ... is just to let the ViewController what type of login I need and customize its view a little bit The results I get are a bit weird, both on simulator and real devices: the task switcher preview for my app is completely black when the app is resumed, the screen remains black as in preview and unresponsive. Only way to exit is to kill the app. before obtaining the weird results above, I had to access all outlets as optional to avoid termination... that's fine for viewDidLoad stuff (I didn't expect the need for this when entering background, since the view had been loaded before - outlet wired) but the strange thing is that I had the same error for an IBAction in viewDidAppear (IBAction for touch-id button called automatically if touch/face-id is available - just a requirement). I believe I'm missing something big here... but no other hints found. No one of the ready-made lock screen solutions come with an example for the background/taskSwicth/resume case). Note that the black/unresponsive screen seems to be same both if I use the mentioned extension for the topmost view controller to present or if I simply try to present it by self.window?.rootViewController?.present(lockVC, animated: false) (which I believe is wrong, but tried anyway) Any help would be apreciated
I found a temporary workaround: Display the lock screen when the app becomes active from background, as suggested by Dev_Tandel Hide information in the task switcher by adding a blur effect on the current screen where the app is sent to background (applicationWillResignActive) and removing it when the app comes active again (applicationDidBecomeActive), as suggested here As said, it's a temporary workaround that i wanted to share, but I don't like this 100%. It is required to show the lock screen in the task swtcher and Ive seen apps doing it (e.g. "oneSafe"). Still looking for help.
Solved, thanks to this post. Apparently I was naive in trying to present a view controller with just its object instance. If I use let lockVC = topViewController.storyboard?.instantiateViewController(withIdentifier: "IDLoginViewController") as! LoginViewController everything works, the lock screen is shown also in the task switcher, no need to blur!
iMessage apps keeps getting restarted
Whenever I swipe outside of my iMessage app to open another iMessage app, and then come back to my iMessage app by swiping back, my iMessage app gets loaded again. Even viewDidLoad of MessagesViewController gets called again. My code in MessagesViewController looks like- class MessagesViewController: MSMessagesAppViewController { override func viewDidLoad() { super.viewDidLoad() openSuitableController() } override func willTransition(to presentationStyle: MSMessagesAppPresentationStyle) { removeAllChildViewControllers() } override func didTransition(to presentationStyle: MSMessagesAppPresentationStyle) { openSuitableController() } } In openSuitableController, I just add a childViewController to MessagesViewController. Because the apps start again, the collectionView gets loaded and scrolled to the top and it leads to flickering. Please do comment if you need more info. PS: I have added Objective-C tag because I don't feel this problem is due to specific language.
I believe this is unavoidable and is just the way that iMessage manages the iMessage apps that it hosts. i.e.: The iMessage framework closes your app when you swipe out and starts up the next one you swipe into. This is true with iOS 11 and prior, it might change in the future of course. iMessage has a rather complicated way of presenting your UI by embedding it in its own sandboxed container view controller hierarchy, this is to ensure secure trust boundaries between iMessage itself and the private chat data and your application. As a result, there is more overhead in presenting your app than if your app was starting up by itself, my guess is that apps are restarted like this in order to minimise resource usage and to protect iMessage from running out of the resources it needs. It's worth keeping your app startup as quick as possible (for example by limiting dynamic library loading time) and keep in mind how your initial content will look as it is swapped for the stored snapshot. I believe its really important to minimise the work that is done when your app starts and when it stops in order to allow the smoothest transitions between apps. When you swipe out of an app to another one iMessage makes a snapshot image of the previous UI state and then at the time of swiping back in to your app it displays that snapshot while your app goes through its initialisation. So it pays to look carefully at the snapshot process and try to make sure your initial UI presentation will transition well from the snapshot that is made by the iMessage framework.
Load entire application at start up
I have a tabbed iOS application with each of the tabs having some sort of json request so the view loads ever so slightly slowly. I want to remove that lag completely so I'm wondering if there is a way to call the ViewDidLoad function from each of the classes during the login phase of the application. if (login == "Success") { UserDefaults.standard.set(true, forKey: "isUserLoggedIn"); UserDefaults.standard.synchronize(); DispatchQueue.main.async { // Load all resources here } } I can understand this can be bad practice if the app is very large, but I think in this scenario the app isn't huge, but the lag between the view controllers is enough to be annoying. I would rather have the user wait at the start for 3-5 seconds whilst everything loads, and have a smooth experience once inside. Is there a way to do this?
You shouldn't call the lifecycle functions of the viewcontroller for yourself. The viewDidLoad function will be called when the view has been loaded. Apple: This method is called after the view controller has loaded its view hierarchy into memory. So calling let _ = viewController.view will trigger the view creation and the call of this function. But i think it's much better to have a startup phase instead. Think about a 'startup'-screen that downloads everything you need (with maybe a spinner and a text) and moves automatically to the content (the tabbar controller) when done. That may also fix the problem of a low network connectivity (think about a download that take a minute for example). You may display that screen modally above or as screen before the tabbbar controller. If you don't like the idea of a startup phase you may also design your ui responsive. Start the download whenever needed / regularly and update your ui according to the results when ready. The ui will be fast then, but uses last known data. The meaningfulness and implementation depends on your content. Update On second thought: If you already have a server login screen, why not download the content directly after the successful download as part of the login? Users do not know if you are still checking the login or downloading some necessary data. You may say that login is only successful if server login AND download are finished successfully.
What can make a UIViewController temporarily become unresponsive?
My app has four View Controllers (VC's): 1) Home: Has a SpriteKit animation on an SKView. A swipe on this VC lets the user proceed to the Authoring VC (next). 2) Authoring: Has a menu (a UITableView). The menu lets the user access ViewGames and Store (below). 3) ViewGames: Contains a UICollectionView, and a nav bar with a close button. This presents a single UICollectionViewCell at a time, and lets the user swipe to proceed to the next cell. Each cell has a SpriteKit animation on a SKView, and also has three buttons. 4) Store: Has an in-app purchase store, with UI implemented as a UITableView. For the purpose of this discussion, the only feature I'm using is SKProductsRequest to fetch back a list of products which are displayed in the table view. Problem: In the ViewGames VC, under some circumstances, the UI of the second and following cells in the collection view operate very slowly. E.g., about a factor of 10 slower than normal. The animation in the SKView is very slow. And the four buttons (three on the collection view cell, one in the nav bar) operate very slowly. Often they won't respond at all and you have to tap them several times. The swipe to go to the next cell responds similarly-- and slowly, if at all. (If I go back to the first cell using swiping, the first cell is also similarly responsive, but initially it doesn't exhibit this problem). Reproducing the problem. The good news is, that in my app, reproducing this problem is very consistent. Here's what will produce it: Launch app > Swipe to go to Authoring > Use menu to go to the Store > exit Store to return to Authoring > exit Authoring back to Home > go to Authoring > go to View Games. Some other facets: A) If I exit View Games, back to Authoring, and then re-enter View Games, this problem is the same. B) No other parts of the app exhibit this sluggishness of UI response. C) If, after getting this behavior in the View Games VC, I now exit ViewGames back to Authoring, re-enter the Store, go back to Authoring, and go back to ViewGames, the problem goes away. D) This problem is only exhibited on iOS9, iOS9.1, iOS9.2 (beta). It doesn't occur on iOS8.4. (All running on physical devices; I've yet to try the simulator). I was using Xcode 7.0.1 initially, but am now using Xcode 7.2 beta and the problem remains the same. My app is targeted at iOS8 and above. E) If I launch the app, and go to Authoring, then ViewGames, this problem doesn't occur. Question: What can make part of the UI run sluggishly, but only temporarily? Avenues explored so far: (i) I've looked at this app in the Time Profiling Instrument, but can't see anything that looks like its soaking up time. (ii) Only one part of the app is doing network interaction, and that's the Store. And the product fetch succeeds, and displays that info. (iii) My best guess right now is that this is related to memory usage. When the symptoms appear, it appear that there is at least a somewhat greater amount of RAM used in going from cell 1 of the Authoring UICollectionView to cell 2 (0.4 to 0.9MB in cases where the problem appears; 0.3MB in cases where the problem does not appear). (iv) In the development history of the app, as I was getting ready to submit v1.0 to Apple, I had a memory leak that exhibited some of these symptoms. However, to my recollection, that memory leak only affected the SpriteKit animations, affected all SpriteKit animations (both on the Home and Authoring VC's), and was not temporary. You had to restart the app to get around it. (v) I've looked at the app quite a bit using Instruments/Leaks/Allocations. There are some leaks, but they appear to be from Apple frameworks, not mine. (vi) I've put breakpoints and log messages in the dealloc/deinit methods and all of the primary classes seem to be deallocating (e.g., the VC's, and the collection view and it's cells). Update1: 11/4/15; 3:47pm MST: The problem is not related specifically to the ViewGames SpriteKit animation. I just disabled the animation in the ViewGames UICollectionViewCell's and the problem still occurs. The sluggishness still happens for swipe and button press responses. Of course, the cells still have an SKView/SKScene. Update2: 11/4/15; 3:55pm MST: I just disabled the product fetch (which uses SKProductsFetch) out of the Store. AND the problem goes away!! Significant narrowing down of the issue! Update3: 11/4/15; 6:10pm MST: With the product fetch in place, but with the delegate of the SKProductsFetch object set to nil, the problem does not occur! It is also relevant to note that a completion handler (called fetchProductsCompletion) that was part of my class construction was also set to nil. Update4: 11/4/15; 6:10pm MST: With the product fetch in place, and with a non-nil delegate for SKProductsFetch, but with fetchProductsCompletion set to nil, the problem does not occur!
You could use sleep(amountTime) if you want to freeze the app totatly. Or use runAfterDelay(amountTime){} to make a delay.
I've found a work-around for the problem. I don't know why it works. But, I'll post some of the code to give a context. This is the method I am using in the Store to fetch products: // Not calling this from init, so that we can make sure there is a network connection, and only give a single error/alert if there is no network connection. private func initiateProductFetch(done:(success:Bool)->()) { let productIds = self.productsInfo!.productIds() if nil == productIds { Assert.badMojo(alwaysPrintThisString: "No product Ids!") } /* Bug #C34, #C39. 10/30/15; I was having a memory retention issue in regards to the fetchProducts completion handler. The SMIAPStore instance wasn't getting deallocated until the *next* time the store was presented because the store delegate was retaining a reference to the completion handler, which had a strong reference to self (SMIAPStore). At first I tried to resolve this by having [unowned self] in the following, but that fails with an exception. Not sure why. And having [weak self] also causes self! to fail-- "fatal error: unexpectedly found nil while unwrapping an Optional value". Why? The only way I've found to work around this is to have a selfCopy as below, which is nil'ed out in the completion handler. Seems really clumsy. For consistency sake, and safety sake, I'm also using this technique in the other self.storeDelegate usages in this class. */ var selfCopy:SMIAPStore? = self self.storeDelegate?.fetchProducts(productIds!) { (products:[SKProduct]?, error:NSError?) in Log.msg("\(products)") if (products != nil && products!.count > 0 && nil == error) { // No error. selfCopy!.productsInfo!.products = products Log.msg("Done fetching products") done(success:true) } else { // Show an error. The VC will not be displayed by now. Returning the error with the done call back will not allow it to be displayed. // It seems possible the products are empty and error is nil (at least this occurs in my debug case above). var message = "" if error != nil { message = error!.localizedDescription } let alert = UIAlertView(title: "Couldn't get product information from Apple!", message: message, delegate: nil, cancelButtonTitle: SMUIMessages.session().OkMsg()) selfCopy!.userMessageDetails = UserMessage.session().showAlert(alert, ofType: UserMessageType.Error) { buttonNumber in done(success:false) } } selfCopy = nil } } The store delegate method (my construction) looks like this: // Call this first to initiate the fetch of the SKProduct info from Apple public func fetchProducts(productIdentifiers:[String], done: (products:[SKProduct]?, error:NSError?) ->()) { self.requestDelegateUseType = .ProductsRequest self.productsRequest = SKProductsRequest(productIdentifiers: Set(productIdentifiers)) self.productsRequest!.delegate = self self.fetchProductsCompletion = done self.productsRequest!.start() } And here is the delegate method for the SKProductsRequest: // Seems like this delegate method gets called *before* the requestDidFinish method. Don't really want to rely on that behavior though. public func productsRequest(request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) { Assert.If(.ProductsRequest != self.requestDelegateUseType, thenPrintThisString: "Didn't have a product request") if 0 == response.invalidProductIdentifiers.count { /* #if DEBUG // Debugging-- simulate an error fetching products. self.fetchProductsCompletion!(products: nil, error: nil) return #endif */ self.fetchProductsCompletion?(products: response.products, error: nil) //self.fetchProductsCompletion = nil } else { let message = "Some products were invalid: \(response.invalidProductIdentifiers)" self.fetchProductsCompletion!(products: nil, error: NSError.create(message)) } } The code as given above does exhibit the problem. When I set the fetchProductsCompletion handler to nil after it is used, the problem goes away. Any ideas as to why?
iAd Interstitial: Determining if ad was shown?
I'm showing interstitials by using destinationVC.interstitialPresentationPolicy = ADInterstitialPresentationPolicy.Automatic before I actually show the destintion view controller. How am I supposed to detect if an interstitial loaded? I need to know so I can know if I should reset a timer that let's me show interstitials every few minutes. It's weird - even with 100% fill rate enabled in my developer settings, my interstitial doesn't always show... I tried implementing ADInterstitialDelegate but it seems interstitialDidLoad doesn't actually execute?
For the fillrate, are you talking about test ads or live ads? iAd has a very low live ads fill rate and is even not supported at all in many countries... For the delegate, did you assign the delegate e.g. self.interstitial.delegate = self; Assign it and do an NSLog for example in the delegate methods like in interstitialDidLoad to test if it s called... P.S. A timer to fire ads isn t really a good idea... Ads should be fired after an action by a user, at a specific time regarding the app lifecycle / usage and should not interrupt, what would obviously happen if you use a timer...