SwiftUI UIViewRepresentable with argument passed and delegate function - ios

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.

Related

Use YPImagePikcer in swiftUI

I'm trying to use YPImagePicker in SwiftUI how can I do it??
https://github.com/Yummypets/YPImagePicker
how can I call this function in SwiftUI?
This can be done using UIViewControllerRepresentable like so...
struct PhotoPicker: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> some UIViewController {
var config = YPImagePickerConfiguration()
YPImagePickerConfiguration.shared = config
let picker = YPImagePicker(configuration: config)
picker.delegate = context.coordinator
picker.didFinishPicking { items, cancelled in
if !cancelled {
let images: [UIImage] = items.compactMap { item in
if case .photo(let photo) = item {
return photo.image
} else {
return nil
}
}
finishedPicking(images)
}
dismiss()
}
return picker
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UINavigationControllerDelegate {
let parent: PhotoPicker
init(_ parent: PhotoPicker) {
self.parent = parent
}
}
}
and then call it like this...
Text("SomeView")
.sheet(isPresented: $showPhotoPicker) {
PhotoPicker()
}

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

Pass variable from UIViewController to SwiftUI View

I can't find a way or a good tutorial to explain how to pass the value of a variable (String or Int) that is owned by a UIViewController to a SwiftUI view that is calling the view.
For example:
class ViewController: UIViewController {
var myString : String = "" // variable of interest
....
func methodThatChangeValueOfString(){
myString = someValue
}
}
// to make the view callable on SwiftUI
extension ViewController: UIViewControllerRepresentable {
typealias UIViewControllerType = ViewController
public func makeUIViewController(context: UIViewControllerRepresentableContext<ViewController>) -> ViewController {
return ViewController()
}
func updateUIViewController(_ uiViewController: ViewController, context: UIViewControllerRepresentableContext<ViewController>) {
}
}
In SwiftUI I'll have
struct ContentView: View {
var body: some View {
ViewController()
}
}
How can I take myString of the ViewController and use it in ContentView?
Thanks in advance
Use MVVM pattern it is what is recommended with SwiftUI.
Share a ViewModel between your SwiftUI View and your UIKit ViewController.
I suggest you start with the basic Apple SwiftUI tutorials. Specifically how to "Interface with UIKit"
https://developer.apple.com/tutorials/swiftui/interfacing-with-uikit
import SwiftUI
struct SwiftUIView: View {
#StateObject var sharedVM: SharedViewModel = SharedViewModel()
var body: some View {
VStack{
UIKitViewController_UI(sharedVM: sharedVM)
Text(sharedVM.myString)
}
}
}
class SharedViewModel: ObservableObject{
#Published var myString = "init String"
}
//Not an extension
struct UIKitViewController_UI: UIViewControllerRepresentable {
typealias UIViewControllerType = UIKitViewController
var sharedVM: SharedViewModel
func makeUIViewController(context: Context) -> UIKitViewController {
return UIKitViewController(vm: sharedVM)
}
func updateUIViewController(_ uiViewController: UIKitViewController, context: Context) {
}
}
class UIKitViewController: UIViewController {
let sharedVM: SharedViewModel
var runCount = 0
init(vm: SharedViewModel) {
self.sharedVM = vm
super.init(nibName: nil, bundle: nil)
//Sample update mimics the work of a Delegate or IBAction, etc
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
self.runCount += 1
self.methodThatChangeValueOfString()
if self.runCount == 10 {
timer.invalidate()
}
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func methodThatChangeValueOfString(){
sharedVM.myString = "method change" + runCount.description
}
}
struct SwiftUIView_Previews: PreviewProvider {
static var previews: some View {
SwiftUIView()
}
}

iOS14 CNContactViewController not showing delete button issue

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

How to pass controller as an inout parameter?

I simply have such protocols:
protocol Containerable {
var containerView: UIView { get }
var containerController: UIViewController { get }
var oldViewController: UIViewController? { get set }
}
protocol ContainerRoutable: class {
func load(controller: UIViewController, into context: inout Containerable)
}
extension ContainerRoutable {
func load(controller: UIViewController, into context: inout Containerable) {
context.oldViewController?.willMove(toParent: nil)
context.oldViewController?.view.removeFromSuperview()
context.oldViewController?.removeFromParent()
controller.view.frame = context.containerView.bounds
context.containerController.addChild(controller)
context.containerView.addSubview(controller.view)
context.oldViewController = controller
controller.didMove(toParent: context.containerController)
}
func loadDashboard(into context: inout Containerable) {
let controller = assembler.resolve(DashboardViewController.self)!
load(controller: controller, into: &context)
}
}
and now on the tap action I need to use it:
mainView.dashboardButton.rx.tap.bind { [weak self] in
self?.mainView.activateDashboardMenuItem()
if var a = self as? Containerable { //warning: Conditional downcast from 'TabBarController?' to 'Containerable' is equivalent to an implicit conversion to an optional 'Containerable'
self?.router.loadDashboard(into: &a)
}
}.disposed(by: bag)
What is self?
class TabBarController: UIViewController, Containerable {
private let mainView: TabBarView
let router: TabBarRoutable
private let bag = DisposeBag()
var oldViewController: UIViewController?
var containerController: UIViewController {
return self
}
var containerView: UIView {
return mainView.containerView
}
}
How to remove the following warning?
Conditional downcast from 'TabBarController?' to 'Containerable' is equivalent to an implicit conversion to an optional 'Containerable'
Update the if-condition as,
if var a: Containerable = self {
self?.router.loadDashboard(into: &a)
}

Resources