Integrating Persona SDK in a SwiftUI View - ios

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)
}
}

Related

How present view over any view in swiftui

I'm implementing a chat application and I have an incoming call
so I have presented an incoming call screen when user Accept I have implemented by the notification center
public extension Notification.Name {
static let incomingCallView = Notification.Name("incomingCallView")
}
struct ModalData {
let onDismiss: (() -> Void)?
let content: (Binding<Bool>) -> AnyView
init<Content: View>(onDismiss: (() -> Void)? = nil,
#ViewBuilder content: #escaping (Binding<Bool>) -> Content) {
self.onDismiss = onDismiss
self.content = { AnyView(content($0)) }
}
static let empty = ModalData { _ in EmptyView() }
}
and this in provider delegate
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
guard let call = callManager.callWithUUID(uuid: action.callUUID) else {
action.fail()
return
}
callOption.ringingPeriod?.invalidate()
callOption.ringingPeriod = nil
socket.inizalizeWebrtc(webRTCClient: WebRTCClient(iceServers: Config.default.webRTCIceServers))
socket.webRTCClient?.offer {(sdp) in
// self.hasLocalSdp = true
self.socket.sendSdp(sdp: sdp, type: "offer", callType: self.callOption.callType == .videoCall
? "video"
: "audio"
)
}
call.answer()
NotificationCenter.default.post(name: .incomingCallView,
object: ModalData(onDismiss: {
}) { isPresented in
IncomingCallScreen(callScreenIsPresented: isPresented)
})
action.fulfill()
}
the problem that I have to add .onReceive in all views to open the incoming call screen if I didn't add .onrecieve it will not present
.onReceive(NotificationCenter.default.publisher(for: .incomingCallView)) { notif in
if let data = notif.object as? ModalData {
modalData = data
incomingCallView = true
}
}
.fullScreenCover(isPresented: $incomingCallView,
onDismiss: modalData.onDismiss) {
modalData.content($incomingCallView)
}
How open it without adding .onReceive in all views is there another solution

