UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Fetch CNContacts and update UI --> Blocking UI while fetching

Forums > SwiftUI

hello! i use a navigationBarButton to request a fetch of the contatcs stored in the device. in general it's working, but when the database is big (like on my real device) it blocks the fetchReuest button (it blocks the whole UI!). at the request i use DisppatchQueue.main.async, but still no improvement. do you have an idea, what i am doing wrong?

all my code is still on trial basis, so not all is nicely done yet.

thanks in advance for your support and help!

import SwiftUI
import Contacts

struct Contact: Identifiable, Hashable {
    var id = UUID()
    var firstName: String //= "No firstName"
    var lastName: String //= "No lastName"
    var phoneNumbers: [String] //= []
    var emailAddresses: [String]// = []

}

class FetchedContacts: ObservableObject, Identifiable {

    @Published var contacts = [Contact]()

    func fetchContacts() {
        contacts.removeAll()
        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, CNContactEmailAddressesKey]
                let request = CNContactFetchRequest(keysToFetch: keys as [CNKeyDescriptor])
                do {
                    try store.enumerateContacts(with: request, usingBlock: { (contact, stopPointer) in

                 //       DispatchQueue.main.async {
                            self.contacts.append(Contact(firstName: contact.givenName, lastName: contact.familyName, phoneNumbers: contact.phoneNumbers.map { $0.value.stringValue }, emailAddresses: contact.emailAddresses.map { $0.value as String }
                            ))

                            self.contacts.sort(by: { $0.firstName < $1.firstName })
                   //     }
                    })

                } catch let error {
                    print("Failed to enumerate contact", error)
                }

            } else {
                print("access denied")
            }
        }
    }
}

struct ContentView: View {

    @ObservedObject var contacts = FetchedContacts()
    @State private var pick = false
    @State private var searchText: String = ""

    var body: some View {

        NavigationView {
            VStack {
                SearchBar(text: $searchText)
                List {

                    ForEach(contacts.contacts.filter {
                        searchText.isEmpty ? true : ($0.firstName + " " + $0.lastName).lowercased().contains(searchText.lowercased())
                    }) { contact in

                        VStack{
                            NavigationLink(destination: DetailedContactView(contact: contact)){

                                HStack{
                                    Text(contact.firstName)
                                    Text(contact.lastName)
                                }
                            }
                        }

                    }
                }
                .padding()
                .navigationTitle("Contacts")
                .navigationBarItems(trailing: Button("Fetch Contacts") {

                    DispatchQueue.main.async {
                        contacts.fetchContacts()
                    }

                })
            }
        }
    }

}
struct SearchBar: UIViewRepresentable {

    @Binding var text: String

    class Coordinator: NSObject, UISearchBarDelegate {

        @Binding var text: String

        init(text: Binding<String>) {
            _text = text
        }

        func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
            if searchText != "" {
                searchBar.showsCancelButton = true
            }
                text = searchText

        }

        func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
            searchBar.text = ""
            searchBar.resignFirstResponder()
            searchBar.showsCancelButton = false
            text = ""
        }

    }

    func makeCoordinator() -> SearchBar.Coordinator {
        return Coordinator(text: $text)
    }

    func makeUIView(context: UIViewRepresentableContext<SearchBar>) -> UISearchBar {
        let searchBar = UISearchBar(frame: .zero)
        searchBar.delegate = context.coordinator
        searchBar.searchBarStyle = .minimal
        searchBar.autocapitalizationType = .none
        return searchBar
    }

    func updateUIView(_ uiView: UISearchBar, context: UIViewRepresentableContext<SearchBar>) {
        uiView.text = text
    }

}

extension UIApplication {
    func endEditing() {
        sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
    }
}
struct DetailedContactView: View {

    var contact: Contact

    var body: some View {
        VStack{
            HStack{
                Text(contact.firstName)
                Text(contact.lastName)
            }
            ForEach(contact.phoneNumbers, id:\.self) { number in
                Text(number)
            }
            ForEach(contact.emailAddresses, id:\.self) { email in
                Text(email)
            }
        }
    }
}

3      

Hey I know this is a little late but I've recently needed to support the Contacts framework and created a very SwiftUI-friendly API that matches what you find when using Apple's own CoreData APIs. As such, I'm posting this here in case it helps you or others. Its open source so feel free to use in your personal or commercial apps :)

Example:

// This willl automatically refresh your view (with animation by default) 
// even if changes are observed from another device 😬
@FetchContactsList private var contacts

https://github.com/SwiftUI-Plus/Connections

4      

@shaps80

I am trying to use your framework. I have a TabView and i only want to request access to contacts when tapping on the second tab. However, when I launch the app all i get is a white screen and it never asks me to access contacts unless i kill rhe app. I think it does this because the 2 tab having the @FetchContactList is called when the TabView loads. Not sure on that.

How do i only request access when tapping the 2 tab with your framework?

Any help would be appreciated.

2      

What you could do is to use tags and a state variable for your Tabs. https://www.hackingwithswift.com/books/ios-swiftui/creating-tabs-with-tabview-and-tabitem

Then you could use the onChange modifier for your state and only call the method when Tab 2 is selected.

2      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.