This is how to wrap UIViewController within SwiftUI's UIViewControllerRepresentable in TCA, along with Binding
import ContactsUI
import Contacts
struct ContactView: UIViewControllerRepresentable {
let store: Store<CNContact, ContactAction>
@ObservedObject var viewStore: ViewStore<CNContact, ContactAction>
init(store: Store<CNContact, ContactAction>) {
self.store = store
self.viewStore = ViewStore(self.store)
}
func makeUIViewController(context: Context) -> CNContactViewController {
let cvc = CNContactViewController(for: viewStore.state)
cvc.delegate = context.coordinator
return cvc
}
func updateUIViewController(_ uiViewController:CNContactViewController, context: Context) {
return
}
class Coordinator: NSObject, CNContactViewControllerDelegate {
var contact: Binding<CNContact>
init(contact: Binding<CNContact>) {
self.contact = contact
// super.init() implicitly called here - Advanced Swift by ObjcIO is Awesome as well
}
func contactViewController(_ viewController: CNContactViewController, didCompleteWith contact: CNContact?) {
contact.apply { (newVal) -> Void in
self.contact.wrappedValue = newVal
}
}
}
func makeCoordinator() -> Coordinator {
return .init(contact: viewStore.binding(send: ContactAction.edited(newValue:) ))
// Using both Get & Set here results in an 2 way unnecessary binding which freezes the screen. Which is why we have the send only overload
// return .init(contact: viewStore.binding(get: { $0.value }, send: ContactAction.edited(newValue:)))
}
}
For the Target-Action pattern, one would configure the addTarget in the makeUIViewController method like such
func makeVC(context) -> VC {
let button = UIButton()
button.addTarget(context.coordinator, #selector(handleTap), for: .touchUpInside)
return button
}
@objc func handleTap(_ button: UIButton) {
// send actions to viewStore here
}
I learnt this from SwiftUI by Tutorials book from RW. The Coordinator helps in communication between UIViewController & SwiftUI.View
Regarding your approach, you have 2 binding variables, presentation and MailComposerAction
Notice, you aren't reading the MailComposerState object here, because Coordinator helps with communication
How about having a Property in your State of type MFMailComposeResult, Error or Result<MFMailComposeResult, Error> and setting it, and then triggerring from an outer body?
If your method works, without memory leaks, well and good
The reason I didn't use that exact signature is because my State requires an Equatable error. So I have a:
struct QueueingMailError: Error, Equatable {}
About that Button's target action handling, I don't see a use case for me there The mail composer after being presented is out of my hands, in terms of interaction. All I care then is knowing when it was dismissed, and what was the result of that dismissal.
Edit:
Technically, Error could even be ignored altogether. If it's not nil, MFMailComposeResult will show failed anyway.