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.