0

I'm building an app that prompts the users for their contacts. I'm brand new to Swift but making decent progress.

I'm invoking this open function from a parent call site and want to receive the response value back from the contactPicker function. I'm a bit unfamiliar with the implied nature of the contactPicker invocation, and was expecting to have to invoke it directly from within the presentContactPicker function, but the documentation seemed to suggest this approach is preferable.

As it stands now, this implementation correctly prompts the Contact Picker UI, and prints the selected contacts to the console. But I need assistance actually passing the contacts to the callback function.

The issue that I have is that I'm not able to access the callback function from inside the contactPicker. I have an intuition that maybe I could attach the callback to the parent class with a technique like self.callback = callback in the open function and then call self.callback() in the contactPicker itself, but it didn't work.

import Foundation
import UIKit
import ContactsUI

@objc(ContactsPicker)
class ContactsPicker : NSObject, CNContactPickerDelegate {
  
  @objc static func requiresMainQueueSetup() -> Bool {
    return false
  }
  
  @objc func open(_ options: NSDictionary, callback: RCTResponseSenderBlock) -> Void {
    DispatchQueue.main.async {
      self._presentContactPicker(options: options)
    }
  }
  
  func _presentContactPicker(options: NSDictionary) -> Void {
      let contactPickerVC = CNContactPickerViewController()
      contactPickerVC.delegate = self
      let controller = RCTPresentedViewController()
      controller?.present(contactPickerVC, animated: true)
  }
  
  func contactPicker(_ picker: CNContactPickerViewController, didSelect contacts: [CNContact]) {
    print("+++++++++++++++++++++++++++")
    print(contacts)
    print("+++++++++++++++++++++++++++")

    ### But how can I access the callback from the exposed open method?
  }
}

Trevor
  • 1,284
  • 3
  • 15
  • 33
  • @Sweeper hey, so I've tried to do `self.callback = callback` in the `open` function and hit the compilation error `Value of type ContactsPicker has no member 'callback'` so I tried to add a simple `func callback () {}` on the parent class but I get the `Cannot assign to value: 'callback' is a method` since I assume it is expecting type of `RCTResponseSenderBlock` not `func` - I tried declaring it as such, but was hitting more and more errors, so at that point I realized I was pretty out of my depth and figured I would ask here! – Trevor Nov 27 '21 at 20:42

1 Answers1

1

maybe I could attach the callback to the parent class with a technique like self.callback = callback in the open function and then call self.callback() in the contactPicker itself

This is the right idea. You can declare a property to store the callback like this:

private var callback: RCTResponseSenderBlock?

@objc func open(_ options: NSDictionary, callback: @escaping RCTResponseSenderBlock) -> Void {
    self.callback = callback
    DispatchQueue.main.async {
        self._presentContactPicker(options: options)
    }
}

Note that the since the callback "escapes" into the class, you should mark the parameter as @escaping.

func contactPicker(_ picker: CNContactPickerViewController, didSelect contacts: [CNContact]) {
    callback(someParameters)
}
Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • you're totally right. I was digging around more after your earlier comment and came to a similar answer. This works great! By the way in the `contactPicker` function, when I invoked `self.callback!([contacts][0])` the callsite receives `[null]` but when I pass `self.callback!([contacts][0].givenName)` the callsite receives `["Kate"]` so I know the data passing is working well, but it seems not to be able to pass the CNContact object itself. Any ideas? Appreciate your help either way – Trevor Nov 27 '21 at 21:30
  • @Trevor I'm not familiar with react native, but it does not likely support `CNContact` objects. Keep in mind that the data you are passing to this callback is going to your JS code, so you probably need to transform the data in such a way that the JS code can read. My first guess would be to turn it into a `[String: Any]`. JS is full of those :) – Sweeper Nov 27 '21 at 21:40
  • Your intuition is spot on. I tried the `description` instance method and it passed all the information I was expecting to see over the bridge as a `string`. My mental model is that I need to pass some JSON like object probably, and your recommendation or [String: Any] makes total sense. Is it a simple approach or do you need a complex utility for mapping over all the "keys" and converting the values to a String? – Trevor Nov 27 '21 at 21:48
  • @Trevor I think you'll have to do this by hand, unfortunately. [Something like this](https://stackoverflow.com/a/45436026/5133585). The answer in the link also turns it into JSON `Data` using `JSONSerialization`, which you don't need. (I don't know what would happen if you did...) – Sweeper Nov 27 '21 at 21:56
  • Sweeper, you were right again. I'm a bit surprised there is no generic capability for converting the `CNContact` into a regular Data or [String: Any] object, but if I use a mapper utility for plucking out all the values then it works great. Cheers for all your help, I really appreciate you! – Trevor Nov 27 '21 at 22:19
  • @Trevor if you want `Data`, you can use `NSKeyedArchiver`, but I’m not sure the data it produces will make sense to JS :) – Sweeper Nov 27 '21 at 22:22