I am looking to load one instance of a Google Admob Banner View throughout multiple View Controllers (including, but not limited to a UITabBarController.)
My attempt is below. I'm using AppDelegate to set the adSize, adUnitID and testDevices. Then in each VC where I want a banner displayed, I set the rootViewController, frame, load request, and then addSubView.
This works, in the fact that the ads show up fine. However, the ads keep changing when I segue or dismiss VC! It appears that a new request is happening everytime VC's change. Which is precisely the result that must be avoided!
AppDelegate
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var adBannerViewFromAppDelegate = GADBannerView()
let loadRequest = GADRequest()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
adBannerViewFromAppDelegate.adSize = kGADAdSizeSmartBannerPortrait
adBannerViewFromAppDelegate.adUnitID = "12345"
loadRequest.testDevices = [kGADSimulatorID, myiPhone]
}
}
ViewController
(This has a button to SecondViewController via Push Segue)
import UIKit
import GoogleMobileAds
class ViewController: UIViewController {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
addBannerToView()
}
func addBannerToView() {
appDelegate.adBannerViewFromAppDelegate.rootViewController = self
appDelegate.adBannerViewFromAppDelegate.load(appDelegate.loadRequest)
appDelegate.adBannerViewFromAppDelegate.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: appDelegate.adBannerViewFromAppDelegate.frame.height)
view.addSubview(appDelegate.adBannerViewFromAppDelegate)
}
}
SecondViewController
import UIKit
import GoogleMobileAds
class SecondViewController: UIViewController {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
addBannerToView()
}
#IBAction func closeButtonPressed(_ sender: UIButton) {
self.dismiss(animated: true, completion: nil)
}
func addBannerToView() {
appDelegate.adBannerViewFromAppDelegate.rootViewController = self
appDelegate.adBannerViewFromAppDelegate.load(appDelegate.loadRequest)
appDelegate.adBannerViewFromAppDelegate.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: appDelegate.adBannerViewFromAppDelegate.frame.height)
view.addSubview(appDelegate.adBannerViewFromAppDelegate)
}
}
How can I get one instance of the Banner created in the AppDelegate to display on multiple ViewControllers? Thanks.
Related
I am trying to push another VC from UICollectionView DidSelect delegate, it is being called,
but oddly, it is not pushing the initiated VC, how can i fix it?
my AppDelegate.swift looks like this:
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
weak var coordinator: MainCoordinator?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
self.window = UIWindow(frame: UIScreen.main.bounds)
coordinator = MainCoordinator(navigationController: UINavigationController())
self.window?.rootViewController = UIStoryboard.splashViewController()
self.window?.makeKeyAndVisible()
return true
}
}
and my SplashViewController, where I switch the RootVC to MainVC in DispatchQueue after the SplashViewController completed:
class SplashViewController: UIViewController {
weak var coordinator: MainCoordinator?
//MARK: -viewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
// MARK: - viewDidAppear()
override func viewDidAppear(_ animated: Bool) {
let animationView = AnimationView()
let animation = Animation.named("SplashAnimation", bundle: Bundle.main)
animationView.animation = animation
animationView.frame = CGRect(origin: .zero, size: CGSize(width: self.view.frame.size.width, height: 200))
animationView.center = self.view.center
animationView.loopMode = .playOnce
animationView.contentMode = .scaleAspectFit
animationView.play { (finished) in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
if let vc = self.storyboard?.instantiateViewController(identifier: "MainViewController") as? MainViewController {
self.view.window?.rootViewController = vc
self.view.window?.makeKeyAndVisible()
}
})
}
view.addSubview(animationView)
}
}
and in my UICollectionView's DidSelect delegate, I am doing this:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if collectionView == categoryCollectionView {
let vc = self.storyboard?.instantiateViewController(identifier: "SymptomViewController") as! SymptomViewController
self.navigationController?.pushViewController(vc, animated: true)
} else {
print("next collectionView not ready yet.. .")
}
}
which usually runs expected, but now it is printing the print statement but never pushing the next VC.. .
First of all, you need to make sure that if collectionView == categoryCollectionView really equals true.
Second, I noticed that your MainViewController doesn't seem to be embedded in a NavigationController, so when you call self.navigationController?.pushViewController(vc, animated: true), it's possibly going to do nothing as the optional chaining will detect that self.navigationController is nil.
Due to my low reputation I cannot add this as a comment, therefore I am posting it in this way.
Have you tried placing a breakpoint on the first line of the collectionView delegate method and checking the collectionView with "po" command in debugger? Since your if statement fails, most probably it is some other collectionView triggering this delegate method. You have to make sure that if collectionView == categoryCollectionView truly returns true.
Also, once app execution is halted on the breakpoint, it is often very useful to check the debug navigator (in case you haven't yet) and get to know what lead to the specified breakpoint.
I am trying to create a framework (Login VC) which contains a view controller. I have successfully imported the framework and presented the VC, but the view is not showing. I have a print function in the imported viewDidLoad and it is printing. What am I missing?
Framework VC:
public class LoginVC: UIViewController {
#IBOutlet weak var button: UIButton! {
didSet {
button.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside)
}
}
public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override public func viewDidLoad() {
super.viewDidLoad()
print("View Loaded") // Firing
}
#objc func buttonPressed() {
print("hello")
}
}
Framework VC Xib:
This is view debugger when I present the framework VC
-- Update: This is how I am showing the VC ---
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
let homeViewController = LoginVC()
homeViewController.view.backgroundColor = UIColor.white
window!.rootViewController = homeViewController
window!.makeKeyAndVisible()
return true
}
-- Update --
Since many comments relate to the app delegate, I first present a general ViewController which then will present my login framework VC.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
let homeViewController = ViewController()
homeViewController.view.backgroundColor = UIColor.white
window!.rootViewController = homeViewController
window!.makeKeyAndVisible()
return true
}
class ViewController: UIViewController {
#IBOutlet weak var button: UIButton! {
didSet {
button.addTarget(self, action: #selector(presentNext), for: .touchUpInside)
}
}
#objc func presentNext() {
let loginVC = LoginVC()
present(loginVC, animated: true, completion: nil)
}
}
Now, when I present the login framework, all I get is a black screen.
-- Update --
I can change the background color of the view in viewdidLoad, but the xib views are not shown. Why is this?..
Frameworks with xibs connected to viewController require explicit loading from the bundle.
Usually when we create a cocoaTouch UIViewController with xib, this is handled for us. However, when using frameworks, it is not handled.
To solve this, I add load the xib in viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
// Frameworks require loading of the xib to connect xib to view controller
let bundle = Bundle(for: type(of: self))
bundle.loadNibNamed("viewControllerName", owner: self, options: nil)
}
You need to call your presentNext() after the ViewController has actually appeared -- not in viewDidLoad, and not even in viewWillAppear.
Like this:
class ViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
presentNext()
}
#objc func presentNext() {
let loginVC = LoginVC()
present(loginVC, animated: true, completion: nil)
}
}
Here's the working test project:
https://github.com/drewster99/SO_LoginVC
Also, maybe double-check that your ViewController.xib has the IBOutlet actually attached for the button. It's actually got to be that. Everything else looks good.
Here's what I've got:
AppDelegate:
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
let homeViewController = ViewController()
homeViewController.view.backgroundColor = UIColor.white
window!.rootViewController = homeViewController
window!.makeKeyAndVisible()
return true
}
}
ViewController.swift:
import UIKit
import LoginFrameworkThing
class ViewController: UIViewController {
#IBOutlet weak var button: UIButton! {
didSet {
button.addTarget(self, action: #selector(presentNext), for: .touchUpInside)
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
#objc func presentNext() {
print("Presenting next...")
let loginVC = LoginVC()
present(loginVC, animated: true, completion: nil)
}
}
The button is connected in ViewController, and LoginVC (both .xib and .swift) exist in the framework.
I updated the sample project. Check the link.
I learn the book iOS Animations by Tutorials
But I don't use Storyboard. I have several ViewControllers created programmatically. I have added RootVC in AppDelegate.swift. This application is working without navigation to the RootVC (going to the beginning) and the screens looks like that:
My question is about how to create such a navigation between different screens (ViewControllers) in Swift 4 (Xcode 10.2.1). It looks like there is an issue with looping... when the last ViewController instantiates the first RootVC and so on...
At the end I would like to have different custom navigation transitions on one ViewController (with .present() and with .navigationController?.pushViewController()
import UIKit
class FadePresentAnimator: NSObject, UIViewControllerAnimatedTransitioning {
let duration = 1.0
var presenting = true
var originFrame = CGRect.zero
var dismissCompletion: (()->Void)?
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
//Setting the transition’s context
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
//Adding a fade transition
let containerView = transitionContext.containerView
let toView = transitionContext.view(forKey: .to)!
containerView.addSubview(toView)
toView.alpha = 0.0
UIView.animate(withDuration: duration,
animations: {
toView.alpha = 1.0
},
completion: { _ in
transitionContext.completeTransition(true)
}
)
}
}
import UIKit
//UIViewControllerTransitioningDelegate for self.present(self.nextScreen, animated: true, completion: nil)
//UINavigationControllerDelegate for self.navigationController?.pushViewController(self.nextScreen, animated: true)
class Screen3: UIViewController, UIViewControllerTransitioningDelegate, UINavigationControllerDelegate {
let nextScreen = RootVC() //4th Screen //<----- EXEC ERRROR
let transition = FadePresentAnimator()
let btnSize:CGFloat = 56.0
let btn1 = ClickableButton()
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Screen 3"
view.backgroundColor = HexColor.Named.BabyBlue
self.navigationController?.delegate = self
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
setupLayout()
}
private func setupLayout() {
//btn1:
view.addSubview(btn1)
btn1.setDefaultTitle(title: "▶") // ⏹ "▶" "■"
btn1.apply(height: btnSize)
btn1.apply(width: btnSize)
btn1.applyDefaultStyle()
if #available(iOS 11.0, *) {
btn1.alignXCenter(to: view.safeAreaLayoutGuide.centerXAnchor)
} else {
// Fallback on earlier versions
}
if #available(iOS 11.0, *) {
btn1.alignYCenter(to: view.safeAreaLayoutGuide.centerYAnchor)
} else {
// Fallback on earlier versions
}
btn1.clickHandler {
self.nextScreen.transitioningDelegate = self
//self.present(self.nextScreen, animated: true, completion: nil)
self.navigationController?.pushViewController(self.nextScreen, animated: true)
}
}
//forward
func animationController(forPresented presented: UIViewController,
presenting: UIViewController, source: UIViewController) ->
UIViewControllerAnimatedTransitioning? {
return transition
}
//backward
func animationController(forDismissed dismissed: UIViewController) ->
UIViewControllerAnimatedTransitioning? {
return nil
}
}
//
// AppDelegate.swift
// Anime-Control-01
//
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
//No Storyboards!
window = UIWindow(frame:UIScreen.main.bounds)
window?.makeKeyAndVisible()
let rootVC = RootVC() //RootVC.swift
let rootController = UINavigationController(rootViewController: rootVC)
window?.rootViewController = rootController
return true
}
}
It looks like there is an issue with looping... when the last ViewController instantiates the first RootVC and so on...
Yeah, you're totally right. The thing is you're creating next ViewController right when current ViewController is initialized.
The simplest way to fix this is to make nextScreen initialized lazily "on demand" by replacing this line
let nextScreen = RootVC()
by this one lazy var nextScreen = RootVC()
Or to create nextScreen variable right before the transition:
btn1.clickHandler {
let nextScreen = RootVC()
nextScreen.transitioningDelegate = self
navigationController?.pushViewController(nextScreen, animated: true)
}
I am trying to call delegate function in AppDelegate, but seems like it never get invoked.
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate,appdelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
if let navigationController = window?.rootViewController, let
viewController = navigationController.childViewControllers.first as? ViewController {
viewController.delegate = self
}
// Override point for customization after application launch.
return true
}
func callfromDelegte(indicator: UIActivityIndicatorView) {
indicator.stopAnimating()
}
ViewController-:
import UIKit
protocol appdelegate:class {
func callfromDelegte(indicator:UIActivityIndicatorView)
}
class ViewController: UIViewController {
#IBOutlet weak var indicator: UIActivityIndicatorView!
weak var delegate:appdelegate?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
indicator.startAnimating()
indicator.hidesWhenStopped = true
}
#IBAction func rotateAction(_ sender: UIButton) {
if delegate != nil{
delegate?.callfromDelegte(indicator: indicator)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Delegate is always nil, it never goes inside function. What is that i don't
now about delegates yet? How does Google GIDSignInDelegate and its delegate functions get called inside AppDelegate from controller class? I know it might be very stupid question but I would still like to know.Thanks
Ok it worked as i have not embeded my controller with navigationController. So it was not going inside if let. It worked simply like this-:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// if let navigationController = window?.rootViewController, let
// viewController = navigationController.childViewControllers.first as? ViewController {
// viewController.delegate = self
// }
// Override point for customization after application launch.
let controller = window?.rootViewController as! ViewController
controller.delegate = self
return true
}
You cannot create the Viewcontroller object directly and set the delegate inside your app delegate. You need to access the Viewcontroller object first because it is the inside the rootViewController so you need to implement like this
if let navigationController = window?.rootViewController, let
viewController = navigationController.childViewControllers.first as? ViewController {
viewController.delegate = self
}
You can try to get AppDelegate instance from shared application. Hope it will help
This is Swift 3
#IBAction func rotateAction(_ sender: UIButton) {
let delegate = UIApplication.shared.delegate as? AppDelegate
delegate?.callfromDelegte(indicator: indicator)
}
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
self.window?.backgroundColor = UIColor.whiteColor()
self.window?.makeKeyAndVisible()
let vc = ViewController()
vc.tabBarItem = UITabBarItem(...)
...
let tabbar = UITabBarController()
tabbar.setViewControllers([...,vc,...], animated: false)
self.window?.rootViewController = tabbar
tabbar.selectedIndex = 2
return true
}
}
class ViewController: UIViewController {
override func loadView() {
super.loadView()
self.view.backgroundColor = UIColor.yellowColor()
//self.automaticallyAdjustsScrollViewInsets = false;
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
}
}
I am not using a story board.
The above causes the ViewControllers view to extend below the tab bar. How can i stop this?
I've tried setting the views frame to
CGRectMake(0, 0, self.view.frame.width, self.view.frame.height - tabBarController.view.frame.height))
but that did not work.
You can use the edgesForExtendedLayout property of UIViewController to set which edges to extend under navigation bars. If you don't want any, you can simply say:
self.edgesForExtendedLayout = .None
For Swift 5 or above
self.edgesForExtendedLayout = []