TCA - Wrapping UIViewControllers in SwiftUI Views - Example

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

import ComposableArchitecture
import Contacts
import ContactsUI
import SwiftUI
import Dependencies
#if canImport(UIKit)
import UIKit
#endif

public enum ContactPickerError: Error {
  case cancelled
}

public struct ContactPicker: ReducerProtocol {
  public enum ContactResult: Equatable {
    case contact(CNContact)
    case contacts([CNContact])
    case property(CNContactProperty)
    case properties([CNContactProperty])
  }
  
  public struct State: Equatable {
    public init(
      result: Result<ContactPicker.ContactResult, ContactPickerError>? = nil
    ) {
      self.result = result
    }
    
    @BindableState var result: Result<ContactResult, ContactPickerError>? = nil
  }
  
  public enum Action: Equatable, BindableAction {
    case binding(BindingAction<State>)
  }
  
  public var body: some ReducerProtocol<State, Action> {
    BindingReducer()
  }
}

struct ContactPickerView: UIViewControllerRepresentable {
  @Environment(\.presentationMode) private var presentationMode

  func makeCoordinator() -> Coordinator {
    .init(
      presentation: presentationMode,
      result: viewStore.binding(\.$result)
    )
  }
  
  let store: StoreOf<ContactPicker>
  let viewStore: ViewStoreOf<ContactPicker>
  
  init(store: StoreOf<ContactPicker>) {
    self.store = store
    self.viewStore = ViewStore(store)
  }
  
  func makeUIViewController(
    context: Context
  ) -> CNContactPickerViewController {
    let cvc = CNContactPickerViewController()
    cvc.displayedPropertyKeys = [CNContactPostalAddressesKey]
    cvc.predicateForSelectionOfContact = NSPredicate(
      format: "postalAddresses.@count <= 1"
    )
    cvc.modalPresentationStyle = .currentContext
    cvc.delegate = context.coordinator
    return cvc
  }
    
  func updateUIViewController(
    _ uiViewController: CNContactPickerViewController,
    context: Context)
  {}
  
  class Coordinator: NSObject, CNContactPickerDelegate {
    var presentation: Binding<PresentationMode>
    var result: Binding<Result<ContactPicker.ContactResult, ContactPickerError>?>
    
    init(
      presentation: Binding<PresentationMode>,
      result: Binding<Result<ContactPicker.ContactResult, ContactPickerError>?>)
    {
      self.presentation = presentation
      self.result = result
    }
    
//    func contactPicker(
//      _ picker: CNContactPickerViewController,
//      didSelectContactProperties contactProperties: [CNContactProperty]
//    ) {
//      self.result.wrappedValue = .success(.properties(contactProperties))
//    }
    
//    func contactPicker(
//      _ picker: CNContactPickerViewController,
//      didSelect contact: CNContact
//    ) {
//      self.result.wrappedValue = .success(.contact(contact))
//    }

    func contactPicker(
      _ picker: CNContactPickerViewController,
      didSelect contactProperty: CNContactProperty
    ) {
      self.result.wrappedValue = .success(.property(contactProperty))
    }

//    func contactPicker(
//      _ picker: CNContactPickerViewController,
//      didSelect contacts: [CNContact]
//    ) {
//      self.result.wrappedValue = .success(.contacts(contacts))
//    }

    func contactPickerDidCancel(_ picker: CNContactPickerViewController) {
      self.result.wrappedValue = .failure(.cancelled)
    }
  }
}