SwiftUI - AdMob Interstitial ad crashes only on iOS 16 - ios

I have a view in which there is an interstitial ad that gets presented. On iOS 15 everything works fine, but on iOS 16 the app crashes with the following error:
SwiftUI/UIViewControllerRepresentable.swift:332: Fatal error:
UIViewControllerRepresentables must be value types: InterstitialAdView
2022-09-22 09:33:06.740743+0200 HowRich[47572:2135353]
SwiftUI/UIViewControllerRepresentable.swift:332: Fatal error:
UIViewControllerRepresentables must be value types: InterstitialAdView
(lldb)
And where there's the #main I get this error:
The code is the following:
InterstitialAdsManager.swift
import GoogleMobileAds
import SwiftUI
import UIKit
class InterstitialAd: NSObject {
var interstitialAd: GADInterstitialAd?
static let shared = InterstitialAd()
func loadAd(withAdUnitId id: String) {
let req = GADRequest()
GADInterstitialAd.load(withAdUnitID: id, request: req) { interstitialAd, err in
if let err = err {
print("Failed to load ad with error: \(err)")
return
}
self.interstitialAd = interstitialAd
}
}
}
final class InterstitialAdView: NSObject, UIViewControllerRepresentable, GADFullScreenContentDelegate {
let interstitialAd = InterstitialAd.shared.interstitialAd
#Binding var isPresented: Bool
var adUnitId: String
init(isPresented: Binding<Bool>, adUnitId: String) {
self._isPresented = isPresented
self.adUnitId = adUnitId
super.init()
interstitialAd?.fullScreenContentDelegate = self
}
func makeUIViewController(context: Context) -> UIViewController {
let view = UIViewController()
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1)) {
self.showAd(from: view)
}
return view
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
}
func showAd(from root: UIViewController) {
if let ad = interstitialAd {
ad.present(fromRootViewController: root)
} else {
print("Ad not ready")
self.isPresented.toggle()
}
}
func adDidDismissFullScreenContent(_ ad: GADFullScreenPresentingAd) {
InterstitialAd.shared.loadAd(withAdUnitId: adUnitId)
isPresented.toggle()
}
}
struct FullScreenModifier<Parent: View>: View {
#Binding var isPresented: Bool
var adUnitId: String
var parent: Parent
var body: some View {
ZStack {
parent
if isPresented {
EmptyView()
.edgesIgnoringSafeArea(.all)
InterstitialAdView(isPresented: $isPresented, adUnitId: adUnitId)
}
}
.onAppear {
InterstitialAd.shared.loadAd(withAdUnitId: adUnitId)
}
}
}
extension View {
public func presentInterstitialAd(isPresented: Binding<Bool>, adUnitId: String) -> some View {
FullScreenModifier(isPresented: isPresented, adUnitId: adUnitId, parent: self)
}
}
The View with the ad:
struct CheckoutView: View {
#State var showAd = false
var body: some View {
ScrollView {
ZStack {
VStack {
//The view
}
.onAppear {
if UserDefaults.standard.string(forKey: "AdCounter") == "0" && UserDefaults.standard.bool(forKey: "AdFree") == false {
showAd = true
UserDefaults.standard.setValue("1", forKey: "AdCounter")
}
UserDefaults.standard.setValue("0", forKey: "AdCounter")
}
.presentInterstitialAd(isPresented: $showAd, adUnitId: myIntersId)
}
}
}
I have updated all pods to the latest available versions (Google Mobile Ads SDK is version 9.11.0).
What's causing the crash on iOS 16 and how can I fix it? Thanks

#State var interstitial = InterstitialAd()
interstitial.showAd()
///InterstitialAd.swift
import GoogleMobileAds
class InterstitialAd: NSObject, GADFullScreenContentDelegate {
var interstitialAd: GADInterstitialAd?
var unitId: String = "your interstitial unitId"
override init() {
super.init()
loadAd()
}
func loadAd() {
let req = GADRequest()
req.scene = UIApplication.shared.connectedScenes.first as? UIWindowScene
GADInterstitialAd.load(withAdUnitID: unitId, request: req) { [self] interstitialAd, err in
if let err = err {
print("Failed to load ad with error: \(err)")
}
self.interstitialAd = interstitialAd
self.interstitialAd?.fullScreenContentDelegate = self
}
}
//Presents the ad if it can, otherwise dismisses so the user's experience is not interrupted
func showAd() {
if let ad = interstitialAd, let root = UIApplication.shared.windows.first?.rootViewController {
ad.present(fromRootViewController: root)
} else {
print("Ad not ready")
self.loadAd()
}
}
func adDidDismissFullScreenContent(_ ad: GADFullScreenPresentingAd) {
//Prepares another ad for the next time view presented
self.loadAd()
print("Ad Closed")
}
func ad(_ ad: GADFullScreenPresentingAd, didFailToPresentFullScreenContentWithError error: Error) {
print("Ad Error")
self.loadAd()
}
}

The solution is to change
final class InterstitialAdView: NSObject, UIViewControllerRepresentable, GADFullScreenContentDelegate {
into
struct InterstitialAdView: NSObject, UIViewControllerRepresentable, GADFullScreenContentDelegate {

Related

Integrating Persona SDK in a SwiftUI View

I'm trying it integrate the Person SDK v2 in a SwiftUI view. It's setup for UIKit to present from a specific UIViewController. Here is my code.
https://docs.withpersona.com/docs/ios-sdk-v2-integration-guide
I'm not sure how to call my present function from SwiftUI. The SDK is setup so when you create that Inquiry object it triggers it's nav to present on the view controller.
struct PersonaInquiry: UIViewControllerRepresentable {
private var viewController = UIViewController()
private var coordinator = Coordinator()
class Coordinator: NSObject, InquiryDelegate {
func inquiryComplete(inquiryId: String, status: String, fields: [String : Persona2.InquiryField]) {
}
func inquiryCanceled(inquiryId: String?, sessionToken: String?) {
}
func inquiryError(_ error: Error) {
}
}
func makeUIViewController(context: Context) -> UIViewController {
return viewController
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
}
func present(templateId: String) {
let config = InquiryConfiguration(templateId: templateId)
// Create the inquiry with the view controller
// as the delegate and presenter.
Inquiry(config: config, delegate: coordinator).start(from: viewController)
}
func makeCoordinator() -> Coordinator {
return coordinator
}
}
struct PersonaInquiry_Previews: PreviewProvider {
static var previews: some View {
PersonaInquiry()
}
}
Here's an example
ContentView:
struct ContentView: View {
#State private var isPresentingSDK = false
#State private var message = ""
var body: some View {
VStack {
Button(
action: {
isPresentingSDK.toggle()
},
label: {
Text("Launch Inquiry from SwiftUI 🚀")
.foregroundColor(Color.white)
.padding()
}
)
.buttonStyle(.borderedProminent)
.buttonBorderShape(.capsule)
.fullScreenCover(
isPresented: $isPresentingSDK,
onDismiss: {
// Do nothing
},
content: {
InquirySDKWrapper(
inquiryComplete: { inquiryId, status, fields in
self.message = """
Inquiry Complete
Inquiry ID: \(inquiryId)
Status: \(String(describing: status))
"""
},
inquiryCanceled: { inquiryId, sessionToken in
self.message = "🤷‍♀️ Inquiry Cancelled"
},
inquiryErrored: { error in
self.message = """
💀 Inquiry Error
\(error.localizedDescription)
"""
}
)
}
)
Text(message)
.multilineTextAlignment(.center)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
InquirySDKWrapper:
import Persona2
import SwiftUI
import UIKit
struct InquirySDKWrapper: UIViewControllerRepresentable {
/// The wrapper VC presents the SDK and acts as its delegate.
/// The delegate methods in turn call the callbacks in the WrapperDelegate
private let wrapperVC: WrapperViewController
/// Pass in the callbacks for each delegate method
init(
inquiryComplete: #escaping (String, String, [String: InquiryField]) -> Void,
inquiryCanceled: #escaping (_ inquiryId: String?, _ sessionToken: String?) -> Void,
inquiryErrored: #escaping (_ error: Error) -> Void
) {
wrapperVC = WrapperViewController()
wrapperVC.inquiryComplete = inquiryComplete
wrapperVC.inquiryCanceled = inquiryCanceled
wrapperVC.inquiryErrored = inquiryErrored
}
func makeUIViewController(context: Context) -> some UIViewController {
return wrapperVC
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
// Do nothing
}
}
final class WrapperViewController: UIViewController {
private var isPresenting = false
// The callbacks
var inquiryComplete: ((_ inquiryId: String, _ status: String, _ fields: [String: InquiryField]) -> Void)!
var inquiryCanceled: ((_ inquiryId: String?, _ sessionToken: String?) -> Void)!
var inquiryErrored: ((_ error: Error) -> Void)!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Otherwise this would trigger once the SDK exits too
guard !isPresenting else { return }
let inquiry = Inquiry(
config: InquiryConfiguration(
templateId: "YOUR TEMPLATE ID HERE"
),
delegate: self
)
inquiry.start(from: self)
isPresenting = true
}
}
extension WrapperViewController: InquiryDelegate {
func inquiryComplete(inquiryId: String, status: String, fields: [String: InquiryField]) {
inquiryComplete(inquiryId, status, fields)
dismiss(animated: true, completion: nil)
}
func inquiryCanceled(inquiryId: String?, sessionToken: String?) {
inquiryCanceled(inquiryId, sessionToken)
dismiss(animated: true, completion: nil)
}
func inquiryError(_ error: Error) {
inquiryErrored(error)
dismiss(animated: true, completion: nil)
}
}

Sheet and modal UIKit and SwiftUI [duplicate]

I have been trying to use EKEventEditViewController with SwiftUI. I tried to follow some suggestions from dev forums as to how to go about adding it with the help of a UIViewControllerRepresentable wrapper with a Coordinator. I am adding the code below.
The EKEventEditViewController is presented correctly but the problem I'm facing is that only some of the fields are editable. I'm attaching a gif showing the interactions.
Has anyone faced this issue ?
Here is the code:
import Foundation
import SwiftUI
import EventKitUI
let eventStore = EKEventStore()
struct NewEventGenerator: UIViewControllerRepresentable {
typealias UIViewControllerType = EKEventEditViewController
#Binding var isShowing: Bool
var theEvent = EKEvent.init(eventStore: eventStore)
func makeUIViewController(context: UIViewControllerRepresentableContext<NewEventGenerator>) -> EKEventEditViewController {
let controller = EKEventEditViewController()
controller.event = theEvent
controller.eventStore = eventStore
controller.editViewDelegate = context.coordinator
return controller
}
func updateUIViewController(_ uiViewController: NewEventGenerator.UIViewControllerType, context: UIViewControllerRepresentableContext<NewEventGenerator>) {
uiViewController.view.backgroundColor = .red
}
func makeCoordinator() -> Coordinator {
return Coordinator(isShowing: $isShowing)
}
class Coordinator : NSObject, UINavigationControllerDelegate, EKEventEditViewDelegate {
#Binding var isVisible: Bool
init(isShowing: Binding<Bool>) {
_isVisible = isShowing
}
func eventEditViewController(_ controller: EKEventEditViewController, didCompleteWith action: EKEventEditViewAction) {
switch action {
case .canceled:
isVisible = false
case .saved:
do {
try controller.eventStore.save(controller.event!, span: .thisEvent, commit: true)
}
catch {
print("Event couldn't be created")
}
isVisible = false
case .deleted:
isVisible = false
#unknown default:
isVisible = false
}
}
}}
Works fine with Xcode 12 / iOS 14. Literally copy-pasted your code added requestAccess & descriptions in Info.plist.
Full tested module, for the case if something might be helpful.
import SwiftUI
import EventKitUI
let eventStore = EKEventStore()
struct NewEventGenerator: UIViewControllerRepresentable {
typealias UIViewControllerType = EKEventEditViewController
#Binding var isShowing: Bool
var theEvent: EKEvent
init(isShowing: Binding<Bool>) {
eventStore.requestAccess(to: .event) { allow, error in
print("Result: \(allow) or [\(error.debugDescription)]")
}
theEvent = EKEvent.init(eventStore: eventStore)
_isShowing = isShowing
}
func makeUIViewController(context: UIViewControllerRepresentableContext<NewEventGenerator>) -> EKEventEditViewController {
let controller = EKEventEditViewController()
controller.event = theEvent
controller.eventStore = eventStore
controller.editViewDelegate = context.coordinator
return controller
}
func updateUIViewController(_ uiViewController: NewEventGenerator.UIViewControllerType, context: UIViewControllerRepresentableContext<NewEventGenerator>) {
uiViewController.view.backgroundColor = .red
}
func makeCoordinator() -> Coordinator {
return Coordinator(isShowing: $isShowing)
}
class Coordinator : NSObject, UINavigationControllerDelegate, EKEventEditViewDelegate {
#Binding var isVisible: Bool
init(isShowing: Binding<Bool>) {
_isVisible = isShowing
}
func eventEditViewController(_ controller: EKEventEditViewController, didCompleteWith action: EKEventEditViewAction) {
switch action {
case .canceled:
isVisible = false
case .saved:
do {
try controller.eventStore.save(controller.event!, span: .thisEvent, commit: true)
}
catch {
print("Event couldn't be created")
}
isVisible = false
case .deleted:
isVisible = false
#unknown default:
isVisible = false
}
}
}}
struct TestEventKitViewInSheet: View { // just created in ContentView body
#State private var showIt = false
var body: some View {
Button("Events") { showIt = true }
.sheet(isPresented: $showIt) {
NewEventGenerator(isShowing: $showIt)
}
}
}

How do I get information about the phone version and phone model? SwiftUI

I have a code that allows me to send an email message from the application. How to get phone model and IOS version data .. I am a new user at SwiftUi, would appreciate any help.
See the example in the screenshot
Example in the picture
Here is the code I have
import Foundation
import SwiftUI
import MessageUI
struct MailView: View {
#State private var showingMail = false
var body: some View {
VStack {
Button("Open Mail") {
self.showingMail.toggle()
}
}
.sheet(isPresented: $showingMail) {
MailComposeViewController(toRecipients: [""], mailBody: "Here is mail body") {
// Did finish action
}
}
}
}
struct MailComposeViewController: UIViewControllerRepresentable {
var toRecipients: [String]
var mailBody: String
var didFinish: ()->()
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<MailComposeViewController>) -> MFMailComposeViewController {
let mail = MFMailComposeViewController()
mail.mailComposeDelegate = context.coordinator
mail.setToRecipients(self.toRecipients)
mail.setMessageBody(self.mailBody, isHTML: true)
return mail
}
final class Coordinator: NSObject, MFMailComposeViewControllerDelegate {
var parent: MailComposeViewController
init(_ mailController: MailComposeViewController) {
self.parent = mailController
}
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
parent.didFinish()
controller.dismiss(animated: true)
}
}
func updateUIViewController(_ uiViewController: MFMailComposeViewController, context: UIViewControllerRepresentableContext<MailComposeViewController>) {
}
}
You can do that like this
struct ContentView: View {
var systemVersion = UIDevice.current.systemVersion
var device = UIDevice.current.name
var body: some View {
VStack {
Text("iOS Version: \(systemVersion)")
Text("Device: \(device)")
}
}
}
https://developer.apple.com/documentation/uikit/uidevice

SwiftUI EKEventEditViewController fields not editable

I have been trying to use EKEventEditViewController with SwiftUI. I tried to follow some suggestions from dev forums as to how to go about adding it with the help of a UIViewControllerRepresentable wrapper with a Coordinator. I am adding the code below.
The EKEventEditViewController is presented correctly but the problem I'm facing is that only some of the fields are editable. I'm attaching a gif showing the interactions.
Has anyone faced this issue ?
Here is the code:
import Foundation
import SwiftUI
import EventKitUI
let eventStore = EKEventStore()
struct NewEventGenerator: UIViewControllerRepresentable {
typealias UIViewControllerType = EKEventEditViewController
#Binding var isShowing: Bool
var theEvent = EKEvent.init(eventStore: eventStore)
func makeUIViewController(context: UIViewControllerRepresentableContext<NewEventGenerator>) -> EKEventEditViewController {
let controller = EKEventEditViewController()
controller.event = theEvent
controller.eventStore = eventStore
controller.editViewDelegate = context.coordinator
return controller
}
func updateUIViewController(_ uiViewController: NewEventGenerator.UIViewControllerType, context: UIViewControllerRepresentableContext<NewEventGenerator>) {
uiViewController.view.backgroundColor = .red
}
func makeCoordinator() -> Coordinator {
return Coordinator(isShowing: $isShowing)
}
class Coordinator : NSObject, UINavigationControllerDelegate, EKEventEditViewDelegate {
#Binding var isVisible: Bool
init(isShowing: Binding<Bool>) {
_isVisible = isShowing
}
func eventEditViewController(_ controller: EKEventEditViewController, didCompleteWith action: EKEventEditViewAction) {
switch action {
case .canceled:
isVisible = false
case .saved:
do {
try controller.eventStore.save(controller.event!, span: .thisEvent, commit: true)
}
catch {
print("Event couldn't be created")
}
isVisible = false
case .deleted:
isVisible = false
#unknown default:
isVisible = false
}
}
}}
Works fine with Xcode 12 / iOS 14. Literally copy-pasted your code added requestAccess & descriptions in Info.plist.
Full tested module, for the case if something might be helpful.
import SwiftUI
import EventKitUI
let eventStore = EKEventStore()
struct NewEventGenerator: UIViewControllerRepresentable {
typealias UIViewControllerType = EKEventEditViewController
#Binding var isShowing: Bool
var theEvent: EKEvent
init(isShowing: Binding<Bool>) {
eventStore.requestAccess(to: .event) { allow, error in
print("Result: \(allow) or [\(error.debugDescription)]")
}
theEvent = EKEvent.init(eventStore: eventStore)
_isShowing = isShowing
}
func makeUIViewController(context: UIViewControllerRepresentableContext<NewEventGenerator>) -> EKEventEditViewController {
let controller = EKEventEditViewController()
controller.event = theEvent
controller.eventStore = eventStore
controller.editViewDelegate = context.coordinator
return controller
}
func updateUIViewController(_ uiViewController: NewEventGenerator.UIViewControllerType, context: UIViewControllerRepresentableContext<NewEventGenerator>) {
uiViewController.view.backgroundColor = .red
}
func makeCoordinator() -> Coordinator {
return Coordinator(isShowing: $isShowing)
}
class Coordinator : NSObject, UINavigationControllerDelegate, EKEventEditViewDelegate {
#Binding var isVisible: Bool
init(isShowing: Binding<Bool>) {
_isVisible = isShowing
}
func eventEditViewController(_ controller: EKEventEditViewController, didCompleteWith action: EKEventEditViewAction) {
switch action {
case .canceled:
isVisible = false
case .saved:
do {
try controller.eventStore.save(controller.event!, span: .thisEvent, commit: true)
}
catch {
print("Event couldn't be created")
}
isVisible = false
case .deleted:
isVisible = false
#unknown default:
isVisible = false
}
}
}}
struct TestEventKitViewInSheet: View { // just created in ContentView body
#State private var showIt = false
var body: some View {
Button("Events") { showIt = true }
.sheet(isPresented: $showIt) {
NewEventGenerator(isShowing: $showIt)
}
}
}

FirebaseUI and SwiftUI LoginG

I´m trying to implement the firebaseUI login in an iOS app using swiftUI.
I´m able to use the main login view, but i can not control the flow after the user was loged on.
This is the code of my "login controller"
import SwiftUI
import FirebaseUI
struct CustomLogin: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIViewController {
let authUI = FUIAuth.defaultAuthUI()
let providers: [FUIAuthProvider] = [
FUIEmailAuth(),
FUIFacebookAuth()
// FUIPhoneAuth(authUI:FUIAuth.defaultAuthUI()),
]
authUI?.providers = providers
let authViewController = authUI?.authViewController()
// let controller = UIViewController()
// authUI!.delegate = self
return authViewController!
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
}
}
struct CustomLogin_Previews: PreviewProvider {
static var previews: some View {
CustomLogin()
}
}
I can show the login flow by add CustomLogin())to my content view.
how can I manage the call backs after the user has logged on?
The user was created in firebase, but the view don´t update.
if someone has implemented a firebaseUI login with SwiftUI I appreciated it.
Thanks
I was able to implement something that worked based off of your code and some Apple tutorials. Basically, you need to add a coordinator to your UIViewControllerRepresentable. You can add your delegate to the coordinator.
Here's my scratch code that works:
import SwiftUI
import FirebaseUI
import Firebase
public var screenWidth: CGFloat {
return UIScreen.main.bounds.width
}
public var screenHeight: CGFloat {
return UIScreen.main.bounds.height
}
struct LoginView : View {
#State private var viewState = CGSize(width: 0, height: screenHeight)
#State private var MainviewState = CGSize.zero
var body : some View {
ZStack {
CustomLoginViewController { (error) in
if error == nil {
self.status()
}
}.offset(y: self.MainviewState.height).animation(.spring())
MainView().environmentObject(DataStore()).offset(y: self.viewState.height).animation(.spring())
}
}
func status() {
self.viewState = CGSize(width: 0, height: 0)
self.MainviewState = CGSize(width: 0, height: screenHeight)
}
}
struct LoginView_Previews : PreviewProvider {
static var previews : some View {
LoginView()
}
}
struct CustomLoginViewController : UIViewControllerRepresentable {
var dismiss : (_ error : Error? ) -> Void
func makeCoordinator() -> CustomLoginViewController.Coordinator {
Coordinator(self)
}
func makeUIViewController(context: Context) -> UIViewController
{
let authUI = FUIAuth.defaultAuthUI()
let providers : [FUIAuthProvider] = [
FUIEmailAuth(),
FUIGoogleAuth(),
FUIOAuth.appleAuthProvider()
]
authUI?.providers = providers
authUI?.delegate = context.coordinator
let authViewController = authUI?.authViewController()
return authViewController!
}
func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<CustomLoginViewController>)
{
}
//coordinator
class Coordinator : NSObject, FUIAuthDelegate {
var parent : CustomLoginViewController
init(_ customLoginViewController : CustomLoginViewController) {
self.parent = customLoginViewController
}
// MARK: FUIAuthDelegate
func authUI(_ authUI: FUIAuth, didSignInWith authDataResult: AuthDataResult?, error: Error?)
{
if let error = error {
parent.dismiss(error)
}
else {
parent.dismiss(nil)
}
}
func authUI(_ authUI: FUIAuth, didFinish operation: FUIAccountSettingsOperationType, error: Error?)
{
}
}
}

Resources