Search code examples
swiftuicontactsui

UIViewControllerRepresentable and CNContactPickerViewController


Can't seem to create a UIViewControllerRepresentable that works with CNContactPickerViewController.

Using Xcode 11 beta 4, I've created number of other UIViewControllerRepresentable using other UIViewController and those have worked fine. I've tried changing the features of the CNContactPickerViewController and different implementations of the delegate.

import SwiftUI
import ContactsUI

// Minimal version
struct LookupContactVCR : UIViewControllerRepresentable {

    func makeUIViewController(context: Context) -> CNContactPickerViewController {
        let contactPickerVC = CNContactPickerViewController()
        contactPickerVC.delegate = context.coordinator
        return contactPickerVC
    }

    func makeCoordinator() -> Coordinator {
        return Coordinator()
    }

    func updateUIViewController(_ uiViewController: CNContactPickerViewController, context: Context) {}

    class Coordinator: NSObject {}
}

extension LookupContactVCR.Coordinator : CNContactPickerDelegate {

    func contactPicker(_ picker: CNContactPickerViewController, didSelect contact: CNContact) {
        print("Chose: \(contact.givenName)")
    }
}

#if DEBUG
struct LookupContact_Previews : PreviewProvider {
    static var previews: some View {
        LookupContactVCR()
    }
}
#endif

No error messages. But the screen is always white with nothing rendered.


Solution

  • First of all, please file a [Bug Report][1] for this issue. [1]: https://bugreport.apple.com

    Secondly, there are 2 workarounds for this issue:

    1. You can use ABPeoplePickerNavigationController which is deprecated but still works.
    2. Create a UIViewController which presents CNContactPickerViewController on viewWillAppear and use this newly created view controller with SwiftUI.

    1. ABPeoplePickerNavigationController

    import SwiftUI
    import AddressBookUI
    
    struct PeoplePicker: UIViewControllerRepresentable {
        typealias UIViewControllerType = ABPeoplePickerNavigationController
    
        final class Coordinator: NSObject, ABPeoplePickerNavigationControllerDelegate, UINavigationControllerDelegate {
            func peoplePickerNavigationController(_ peoplePicker: ABPeoplePickerNavigationController, didSelectPerson person: ABRecord) {
                <#selected#>
            }
            
            func peoplePickerNavigationControllerDidCancel(_ peoplePicker: ABPeoplePickerNavigationController) {
                <#cancelled#>
            }
        }
    
        func makeCoordinator() -> Coordinator {
            return Coordinator()
        }
    
        func makeUIViewController(context: UIViewControllerRepresentableContext<PeoplePicker>) -> PeoplePicker.UIViewControllerType {
            let result = UIViewControllerType()
            result.delegate = context.coordinator
            return result
        }
        
        func updateUIViewController(_ uiViewController: PeoplePicker.UIViewControllerType, context: UIViewControllerRepresentableContext<PeoplePicker>) { }
    
    }
    

    2. CNContactPickerViewController

    EmbeddedContactPickerViewController

    import Foundation
    import ContactsUI
    import Contacts
    
    protocol EmbeddedContactPickerViewControllerDelegate: AnyObject {
        func embeddedContactPickerViewControllerDidCancel(_ viewController: EmbeddedContactPickerViewController)
        func embeddedContactPickerViewController(_ viewController: EmbeddedContactPickerViewController, didSelect contact: CNContact)
    }
    
    class EmbeddedContactPickerViewController: UIViewController, CNContactPickerDelegate {
        weak var delegate: EmbeddedContactPickerViewControllerDelegate?
        
        override func viewWillAppear(_ animated: Bool) {
            super.viewWillAppear(animated)
            self.open(animated: animated)
        }
        
        private func open(animated: Bool) {
            let viewController = CNContactPickerViewController()
            viewController.delegate = self
            self.present(viewController, animated: false)
        }
        
        func contactPickerDidCancel(_ picker: CNContactPickerViewController) {
            self.dismiss(animated: false) {
                self.delegate?.embeddedContactPickerViewControllerDidCancel(self)
            }
        }
        
        func contactPicker(_ picker: CNContactPickerViewController, didSelect contact: CNContact) {
            self.dismiss(animated: false) {
                self.delegate?.embeddedContactPickerViewController(self, didSelect: contact)
            }
        }
        
    }
    

    EmbeddedContactPicker

    import SwiftUI
    import Contacts
    import Combine
    
    struct EmbeddedContactPicker: UIViewControllerRepresentable {
        typealias UIViewControllerType = EmbeddedContactPickerViewController
        
        final class Coordinator: NSObject, EmbeddedContactPickerViewControllerDelegate {
            func embeddedContactPickerViewController(_ viewController: EmbeddedContactPickerViewController, didSelect contact: CNContact) {
                <#selected#>
            }
            
            func embeddedContactPickerViewControllerDidCancel(_ viewController: EmbeddedContactPickerViewController) {
                <#cancelled#>
            }
        }
    
        func makeCoordinator() -> Coordinator {
            return Coordinator()
        }
    
        func makeUIViewController(context: UIViewControllerRepresentableContext<EmbeddedContactPicker>) -> EmbeddedContactPicker.UIViewControllerType {
            let result = EmbeddedContactPicker.UIViewControllerType()
            result.delegate = context.coordinator
            return result
        }
        
        func updateUIViewController(_ uiViewController: EmbeddedContactPicker.UIViewControllerType, context: UIViewControllerRepresentableContext<EmbeddedContactPicker>) { }
    
    }