SwiftUI - AdMob Interstitial ad crashes only on iOS 16

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 {

SwiftUI UIViewRepresentable with argument passed and delegate function

I am implementing a UIViewController with a ViewModel as an argument passed to the UIViewController, but I can't seem to make the delegate functions to work, what is the correct way of doing this?
CartView.swift
struct PaymentWrapper: UIViewControllerRepresentable {
typealias UIViewControllerType = CustomUIPaymentViewController
#ObservedObject var viewModel: CartViewModel
var vc: CustomUIPaymentViewController?
var foo: (String) -> Void
public init(viewModel: CartViewModel) {
self.viewModel = viewModel
self.vc = CustomUIPaymentViewController.init(token: self.viewModel.mtToken)
}
func makeUIViewController(context: Context) -> CustomUIPaymentViewController {
return vc!
}
func updateUIViewController(_ uiViewController: CustomUIPaymentViewController, context: Context) {
// code
}
func makeCoordinator() -> Coordinator {
Coordinator(vc: vc!, foo: foo)
}
class Coordinator: NSObject, CustomUIPaymentViewControllerDelegate, CustomUINavigationControllerDelegate {
var foo: (String) -> Void
init(vc: CustomUIPaymentViewController, foo: #escaping (String) -> Void) {
self.foo = foo
super.init()
vc.delegate = self
}
func paymentViewController(_ viewController: CustomUIPaymentViewController!, paymentFailed error: Error!) {
foo("FAILED")
}
func paymentViewController(_ viewController: CustomUIPaymentViewController!, paymentPending result: TransactionResult!) {
foo("PENDING")
}
func paymentViewController(_ viewController: CustomUIPaymentViewController!, paymentSuccess result: TransactionResult!) {
foo("SUCCESS")
}
func paymentViewController_paymentCanceled(_ viewController: CustomUIPaymentViewController!) {
foo("CANCEL")
}
//This delegate methods is added on ios sdk v1.16.4 to handle the new3ds flow
func paymentViewController(_ viewController: CustomUIPaymentViewController!, paymentDeny result: TransactionResult!) {
}
}
}
struct CartView: View {
#ObservedObject var viewModel = CartViewModel()
var body: some View {
VStack {
Header(title: "Cart", back: false)
}
.sheet(isPresented: $viewModel.showPayment) {
PaymentWrapper(viewModel: self.viewModel) { data in
print(data)
// This returns error "Extra trailing closure passed in call"
}
}
}
}
How do I get the delegate to work? what am I doing wrong? Thank you in advance.

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

Is there a SwiftUI equivalent for viewWillDisappear(_:) or detect when a view is about to be removed?

In SwiftUI, I'm trying to find a way to detect that a view is about to be removed only when using the default navigationBackButton. Then perform some action.
Using onDisappear(perform:) acts like viewDidDisappear(_:), and the action performs after another view appears.
Or, I was thinking the above problem might be solved by detecting when the default navigationBarBackButton is pressed. But I've found no way to detect that.
Is there any solution to perform some action before another view appears?
(I already know it is possible to do that by creating a custom navigation back button to dismiss a view)
Here is approach that works for me, it is not pure-SwiftUI but I assume worth posting
Usage:
SomeView()
.onDisappear {
print("x Default disappear")
}
.onWillDisappear { // << order does NOT matter
print(">>> going to disappear")
}
Code:
struct WillDisappearHandler: UIViewControllerRepresentable {
func makeCoordinator() -> WillDisappearHandler.Coordinator {
Coordinator(onWillDisappear: onWillDisappear)
}
let onWillDisappear: () -> Void
func makeUIViewController(context: UIViewControllerRepresentableContext<WillDisappearHandler>) -> UIViewController {
context.coordinator
}
func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<WillDisappearHandler>) {
}
typealias UIViewControllerType = UIViewController
class Coordinator: UIViewController {
let onWillDisappear: () -> Void
init(onWillDisappear: #escaping () -> Void) {
self.onWillDisappear = onWillDisappear
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
onWillDisappear()
}
}
}
struct WillDisappearModifier: ViewModifier {
let callback: () -> Void
func body(content: Content) -> some View {
content
.background(WillDisappearHandler(onWillDisappear: callback))
}
}
extension View {
func onWillDisappear(_ perform: #escaping () -> Void) -> some View {
self.modifier(WillDisappearModifier(callback: perform))
}
}
You can bind the visibility of the child view to some state, and monitor that state for changes.
When the child view is pushed, the onChange block is called with show == true. When the child view is popped, the same block is called with show == false:
struct ParentView: View {
#State childViewShown: Bool = false
var body: some View {
NavigationLink(destination: Text("child view"),
isActive: self.$childViewShown) {
Text("show child view")
}
.onChange(of: self.childViewShown) { show in
if show {
// child view is appearing
} else {
// child view is disappearing
}
}
}
}
Here's a slightly more succinct version of the accepted answer:
private struct WillDisappearHandler: UIViewControllerRepresentable {
let onWillDisappear: () -> Void
func makeUIViewController(context: Context) -> UIViewController {
ViewWillDisappearViewController(onWillDisappear: onWillDisappear)
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
private class ViewWillDisappearViewController: UIViewController {
let onWillDisappear: () -> Void
init(onWillDisappear: #escaping () -> Void) {
self.onWillDisappear = onWillDisappear
super.init(nibName: nil, bundle: nil)
}
#available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
onWillDisappear()
}
}
}
extension View {
func onWillDisappear(_ perform: #escaping () -> Void) -> some View {
background(WillDisappearHandler(onWillDisappear: perform))
}
}
you have a couple of actions for each object that you want to show on the screen
func onDisappear(perform action: (() -> Void)? = nil) -> some View
//Adds an action to perform when this view disappears.
func onAppear(perform action: (() -> Void)? = nil) -> some View
//Adds an action to perform when this view appears.
you can use the like the sample ( in this sample it affects on the VStack):
import SwiftUI
struct TestView: View {
#State var textObject: String
var body: some View {
VStack {
Text(textObject)
}
.onAppear {
textObject = "Vertical stack is appeared"
}
.onDisappear {
textObject = ""
}
}
}
struct TestView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
TestView()
}
}
}
You can trigger the change of the #Environment .scenePhase like this :
struct YourView: View {
#Environment(\.scenePhase) var scenePhase
var body: Some View {
VStack {
// Your View code
}
.onChange(of: scenePhase) { phase in
switch phase {
case .active:
print("active")
case .inactive:
print("inactive")
case .background:
print("background")
#unknown default:
print("?")
}
}
}
}

Resources