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

SOLVED: Filter List

Forums > Swift

I am trying to filter a list with a search bar and only have those sections and data under those sections in the filtered list when I search for a hymn. Here is my struct, my computed property that sorts everything for me into the right sections. If I add the filter to the ForEach loop on the hymns it leaves section headers with no data and it just looks horrible. Can someone help me with this?

Struct I use that models my JSON File JSONModel Struct

struct JSONModel: Codable, Identifiable {
    let id: String
    let instrumentalMP3: String
    let vocalMP3: String
    let hymnName: String
    let hymnNumber: Int
    let sectionIndex: Int
    let alphabetIndex: Int
    let topicIndexes: [Int]
}

How I am sorting the data (Computed Property)

    private var sectionedData: [[JSONModel]] {
        let sections = Set<Int>(listOfHymns.allHymns.map { $0.sectionIndex }).sorted()
        var sectionedArray = [[JSONModel]]()
        for section in sections {
            let hymns = listOfHymns.allHymns.filter { $0.sectionIndex == section }
            sectionedArray.append(hymns)
        }
        return sectionedArray
    }

How can I filter the list on hymn and leave only the hymns and headers that are left. I took out my filter I had on the foreach for hymns. I thought it would be easier for you guys to see.

List {
          ForEach(0..<self.sectionedData.count, id: \.self) { sectionIdx in
              Section(header: SectionHeaderView(headerName: categories[sectionIdx])) {
                  ForEach(self.sectionedData[sectionIdx], id: \.id) { hymn in
                      HymnRowView(hymnModel: hymn)
                  }
              }
          }
      }

Thanks, Mark

2      

hi Mark,

the sectionData appears to work fine, although you start with allHymns. instead, begin by first applying your search criteria to allHymns and work from there. for example:

var sectionedData: [[JSONModel]] {
  let filteredList = listOfHymns.allHymns.filter { searchTextAppears(in: $0.hymnName }
    let sections = Set<Int>(filteredList.map { $0.sectionIndex }).sorted()
    var sectionedArray = [[JSONModel]]()
    for section in sections {
        let hymns = filteredList.filter { $0.sectionIndex == section }
        sectionedArray.append(hymns)
    }
    return sectionedArray
}

my suggested function searchTextAppears assumes you are looking for hymn names that contain the value of, say, a @State private var searchText: String = "" so that when that text changes, you get a redraw of the view. one that i have used is in one of my projects is this, which handles case sensistivity and whitespace:

func searchTextAppears(in name: String) -> Bool {
    let cleanedSearchText = searchText.trimmingCharacters(in: .whitespacesAndNewlines)
    if cleanedSearchText.isEmpty {
        return true
    }
    return name.localizedCaseInsensitiveContains(cleanedSearchText.lowercased()) // .lowercased may not be necessary here
}

now, however, you need a better setup for the List to provide only those sections of interest. my suggestion would be this,

List {
    ForEach(sectionedData, id: \.self) { section in // <-- section is of type [JSONModel]
        Section(header: SectionHeaderView(headerName: categories[section[0].sectionIndex])) {
            ForEach(section, id: \.id) { hymn in
                HymnRowView(hymnModel: hymn)
            }
        }
    }
}

hope that helps,

DMG

2      

Hi DMG,

Thank you for the help. I appreciate that. I will give this a try. Now I have a question out on this forum about error checking with CoreData. Do you know how to do this? They put in boiler plate code and user fatal error and then tell us not to use it. So my question is how do we error check correctly when performing a do/try catch block when saving or creating or deleting a new record.

Thanks Mark

2      

Mark,

i saw your previous question about Core Data Error Checking and was hoping someone else might pick up on this, only because handling an error in startup with Core Data is a tricky problem. perhaps showing an Alert with the error message is the best you can do before closing the program. i have not done anything on the startup issue.

but i will somewhat tackle your more specific question

how do we error check correctly when performing a do/try catch block when saving or creating or deleting a new record

creating, editing, or deleting a CD object does not generate an error itself, but the concern is in saving the context. a lot of Hudson's code would either

  • add a simple try? moc.save(), where moc is the managedObjectContext retrieved from the environment ... in one video, i think Paul actually says, essentially, that it's not worth worrying about too much here because a save almost never fails ... or
  • add a block like this, but without specifically filling in the comment line:
do {
    try self.managedObjectContext.save()
} catch let error as NSError {
    // handle the Core Data error
}

the old AppDelegate code (some of which appears in the new Persistence.swift file that defines a singleton PersistenceController instance) used to include this code, which simply logs a saving error:

    var context: NSManagedObjectContext { persistentContainer.viewContext }

    func saveContext () {
        if context.hasChanges {
            do {
                try context.save()
            } catch let error as NSError {
                NSLog("Unresolved error saving context: \(error), \(error.userInfo)")
            }
        }
    }

so i follow up all changes to core data objects simply with the equivalent of PersistenceController.shared.saveContext() so i don't have do/try/catch blocks clogging up my own code. especially for development, you should always do this and avoid using try? moc.save().

that may not be the answer you were searching for; and i am sure that the situation gets more complicated if you use NSPersistentCloudKitContainer. i expect i'll start finding this out as my next project is hooking up to the cloud.

good luck,

DMG

2      

Hi DMG,

I'm not sure what you mean or how to do what you suggested below?

so i follow up all changes to core data objects simply with the equivalent of PersistenceController.shared.saveContext() so i don't have do/try/catch blocks clogging up my own code. especially for development, you should always do this and avoid using try? moc.save().

Thanks, Mark

2      

Mark,

sorry for any confusion. you have three choices once you add, edit, or delete a CD object:

(1) you finish by writing the one-liner

try? moc.save()

if there's an error, you don't know about it. but if you're in the middle of development, you really do need to know about errors. the most common one is that you open a sheet and use this code, but don't realize that the CD environment was never passed to the sheet and the value of the context variable moc is meaningless.

(2) you finish by writing the same, five-line generic do/try/catch block

do {
  try moc.save()
} catch let error as NSError {
  NSLog("Error saving whatever i just did: \(error.localizedDescription), \(error.userInfo)")
}

you'll get tired of doing this.

(3) you finish by writing the one-liner

PersistenceController.shared.saveContext()

where the error is logged, assuming the PersistenceController has the code i suggested above to catch the error and log it.

hope that helps,

DMG

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.