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

Day 85 - HotProspects - help finding solution for deleting from an array that's a computed property

Forums > 100 Days of SwiftUI

Really scratching my head with this one and hoping someone can point out where I'm going wrong. I finished up HotProspects, but I wanted to practice adding a couple extra features, like deleting entries. For my array of prospects that I gave the 'List' and for completing challenge #3, I ended up needing two enums and two computed properties, the first 'filteredProspects' to sort between Everyone, Contacted and Uncontacted, and a second one 'sortedProspects' to determine the sorting method based on the status of the '@State var sortByName = true' property. When it came to adding the delete() method, I knew I needed a '@State' wrapper around the array, and I also found that I couldn't do that with a computed property. So my thought went to... can I put the logic inside the second computed property 'sortedProspects' in a method, return a new array that is tracked by a @State variable, give that array to delete() and have it work? The code compiles, but as soon as I delete a row it pops back in. I'm struggling to see how I can make the chain work. You'll see how I'm trying to use getFilteredAndSortedProspects() to take my @State array called prospectsArray, apply the post-filter sorting part of the logic, and give it back, but it's not working as I intend. Writing functions like that is really a stretch for me so I'd greatly appreciate any feedback! Here's my 'ProspectsView'

struct ProspectsView: View {
  enum FilterType {
    case none, contacted, uncontacted
  }

  @EnvironmentObject var prospects: Prospects
  @State private var prospectsArray = [Prospect]()

  @State private var isShowingScanner = false
  @State private var showingActionSheet = false
  @State private var sortedByName = true

  let filter: FilterType
  var title: String {
    switch filter {
    case .none:
      return "Everyone"
    case .contacted:
      return "Contacted people ✔️"
    case .uncontacted:
      return "Uncontacted people"
    }
  }

  var filteredProspects: [Prospect] {
    switch filter {
    case .none:
      return prospects.people
    case .contacted:
      return prospects.people.filter { $0.wasContacted }
    case .uncontacted:
      return prospects.people.filter { !$0.wasContacted }
    }
  }

  var body: some View {
    NavigationView {
      List {
        ForEach(getFilteredAndSortedProspects(prospectsArray)) { prospect in
          // Challenge 1
          if validateView(prospect) {
            HStack {
              NameAndEmailStack(prospect: prospect)
              Spacer()
              Image(systemName: "checkmark")
                .padding()
            }
          } else {
            NameAndEmailStack(prospect: prospect)
          }
        }
        .onDelete(perform: delete)
      }
      .navigationBarTitle(title)
      .navigationBarItems(leading:
                            Button(action: { self.showingActionSheet = true })
                            {
                              Image(systemName: "arrow.up.arrow.down.square")
                              Text("Sort by")
                            },
                          trailing:
                            Button(action: { self.isShowingScanner = true
                            }) {
                              Image(systemName: "qrcode.viewfinder")
                              Text("Scan")
                            })
      .sheet(isPresented: $isShowingScanner, onDismiss: prospects.saveData) {
        CodeScannerView(codeTypes: [.qr], simulatedData: "Paul Hudson\npaul@hackingwithswift.com", completion: self.handleScan)
      }
      // Challenge 3
      .actionSheet(isPresented: $showingActionSheet) {
        ActionSheet(title: Text("Sort by:"), buttons: [
                        .default(Text("NAME")) { self.sortedByName = true }, .default(Text("MOST RECENT")) { self.sortedByName = false }, .cancel()])
      }
      .onAppear(perform: prospects.loadData)
    }
  }

  func handleScan(result: Result<String, CodeScannerView.ScanError>) {
    self.isShowingScanner = false
    switch result {
    case .success(let code):
      let details = code.components(separatedBy: "/n")
      guard details.count == 2 else { return }

      let person = Prospect()
      person.name = details[0]
      person.emailAddress = details[1]
      self.prospects.add(person)

    case .failure(let error):
      print("Scanning failed")
    }
  }

  func getFilteredAndSortedProspects(_ array: [Prospect]) -> [Prospect] {
    var array = prospectsArray
    if sortedByName {
      array = filteredProspects.sorted { $0.name < $1.name }
      return array
    } else {
      array = filteredProspects.reversed()
      return array
    }
  }

  func delete(at offsets: IndexSet) {
    prospectsArray.remove(atOffsets: offsets)
  }

  func validateView(_ prospect: Prospect) -> Bool {
    if prospect.wasContacted == true && filter == .none {
      return true
    } else {
      return false
    }
  }
}

3      

Hey jessielinden;

I had the same problem - the key to solving it was to realize that the sorted array had different indexes than filteredProspects.

I added this function to Prospects class (added it here for proper encapsulation):

    func remove(at offsets: IndexSet) {
        people.remove(atOffsets: offsets)
        saveData()
    }

Then, in ProspectsView I altered the onDelete call to find the index of the id in filteredProspects array that matched the id in my sroted array. Then I called remove() on Prospects class:

.onDelete {
                    (offset) in
                    for i in offset {
                        if let found = filteredProspects.firstIndex(where: { $0.id == sortedProspects[i].id }) {
                            let idx: IndexSet = IndexSet(integer: found)
                            prospects.remove(at: idx)
                        }
                    }
                }

3      

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

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.