How can I filter core data based records to avoid the appearance of duplicates. (Swift 4) - core-data

I am pulling records from a core data entity. The records each contain "brand", "model", "size", and "price". There are various size and cost options.
I want to see unique records of "brand" and "model". As you can see from the screenshot, that is not working.
This is a code snip I've written so far:
let sortDescriptor: NSSortDescriptor!
let defaults: UserDefaults = UserDefaults.standard
if defaults.integer(forKey: "sequenceSwitchValue") == 0 {
sortDescriptor = NSSortDescriptor(key: "brand", ascending: true, selector: #selector(NSString.localizedCaseInsensitiveCompare(_:)))
} else {
sortDescriptor = NSSortDescriptor(key: "brand", ascending: false, selector: #selector(NSString.localizedCaseInsensitiveCompare(_:)))
}
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: filmEntity)
request.returnsDistinctResults = true
request.propertiesToFetch = ["brand", "model"]
request.sortDescriptors = [sortDescriptor]
request.predicate = nil
do {
films = try context.fetch(request) as! [FilmEntity]
} catch let error as NSError {
print("Error While Fetching Data From DB: \(error.userInfo)")
}

Related

Core Data migration testing: relationships

I'm trying to test a Core Data migration, but I'm not able to fetch the objects from the database and assert their relationships:
func testV1toV2Migration() async throws {
// read and load the old model
let oldModelURL = Bundle(for: PersistenceController.self).url(forResource: "Remoti.momd/Remoti", withExtension: "mom")!
let oldManagedObjectModel = NSManagedObjectModel(contentsOf: oldModelURL)
XCTAssertNotNil(oldManagedObjectModel)
let coordinator = NSPersistentStoreCoordinator(managedObjectModel: oldManagedObjectModel!)
try! coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: nil)
let managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = coordinator
let jobType = createJobType(in: managedObjectContext)
let category = createCategory(in: managedObjectContext)
let location = createLocation(in: managedObjectContext)
let jobList = createJobList(in: managedObjectContext)
let job = createJob(in: managedObjectContext, location, category, jobType, jobList)
try! managedObjectContext.save()
// migrate the store to the new model version
let newModelURL = Bundle(for: PersistenceController.self).url(forResource: "Remoti.momd/Remoti 2", withExtension: "mom")!
let newManagedObjectModel = NSManagedObjectModel(contentsOf: newModelURL)
XCTAssertNotNil(newManagedObjectModel)
let mappingModel = try? NSMappingModel.inferredMappingModel(
forSourceModel: oldManagedObjectModel!,
destinationModel: newManagedObjectModel!
)
let migrationManager = NSMigrationManager(sourceModel: oldManagedObjectModel!, destinationModel: newManagedObjectModel!)
try! migrationManager.migrateStore(
from: self.url,
sourceType: NSSQLiteStoreType,
with: mappingModel,
toDestinationURL: self.newUrl,
destinationType: NSSQLiteStoreType)
let newCoordinator = NSPersistentStoreCoordinator(managedObjectModel: newManagedObjectModel!)
try! newCoordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: self.newUrl, options: nil)
let newManagedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
newManagedObjectContext.persistentStoreCoordinator = newCoordinator
// Testing the migration: Category
let categoryFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Category")
let categories = try! newManagedObjectContext.fetch(categoryFetchRequest) as! [NSManagedObject]
XCTAssertNotNil(categories)
XCTAssertEqual(categories.count, 1)
XCTAssertEqual(categories.first?.value(forKey: "name") as? String, "Software Development")
// Testing the migration: Job
let jobFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Job")
let jobs = try! newManagedObjectContext.fetch(jobFetchRequest) as! [NSManagedObject]
XCTAssertNotNil(jobs)
XCTAssertEqual(jobs.count, 1)
let jobToAssert = try? XCTUnwrap(jobs.first) as? Job
XCTAssertEqual(jobToAssert?.category, categories.first) <-- `XCTAssertEqual failed: ("nil") is not equal to ("Optional(<Category: 0x600002521040> (entity: Category; id: 0x8536c4beaabab28c <x-coredata://E5`
...
How can the relationships be tested?
Thanks in advance!

