Skip to content

Instantly share code, notes, and snippets.

@seanwoodward
Last active May 27, 2024 03:31
Show Gist options
  • Save seanwoodward/e35e3fb29b5a69a37860beb50c22f5fc to your computer and use it in GitHub Desktop.
Save seanwoodward/e35e3fb29b5a69a37860beb50c22f5fc to your computer and use it in GitHub Desktop.
Using CNContactPickerViewController from SwiftUI View
import SwiftUI
import Contacts
import ContactsUI
struct SomeView: View {
@State var contact: CNContact?
var body: some View {
VStack {
Text("Selected: \(contact?.givenName ?? "")")
ContactPickerButton(contact: $contact) {
Label("Select Contact", systemImage: "person.crop.circle.fill")
.fixedSize()
}
.fixedSize()
.buttonStyle(.borderedProminent)
}
}
}
struct SomeView_Previews: PreviewProvider {
static var previews: some View {
SomeView()
}
}
struct ContactPickerButton<Label: View>: UIViewControllerRepresentable {
class Coordinator: NSObject, CNContactPickerDelegate {
var onCancel: () -> Void
var viewController: UIViewController = .init()
var picker = CNContactPickerViewController()
@Binding var contact: CNContact?
// Possible take a binding
init<Label: View>(contact: Binding<CNContact?>, onCancel: @escaping () -> Void, @ViewBuilder content: @escaping () -> Label) {
self._contact = contact
self.onCancel = onCancel
super.init()
let button = Button<Label>(action: showContactPicker, label: content)
let hostingController: UIHostingController<Button<Label>> = UIHostingController(rootView: button)
hostingController.view?.backgroundColor = .clear
hostingController.view?.sizeToFit()
(hostingController.view?.frame).map {
hostingController.view!.widthAnchor.constraint(equalToConstant: $0.width).isActive = true
hostingController.view!.heightAnchor.constraint(equalToConstant: $0.height).isActive = true
viewController.preferredContentSize = $0.size
}
hostingController.willMove(toParent: viewController)
viewController.addChild(hostingController)
viewController.view.addSubview(hostingController.view)
hostingController.view.anchor(to: viewController.view)
picker.delegate = self
}
func showContactPicker() {
viewController.present(picker, animated: true)
}
func contactPickerDidCancel(_ picker: CNContactPickerViewController) {
onCancel()
}
func contactPicker(_ picker: CNContactPickerViewController, didSelect contact: CNContact) {
self.contact = contact
}
func makeUIViewController() -> UIViewController {
return viewController
}
func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<ContactPickerButton>) {
}
}
@Binding var contact: CNContact?
@ViewBuilder
var content: () -> Label
var onCancel: () -> Void
init(contact: Binding<CNContact?>, onCancel: @escaping () -> () = {}, @ViewBuilder content: @escaping () -> Label) {
self._contact = contact
self.onCancel = onCancel
self.content = content
}
func makeCoordinator() -> Coordinator {
.init(contact: $contact, onCancel: onCancel, content: content)
}
func makeUIViewController(context: Context) -> UIViewController {
context.coordinator.makeUIViewController()
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
context.coordinator.updateUIViewController(uiViewController, context: context)
}
}
fileprivate extension UIView {
func anchor(to other: UIView) {
self.translatesAutoresizingMaskIntoConstraints = false
self.topAnchor.constraint(equalTo: other.topAnchor).isActive = true
self.bottomAnchor.constraint(equalTo: other.bottomAnchor).isActive = true
self.leadingAnchor.constraint(equalTo: other.leadingAnchor).isActive = true
self.trailingAnchor.constraint(equalTo: other.trailingAnchor).isActive = true
}
}
@l-lemesev
Copy link

Works nicely. Probably makes sense to add hostingController.view?.backgroundColor = .clear

@seanwoodward
Copy link
Author

@l-lemesev, sounds reasonable. thanks

@Jonas-Socas
Copy link

I was trying to use it inside a toolbar menu, but it is not appearing. do you think there is a way to make works there?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment