How to run watch app in the background using WKExtendedRuntimeSession - swift

Thank you for taking a look at this page.
I am developing a Watch App and would like to do some background data processing in SwiftUI.
I tried to run it using WKExtendedRuntimeSession, but it doesn't seem to be running when it becomes inactive.
import SwiftUI
#main
struct FirebaseExample_Watch_AppApp: App {
var session = WKExtendedRuntimeSession()
init() {
session.start()
// MARK:- Extended Runtime Session Delegate Methods
func extendedRuntimeSessionDidStart(_ extendedRuntimeSession: WKExtendedRuntimeSession) {
print(1)
}
func extendedRuntimeSessionWillExpire(_ extendedRuntimeSession: WKExtendedRuntimeSession) {
print(2)
}
func extendedRuntimeSession(_ extendedRuntimeSession: WKExtendedRuntimeSession, didInvalidateWith reason: WKExtendedRuntimeSessionInvalidationReason, error: Error?) {
print(3)
}
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
But the result is nothing happens the moment I put my arm down and the app screen disappears.
I alse tried to do this;
                 
init() {
        func startTimerButtonPressed() {
print(WKApplicationState.RawValue())
var session = WKExtendedRuntimeSession()
session.start()
print(WKExtendedRuntimeSessionState.RawValue())
}
The results are all zeros and do not appear to be initiated.
If you are familiar with WKExtendedRuntimeSession, please let me know how to run it.
Reference
https://developer.apple.com/documentation/watchkit/wkextendedruntimesession

You are not setting a delegate for your session. A possible implementation would look like this:
#main
struct TestWatchOSOnly_Watch_AppApp: App {
#State var session = WKExtendedRuntimeSession()
//define and create the delegate
#State var delegate = WKDelegate()
var body: some Scene {
WindowGroup {
ContentView()
.onAppear{
//create a new session
session = WKExtendedRuntimeSession()
//assign the delegate
session.delegate = delegate
//start the session
session.start()
}
}
}
}
// define the delegate and its methods
class WKDelegate: NSObject, WKExtendedRuntimeSessionDelegate{
func extendedRuntimeSession(_ extendedRuntimeSession: WKExtendedRuntimeSession, didInvalidateWith reason: WKExtendedRuntimeSessionInvalidationReason, error: Error?) {
print(reason.rawValue)
}
func extendedRuntimeSessionDidStart(_ extendedRuntimeSession: WKExtendedRuntimeSession) {
print("did start")
}
func extendedRuntimeSessionWillExpire(_ extendedRuntimeSession: WKExtendedRuntimeSession) {
print("will expire")
}
}

Related

How to extend WKExtendedRuntimeSession

Thank you for taking a look at this page. I am developing a Watch App and would like to do some background data processing in SwiftUI. However, the background stops after an hour at the most. how can I make it run for more than an hour?
#main
struct TestWatchOSOnly_Watch_AppApp: App {
#State var session = WKExtendedRuntimeSession()
//define and create the delegate
#State var delegate = WKDelegate()
var body: some Scene {
WindowGroup {
ContentView()
.onAppear{
//create a new session
session = WKExtendedRuntimeSession()
//assign the delegate
session.delegate = delegate
//start the session
session.start()
}
}
}
}
// define the delegate and its methods
class WKDelegate: NSObject, WKExtendedRuntimeSessionDelegate{
func extendedRuntimeSession(_ extendedRuntimeSession: WKExtendedRuntimeSession, didInvalidateWith reason: WKExtendedRuntimeSessionInvalidationReason, error: Error?) {
print(reason.rawValue)
}
func extendedRuntimeSessionDidStart(_ extendedRuntimeSession: WKExtendedRuntimeSession) {
print("did start")
}
func extendedRuntimeSessionWillExpire(_ extendedRuntimeSession: WKExtendedRuntimeSession) {
print("will expire")
}
}
It is a private application for work and will not be made public. Also, I know that there is no mention of this in the official Apple documentation. Please let me know what possible ways to go about this.

Admob rewarded ad won't load

I have build a SwiftUI .sheet() that appears so the user can see their statistics and can click on a button that then will show a rewarded video to give the user some extra points. The code is as follows:
import SwiftUI
import GoogleMobileAds
struct Scoresheet: View {
#State private var rewardedadd = ViewController()
var body: some View {
VStack {
// all sorts of content.
// if a user clicks on a specific button it will call func loadadd()
}
.onAppear {
rewardedadd.loadRewardedAd()
}
}
func loadadd() {
rewardedadd.show()
}
}
In another file I have the following:
import UIKit
import GoogleMobileAds
class ViewController: UIViewController, GADFullScreenContentDelegate {
var rewardedAd: GADRewardedAd?
func loadRewardedAd() {
let request = GADRequest()
GADRewardedAd.load(withAdUnitID:"ca-app-pub-3940256099942544/1712485313",
request: request,
completionHandler: { [self] ad, error in
if let error = error {
print("Failed to load rewarded ad with error: \(error.localizedDescription)")
return
}
rewardedAd = ad
print("Rewarded ad loaded.")
rewardedAd?.fullScreenContentDelegate = self
})
}
func show() {
if let ad = rewardedAd {
ad.present(fromRootViewController: self) {
let reward = ad.adReward
print("Reward received with currency \(reward.amount), amount \(reward.amount.doubleValue)")
// TODO: Reward the user.
}
} else {
print("Ad wasn't ready")
}
}
func ad(_ ad: GADFullScreenPresentingAd, didFailToPresentFullScreenContentWithError error: Error) {
print("Ad did fail to present full screen content. Error: \(error.localizedDescription)")
}
func adWillPresentFullScreenContent(_ ad: GADFullScreenPresentingAd) {
print("Ad will present full screen content.")
}
func adDidDismissFullScreenContent(_ ad: GADFullScreenPresentingAd) {
print("Ad did dismiss full screen content.")
}
}
I keep getting the following error:
Ad did fail to present full screen content. Error: The provided view controller is not being presented.
That is the error from func ad in class ViewController. Does anyone know what I can do to fix this issue?
---- edit ----
I have rebuild my viewcontroller class to the following:
import SwiftUI
import GoogleMobileAds
struct RewardedAd: UIViewRepresentable {
#State private var rewardedAd: GADRewardedAd
let adUnitId = "ca-app-pub-3940256099942544/1712485313"
func makeCoordinator() -> CoordinatorVideoAd {
return CoordinatorVideoAd()
}
func makeUIView(context: Context) -> GADRewardedAd {
let request = GADRequest()
rewardedAd.load(withAdUnitID: adUnitId,
request: request, completionHandler: { [self] ad, error in
if let error = error {
print("Failed to load rewarded ad with error: \(error.localizedDescription)")
return
}
rewardedAd = ad
print("Rewarded ad loaded.")
rewardedAd?.fullScreenContentDelegate = self
})
return rewardedAd
}
func updateUIView(_ uiView: GADRewardedAd, context: Context) { }
class CoordinatorVideoAd: NSObject, GADFullScreenContentDelegate {
func ad(_ ad: GADFullScreenPresentingAd, didFailToPresentFullScreenContentWithError error: Error) {
print("Ad did fail to present full screen content.")
}
func adWillPresentFullScreenContent(_ ad: GADFullScreenPresentingAd) {
print("Ad will present full screen content.")
}
func adDidDismissFullScreenContent(_ ad: GADFullScreenPresentingAd) {
print("Ad did dismiss full screen content.")
}
}
}
I new have two error's:
Type 'RewardedAd' does not conform to protocol 'UIViewRepresentable'
Static member 'load' cannot be used on instance of type 'GADRewardedAd'
How can I fix this?
I believe that's now how you present a UIKit component to a swiftUI. You should read it in the apple docs about how to do it.
Here's an example how to do it :
First, create a struct that inherit the UIViewControllerRepresentable protocol
it'll look like this
struct SomethingViewController: UIViewControllerRepresentable {
typealias UIViewControllerType = [enter your UIViewController type]
}
add the type, for example, here i;m creating a FakeViewController
typealias UIViewControllerType = FakeViewController
it'll generate the nescessary method
Last, just customize it whahtever you want
struct SomethingViewController: UIViewControllerRepresentable {
#Binding var colorIDX: Int
let colors: [UIColor] = [.red,.blue,.green]
func makeUIViewController(context: Context) -> FakeViewController {
//Construct your ViewController
let viewController = FakeViewController()
viewController.bgColor = .systemPink
return viewController
}
func updateUIViewController(_ uiViewController: FakeViewController, context: Context) {
// Update your ViewController
uiViewController.view.backgroundColor = colors[colorIDX]
}
}
and setup in the SwiftUIView to look like this (depending on how do you want it, navigate to UIViewController or present it)
struct ContentView: View {
#State var isShow: Bool = false
#State var colorIDX: Int = 0
var body: some View {
VStack {
Button("show") {
if colorIDX >= 2 {
colorIDX = 0
} else {
colorIDX += 1
}
isShow.toggle()
}
}.sheet(isPresented: $isShow) {
} content: {
SomethingViewController(colorIDX: $colorIDX)
}
}
}
you see, every time i update something in the swiftUI View, it updates my ViewController and everytime I push the button, it'll present the ViewController
if you want to use a navigationController, just use it like any other swiftUI component/views
And that;s how you use a UIViewController in SwiftUI

How can I update the view when the app is done scanning for NFC?

I have a Swift application that reads from an NFC card. I want it to show the data it read on the screen, which I am able to do if I have a button that checks for updated data using the getDetected() function. I want to, however, update the view when it is done reading the NFC tag so I can immediately display the data. How can I do this?
NFC Reader class:
import Foundation
import CoreNFC
class NFCReader: NSObject, NFCNDEFReaderSessionDelegate {
var detected = [NFCNDEFMessage]()
var session: NFCNDEFReaderSession?
func beginScanning() {
guard NFCNDEFReaderSession.readingAvailable else { return }
session = NFCNDEFReaderSession(delegate: self, queue: nil, invalidateAfterFirstRead: true)
session?.alertMessage = "Hold your iPhone near the reader to unlock."
session?.begin()
}
func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
self.detected = messages
self.session = nil
}
func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
self.session = nil
}
func getDetected() -> [NFCNDEFMessage] {
return detected
}
}
you could try the following approach, using a ObservableObject.
Whenever the #Published var detected is changed, the UI will be updated.
class NFCReader: NSObject, NFCNDEFReaderSessionDelegate, ObservableObject { // <--- here
#Published var detected = [NFCNDEFMessage]() // <--- here
var session: NFCNDEFReaderSession?
func beginScanning() {
guard NFCNDEFReaderSession.readingAvailable else { return }
session = NFCNDEFReaderSession(delegate: self, queue: nil, invalidateAfterFirstRead: true)
session?.alertMessage = "Hold your iPhone near the reader to unlock."
session?.begin()
}
func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
self.detected = messages
self.session = nil
}
func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
self.session = nil
}
// no real need for this
func getDetected() -> [NFCNDEFMessage] {
return detected
}
}
struct ContentView: View {
#StateObject var readerNFC = NFCReader() // <-- here
var body: some View {
ForEach(readerNFC.detected, id: \.self) { msg in
// .....
}
}
}