SwiftUI and dynamic NSSortDescriptors in #FetchRequest

I have a list with items that contain a title and a date. User can set what to sort on (title or date).
However I can't figure out how to change the NSSortDescriptor dynamically.
import SwiftUI
import CoreData
struct ContentView: View {
#Environment(\.managedObjectContext) private var viewContext
#FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \Test.title, ascending: true)], animation: .default) private var items: FetchedResults<Test>
#State private var sortType: Int = 0
#State private var sortDescriptor: NSSortDescriptor = NSSortDescriptor(keyPath: \Test.title, ascending: true)
var body: some View {
Picker(selection: $sortType, label: Text("Sort")) {
Text("Title").tag(0)
Text("Date").tag(1)
}
.pickerStyle(SegmentedPickerStyle())
.onChange(of: sortType) { value in
sortType = value
if sortType == 0 {
sortDescriptor = NSSortDescriptor(keyPath: \Test.title, ascending: true)
} else {
sortDescriptor = NSSortDescriptor(keyPath: \Test.date, ascending: true)
}
}
List {
ForEach(items) { item in
let dateString = itemFormatter.string(from: item.date!)
HStack {
Text(item.title!)
Spacer()
Text(dateString)
}
}
}
.onAppear(perform: {
if items.isEmpty {
let newEntry1 = Test(context: self.viewContext)
newEntry1.title = "Apple"
newEntry1.date = Date(timeIntervalSince1970: 197200800)
let newEntry2 = Test(context: self.viewContext)
newEntry2.title = "Microsoft"
newEntry2.date = Date(timeIntervalSince1970: 168429600)
let newEntry3 = Test(context: self.viewContext)
newEntry3.title = "Google"
newEntry3.date = Date(timeIntervalSince1970: 904903200)
let newEntry4 = Test(context: self.viewContext)
newEntry4.title = "Amazon"
newEntry4.date = Date(timeIntervalSince1970: 773402400)
if self.viewContext.hasChanges {
try? self.viewContext.save()
}
}
})
}
}
private let itemFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .none
return formatter
}()
When I change
#FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \Test.title, ascending: true)], animation: .default) private var items: FetchedResults<Test>
to
#FetchRequest(sortDescriptors: [sortDescriptor], animation: .default) private var items: FetchedResults<Test>
the error "Cannot use instance member 'sortDescriptor' within property initializer; property initializers run before 'self' is available" appears.
I also tried to store the NSSortDescriptor in a UserDefault and create an init that creates it's own FetchRequest.. still no dynamic sorting...
Anyone a pointer where to look to solve this problem?
Whole project found here: https://github.com/l1ghthouse/FRDSD
Solved! Thanks to Asperi pointing to this QA: https://stackoverflow.com/a/59345830/12299030
import SwiftUI
import CoreData
struct ContentView: View {
#Environment(\.managedObjectContext) private var viewContext
#AppStorage("firstLaunch") var firstLaunch: Bool = true
#State var sortDescriptor: NSSortDescriptor = NSSortDescriptor(keyPath: \Test.title, ascending: true)
#State private var sortType: Int = 0
var body: some View {
Picker(selection: $sortType, label: Text("Sort")) {
Text("Title").tag(0)
Text("Date").tag(1)
}
.pickerStyle(SegmentedPickerStyle())
.onChange(of: sortType) { value in
sortType = value
if sortType == 0 {
sortDescriptor = NSSortDescriptor(keyPath: \Test.title, ascending: true)
} else {
sortDescriptor = NSSortDescriptor(keyPath: \Test.date, ascending: true)
}
}
ListView(sortDescripter: sortDescriptor)
.onAppear(perform: {
if firstLaunch == true {
let newEntry1 = Test(context: self.viewContext)
newEntry1.title = "Apple"
newEntry1.date = Date(timeIntervalSince1970: 197200800)
let newEntry2 = Test(context: self.viewContext)
newEntry2.title = "Microsoft"
newEntry2.date = Date(timeIntervalSince1970: 168429600)
let newEntry3 = Test(context: self.viewContext)
newEntry3.title = "Google"
newEntry3.date = Date(timeIntervalSince1970: 904903200)
let newEntry4 = Test(context: self.viewContext)
newEntry4.title = "Amazon"
newEntry4.date = Date(timeIntervalSince1970: 773402400)
if self.viewContext.hasChanges {
try? self.viewContext.save()
}
firstLaunch = false
}
})
}
}
struct ListView: View {
#FetchRequest var items: FetchedResults<Test>
#Environment(\.managedObjectContext) var viewContext
init(sortDescripter: NSSortDescriptor) {
let request: NSFetchRequest<Test> = Test.fetchRequest()
request.sortDescriptors = [sortDescripter]
_items = FetchRequest<Test>(fetchRequest: request)
}
var body: some View {
List {
ForEach(items) { item in
let dateString = itemFormatter.string(from: item.date!)
HStack {
Text(item.title!)
Spacer()
Text(dateString)
}
}
}
}
}
private let itemFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .none
return formatter
}()

