Skip to main content

Building a contact list using SwiftUI has many challenges.

One challenge is having multiple lists displayed on the one screen.  For example, known contacts using your app against contacts on the phone.  Initially, I tried the following code

SearchBarView(text: $searchText, placeholder: "Type here")
List {
  Section(header: ContactListHeader(listTitle: "rivals list")) {
    ForEach(self.userData.rivals.filter{
      self.searchText.isEmpty ? true :
        $0.name.lowercased()
          .contains(self.searchText.lowercased())
    }, id: \.self.name) {
      contact in
      NavigationLink(destination: RivalDetail(rival: contact)) {
          RivalRow(rival: contact)
      }
    }
  }
}.navigationBarTitle(Text("Rivals"))

List {  
  Section(header: ContactListHeader(listTitle: "contact list")) {
    ForEach(self.store.contacts.filter{
      self.searchText.isEmpty ? true : $0.name.lowercased()
          .contains(self.searchText.lowercased())
    }, id: \.self.name) {
      (contact: CNContact) in
      NavigationLink(destination: ContactDetail(contact: contact)) {
          ContactRow(contact: contact)
      }
    }
  }
}.navigationBarTitle(Text("Contacts"))

 

The output of the script noted was only to see the search bar and that it!  Neither of the two lists I wanted to display.  However, once I wrapped them in a VStack everything showed.  Except not quite how I was anticipating.  The display was:

  • Search bar - correct
  • Rival list - correct, however was scrolling half screen
  • Contact list - correct, however was scrolling half screen

The issue with scrolling half screens was the rival list would scroll and have no visual impact on the contact list.  Whereas, I was wanting one list that once you scroll to the end of the rival list, the contact list would appear.

Having two List calls was the issue in this instance.  

The solution is to have one List call and separate the two areas with sections:

SearchBarView(text: $searchText, placeholder: "Type here")
List {
  Section(header: ContactListHeader(listTitle: "rivals list")) {
    ForEach(self.userData.rivals.filter{
      self.searchText.isEmpty ? true :
        $0.name.lowercased()
          .contains(self.searchText.lowercased())
    }, id: \.self.name) {
      contact in
      NavigationLink(destination: RivalDetail(rival: contact)) {
          RivalRow(rival: contact)
      }
    }
  }
  
  Section(header: ContactListHeader(listTitle: "contact list")) {
    ForEach(self.store.contacts.filter{
      self.searchText.isEmpty ? true : $0.name.lowercased()
          .contains(self.searchText.lowercased())
    }, id: \.self.name) {
      (contact: CNContact) in
      NavigationLink(destination: ContactDetail(contact: contact)) {
          ContactRow(contact: contact)
      }
    }
  }
  
}.onAppear {
  DispatchQueue.main.async {
    self.store.fetch()
  }
}
.navigationBarTitle(Text("Contacts"))
.navigationBarItems(
    trailing: EditButton()
)

The outcome you want to achieve is one List with multiple ForEach's. So the display is clear you can make it a grouped List with multiple sections as noted above.

Related articles

Andrew Fletcher12 Aug 2022
Using SwiftUI URLComponent to change a URL's scheme
The challenge I was facing, I had written a script to scan barcodes and use Google book API to view the contents.  However, a snippet of the JSON response { "contentVersion": "0.2.0.0.preview.0", "panelizationSummary": { "containsEpubBubbles": false, ...
Andrew Fletcher12 Mar 2021
SwiftUI - custom navigation bar title
When managing a navigation title in Swift, you will have trodden down the path.  Previously you have entered something like .navigationTitle("Title") If you wanted to alter the font used for the navigation area, alter the init() in the view: struct YourView: View { //...