I have an app that shows an interstitial ad after a few pages on a UIPageViewController. Usually the ad is OK, and I can dismiss it by its own 'X' button. But there are some ads (especially the ones about games like Clash of Kings, i've noticed) that will not dismiss some times regardless of how many times I hit the 'X' button.
I even have a time that dismisses modals such as the interstitial after 5 seconds, but with these particular ads, it won't work.
Here is my code:
func createAndLoadInterstitial() -> GADInterstitial {
let interst = GADInterstitial(adUnitID: "..")
interst.delegate = self
let request = GADRequest()
interst.load(request)
return interst
}
func showInterstitial() {
if interstitial.isReady {
self.interstitial.present(fromRootViewController: self)
self.timer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(self.dismissInterstitial), userInfo: nil, repeats: false)
} else {
print("Ad wasn't ready")
}
}
func dismissInterstitial() {
self.dismiss(animated: true, completion: nil) //THIS WORKS FINE EXCEPT FOR THESE PARTICULAR ADS.
}
I tried making sure it was on the main thread as well with DispatchQueue.main.async but again, this doesn't work every time.
Is there a way to fix this, or something I might be missing?
Thanks.
Haven't used ads before but saw this behavior in games. Things that come to my mind:
Does it have a delegate method which is called and then you dismiss the ad?
If there is no delegate method then might be that the ad is 'cheating' and shows the 'x' button with no purpose.
Possible solution: How about creating a transparent view and create a gesture recognizer and close when it's tapped?
Related
I have Login screen and I would like to present it or just add it to navigation, it doesn't matter right now, still not sure which way I would like it to be, everytime my app comes back to foreground. Code below partially works, it does start face id, but actually whole login screen isn't shown, I can still see my last screen that was opened before entering background. I would like to hide everything with login screen. When I print childrens in appMovedToBackground function, correct view controller is printed, which means its added, but as I said, its not showing, only icon from face id can be seen. Code below is on one view controller only, currently, I would like this to be applied to all my view controllers, should I move functionality to appdelegate? Thanks in advance.
let notificationCenter = NotificationCenter.default
override func viewDidLoad() {
super.viewDidLoad()
notificationCenter.addObserver(self, selector: #selector(appMovedToBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
notificationCenter.addObserver(self, selector: #selector(appCameToForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
}
#objc func appMovedToBackground() {
print("its in background")
let authVC = AuthViewController()
navigationController?.topViewController?.addChild(authVC)
}
#objc func appCameToForeground() {
print("app enters foreground")
if let authVC = navigationController?.topViewController?.children.first as? AuthViewController {
print("willEnterForegroundNotification")
authVC.checkBiometricsAndStart()
}
}
Here you need to correctly add the vc
#objc func appMovedToBackground() {
print("its in background")
guard let added = navigationController?.topViewController else { return }
let authVC = AuthViewController()
authVC.view.frame = UIScreen.main.bounds
added.view.addSubview(authVC.view)
added.addChild(authVC)
}
Regarding that you need it for all vcs , yes implementing it inside AppDelegate is the easiest way
Something really odd is happening with my code.
I made a rather simple Timer function that is triggered by button.
The button calls a first method, then the method use another function to do the counting, then it triggers something when the time's up.
And everything works fine.
here's the code.
// This is the declaration of the launch button.
#IBAction func playLater(_ sender: Any) {
if isTimerRunning == false {
runTimer()
}
}
var seconds = 10
var timer = Timer()
var isTimerRunning = false
var resumeTapped = false
//the timer function that sets up the duration and launches the counting.
func runTimer() {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: (#selector(self.updateTimer)), userInfo: nil, repeats: true)
isTimerRunning = true
}
//this part is making sure that you go from 10 to 0 and when it's at 0, something happens. In this very case, it plays a song.
#objc func updateTimer() {
if seconds < 1 {
timer.invalidate()
isTimerRunning = false
playNow((Any).self)
} else {
seconds -= 1
timerLabel.text = timeString(time: TimeInterval(seconds))
timerLabel.text = String(seconds)
}
}
The thing is that I also want to be able to triger that same function from the Apple Watch.
I made a WCSession that is working well, the messages are passing, it's ok.
So I'm using this code to launch the same function when the user pushes a button on the apple Watch. That part of the code is on the same iOS swift file.
#available(iOS 9.0, *)
func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
// do something
isTimerRunning = false
playLater(Any.self)
}
As you can see, I'm not even trying to add some code, I just call the same function used with the iOS button.
But this time, it's not working. The first part of the method is responding, I saw that runTimer() is working, but it's not going to updateTimer().
Maybe I'm missing something here, but, what is the difference ? Why if it comes from the push of a button it's working, and if it's called directly from "the inside" nothing happens ?
If you have any educated guesses, or even, clues, I'd be grateful.
Thanks !
This question has been asked and answered multiple times here. And i've read them all, implemented all suggestions, and still it does not work. I'm currently doing it with a NotificationObserver and the ad is created, however it always returns false for isReady. Please let me know if anything sticks out.
GameViewController
var interstitial: GADInterstitial!
In viewDidLoad
interstitial = createAndLoadInterstitial()
NotificationCenter.default.addObserver(self, selector: #selector(self.showInterstitial), name:NSNotification.Name(rawValue: "showInterAd"), object: nil);
Ad functions
func showInterstitial(){
if (interstitial!.isReady) {
self.interstitial.present(fromRootViewController: self)
}else{
print("ad not ready?")
}
}
func createAndLoadInterstitial() -> GADInterstitial {
print("making ad")
let interstitial = GADInterstitial(adUnitID: "ca-app-pub-6956068899232786/9340371153")
interstitial.delegate = self
let request = GADRequest()
request.testDevices = [kGADSimulatorID]
interstitial.load(request)
return interstitial
}
func interstitialDidDismissScreen(_ ad: GADInterstitial) {
interstitial = createAndLoadInterstitial()
}
GameScene.swift
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "showInterAd"), object: nil)
All functions get called at correct times, but the ad just never becomes ready. I have triple checked the adUnitID, and tried on multiple devices/simulators.
So i've got it working. I do not know why this worked, and i'm assuming it has nothing to do with the app itself, but a bug or something within AdMob.
The fix was to replace the ad_unit_id with a different one from one of my other apps, which then made the ad become ready and display. I was then able to replace the ad_unit_id back to the correct one for this app and it still works.
It's like it needed one that had been used before to jumpstart it in to working. It's got me baffled, but hopefully this helps someone else.
I have this code:
func createAndLoadInterstitial() {
interstitial = GADInterstitial(adUnitID: "ca-app-pub-xxxxxxxxxx/xxxxx")
interstitial.delegate = self
let request = GADRequest()
interstitial.loadRequest(request)
}
override func viewDidAppear(animated: Bool) {
if (interstitial.isReady && showAd) {
showAd = false
// print("iterstitialMain is ready")
interstitial.presentFromRootViewController(self)
self.createAndLoadInterstitial()
}
else {
showAd = true
}
}
and it works. But it shows an ad every time the user taps on the back button.
I want to show an ad only one time when the user taps on the back button. Would it be better to show an ad after some time instead? For example, every 5 minutes?
Where are you setting showAd to true initially? The logic in your if statement is the main issue.
After you set showAd = false in your if statement, the next time viewDidAppear is called your else statement will be executed, setting showAd back to true.
What you should do is check your Bool, and then check interstitial.isReady. Then, in your GADInterstitial's interstitialDidDismissScreen delegate method you would update your showAd Bool and request another GADInterstitial if you'd like. You say you only want to show one GADInterstitial so requesting another is not necessary. For example:
override func viewDidAppear(animated: Bool) {
if showAd { // Should we show an ad?
if interstitial.isReady { // Is the ad ready?
interstitial.presentFromRootViewController(self)
}
}
else {
// Do nothing
}
}
func interstitialDidDismissScreen(ad: GADInterstitial!) {
// Ad was presented and dismissed
print("interstitialDidDismissScreen")
showAd = false // Don't show anymore ads
}
Also, you can change:
let request = GADRequest()
interstitial.loadRequest(request)
to just:
interstitial.loadRequest(GADRequest())
in your createAndLoadInterstitial function.
To answer the second part of your question asking if you should present an ad after some time delay, that is a violation of the AdMob program policies.
Examples of non-compliant implementations:
Interstitial ads that appear before the app has opened or after the app has closed.
Interstitial ads that are triggered after a user closes another interstitial ad.
Interstitial ads loading unexpectedly while a user is viewing the app’s content. Remember to only serve interstitials between pages of content.
Interstitial ads that trigger after every user click.
Interstitial ads that appear during periods of game play or heavy user interaction.
I have my interstitial ads set up in a way where if the user dies 4 times then a full page ads pops up. numberOfLoses increases everytime the player dies.
numberOfLosses++
if numberOfLosses == 4 {
gameViewController?.showFullScreenAd()
numberOfLosses = 0
}
this works on the simulator and on my device the first time, but then i never see it working again, no matter how many times I die. Is this something that will work once its live on the app store or am i doing something wrong?
func showFullScreenAd() {
interstitial.delegate = self
self.interstitialPresentationPolicy = ADInterstitialPresentationPolicy.Manual
self.requestInterstitialAdPresentation()
}
if i println(self.requestInterstitialAdPresentation()) i get false after the first time
here is my updated code (works but multiple ads being loaded at same time)
//MARK: interstitial ad delegate
func showFullScreenAd() {
viewForAd = UIView(frame: screenbounds)
viewForAd.frame = CGRectOffset(viewForAd.frame, 0, screenbounds.size.height)
self.view.addSubview(viewForAd)
iADInterstitial = ADInterstitialAd()
iADInterstitial.delegate = self
self.interstitialPresentationPolicy = ADInterstitialPresentationPolicy.Manual
self.requestInterstitialAdPresentation()
}
func interstitialAdDidUnload(interstitialAd: ADInterstitialAd!) {
iADInterstitial = nil
println("did unload")
}
func interstitialAd(interstitialAd: ADInterstitialAd!, didFailWithError error: NSError!) {
viewForAd.removeFromSuperview()
viewForAd = nil
iADInterstitial = nil
println("failed with error: \(error.localizedDescription)")
}
func interstitialAdDidLoad(interstitialAd: ADInterstitialAd!) {
println("Ad did load")
iADInterstitial.presentInView(viewForAd)
UIView.beginAnimations("", context: nil)
viewForAd.frame = CGRectOffset(viewForAd.frame, 0, -screenbounds.size.height)
UIView.commitAnimations()
}
func interstitialAdActionDidFinish(interstitialAd: ADInterstitialAd!) {
UIView.beginAnimations("", context: nil)
viewForAd.frame = CGRectOffset(viewForAd.frame, 0, screenbounds.size.height)
UIView.commitAnimations()
println("action did finish")
}
You don't need interstitial.delegate = self (and whatever other code you've not shown that goes along with it.)
You only need two or three lines of code:
// Preloads an ad, so call on app startup (optional but recommended).
[UIViewController prepareInterstitialAds];
// Call once on your viewController when you create it.
self.interstitialPresentationPolicy = ADInterstitialPresentationPolicyManual;
// Call when you want to show an ad (will show if one is available)
[self requestInterstitialAdPresentation];
Thats all there is to the new methods in iOS 7. Whatever other code you have may be causing problems so remove it, and if you still have problems (especially in test with 100% fill rate) then that needs further investigation.