I was looking at integrating the Razorpay checkout feature with iOS in Xcode and found the official documentation at https://razorpay.com/docs/payment-gateway/ios-integration/standard/. The documentation helps with integrating the Razorpay with UIViewController. The iOS app I am building does not make use of the storyboard and is strictly SwiftUI. I have looked at multiple ways of incorporating the UIViewController in SwiftUI which is totally possible with UIViewRepresentable but the code structure uses
struct ComponentName: UIViewRepresentable{}
But Razorpay SDK for iOS wants to implement RazorpayPaymentCompletionProtocol to a class and not struct. How do I go about in using this in a strictly SwiftUI application?
You can use coordinators to manage the view controllers, and that coordinator will RazorpayPaymentCompletionProtocol.
Example:
struct ComponentName: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> CheckoutViewController {
.init()
}
func updateUIViewController(_ uiViewController: CheckoutViewController, context: Context) { }
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, RazorpayPaymentCompletionProtocol {
let parent: ComponentName
typealias Razorpay = RazorpayCheckout
var razorpay: RazorpayCheckout!
init(_ parent: ComponentName) {
self.parent = parent
RazorpayCheckout.initWithKey(razorpayTestKey, andDelegate: self)
}
func onPaymentError(_ code: Int32, description str: String) {
print("error: ", code, str)
// self.presentAlert(withTitle: "Alert", message: str)
// parent.alert with message
}
func onPaymentSuccess(_ payment_id: String) {
print("success: ", payment_id)
// self.presentAlert(withTitle: "Success", message: "Payment Succeeded")
}
}
}
class CheckoutViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// self.showPaymentForm()
}
}
Related
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.
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
I'm trying to implement Stripe in my SwiftUI app using the STPApplePayContextDelegate.
I have created a class that conforms to this delegate according to this documentation but no luck. I get this error: // Type of expression is ambiguous without more context in this line let applePayContext = STPApplePayContext(paymentRequest: paymentRequest, delegate: self)
What am I doing wrong here?
struct PaymentButtonController : UIViewControllerRepresentable {
class Coordinator : NSObject, STPApplePayContextDelegate {
var vc : UIViewController?
#objc func buttonPressed() {
let merchantIdentifier = "merchant.com.your_app_name"
let paymentRequest = StripeAPI.paymentRequest(withMerchantIdentifier: merchantIdentifier, country: "US", currency: "USD")
// Configure the line items on the payment request
paymentRequest.paymentSummaryItems = [
// The final line should represent your company;
// it'll be prepended with the word "Pay" (i.e. "Pay iHats, Inc $50")
PKPaymentSummaryItem(label: "iHats, Inc", amount: 50.00),
]
// Initialize an STPApplePayContext instance
if let applePayContext = STPApplePayContext(paymentRequest: paymentRequest, delegate: self) {
// Present Apple Pay payment sheet
if let vc = vc {
applePayContext.presentApplePay(on: vc)
}
} else {
// There is a problem with your Apple Pay configuration
}
}
func applePayContext(_ context: STPApplePayContext, didCreatePaymentMethod paymentMethod: STPPaymentMethod, paymentInformation: PKPayment, completion: #escaping STPIntentClientSecretCompletionBlock) {
let clientSecret = "..."
print("ENDLICH")
// Retrieve the PaymentIntent client secret from your backend (see Server-side step above)
// Call the completion block with the client secret or an error
completion(clientSecret, nil);
}
func applePayContext(_ context: STPApplePayContext, didCompleteWith status: STPPaymentStatus, error: Error?) {
print("ENDLICH")
switch status {
case .success:
// Payment succeeded, show a receipt view
break
case .error:
// Payment failed, show the error
break
case .userCancellation:
// User cancelled the payment
break
#unknown default:
fatalError()
}
}
}
func makeCoordinator() -> Coordinator {
return Coordinator()
}
func makeUIViewController(context: Context) -> UIViewController {
let button = PKPaymentButton(paymentButtonType: .plain, paymentButtonStyle: .automatic)
button.addTarget(context.coordinator, action: #selector(context.coordinator.buttonPressed), for: .touchUpInside)
let vc = UIViewController()
context.coordinator.vc = vc
vc.view.addSubview(button)
return vc
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
}
}
The function you're calling (presentApplePay) expects a UIViewController as its input, but you're passing self, which is your ApplePayContext, defined above as NSObject, ObservableObject, STPApplePayContextDelegate.
The challenge that you're going to face is getting a UIViewController context to pass to it, as you won't have any references to a UIViewController in pure SwiftUI.
You have a few possible solutions:
In your SceneDelegate, pass a reference of your UIHostingController down to your views, then use it as the argument to presentApplePay
Use UIViewControllerRepresentable to get a UIViewController you can embed into your SwiftUI view and past as the argument (https://developer.apple.com/documentation/swiftui/uiviewcontrollerrepresentable)
Use a library like SwiftUI-Introspect to get the underlying UIViewController to your current SwiftUI views (https://github.com/siteline/SwiftUI-Introspect)
Update:
In response your request for code, here's something to start with. Note that not everything is hooked up yet -- you'll need to connect the buttonPressed method, add layout constraints to the button, etc, but it gives you a way to figure out how to get a reference to a UIViewController
struct PaymentButtonController : UIViewControllerRepresentable {
class Coordinator : NSObject {
var vc : UIViewController?
#objc func buttonPressed() {
print("Button with VC: \(vc)")
}
}
func makeCoordinator() -> Coordinator {
return Coordinator()
}
func makeUIViewController(context: Context) -> UIViewController {
let button = PKPaymentButton()
button.addTarget(context.coordinator, action: #selector(context.coordinator.buttonPressed), for: .touchUpInside)
let vc = UIViewController()
context.coordinator.vc = vc
vc.view.addSubview(button)
return vc
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
}
}
Embed in your view by using this in your SwiftUI code:
PaymentButtonController()
When I press Edit from contact card, my CNContactViewController is not showing the delete option in the bottom of the screen.
NB: the button remains shown for iOS 13.
import Foundation
import ContactsUI
import SwiftUI
struct CNContactViewControllerRepresentable: UIViewControllerRepresentable {
typealias UIViewControllerType = CNContactViewController
var contact: Binding<CNContact>
var presentingEditContact: Binding<Bool>
func makeCoordinator() -> CNContactViewControllerRepresentable.Coordinator {
Coordinator(self)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<CNContactViewControllerRepresentable>) -> CNContactViewControllerRepresentable.UIViewControllerType {
let controller = CNContactViewController(forNewContact: contact.wrappedValue)
controller.delegate = context.coordinator
return controller
}
func updateUIViewController(_ uiViewController: CNContactViewControllerRepresentable.UIViewControllerType, context: UIViewControllerRepresentableContext<CNContactViewControllerRepresentable>) {
//
}
// Nested coordinator class, the prefered way stated in SwiftUI documentation.
class Coordinator: NSObject, CNContactViewControllerDelegate {
var parent: CNContactViewControllerRepresentable
init(_ contactDetail: CNContactViewControllerRepresentable) {
self.parent = contactDetail
}
func contactViewController(_ viewController: CNContactViewController, didCompleteWith contact: CNContact?) {
parent.contact.wrappedValue = contact ?? parent.contact.wrappedValue
parent.presentingEditContact.wrappedValue = false
}
func contactViewController(_ viewController: CNContactViewController, shouldPerformDefaultActionFor property: CNContactProperty) -> Bool {
return true
}
}
}
.sheet(isPresented: $viewModel.presentingEditContact) {
NavigationView {
if #available(iOS 14, *) {
return AnyView(CNContactViewControllerRepresentable(contact: self.$viewModel.contact, presentingEditContact: $viewModel.presentingEditContact)
.navigationBarTitle("Edit Contact")
.edgesIgnoringSafeArea(.top))
} else {
return AnyView(CNContactViewControllerRepresentable(contact: self.$viewModel.contact, presentingEditContact: $viewModel.presentingEditContact)
.edgesIgnoringSafeArea(.top))
}
}
}
Suppose you have a legacy view controller that I'd like to use with SwiftUI. The view controller has one #Published property that contains it current state:
class LegacyViewController: UIViewController {
enum State {
case opened
case closed
case halfOpened
}
#Published var state: State
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
self.state = .closed
super.init(nibName: nil, bundle: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
// state is changed after some time
}
}
Ideally, I'd like to use it with SwiftUI like this:
struct ContentView: View {
#State var state: LegacyViewController.State
var body: some View {
VCWrapper(state: $state).overlay (
Text("\(state)")
)
}
}
which would mean that I need to implement UIViewControllerRepresentable protocol:
struct VCWrapper: UIViewControllerRepresentable {
#Binding var state: LegacyViewController.State
func makeUIViewController(context: Context) -> LegacyViewController {
let vc = LegacyViewController(nibName: nil, bundle: nil)
/// where to perform the actual binding?
return vc
}
func updateUIViewController(_ uiViewController: LegacyViewController, context: Context) {
}
}
However, I'm having trouble figuring out where to do the actual binding from state property of the LegacyViewController to the state property exposed by VCWrapper. If LegacyViewController exposed a delegate, I could implement the binding through the Coordinator object, but I'm not so sure how to do this considering that I don't use a delegate object?
Here is possible solution - use Combine. Tested with Xcode 12 / iOS 14.
import Combine
struct VCWrapper: UIViewControllerRepresentable {
#Binding var state: LegacyViewController.State
func makeUIViewController(context: Context) -> LegacyViewController {
let vc = LegacyViewController(nibName: nil, bundle: nil)
// subscribe to controller state publisher and update bound
// external state
context.coordinator.cancelable = vc.$state
.sink {
DispatchQueue.main.async {
_state.wrappedValue = $0
}
}
return vc
}
func updateUIViewController(_ uiViewController: LegacyViewController, context: Context) {
}
func makeCoordinator() -> Coordinator {
Coordinator()
}
class Coordinator {
var cancelable: AnyCancellable?
}
}