fetch core data string and place in a label (Swift4)

I am trying to call 2 different core data strings and place them each on separate labels. Right now I am getting the error Cannot invoke initializer for type 'init(_:)' with an argument list of type '([NSManagedObject])'. This error is coming from j1.text = String(itemsName). I added both view controllers for saving and displaying.
import UIKit
import CoreData
class ViewController: UIViewController {
#IBOutlet var j1 : UITextField!
#IBOutlet var j2 : UITextField!
#IBAction func save(){
let appD = UIApplication.shared.delegate as! AppDelegate
let context = appD.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "Team", in : context)!
let theTitle = NSManagedObject(entity: entity, insertInto: context)
theTitle.setValue(j1.text, forKey: "score")
theTitle.setValue(j2.text, forKey: "alba")
do {
try context.save()
}
catch {
print("Tom Corley")
}
}}
class twoVC: UIViewController {
#IBOutlet var j1 : UILabel!
#IBOutlet var j2 : UILabel!
var itemsName : [NSManagedObject] = []
var itemsName2 : [NSManagedObject] = []
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let appD = UIApplication.shared.delegate as! AppDelegate
let context = appD.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Team")
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "score", ascending: true)]
let fetchRequest2 = NSFetchRequest<NSManagedObject>(entityName: "Team")
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "alba", ascending: true)]
do {
itemsName = try context.fetch(fetchRequest)
itemsName2 = try context.fetch(fetchRequest2)
if let score = itemsName[0].value(forKey: "score") {
j1.text = (score as! String)
}
if let alba = itemsName2[0].value(forKey: "alba") {
j2.text = (alba as? String)
}
}catch {
print("Ashley Tisdale")
}
}}
Loop over the result from the fetch and append to a string that is then used as value for the label, this goes inside the do{...} where you do the fetch today. Note that I am only using one fetch request here.
itemsName = try context.fetch(fetchRequest)
var mergedScore: String = ""
var mergedAlba: String = ""
for item in itemsName {
if let score = item.value(forKey: "score") as? String {
mergedScore.append(score)
mergedScore.append(" ") //separator
}
if let alba = item.value(forKey: "alba") as? String {
mergedScore.append(alba)
mergedScore.append(" ") //separator
}
}
j1.text = mergedScore
j2.text = mergedAlba
Try this one it's Working for me Swift 4 I think You need to store the value as int which are used as sortDescriptor.
func FetchManagedObjectFromDatabaseForStoreData(Entity :NSEntityDescription) ->
[NSManagedObject]
{
let fetchRequest = NSFetchRequest<NSFetchRequestResult>()
// Add Sort Descriptor
let sortDescriptor = NSSortDescriptor(key: "order", ascending: true)
let sortDescriptor1 = NSSortDescriptor(key: "is_favourite", ascending: false)
fetchRequest.sortDescriptors = [sortDescriptor,sortDescriptor1]
// Create Entity Description
fetchRequest.entity = Entity
let result : [NSManagedObject] = []
// Execute Fetch Request
do{
let result = try appDelegate.managedObjectContext.fetch(fetchRequest) as! [NSManagedObject]
if result.count > 0
{
return result
}
else
{
// return result
}
}
catch{
let fetchError = error as NSError
print(fetchError)
}
return result
}
For Fetch Data
// Create Entity Description
let entityDescription = NSEntityDescription.entity(forEntityName: "Your Entity Name Here", in: appDel.managedObjectContext)
let DataObject = FetchManagedObjectFromDatabaseForStoreData(Entity: entityDescription!)
//Convert Array of NSManagedObject into array of [String:AnyObject]
for item in DataObject{
let keys = Array(item.entity.attributesByName.keys)
// Here is your result
print((item.dictionaryWithValues(forKeys: keys) as NSDictionary).value(forKey: "id") as Any) // And so On Whatewer you Fetch
}