Using UIApplicationDelegateAdaptor to get callbacks from userDidAcceptCloudKitShareWith not working

I'm trying to get notified when userDidAcceptCloudKitShareWith gets called. Traditionally this was called in the App Delegate but since I am building an iOS 14+ using App as my root object. I couldn't find any documentation out yet as far as how to add userDidAcceptCloudKitShareWith to my App class, so I am using UIApplicationDelegateAdaptor to use an App Delegate class, however it doesn't seem like userDidAcceptCloudKitShareWith is ever getting called?
import SwiftUI
import CloudKit
// Our observable object class
class ShareDataStore: ObservableObject {
static let shared = ShareDataStore()
#Published var didRecieveShare = false
#Published var shareInfo = ""
}
#main
struct SocialTestAppApp: App {
#StateObject var shareDataStore = ShareDataStore.shared
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView().environmentObject(shareDataStore)
}
}
}
class AppDelegate: NSObject, UIApplicationDelegate {
let container = CKContainer(identifier: "iCloud.com.TestApp")
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
print("did finish launching called")
return true
}
func application(_ application: UIApplication, userDidAcceptCloudKitShareWith cloudKitShareMetadata: CKShare.Metadata) {
print("delegate callback called!! ")
acceptShare(metadata: cloudKitShareMetadata) { result in
switch result {
case .success(let recordID):
print("successful share!")
ShareDataStore.shared.didRecieveShare = true
ShareDataStore.shared.shareInfo = recordID.recordName
case .failure(let error):
print("failure in share = \(error)")
}
} }
func acceptShare(metadata: CKShare.Metadata,
completion: #escaping (Result<CKRecord.ID, Error>) -> Void) {
// Create a reference to the share's container so the operation
// executes in the correct context.
let container = CKContainer(identifier: metadata.containerIdentifier)
// Create the operation using the metadata the caller provides.
let operation = CKAcceptSharesOperation(shareMetadatas: [metadata])
var rootRecordID: CKRecord.ID!
// If CloudKit accepts the share, cache the root record's ID.
// The completion closure handles any errors.
operation.perShareCompletionBlock = { metadata, share, error in
if let _ = share, error == nil {
rootRecordID = metadata.rootRecordID
}
}
// If the operation fails, return the error to the caller.
// Otherwise, return the record ID of the share's root record.
operation.acceptSharesCompletionBlock = { error in
if let error = error {
completion(.failure(error))
} else {
completion(.success(rootRecordID))
}
}
// Set an appropriate QoS and add the operation to the
// container's queue to execute it.
operation.qualityOfService = .utility
container.add(operation)
}
}
Updated based on Asperi's Answer:
import SwiftUI
import CloudKit
class ShareDataStore: ObservableObject {
static let shared = ShareDataStore()
#Published var didRecieveShare = false
#Published var shareInfo = ""
}
#main
struct athlyticSocialTestAppApp: App {
#StateObject var shareDataStore = ShareDataStore.shared
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
let sceneDelegate = MySceneDelegate()
var body: some Scene {
WindowGroup {
ContentView().environmentObject(shareDataStore)
.withHostingWindow { window in
sceneDelegate.originalDelegate = window.windowScene.delegate
window.windowScene.delegate = sceneDelegate
}
}
}
}
class MySceneDelegate: NSObject, UIWindowSceneDelegate {
let container = CKContainer(identifier: "iCloud.com...")
var originalDelegate: UIWindowSceneDelegate?
var window: UIWindow?
func sceneWillEnterForeground(_ scene: UIScene) {
print("scene is active")
}
func sceneWillResignActive(_ scene: UIScene) {
print("scene will resign active")
}
// forward all other UIWindowSceneDelegate/UISceneDelegate callbacks to original, like
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
originalDelegate?.scene!(scene, willConnectTo: session, options: connectionOptions)
}
func windowScene(_ windowScene: UIWindowScene, userDidAcceptCloudKitShareWith cloudKitShareMetadata: CKShare.Metadata) {
print("delegate callback called!! ")
acceptShare(metadata: cloudKitShareMetadata) { result in
switch result {
case .success(let recordID):
print("successful share!")
ShareDataStore.shared.didRecieveShare = true
ShareDataStore.shared.shareInfo = recordID.recordName
case .failure(let error):
print("failure in share = \(error)")
}
}
}
}
extension View {
func withHostingWindow(_ callback: #escaping (UIWindow?) -> Void) -> some View {
self.background(HostingWindowFinder(callback: callback))
}
}
struct HostingWindowFinder: UIViewRepresentable {
var callback: (UIWindow?) -> ()
func makeUIView(context: Context) -> UIView {
let view = UIView()
DispatchQueue.main.async { [weak view] in
self.callback(view?.window)
}
return view
}
func updateUIView(_ uiView: UIView, context: Context) {
}
}
In Scene-based application the userDidAcceptCloudKitShareWith callback is posted to Scene delegate, but in SwiftUI 2.0 App-based application the scene delegate is used by SwiftUI itself to provide scenePhase events, but does not provide native way to handle topic callback.
The possible approach to solve this is to find a window and inject own scene delegate wrapper, which will handle userDidAcceptCloudKitShareWith and forward others to original SwiftUI delegate (to keep standard SwiftUI events working).
Here is a couple of demo snapshots based on https://stackoverflow.com/a/63276688/12299030 window access (however you can use any other preferable way to get window)
#main
struct athlyticSocialTestAppApp: App {
#StateObject var shareDataStore = ShareDataStore.shared
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
let sceneDelegate = MySceneDelegate()
var body: some Scene {
WindowGroup {
ContentView().environmentObject(shareDataStore)
.withHostingWindow { window in
sceneDelegate.originalDelegate = window?.windowScene.delegate
window?.windowScene.delegate = sceneDelegate
}
}
}
}
class MySceneDelegate : NSObject, UIWindowSceneDelegate {
var originalDelegate: UISceneDelegate?
func windowScene(_ windowScene: UIWindowScene, userDidAcceptCloudKitShareWith cloudKitShareMetadata: CKShareMetadata) {
// your code here
}
// forward all other UIWindowSceneDelegate/UISceneDelegate callbacks to original, like
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
originalDelegate?.scene(scene, willConnectTo: session, options: connectionOptions)
}
}
Check out this question that has a lot of useful things to check across several possible answers:
CloudKit CKShare userDidAcceptCloudKitShareWith Never Fires on Mac App
Be sure to add the CKSharingSupported key to your info.plist, and then try putting the userDidAcceptCloudKitShareWith in different places using the answers in the above link (where you put it will depend on what kind of app you're building).

How do I use the Google Mobile Ads SDK in SwiftUI, or use the UIKit UIViewController within a SwiftUI view?

I have a SwiftUI view that I want to open a rewarded ad from the Google Mobile Ads SDK when I press a button. The instructions for loading the ads (https://developers.google.com/admob/ios/rewarded-ads#create_rewarded_ad) are in UIKit, and I'm struggling to use them in my SwiftUI app. Is there a way to load the ads using SwiftUI, or if I use UIKit, how do I integrate it into SwiftUI?
This is the SwiftUI parent view:
struct AdMenu: View {
var body: some View {
NavigationView {
NavigationLink(destination: Ads())
{
Text("Watch Ad")
}
}
}
}
I don't know UIKit, but I think this is the code I want to use in SwiftUI:
class ViewController: UIViewController, GADRewardedAdDelegate {
var rewardedAd: GADRewardedAd?
var adRequestInProgress = false
#IBAction func doSomething(sender: UIButton) {
if rewardedAd?.isReady == true {
rewardedAd?.present(fromRootViewController: self, delegate:self)
}else {
let alert = UIAlertController(
title: "Rewarded video not ready",
message: "The rewarded video didn't finish loading or failed to load",
preferredStyle: .alert)
let alertAction = UIAlertAction(
title: "OK",
style: .cancel,
handler: { [weak self] action in
// redirect to AdMenu SwiftUI view somehow?
})
alert.addAction(alertAction)
self.present(alert, animated: true, completion: nil)
}
}
func createAndLoadRewardedAd() {
rewardedAd = GADRewardedAd(adUnitID: "ca-app-pub-3940256099942544/1712485313")
adRequestInProgress = true
rewardedAd?.load(GADRequest()) { error in
self.adRequestInProgress = false
if let error = error {
print("Loading failed: \(error)")
} else {
print("Loading Succeeded")
}
}
return rewardedAd
}
// Tells the delegate that the user earned a reward
func rewardedAd(_ rewardedAd: GADRewardedAd, userDidEarn reward: GADAdReward) {
print("Reward received with currency: \(reward.type), amount \(reward.amount).")
}
// Tells the delegate that the rewarded ad was presented
func rewardedAdDidPresent(_ rewardedAd: GADRewardedAd) {
print("Rewarded ad presented.")
}
// Tells the delegate that the rewarded ad was dismissed
func rewardedAdDidDismiss(_ rewardedAd: GADRewardedAd) {
print("Rewarded ad dismissed.")
}
// Tells the delegate that the rewarded ad failed to present
func rewardedAd(_ rewardedAd: GADRewardedAd, didFailToPresentWithError error: Error) {
rewardedAd = createAndLoadRewardedAd()
print("Rewarded ad failed to present.")
}
override func viewDidLoad() {
super.viewDidLoad()
if !adRequestInProgress && !(rewardedAd?.isReady ?? false) {
rewardedAd = createAndLoadRewardedAd()
}
I have used a very simple approach. I have a delegate class which loads the rewarded Ad and informs the view that its loaded, hence the view presents it. When user watches the full Ad, same delegate gets the success callback and it informs the view about it.
The code for delegate looks like:-
class RewardedAdDelegate: NSObject, GADRewardedAdDelegate, ObservableObject {
#Published var adLoaded: Bool = false
#Published var adFullyWatched: Bool = false
var rewardedAd: GADRewardedAd? = nil
func loadAd() {
rewardedAd = GADRewardedAd(adUnitID: "ca-app-pub-3940256099942544/1712485313")
rewardedAd!.load(GADRequest()) { error in
if error != nil {
self.adLoaded = false
} else {
self.adLoaded = true
}
}
}
/// Tells the delegate that the user earned a reward.
func rewardedAd(_ rewardedAd: GADRewardedAd, userDidEarn reward: GADAdReward) {
adFullyWatched = true
}
/// Tells the delegate that the rewarded ad was presented.
func rewardedAdDidPresent(_ rewardedAd: GADRewardedAd) {
self.adLoaded = false
}
/// Tells the delegate that the rewarded ad was dismissed.
func rewardedAdDidDismiss(_ rewardedAd: GADRewardedAd) {}
/// Tells the delegate that the rewarded ad failed to present.
func rewardedAd(_ rewardedAd: GADRewardedAd, didFailToPresentWithError error: Error) {}
}
Now you need a view to initiate and present the RewardedAd:-
struct RewardedAd: View {
#ObservedObject var adDelegate = RewardedAdDelegate()
var body: some View {
if adDelegate.adLoaded && !adDelegate.adFullyWatched {
let root = UIApplication.shared.windows.first?.rootViewController
self.adDelegate.rewardedAd!.present(fromRootViewController: root!, delegate: adDelegate)
}
return Text("Load ad").onTapGesture {
self.adDelegate.loadAd()
}
}
}
Explanation:-
In the above view when user taps on Load Ad, we initiate the loading and then the delegate updates the Published Boolean. This informs our view that ad is Loaded and we call:-
let root = UIApplication.shared.windows.first?.rootViewController
self.adDelegate.rewardedAd!.present(fromRootViewController: root!, delegate: adDelegate)
The Ad is now playing on screen and if user watches is completely, the delegate will get a success callback and it'll update another Published Boolean. This will inform us that we need to reward the user now (you can add your way to handle/reward the user).
I figured out a way how to nicely translate the objective-c code from the AdMob page to something you can easily use in SwiftUI.
final class RewardedAd {
private let rewardId = "ca-app-pub-3940256099942544/1712485313" // TODO: replace this with your own Ad ID
var rewardedAd: GADRewardedAd?
init() {
load()
}
func load(){
let request = GADRequest()
// add extras here to the request, for example, for not presonalized Ads
GADRewardedAd.load(withAdUnitID: rewardId, request: request, completionHandler: {rewardedAd, error in
if error != nil {
// loading the rewarded Ad failed :(
return
}
self.rewardedAd = rewardedAd
})
}
func showAd(rewardFunction: #escaping () -> Void) -> Bool {
guard let rewardedAd = rewardedAd else {
return false
}
guard let root = UIApplication.shared.keyWindowPresentedController else {
return false
}
rewardedAd.present(fromRootViewController: root, userDidEarnRewardHandler: rewardFunction)
return true
}
}
I also added an extension to get the root view easier:
extension UIApplication {
var keyWindow: UIWindow? {
return UIApplication.shared.connectedScenes
.filter { $0.activationState == .foregroundActive }
.first(where: { $0 is UIWindowScene })
.flatMap({ $0 as? UIWindowScene })?.windows
.first(where: \.isKeyWindow)
}
var keyWindowPresentedController: UIViewController? {
var viewController = self.keyWindow?.rootViewController
if let presentedController = viewController as? UITabBarController {
viewController = presentedController.selectedViewController
}
while let presentedController = viewController?.presentedViewController {
if let presentedController = presentedController as? UITabBarController {
viewController = presentedController.selectedViewController
} else {
viewController = presentedController
}
}
return viewController
}
}
If you add that to your project you can just call it like this in your SwiftUI code:
var rewardAd: RewardedAd
init(){
self.rewardAd = RewardedAd()
rewardAd.load()
}
var body: some View {
Button(action: {
self.rewardAd.showAd(rewardFunction: {
// TODO: give the user a reward for watching
self.collectedDiamonds += 1
})
}) {
Text("Watch Ad - Earn 💎")
.foregroundColor(.white)
.padding()
}
.background(Capsule().fill(Color.blue))
}