So I have two view controllers in my application:
StartViewController (Root view controller) and GameViewController
I am presenting my Rewarded video from GameViewController
Everything works perfectly, except for when the user presses "Skip now" on the rewarded video. It dismisses GameViewController and goes back to StartViewController which is my root view controller.
If the user watches the entire video, it works as intended.
The code for presenting rewarded view from GameViewController:
func playReward()
{
if rewardVideo!.isReady
{
if var topController = UIApplication.shared.keyWindow?.rootViewController
{
while let presentedViewController = topController.presentedViewController
{
// Make top controller topmost view controller
topController = presentedViewController
}
rewardVideo!.present(fromRootViewController: topController)
}
}
}
I temporarily changed the root to GameViewController to see if this was the issue and doing so fixed it, so I know it is an issue related to the root view controller and the "Skip now" button on the rewarded video.
I had the same issue. I solved it by overriding the func dismiss(animated flag: Bool, completion: (() -> Void)? = nil method.
Here is what I did.
var didOpenRewardedVideo:Int = 0
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
if didOpenRewardedVideo == 1 {
didOpenRewardedVideo = 2
super.dismiss(animated: flag, completion: completion)
}
else if didOpenRewardedVideo == 2{
didOpenRewardedVideo = 0
}
else{
super.dismiss(animated: flag, completion: completion)
}
}
func showRewardedVideo()
{
didOpenRewardedVideo = 1
GADRewardBasedVideoAd.sharedInstance().present(fromRootViewController: self)
}
Before showing the rewardedAd, do not forget to check if it's ready or not.
GADRewardBasedVideoAd.sharedInstance().isReady == true
When rewardedAd is presented didOpenRewardedVideo is set to 1. When user dismissing rewardedAd didOpenRewardedVideo is 1 and calling super.dismiss(animated: flag, completion: completion). Then didOpenRewardedVideo is set to 2. Now I know dismiss(animated flag: Bool, completion: (() -> Void)? = nil) will be called once more. This time I don't call the super method and set didOpenRewardedVideo to 0. I know if I dismiss my UIViewController will be dismissed.
#mialkan,
your solution does not work. I don't why no one talk about this issue. I am also facing such problem.
import GoogleMobileAds import sdk in your class or viewcontroller
GADRewardBasedVideoAdDelegate Add this in your class or viewcontroller
var RewardBasedVideo: GADRewardBasedVideoAd? **initialize AdController object **
override func viewDidLoad()
{
super.viewDidLoad()
RewardBasedVideo=GADRewardBasedVideoAd.sharedInstance()
RewardBasedVideo?.delegate = self
}
//MARK:- WATCH AD BUTTON CLICK
#IBAction func WatchAdBtn_Click(_ sender: UIButton)
{
if RewardBasedVideo?.isReady == true
{
RewardBasedVideo?.present(fromRootViewController: self)
} else
{
//Show alert here "Ads is not ready to load"
}
}
func rewardBasedVideoAdDidClose(_ rewardBasedVideoAd: GADRewardBasedVideoAd)
{
print("Reward based video ad is closed.")
}
For those of you who is facing the the same issue:
You can create a new class for your rootViewController (TabBarController or NavigationController etc.) and implement there something like that:
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
dismissalCounter += 1
if (dismissalCounter < 2) {
super.dismiss(animated: flag, completion: completion)
}
}
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
dismissalCounter = 0
}
override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
dismissalCounter = 0
super.present(viewControllerToPresent, animated: flag, completion: completion)
}
var dismissalCounter : Int = 0
Important! Use this functions inside TabBarController or NavigationController, otherwise it is not gonna work
UPD:
In my case unfortunatly it breaks all NavigationControllers inside a TabBarController (titles don't show and there are no buttons inside them), if I will figure fix actions, I'll let you know
UPD2:
Pretty obvious decision will be to change the initialViewController and view add from it, it'll not be dismissed
UPD3:
I solved this very and very strange:
class ViewController : UIViewController {
override func viewWillAppear(_ animated: Bool) {
if GADRewardBasedVideoAd.sharedInstance().isReady == false {
let request = GADRequest()
rewardBasedVideo!.load(request, withAdUnitID: "ca-app-pub-3940256099942544/1712485313")
}
}
var rewardBasedVideo: GADRewardBasedVideoAd?
#IBAction func ad_button_click(_ sender: Any) {
if rewardBasedVideo!.isReady == true {
let bl = blur()
self.present(bl, animated: true, completion: {
self.rewardBasedVideo?.present(fromRootViewController: bl)
})
}
}
}
class blur : UIViewController {
override func viewDidLoad() {
checkForKeyWindow()
}
func checkForKeyWindow() {
DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: {
if (UIApplication.topViewController() == self) {
print("dismissed and forgotten")
self.dismiss(animated: true, completion: nil)
} else {
print("not keywindow")
self.checkForKeyWindow()
}
})
}
#objc func close() {
self.dismiss(animated: true, completion: nil)
}
}
extension UIApplication {
class func topViewController(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(base: nav.visibleViewController)
}
if let tab = base as? UITabBarController {
let moreNavigationController = tab.moreNavigationController
if let top = moreNavigationController.topViewController, top.view.window != nil {
return topViewController(base: top)
} else if let selected = tab.selectedViewController {
return topViewController(base: selected)
}
}
if let presented = base?.presentedViewController {
return topViewController(base: presented)
}
return base
}
}
Related
I have two Present view controllers. The thing i want to do is when the second Present view controller is dismissed it will automatically reload the first present view controller(Table view). note: first view controller holds a table view, basically i want to reload the table view of first controller.
ViewWillAppear code:
override func viewWillAppear(_ animated: Bool) {
tableViewReloadFromCreateProductVC()
}
func tableViewReloadFromCreateProductVC () {
tableView.reloadData()
}
Calling from second view controller code:
SecondViewController.tableViewReloadFromCreateProductVC()
navigationController?.popViewController(animated: true)
dismiss(animated: true, completion: nil)
FirstViewController calling 2nd view controller
#IBAction func CallSecondViewButton(_ sender: Any) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "YourViewControllerIdentifier") as! YourViewController
controller.modalPresentationStyle = .fullScreen
self.present(controller, animated: true, completion: nil)
}
just write the code in viewWillAppear() method of the view controller that you want to reload like this
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
//perform api call if any
yourTableView.reloadData()
}
2nd view controller
#IBAction func CloseButton(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
}
after dissmissing the viewWillAppear method of firstViewController will autometically called.
The First two snippets are for first view controller and the last one is for second view controller
Reloading the entire table view could sometimes be costly and it also sounds like you're making an API call as well so unless you want your table view to be reloaded and the API call made every time the view controller becomes visible whether or not you've made changes to it, you want the reloading to be done only when it's necessary.
You can try it in a few different ways:
class CreateProductVC: UITableViewController {
#IBAction func presentSecondVC() {
if let secondVC = storyboard?.instantiateViewController(identifier: "SecondVC") as? SecondViewController {
secondVC.delegate = self
present(secondVC, animated: true, completion: nil)
}
}
}
class SecondViewController: UIViewController {
weak var delegate: CreateProductVC?
#IBAction func dismissSecondVC() {
self.dismiss(animated: true) {
self.delegate?.tableView.reloadData()
}
}
}
or
class CreateProductVC: UITableViewController {
#IBAction func presentSecondVC() {
if let secondVC = storyboard?.instantiateViewController(identifier: "SecondVC") as? SecondViewController {
secondVC.isDismissed = { [weak self] in
self?.tableView.reloadData()
}
present(secondVC, animated: true, completion: nil)
}
}
}
class SecondViewController: UIViewController {
var isDismissed: (() -> Void)?
#IBAction func dismissSecondVC() {
self.dismiss(animated: true) {
self.isDismissed?()
}
}
}
or if you want more fine-grained control over what to do with the new data:
protocol ReloadVC {
func reload(_ value: String)
}
class CreateProductVC: UITableViewController, ReloadVC {
var dataSource: [String]! {
didSet {
tableView.reloadData()
}
}
#IBAction func presentSecondVC() {
if let secondVC = storyboard?.instantiateViewController(identifier: "SecondVC") as? SecondViewController {
secondVC.delegate = self
present(secondVC, animated: true, completion: nil)
}
}
func reload(_ value: String) {
dataSource.append(value)
}
}
class SecondViewController: UIViewController {
var delegate: ReloadVC?
#IBAction func dismissSecondVC() {
self.dismiss(animated: true) {
let someValue = "Some Value"
self.delegate?.reload(someValue)
}
}
}
I use default implementation of UINavigationController with default gesture for popping left to right (interactivePopGestureRecognizer). How can I make interactivePopGestureRecognizer popping to root controller instead of only 1 top controller?
I found a way to just delete previous viewController from stack after pushing a new one.
navigationController.pushViewController(newViewController, animated: true, completion: {
self.navigationController.removePreviousViewController()
})
A here is an extension
extension UINavigationController {
func pushViewController(_ viewController: UIViewController, animated: Bool, completion: #escaping () -> Void) {
pushViewController(viewController, animated: animated)
guard animated, let coordinator = transitionCoordinator else {
DispatchQueue.main.async { completion() }
return
}
coordinator.animate(alongsideTransition: nil) { _ in completion() }
}
func removePreviousViewController() {
if viewControllers.count > 2 {
viewControllers.removePrevious()
}
}
}
Some helpers
extension Array {
mutating func removePrevious() {
remove(at: count - 2)
}
}
I currently have a button that currently displays a UIPageViewController as an overlay on the homescreen. The user can always click on it and it would still display this ViewControllers embedded in a UIPageViewController. However at the moment, when the user clicks on the button to display the overlay and closes it and opens it again. The last page index / state of the last page is saved and displayed again. What i would like to do is to always display the first ViewController in the UIPageviewController. I have tried different ways but haven't had any luck.
Here is the screen capture that shows how the current mechanism works
https://gph.is/g/Z2QlyDa
HomeHelpViewController that contains the UIPageViewController
class HomeHelpViewController: UIPageViewController, UIPageViewControllerDataSource {
var helpPages: [UIViewController] = []
var currentIndex: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
let helpStoryboard = UIStoryboard(name: "HomeHelp", bundle: nil)
for scanIdentifier in ["homeHelpVC1","homeHelpVC2","homeHelpVC3","homeHelpVC4"] {
let scanVC = helpStoryboard.instantiateViewController(withIdentifier: scanIdentifier)
self.helpPages.append(scanVC)
}
self.dataSource = self
self.setViewControllers([self.helpPages.first!], direction: .forward, animated: true)
}
//MARK:- UIPageViewControllerDataSource
func pageViewController(_ pPageViewController: UIPageViewController, viewControllerBefore pViewController: UIViewController) -> UIViewController? {
self.currentIndex = self.helpPages.firstIndex(of: pViewController)!
if self.currentIndex > 0 {
return self.helpPages[self.currentIndex - 1]
}
return nil
}
func pageViewController(_ pPageViewController: UIPageViewController, viewControllerAfter pViewController: UIViewController) -> UIViewController? {
self.currentIndex = self.helpPages.firstIndex(of: pViewController)!
if self.currentIndex < (self.helpPages.count - 1) {
return self.helpPages[self.currentIndex + 1]
}
return nil
}
func presentationCount(for pPageViewController: UIPageViewController) -> Int {
return self.helpPages.count
}
func presentationIndex(for pPageViewController: UIPageViewController) -> Int {
return self.currentIndex
}
}
HomePageViewController that has the button display the UIPageViewController as an overlay
class HomePageViewController: UIViewController {
#IBOutlet weak var helpOverlay: BaseOverlayView!
#IBAction func helpButton(_ pSender: Any) {
self.helpButton.setImage(AppDelegate.helpButtonImage(tutorial: true), for: .normal)
self.helpOverlay.showOnView(self.view)
}
#IBAction func closeHelp(_ pSender: UIButton) {
self.helpOverlay.removeFromView()
self.helpButton.setImage(AppDelegate.helpButtonImage(tutorial: false), for: .normal)
}
}
declare a variable to access your HomeHelpViewController inside your HomePageViewController
class HomePageViewController: UIViewController {
weak var homePageController : HomeHelpViewController?
#IBOutlet weak var helpOverlay: BaseOverlayView!
#IBAction func helpButton(_ pSender: Any) {
if let homePageController = homePageController {
homePageController.setViewControllers([homePageController.helpPages.first!], direction: .forward, animated: true)
}
self.helpButton.setImage(AppDelegate.helpButtonImage(tutorial: true), for: .normal)
self.helpOverlay.showOnView(self.view)
}
#IBAction func closeHelp(_ pSender: UIButton) {
self.helpOverlay.removeFromView()
self.helpButton.setImage(AppDelegate.helpButtonImage(tutorial: false), for: .normal)
}
}
inside this loop assign your HomeHelpViewController
for scanIdentifier in ["homeHelpVC1","homeHelpVC2","homeHelpVC3","homeHelpVC4"] {
let scanVC = helpStoryboard.instantiateViewController(withIdentifier: scanIdentifier) as? HomePageViewController
scanVc?.homePageController = self
self.helpPages.append(scanVC)
}
This may be because the UIPageViewController was not properly dismissed.
func dismiss(animated flag: Bool, completion: (() -> Void)? = nil)
You can refer to the documentation here: https://developer.apple.com/documentation/uikit/uiviewcontroller/1621505-dismiss
This effectively deallocates the UIPageViewController so that once the button is pressed, it will reinitialized again at the first page.
I have an ViewController with GADRewardBasedVideoAd.
I can easily play and close ads, but with the ad closes ViewController as well.
What can I do?
#IBAction func ad_button_click(_ sender: Any) {
if GADRewardBasedVideoAd.sharedInstance().isReady == true
{
GADRewardBasedVideoAd.sharedInstance().present(fromRootViewController: self)
}
}
For those of you who is facing the the same issue:
You can create a new class for your rootViewController (TabBarController or NavigationController etc.) and implement there something like that:
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
dismissalCounter += 1
if (dismissalCounter < 2) {
super.dismiss(animated: flag, completion: completion)
}
}
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
dismissalCounter = 0
}
override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
dismissalCounter = 0
super.present(viewControllerToPresent, animated: flag, completion: completion)
}
var dismissalCounter : Int = 0
Important! Use this functions inside TabBarController or NavigationController, otherwise it is not gonna work
UPD:
In my case unfortunatly it breaks all NavigationControllers inside a TabBarController (titles don't show and there are no buttons inside them), if I will figure fix actions, I'll let you know
UPD2:
Pretty obvious decision will be to change the initialViewController and view add from it, it'll not be dismissed
UPD3:
I solved this very and very strange:
class ViewController : UIViewController {
override func viewWillAppear(_ animated: Bool) {
if GADRewardBasedVideoAd.sharedInstance().isReady == false {
let request = GADRequest()
rewardBasedVideo!.load(request, withAdUnitID: "ca-app-pub-3940256099942544/1712485313")
}
}
var rewardBasedVideo: GADRewardBasedVideoAd?
#IBAction func ad_button_click(_ sender: Any) {
if rewardBasedVideo!.isReady == true {
let bl = blur()
self.present(bl, animated: true, completion: {
self.rewardBasedVideo?.present(fromRootViewController: bl)
})
}
}
}
class blur : UIViewController {
override func viewDidLoad() {
checkForKeyWindow()
}
func checkForKeyWindow() {
DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: {
if (UIApplication.topViewController() == self) {
print("dismissed and forgotten")
self.dismiss(animated: true, completion: nil)
} else {
print("not keywindow")
self.checkForKeyWindow()
}
})
}
#objc func close() {
self.dismiss(animated: true, completion: nil)
}
}
extension UIApplication {
class func topViewController(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(base: nav.visibleViewController)
}
if let tab = base as? UITabBarController {
let moreNavigationController = tab.moreNavigationController
if let top = moreNavigationController.topViewController, top.view.window != nil {
return topViewController(base: top)
} else if let selected = tab.selectedViewController {
return topViewController(base: selected)
}
}
if let presented = base?.presentedViewController {
return topViewController(base: presented)
}
return base
}
}
In my app I have a SFSafariViewController that I am displaying modally. Upon dismissal, the presenting ViewController does not have its dismiss method called. Code for my subclass of UIViewController:
override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
print("will present")
super.present(viewControllerToPresent, animated: flag) {
completion?()
print("did present")
}
}
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
print("will dismiss)")
super.dismiss(animated: flag) {
completion?()
print("did dismiss")
}
}
func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
print("finished with \(controller)")
}
func testSVC() {
let svc = SFSafariViewController(url: URL(string: "https://www.stackoverflow.com")!)
svc.delegate = self
self.present(svc, animated: true) {
print("presented \(svc)")
}
}
Calling testSVC() and then tapping "Done" in the SafariViewController produces the following output:
will present
presented <SFSafariViewController: 0x7fd981b2f200>
did present
finished with <SFSafariViewController: 0x7fd981b2f200>
And that's it. The dismiss print statements are missing. Can anyone help me figure out why dismiss is not being called? I thought that all UIViewController dismissals were forwarded to the presenting UIViewController.
When you dismiss the SFSafariViewController the dismiss method for svc is fired not the ViewController you're in. If you want to override the dismiss for the SFSafariViewController then you should subclass it and override the dismiss func in that subclass.