CORE DATA: memory not being released even after manually faulting and doing a managed object reset during xml parsing of large mb file

I am parsing large kml file(18 MB) using NSXMLParser and simultaneously storing coordinates of every place in core data, what i am observing is memory is not being released after parsing 3/4th of the file memory piles up and application crashes, i have tried most of the answers given on stackoverflow but were not useful. Any help is mostly welcomed.
var currentElement : String = ""
var place:Place?
var polygon:Polygon?
var placeName = ""
var shouldAddCoordinates = false
private lazy var privateManagedObjectContext: NSManagedObjectContext = {
var managedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = (UIApplication.shared.delegate as! AppDelegate).managedObjectContext.persistentStoreCoordinator
return managedObjectContext
}()
override init() {
super.init()
}
// Xml Parser delegate methods
func parserDidStartDocument(_ parser: XMLParser) {
print("..Xml parsing has started")
}
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
currentElement = elementName
if elementName == "Placemark" {
place = NSEntityDescription.insertNewObject(forEntityName: "Place", into: privateManagedObjectContext) as? Place
place?.placeName = ""
}
if elementName == "Polygon" {
polygon = NSEntityDescription.insertNewObject(forEntityName: "Polygon", into: privateManagedObjectContext) as? Polygon
let fetchRequest = NSFetchRequest<Place>(entityName: "Place")
fetchRequest.predicate = NSPredicate(format: "placeName == %#", placeName)
do{
let fetchResult = try privateManagedObjectContext.fetch(fetchRequest) as [Place]
polygon?.place = place
polygon?.placeName = placeName
shouldAddCoordinates = fetchResult.first?.placeName == placeName ? true:false
}
catch{}
}
if elementName == "coordinates" {
let fetchRequest = NSFetchRequest<Place>(entityName: "Place")
fetchRequest.predicate = NSPredicate(format: "placeName == %#", placeName)
let fetchResult = try? privateManagedObjectContext.fetch(fetchRequest) as [Place]
shouldAddCoordinates = fetchResult?.first?.placeName == placeName ? true:false
}
if elementName == "name"{
placeName = ""
}
}
func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
currentElement = elementName
if elementName == "coordinates" {
let fetchRequest = NSFetchRequest<Place>(entityName: "Polygon")
let sortDescriptor = NSSortDescriptor(key: "polygonID", ascending: true )
fetchRequest.sortDescriptors = [sortDescriptor]
let fetchResult = try? privateManagedObjectContext.fetch(fetchRequest as! NSFetchRequest<NSFetchRequestResult>) as! [Polygon]
if fetchResult?.count == 0{
polygon?.polygonID = 1
}
else{
polygon?.polygonID = Int64(fetchResult!.count)
}
polygon?.place = place
}
if elementName == "coordinates" || elementName == "Polygon" || elementName == "name"{
if self.privateManagedObjectContext.hasChanges{
privateManagedObjectContext.performAndWait {
do{
try self.privateManagedObjectContext.save()
}
catch{}
}
}
}
else if elementName == "Placemark"{
placeName = ""
privateManagedObjectContext.performAndWait {
do{
try self.privateManagedObjectContext.save()
}
catch{}
}
privateManagedObjectContext.reset()
}
}
func parser(_ parser: XMLParser, foundCharacters string: String) {
if currentElement == "name" {
if string == "\n" {
return
}
place?.placeName? += string
placeName += string
}
if currentElement == "coordinates"{
if shouldAddCoordinates {
privateManagedObjectContext.performAndWait{
if string == "\n" {
return
}
else{
let coordinates = NSEntityDescription.insertNewObject(forEntityName: "Coordinates", into: self.privateManagedObjectContext) as! Coordinates
let longitude = (string.components(separatedBy: ",") as [NSString]).first!.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) as NSString
let latitude = (string.components(separatedBy: ",") as [NSString]).last!.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) as NSString
let latitudeDegrees : CLLocationDegrees = latitude.doubleValue
let longitudeDegrees: CLLocationDegrees = longitude.doubleValue
coordinates.latitude = latitudeDegrees as NSNumber?
coordinates.longitude = longitudeDegrees as NSNumber?
coordinates.coordinatesDate = Date() as NSDate
coordinates.polygon = self.polygon
print("\(String(describing: coordinates.polygon?.placeName!)) has coordinates \(coordinates.latitude!),\(coordinates.longitude!)")
}
}
}
}
}
func parserDidEndDocument(_ parser: XMLParser) {
print("..Parser has finished parsing..")
}
func getManagedObject() -> NSManagedObjectContext{
let managedObjectContext = (UIApplication.shared.delegate as! AppDelegate).managedObjectContext
return managedObjectContext
}
func saveCoordinates(){
let url = Bundle.main.url(forResource: "IND_adm1_1", withExtension: "kml")
let xmlParser = XMLParser(contentsOf: url!)
xmlParser?.delegate = self
_ = xmlParser?.parse()
}

