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.
I stumbled across this wanting to make something similar. Here's what I ended up with using the new "Dependencies" structure along with the Reducer Protocol. Note that some of the delegate callbacks are commented out. Apparently, you should only register with the one callback you want to receive in order for it to fire. Mine is set up to just get an address from the contact, but could be trivially modified to get an entire contact record(s) or some property(ies).