I have this app that I am trying to use AdMob with that is made with SwiftUI. I had been using official AdMob documentation with SwiftUI to help me do this. I have one issue however, the ad successfully gets loaded, but the ad is located underneath another view, much like a ZStack without a ZStack present. No matter where I put it, it gets placed beneath a view. I have used the .frame() attribute, and it works using the BannerAd view, but then there is a giant white space there when the ad can't be loaded. Here are screenshots that may help solve the issue:
(Image on the left) This is what happens using the Google provided code (They say that the banner would reload into its own view when the ad is loaded, but does not appear to happen) As you can see, the add is below the name of the current herb.
(Image on the right)This is what happens when using the .frame() attribute before the ad is loaded. The ad loads correctly, but leaves this white space when not loaded or unable to load.
Here is the code to make the banner work with SwiftUI
import SwiftUI
import Foundation
import GoogleMobileAds
protocol BannerViewControllerWidthDelegate: AnyObject {
func bannerViewController(_ bannerViewController: BannerViewController, didUpdate width: CGFloat)
}
class BannerViewController: UIViewController {
weak var delegate: BannerViewControllerWidthDelegate?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Tell the delegate the initial ad width.
delegate?.bannerViewController(self, didUpdate: view.frame.inset(by: view.safeAreaInsets).size.width)
}
override func viewWillTransition(
to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator
) {
coordinator.animate { _ in
// do nothing
} completion: { _ in
// Notify the delegate of ad width changes.
self.delegate?.bannerViewController(
self, didUpdate: self.view.frame.inset(by: self.view.safeAreaInsets).size.width)
}
}
}
struct BannerAd: UIViewControllerRepresentable {
#State private var viewWidth: CGFloat = .zero
private let bannerView = GADBannerView()
private let adUnitID = "ca-app-pub-3940256099942544/6300978111"
// Make the view to be used in SwiftUI
func makeUIViewController(context: Context) -> some UIViewController {
let bannerViewController = BannerViewController()
bannerView.adUnitID = adUnitID
bannerView.rootViewController = bannerViewController
bannerViewController.view.addSubview(bannerView)
bannerViewController.delegate = context.coordinator
return bannerViewController
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
if(viewWidth != .zero){
// Nothing happens
} else {
// Return when the size is greater than 0
//TODO: Fix !!!! This does not push other views down as it should, a frame could be set, but the ad does not push everything down or up
return
}
// Request a banner ad with the updated viewWidth.
bannerView.adSize = GADCurrentOrientationAnchoredAdaptiveBannerAdSizeWithWidth(viewWidth)
bannerView.load(GADRequest())
}
}
}
The code may be long, but you can ignore the coordinator as that is only for listeners, not nay real displaying of the advertisement.
What I want to happen exactly is for the Ad to move every object down so that when the ad is loaded, it will fit respectfully at the top of the page. I have looked at all sorts of sources, but I cannot find any solutions to this anywhere. Please help me, and thank you!
Related
I'm trying to change a macOS menubar icon at the click of a button based on this question.
However, when I run the app and click the button, I get a Could not cast value of type 'SwiftUI.AppDelegate' (0x20dfafd68) to 'MenuBarTest.AppDelegate' (0x10442c580). error on this line:
let appDelegate = NSApplication.shared.delegate as! AppDelegate
Main Swift File
#main
struct MenuBarTestApp: App {
#NSApplicationDelegateAdaptor(AppDelegate.self) var delegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
class AppDelegate: NSObject, NSApplicationDelegate {
var statusItem: NSStatusItem?
var popOver = NSPopover()
func applicationDidFinishLaunching(_ notification: Notification) {
let menuView = MenuBarView()
popOver.behavior = .transient
popOver.animates = true
popOver.contentViewController = NSViewController()
popOver.contentViewController?.view = NSHostingView(rootView: menuView)
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
if let MenuButton = statusItem?.button {
MenuButton.image = NSImage(named: "settings_bw")
MenuButton.image?.size = NSSize(width: 18.0, height: 18.0)
MenuButton.action = #selector(MenuButtonToggle)
}
}
func setIcon(colorType:String) {
var icon = NSImage(named: "settings_color")
if colorType != "color" {
icon = NSImage(named: "settings_bw")
}
statusItem?.button?.image = icon
}
#objc
func MenuButtonToggle(sender: AnyObject) {
if popOver.isShown {
popOver.performClose(sender)
} else {
if let MenuButton = statusItem?.button {
self.popOver.show(relativeTo: MenuButton.bounds, of: MenuButton, preferredEdge: NSRectEdge.minY)
popOver.contentViewController?.view.window?.makeKey()
}
}
}
}
MenuBar View
struct MenuBarView: View {
var body: some View {
VStack {
Button(action: {
let appDelegate = NSApplication.shared.delegate as! AppDelegate
appDelegate.setIcon(colorType: "color")
}, label: {
Text("Change Icon")
})
}
.frame(width: 150, height: 100)
}
}
First, I'll answer the question as asked, but then I'll follow up with why it actually doesn't matter because it's solving the wrong problem.
Accessing the App Delegate in a SwiftUI lifecycle app
When you're using SwiftUI to manage your app lifecycle, you don't use an app delegate directly. SwiftUI will set the application's app delegate to an instance of its own class, SwiftUI.AppDelegate. This is what you saw when trying to cast it to MenuBarTest.AppDelegate. You can't cast it to your class, because it's not an instance of your class.
As a backwards compatibility feature, SwiftUI's app delegate lets you provide your own delegate instance to that it will forward to. This is wired up with the NSApplicationDelegateAdaptor property wrapper. If you want to access an instance, that delegate instance property is the way to get at it (not NSApplication.shared.delegate).
Don't even use the App Delegate
You can disregard everything I said so far, because this is just a bad design, and you shouldn't use it. The App Delegate should be strictly limited to code related to managing the life cycle of your app and how it interacts with the system. It should not be a kitchen-junk-drawer of any code that fits.
Having random code right in the App Delegate is the AppKit equivalent to putting all your code in the main() function of a C program. It's done for brevity, but rarely a good idea. This is what you'll often see in tutorials, which aim to keep file and line count at a minimum, to illustrate some other point (e.g. Related to menu bar configuration, in this case).
As you'll notice, none of the code in this app delegate does much that's specific to the App life cycle. It just calls out to NSStatusBar. This code can just be put in any other appropriate place, like any other stateful SwiftUI code.
I'm trying to display a custom SwiftUI view similar to a Toast in Android.
My issue is that I would like to display this particular view above everything else, using the current UIWindow.
Currently, while working on static func displayToastAboveAll() located in my ToastView, this is how far i got
public struct ToastView: View {
static func displayToastAboveAll() {
let window = UIApplication.shared.windows.filter { $0.isKeyWindow }.first // window
let viewToShow = ToastView(my params) // my view to display
// This part I'm not sure of
let hostingController = UIHostingController(rootView: viewToShow)
window?.addSubview(hostingController.view)
}
public var body: some View {
// MyDesign
}
}
Any idea how should I use the window to put the ToastView at its proper place, and still being able to navigate within the app (and use the outlets) while having the view displayed ?
I managed to do what I wanted.
Basically, this code is working, but I had to remove some constraints from my SwiftUI view and add them with UIKit using the static func.
Also, I had to pass by a modifier (see below) and put ToastView init in private.
public struct ToastModifier: ViewModifier {
public func body(content: Content) -> some View {
content
}
}
extension View {
public func toast() -> some View {
ToastView.displayToastAboveAll()
return modifier(ToastModifier())
}
}
This is done to force the use of either modifier (SwiftUI, by doing .toast, just like you'd do .alert) or directly by calling the static func ToastView.displayToastAboveAll() (UIKit).
Indeed, I dont wont this Toast to be a part of the view, I want to trigger it like an alert.
Finally, special warning because passing ToastView into UIHostingViewController will mess with some of the animations.
I had to rewrite animations in UIKit in order to have a nice swipe & fade animation.
CODED IN SWIFTUI
I have implemented a settings page within my SwiftUI app that is presented upon clicking the button within the view. This view is wrapped within a sheet. You can see the code below:
TabsBar(showOptionsTab: $showOptionsTab)
.sheet(isPresented: $showOptionsTab)
{
OptionsTab(showOptionsTab: self.$showOptionsTab)
}
I have implemented an interstitial within this "OptionsTab" that is created when the options tab is loaded within the .onAppear() call and presented when the back button is selected. You can the code from the OptionsTab below:
#State private var interstitial : GADInterstitial!
VStack
{
... other code
Button(action:
{
if ( self.interstitial.isReady)
{
let root = UIApplication.shared.windows.first?.rootViewController
self.interstitial.present(fromRootViewController: root!)
}
self.showOptionsTab.toggle()
})
{
Image(systemName: "xmark")
.font(.largeTitle)
}
}
.onAppear
{
self.interstitial = GADInterstitial(adUnitID: "addID")
let req = GADRequest()
self.interstitial.load(req)
}
I have additional code within the button that logs the status of the interstitial, and it shows it as being READY, and the present code is being hit... but it never presents itself.
I have implemented this exact code in the ContentView, and the interstitial loads perfectly fine. Something about this code being within the sheet is causing the interstitial not to present.
Is the rootViewController the incorrect view to load the interstitial on when in a view that is presented from a sheet? Otherwise, what else am I doing wrong?
Thanks for the help in advance!
Try this
TabsBar(showOptionsTab: $showOptionsTab)
.sheet(isPresented: $showOptionsTab)
{
OptionsTab(showOptionsTab: self.$showOptionsTab)
.OnDisapear
{
if ( self.interstitial.isReady)
{
let root = UIApplication.shared.windows.first?.rootViewController
self.interstitial.present(fromRootViewController: root!)
}
}
}
}
And then add the state interstitial variable within this view, and onAppear of the ContextView, create the interstitial there.
Solution 2:
(not tested but should work):
present on the presentingViewController instead of the rootViewController
if ( self.interstitial.isReady)
{
let root = UIApplication.shared.windows.first?.rootViewController?.presentingViewController
self.interstitial.present(fromRootViewController: root!)
}
check the reason, at the end of the answer
Solution 1:
it sounds like that there's something that prevents Interstitial ad to be presented when a sheet view is already presented
in my case i wanted to present the ad immediately after the sheet view is presented, which didn't work
what worked is, Presenting the ad, and after it being dismissed, the sheet view will be presented , and it worked!
for the Interstitial i used this code
import SwiftUI
import GoogleMobileAds
import UIKit
final class Interstitial:NSObject, GADInterstitialDelegate{
var interstitialID = "ca-app-pub-3940256099942544/4411468910"
var interstitial:GADInterstitial = GADInterstitial(adUnitID: "ca-app-pub-3940256099942544/4411468910")
var sheet: (() -> Void)? = nil //this closure will be executed after dismissing the ad
override init() {
super.init()
LoadInterstitial()
}
func LoadInterstitial(){
let req = GADRequest()
self.interstitial.load(req)
self.interstitial.delegate = self
}
func showAd(then sheet:(() -> Void)?){
if self.interstitial.isReady{
let root = UIApplication.shared.windows.first?.rootViewController
self.interstitial.present(fromRootViewController: root!)
self.sheet = sheet
}
else{
print("Not Ready")
}
}
func interstitialDidDismissScreen(_ ad: GADInterstitial) {
self.interstitial = GADInterstitial(adUnitID: interstitialID)
LoadInterstitial()
if let presentSheet = sheet {
presentSheet()
}
}
}
here we pass whatever we want to happen to the closure which will be called when interstitialDidDismissScreen is called
now instead of switching the state when button is pressed, it will be switched after the ad is dimissed
at the top of your contentView:
struct HistoryView: View {
private var fullScreenAd: Interstitial!
init() {
fullScreenAd = Interstitial()
}
}
then in your button:
Button(action: {
self.interstitial.showAd {
self.showOptionsTab.toggle()
}
})
Note: i used the code of interstitial Ad from this gist
Also, AFAIK we can't tell if it's an bug in swiftUI, or it's something googleAdmob was supposed to take care of ?, there's no SwiftUI support From google so we can't blame them.
the problem AFAIK, that in iOS 13, grabbing the root ViewController will return the View Controller that presented the sheet, and when trying to present the ad, UIKit will complain that you are trying to present A view controller while a view controller is already present(the sheet),
Solution ? simple, present the Ad, on the presented view Controller(The sheet)
I am working on a mac application, and I like to make initial window be in maximized state, like when you are pressing green button with plus sign.
I don't want it to be full screen.
An app in its zoomed state is not the same thing as "maximized." The green plus icon indicates zoom, which means "the appropriate size for this content." In some applications that's the visible frame (as Eric D. discusses), but it can be almost anything. Try zooming a Safari window for instance.
Assuming you really want "maximized" and not "zoom", then Eric is on the right track, but it can be done better. First, you should use the window's screen if it has one. Also, you should not animate the window resize during launch (since that can look awkward on launch).
func applicationDidFinishLaunching(aNotification: NSNotification) {
if let screen = window.screen ?? NSScreen.mainScreen() {
window.setFrame(screen.visibleFrame, display: true)
}
}
You may want to consider using a NSWindowController to manage this rather than putting it in the application delegate. In that case, you can put this in windowDidLoad. Window controllers are a pretty common tool in AppKit (as opposed to view controllers, which are not historically as common).
If you actually want zoom behavior, familiarize yourself with the the NSWindowDelegate method windowWillUseStandardFrame(_:defaultFrame:). You shouldn't generally call zoom(_:) directly on launch because that will animate, but whatever logic you do in the delegate should be used to compute your frame. Again, make sure to adjust your frame to live on the window's screen if it has one, rather than the main screen.
Ideally, you really should be honoring the last frame that the user used rather than forcing it to the visible frame. That's called frameAutosave in Cocoa if you want to research that more. A window controller will help you manage that somewhat automatically if you just set a autosave name in Interface Builder. (Though it's slightly complicated by needing to compute the frame on first launch to get the visible frame, so it won't be completely automatic.)
Do give some careful thought before making your default frame be the visible frame in any case. That can be really enormous on large monitors (there are still a lot of 30" Cinema displays out there, but even on a 27" it can be pretty overwhelming). Sometimes that's fine depending on your app, but I often find that it's worth defining a maximum initial size (while allowing the user to make it larger).
You can "zoom" a window to the max available space by using NSScreen's visibleFrame as the target frame. Let's say window is your NSWindow IBOutlet:
if let screen = NSScreen.mainScreen() {
window.setFrame(screen.visibleFrame, display: true, animate: true)
}
For example, in the AppDelegate.swift:
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var window: NSWindow!
func applicationDidFinishLaunching(aNotification: NSNotification) {
if let screen = NSScreen.mainScreen() {
window.setFrame(screen.visibleFrame, display: true, animate: true)
}
}
in Swift 4.2:
class ViewController: NSViewController {
override func viewDidAppear() {
super.viewDidAppear()
view.window?.zoom(self) //bespread the screen
//view.window?.toggleFullScreen(self) //fullscreen
}
2020 | SWIFT 5.1:
use extension:
extension NSWindowController {
func maximize() { self.window?.zoom(self) }
}
just call maximize() of NSWindowController instance :)
Swift 5
If anyone's still having issues, trying calling the zoom function the main thread. Worked for me.
DispatchQueue.main.async {
self.view.window?.zoom(self)
}
Hi Guys I really appreciate your help.
I am working on a document based mac application. I put the code you provided in the makeWindowControllers() of Document class and it works like a charm.
Thank you very much. Here is the code I use.
override func makeWindowControllers() {
// Returns the Storyboard that contains your Document window.
let storyboard = NSStoryboard(name: "Main", bundle: nil)
let windowController = storyboard.instantiateControllerWithIdentifier("Document Window Controller") as! NSWindowController
self.addWindowController(windowController)
if let screen = NSScreen.mainScreen() {
windowController.window?.setFrame(screen.visibleFrame, display: true, animate: true)
}
}
this code works well only on single-windowed application, but it's really easy to edit to work with multy-windowed application
usage to maximize and unmaximize window:
TheApp.maximized.toggle()
Source code
public class TheApp {
static var maximized: Bool {
get {
guard let visibleFrame = NSScreen.main?.visibleFrame,
let window = NSApp.mainWindow
else { return false }
return window.frame == visibleFrame
}
set { NSApp.mainWindow?.zoom(newValue) }
}
static var fullscreen: Bool {
get {
guard let screenFrame = NSScreen.main?.frame,
let window = NSApp.mainWindow
else { return false }
return window.frame == screenFrame
} set {
NSApp.mainWindow?.toggleFullScreen(newValue)
}
}
static var mimimized: Bool {
get { NSApp.mainWindow?.isMiniaturized ?? false }
set { NSApp?.mainWindow?.miniaturize(newValue) }
}
}
I have an AdBannerView inside my game, but it keeps showing randomly even though I set it to hidden, it pops from the bottom pushing the view up.
Here's the code I have thus far in GameScene:
var iAd = ADBannerView()
override func didMoveToView(view: SKView) {
iAd.delegate = self
iAd.hidden = true
iAd.autoresizingMask = UIViewAutoresizing.FlexibleTopMargin
view.addSubview(iAd)
}
func bannerViewDidLoadAd(banner: ADBannerView!) {
if (!isStarted){ // <- If game has started
iAd.hidden = false
}
}
func bannerView(banner: ADBannerView!, didFailToReceiveAdWithError error: NSError!) {
print("Ad Fail")
iAd.hidden = true
}
func newGame() {
iAd.hidden = true
}
func gameOver() {
iAd.hidden = false
}
Sometimes the ad shows during gameplay, sometimes it shows at the top, other times at the bottom.
My questions are:
How do I position it at the top?
How do I make it so that it stops appearing during gameplay?
How do I make it stop pushing the view up?
How do I make it stop showing if it failed to load (it currently does show)
MORE INFO: I tried this code in ViewController, but ended up with the same results.
For question 1: You have set the frame property to position the banner ad:
iAd.frame = CGRectMake(0, view.frame.size.height - iAd.frame.size.height, view.frame.size.width, iAd.frame.size.height);
Sounds like you've created an ADBannerView in Interface Builder in addition to including self.canDisplayBannerAds somewhere in your project.
The ADBannerView displaying on the bottom of your devices screen is created by self.canDisplayBannerAds = true. self.canDisplayBannerAds = true can be used for a no hassle way of implementing iAd banners in your application. This will create an ADBannerView for you and show or hide the ADBannerView depending on whether it receives an ad or not from the iAd network.
If you have included self.canDisplayBannerAds = true in your project you need to remove it.
As for hiding the ADBannerView when it fails to receive an advertisement, you need to implement the ADBannerView's delegate methods: ADBannerViewDelegate Protocol Reference. Then, in bannerView(_:didFailToReceiveAdWithError:) you'd set your ADBannerView's alpha property to 0.