I am using a CoreData
in my project. It has 2 separate Entities
: Currency
and Currency2
. The entities have identical attributes, but store a different data which populates a tableView
. And user can choose which data he wants to see in the tableView
by picking the option in app settings (I can store his choice by saving it in UserDefaults
).
The problem here is how can I populate the tableView with a different Entity
data? I can't just change a name like in string literal in here from Currency
to Currency2
:
private var fetchedResultsController: NSFetchedResultsController<Currency>!
It will just give me an error. So I assume I should create one more fetchedResultsController...:
private var fetchedResultsController: NSFetchedResultsController<Currency2>!
But then I should double all the below code since I need to switch between them. And what If in future I will need to switch between 3 or 4 different Entities
?
How can I make the code reusable and at the same time receive a desired switch result with NSFetchedResultsController
?
For now my NSFetchedResultsController
set up as follows:
class CurrencyViewController: UIViewController {
private var fetchedResultsController: NSFetchedResultsController<Currency>!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
setupFetchedResultsController()
}
//MARK: - TableView DataSource Methods
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return fetchedResultsController.sections![section].numberOfObjects
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "currencyCell", for: indexPath) as! CurrencyTableViewCell
let currency = fetchedResultsController.object(at: indexPath)
cell.shortName.text = currency.shortName
cell.fullName.text = currency.fullName
return cell
}
//MARK: - NSFetchedResultsController Setup & Delegates
func setupFetchedResultsController(with searchPredicate: NSPredicate? = nil) {
let predicate = NSPredicate(format: "isForCurrencyScreen == YES")
var sortDescriptor: NSSortDescriptor {
if pickedSection == "По имени" {
return NSSortDescriptor(key: "fullName", ascending: sortingOrder)
} else {
return NSSortDescriptor(key: "shortName", ascending: sortingOrder)
}
fetchedResultsController = coreDataManager.createCurrencyFetchedResultsController(with: predicate, and: sortDescriptor)
fetchedResultsController.delegate = self
try? fetchedResultsController.performFetch()
tableView.reloadData()
}
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
}
And this is also my coreDataManager.createCurrencyFetchedResultsController
method:
private let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
func createCurrencyFetchedResultsController(with predicate: NSPredicate? = nil, and sortDescriptor: NSSortDescriptor? = nil) -> NSFetchedResultsController<Currency> {
let request: NSFetchRequest<Currency> = Currency.fetchRequest()
let baseSortDescriptor = NSSortDescriptor(key: "shortName", ascending: true)
request.predicate = predicate
return NSFetchedResultsController(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
}
CodePudding user response:
If your entities always have the same set of attributes and aren't fundamentally different, consider making them just one entity and differentiate them by adding an extra attribute. That will make the system infinitely extensible without writing much of extra code, and the code will be much simpler.
If you really need them be separate entities, I'd suggest the following:
- Create a new base entity that will act as a parent class for both entities. Let's call it
BaseCurrency
. - Specify it as a parent entity for both
Currency
andCurrency2
. - Create shared attributes in
BaseCurrency
and remove them from the child entities. The result will look like this:
- Create a generic factory method that returns an
NSFetchedResultsController
for any entity:
enum SortingField {
case name
case symbol
}
private func makeFetchedResultsController<T: BaseCurrency>(
context: NSManagedObjectContext,
sortBy: SortingField
) -> NSFetchedResultsController<T> {
let request: NSFetchRequest<T> = NSFetchRequest(entityName: T.description())
let sortDescriptor: NSSortDescriptor = {
switch sortBy {
case .name:
return NSSortDescriptor(keyPath: \T.name, ascending: true)
case .symbol:
return NSSortDescriptor(keyPath: \T.symbol, ascending: true)
}
}()
request.sortDescriptors = [sortDescriptor]
return NSFetchedResultsController(
fetchRequest: request,
managedObjectContext: context,
sectionNameKeyPath: nil,
cacheName: String(describing: T.self)
)
}
Note that this method doesn't accept any sort descriptors or predicates but rather an enum. It allows to abstract from concrete field names and be able to use key paths of a generic types inside the method. If you need more advanced sorting/filtering capabilities, it's possible to build upon this idea by introducing more sophisticated data structures that describe filters and sorting.
- When you need to create a controller for a specific entity, call the factory method:
let frc: NSFetchedResultsController<Currency2> =
makeFetchedResultsController(
context: context,
sortBy: .symbol
)
You have to explicitly specify the type of the controller here of course.
This is just a basic limited example but hopefully you can use it as a foundation and build on top of it.