How to display many to many relationship with nsfetchedresultcontroller?

If I use a one to many, I can display all the associated NSSet records but many to many doesn't seem to work the same.
On my segue I pass the gymnast to a VC in which I would like to display all associated meets which works fine in a one to many.
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if let objs = fetchedResultsController.fetchedObjects where objs.count > 0 {
let item = objs[indexPath.row] as! Gymnast
performSegueWithIdentifier("displayMeets", sender: item)
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "displayMeets" {
let vc = segue.destinationViewController as! SelectMeetVC
vc.selectedGymnast = sender as? Gymnast
}
}
I get a crash unless I comment out the NSPREDICATE
func loadFetchRequest() {
self.fetchedResultsController = nil
let fetchRequest = NSFetchRequest(entityName: "Meet")
let sortDescriptor1 = NSSortDescriptor(key: "meetName", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor1]
//let filter = NSPredicate(format: "gymnasts.fullName == %#", "\(selectedGymnast.fullName!)")
//fetchRequest.predicate = filter
let controller = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: AD.managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
controller.delegate = self
fetchedResultsController = controller
do {
try fetchedResultsController.performFetch()
} catch {
fatalError("Error retrieving core data")
}
}
Because the gymnasts relationship is to-many, you need to amend the predicate so that it selects a Meet if any of its gymnasts matches:
let filter = NSPredicate(format: "ANY gymnasts.fullName == %#", "\(selectedGymnast.fullName!)")
Note also that there's no need to use the fullName attribute - you can match the Gymnast object itself:
let filter = NSPredicate(format: "ANY gymnasts == %#", selectedGymnast)

Resources