Implementation plan
- Use the Contacts framework to get phone contacts.
- Put them on the SwiftUI List.
- Use the
UIViewRepresentable
protocol to createUISearchBar
for SwiftUI. - Filter contact list based on search text.
Get Started – Add Privacy Description
The Contacts framework allows you to retrieve contact information without making any changes.
To access that application, you first need to set privacy contacts using descriptions in the info.plist
file.
Just add NSContactsUsageDescription
with explanatory content.
Combining UISearchBar with UIViewRepresentable and Coordinators
Currently SwiftUI does not support integrated search bars.
Therefore, we need to conform our struct to the UIViewRepresentable
protocol and initialize the UISearchBar
of the UIKit in it as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | struct SearchBarView: UIViewRepresentable { @Binding var text: String var placeholder: String func makeCoordinator() -> Coordinator { return Coordinator(text: $text) } func makeUIView(context: Context) -> UISearchBar { let searchBar = UISearchBar(frame: .zero) searchBar.delegate = context.coordinator searchBar.placeholder = placeholder searchBar.searchBarStyle = .minimal searchBar.autocapitalizationType = .none searchBar.showsCancelButton = true return searchBar } func updateUIView(_ uiView: UISearchBar, context: Context) { uiView.text = text } } |
The makeCoordinator()
function allows us to create a Coordinator
class, which is responsible for communicating changes from UIKit View to the SwiftUI interface.
We will define the Coordinator
class, and also declare the UISearchBarDelegate
protocol:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class Coordinator: NSObject, UISearchBarDelegate { @Binding var text: String init(text: Binding<String>) { _text = text } func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { text = searchText } func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { searchBar.resignFirstResponder() } } |
Create ObservableObject class to get contacts
ObservableObject
protocol is used to notify changes in SwiftUI view through the @Published
property.
In the following code, we will retrieve the contacts, make sure permissions are granted and then store them in the @Published
property:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | class ContactStore: ObservableObject { @Published var contacts: [CNContact] = [] @Published var error: Error? = nil func fetchContacts() { let store = CNContactStore() store.requestAccess(for: .contacts) { (granted, error) in if let error = error { print("failed to request access", error) return } if granted { let keys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey] let request = CNContactFetchRequest(keysToFetch: keys as [CNKeyDescriptor]) request.sortOrder = .givenName do { var contactsArray = [CNContact]() try store.enumerateContacts(with: request, usingBlock: { (contact, stopPointer) in if (contact.phoneNumbers.first?.value.stringValue) != nil{ contactsArray.append(contact) } }) self.contacts = contactsArray } catch let error { print("Failed to enumerate contact", error) } } else { print("access denied") } } } } extension CNContact: Identifiable { var name: String { return [givenName, familyName].filter{ $0.count > 0}.joined(separator: " ") } } |
SwiftUI needs a way to identify every single contact. To do that, we created an extension at the conform end with the Identifiable
protocol.
Now all is set up to integrate the search bar.
Integrate SearchBarView
We use @EnvironmentObject
to retrieve contacts.
In the following code, using the SwiftUI List, we can retrieve the contacts and filter them based on the values entered in searchText
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | struct ContentView: View { @EnvironmentObject var store: ContactStore @State private var searchText : String = "" var body: some View { NavigationView { VStack { SearchBarView(text: $searchText, placeholder: "Type here") List{ ForEach(self.store.contacts.filter{ self.searchText.isEmpty ? true : $0.givenName.lowercased().contains(self.searchText.lowercased()) }, id: .self.name) { (contact: CNContact) in VStack(alignment: .leading){ Text(contact.name).font(.headline) Text(contact.phoneNumbers.first?.value.stringValue ?? "").font(.subheadline) } } }.onAppear{ DispatchQueue.main.async { self.store.fetchContacts() } } .navigationBarTitle(Text("SwiftUI Contacts")) } } } } |
Result:
Conclude
We have set up the SwiftUI contacts application with a fairly quick search function – although the search bar is not supported in SwiftUI.
Doing the same thing with the UITableView and the UISearchBar will be quite time-consuming, effort-intensive and code-intensive.
Hope to see SwiftUI 2.0 in WWDC 2020 supporting new features.
You can download the full source code from the Github Repository .
Thank you for watching here.
Source: medium.com