Preventing saving duplicate values in CoreData - swift

I'm attempting to use this method below to save or create a GameMO object which is a subclass of NSManagedObject. The problem is that when I call my winnerChanged() method below when I change a UISegmentedControl's selected value, I call this saveOrCreateGameMO() method below and that ends up creating a new GameMO object stored in CoreData instead of loading up the one already in CoreData and just updating it. I can't figure out why this is happening. Here's what I see in the logs when I change the winner of a game:
Saving game because winner was changed
Updating Game to CoreData
This tells me that the game is trying to update an already existing GameMO object because the NSPredicate in the FetchRequest is evaluating to true and is finding at least one game in CoreData with the same id (just a random UUID string) and is using that instead of creating a new one.
Even more confusing, the first time I run my app (after removing it from the simulator entirely to reset CoreData's persistent store) it spits out Updating Conference to CoreData and Updating Team to CoreData from saveOrCreateConference() and saveOrCreateTeam() respectively, indicating that it's already somehow found a valid value for the NSPredicate like NSPredicate(format: "name == %#", teamMO.name). How is this possible, if the persistent store is cleared? I've also included how I'm loading games below.
#IBAction func winnerChanged(_ sender: UISegmentedControl) {
os_log("Saving game because winner was changed", type: .debug)
let viewcell = sender.superview?.superview as? ConferenceGamesTableViewCell
guard viewcell != nil else {
os_log("viewcell is nil in winnerChanged in ConferenceGamesTableViewController", type: .debug)
return
}
guard let game = viewcell?.game else {
os_log("Could not unwrap game in winnerChanged in ConferenceGamesTableViewController", type: .debug)
return
}
guard let winnerName = sender.titleForSegment(at: sender.selectedSegmentIndex) else {
os_log("Could not unwrap game winner from UISegmentedControl in ConferenceGamesTableViewController", type: .debug)
return
}
guard let winnerConferenceName = Conference.name(forTeamName: winnerName) else {
os_log("Could not unwrap game winner conference name in ConferenceGamesTableViewController", type: .debug)
return
}
game.winner = Team(teamName: winnerName, conferenceName: winnerConferenceName)
guard let gameMO = GameMO.newGameMO(fromGame: game) else {
os_log("Could not unwrap gameMO from game in ConferenceGamesTableViewController", type: .debug)
return
}
let dataModelManager = DataModelManager.shared
dataModelManager.saveOrCreateGameMO(gameMO: gameMO)
}
public func saveOrCreateGameMO(gameMO: GameMO) {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "GameMO")
fetchRequest.predicate = NSPredicate(format: "id == %#", gameMO.id)
do {
let test = try managedContext.fetch(fetchRequest)
let objectUpdate = test[0] as! GameMO
objectUpdate.setValue(gameMO.id, forKey: "id")
objectUpdate.setValue(gameMO.conferencesNames, forKeyPath: "conferencesNames")
objectUpdate.setValue(gameMO.confidence, forKeyPath: "confidence")
objectUpdate.setValue(gameMO.contestantsNames, forKeyPath: "contestantsNames")
objectUpdate.setValue(gameMO.winnerName, forKeyPath: "winnerName")
do {
try managedContext.save()
os_log("Updating Game to CoreData", type: .debug)
} catch let error as NSError {
print("Could not update game to CoreData. \(error), \(error.userInfo)")
}
} catch {
os_log("Could not fetch game from CoreData. Saving it as a new game.", type: .debug)
let entity = NSEntityDescription.entity(forEntityName: "Game", in: managedContext)!
let newGameMO = GameMO(entity: entity, insertInto: managedContext)
newGameMO.setValue(gameMO.id, forKey: "id")
newGameMO.setValue(gameMO.conferencesNames, forKeyPath: "conferencesNames")
newGameMO.setValue(gameMO.confidence, forKeyPath: "confidence")
newGameMO.setValue(gameMO.contestantsNames, forKeyPath: "contestantsNames")
newGameMO.setValue(gameMO.winnerName, forKeyPath: "winnerName")
do {
try managedContext.save()
} catch let error as NSError {
print("Could not save new game to CoreData. \(error), \(error.userInfo)")
}
}
}
public func loadGames() {
os_log("loadGames() called", log: OSLog.default, type: .debug)
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<GameMO>(entityName: "GameMO")
do {
var gamesMO = try managedContext.fetch(fetchRequest)
os_log("Loading %d games", log: OSLog.default, type: .debug, gamesMO.count)
if gamesMO.count == 0 {
guard let game1 = GameMO.newGameMO(id: UUID().uuidString, contestants: ["Elon", "NC A&T"], winner: "Elon", confidence: 65, conferences: [.caa], week: 0),
let game2 = GameMO.newGameMO(id: UUID().uuidString, contestants: ["James Madison", "Towson"], winner: "James Madison", confidence: 85, conferences: [.caa], week: 0),
let game3 = GameMO.newGameMO(id: UUID().uuidString, contestants: ["Samford", "Youngstown State"], winner: "Samford", confidence: 75, conferences: [.mvfc], week: 0),
let game4 = GameMO.newGameMO(id: UUID().uuidString, contestants: ["Elon", "The Citadel"], winner: "Elon", confidence: 60, conferences: [.caa, .southern], week: 1),
let game5 = GameMO.newGameMO(id: UUID().uuidString, contestants: ["Elon", "Richmond"], winner: "Elon", confidence: 80, conferences: [.caa], week: 2) else {
os_log("Could not create stub games in DataModelManager.loadGames()", type: .default)
return
}
gamesMO = [game1, game2, game3, game4, game5]
os_log("Needed to load games for the first time", log: OSLog.default, type: .debug)
}
self.allGames = gamesMO.map { (gameMO) -> Game in
if let game = Game(fromGameMO: gameMO) {
return game
} else {
return Game()
}
}
} catch let error as NSError {
print("Could not fetch games. \(error), \(error.userInfo)")
}
}
public static func newGameMO(fromGame game: Game) -> GameMO? {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return nil
}
let managedContext = appDelegate.persistentContainer.viewContext
let gameMO = GameMO(context: managedContext)
gameMO.id = game.id
guard let teamName1 = game.contestants.first?.name, let teamName2 = game.contestants.last?.name else {
os_log("Could not unwrap team names in GameMO.newGameMO(fromGame:)", type: .debug)
return nil
}
gameMO.contestantsNames = [teamName1, teamName2]
gameMO.confidence = game.confidence
guard let conferenceName1 = game.contestants.first?.conferenceName, let conferenceName2 = game.contestants.last?.conferenceName else {
os_log("Could not unwrap conference names in GameMO.newGameMO(fromGame:)", type: .debug)
return nil
}
gameMO.conferencesNames = [conferenceName1, conferenceName2]
gameMO.week = game.week
gameMO.winnerName = game.winner.name
return gameMO
}

Related

save string over saved string in core data

In my swift code below the code saves an item in core data. The goal is to overwrite that item. I am getting a runtime error at
CoreDataHandler.changeName(user: fetchUser!\[indexNumber\], jessica: "jo")
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
I don't know how to wrap in the index number. The goal is it to print judo then jo
import UIKit;import CoreData
class ViewController: UIViewController {
var fetchUser: [UserInfo]? = nil
var indexNumber : Int = 0
override func viewDidLoad() {
super.viewDidLoad()
CoreDataHandler.saveObject2( name: "judo")
getText(textNo: indexNumber)
saveTheItem()
}
#objc func saveTheItem(){
CoreDataHandler.changeName(user: fetchUser![indexNumber], jessica: "jo")
}
func getText(textNo:Int) {
// first check the array bounds
let info = helpText.shareInstance.fetchText()
if info.count > textNo {
if let imageData = info[textNo].name
{
print(imageData)
} else {
// no data
print("data is empty Textss")
}
} else {
// image number is greater than array bounds
print("you are asking out of bounds")
}
}
}
class CoreDataHandler : NSManagedObject {
class func saveObject2( name: String) -> Bool {
let context = getContext()
let entity = NSEntityDescription.entity(forEntityName: "UserInfo", in: context)
let managedObject = NSManagedObject(entity: entity!, insertInto: context)
managedObject.setValue(name, forKey: "name")
do{
try context.save()
return true
}
catch {
return false
}
}
private class func getContext() -> NSManagedObjectContext{
let appD = UIApplication.shared.delegate as! AppDelegate
return appD.persistentContainer.viewContext
}
class func changeName(user: UserInfo,jessica : String) -> Bool
{
let context = getContext()
user.name = jessica
print(jessica)
do{
try context.save()
return true
}
catch{
return false
}
}
}
class helpText: UIViewController{
private class func getContext() -> NSManagedObjectContext {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
return appDelegate.persistentContainer.viewContext
}
static let shareInstance = helpText()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
func saveName(data: String) {
let imageInstance = UserInfo(context: context)
imageInstance.name = data
do {
try context.save()
} catch {
print(error.localizedDescription)
}
}
func fetchText() -> [UserInfo] {
var fetchingImage = [UserInfo]()
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "UserInfo")
do {
fetchingImage = try context.fetch(fetchRequest) as! [UserInfo]
} catch {
print("Error while fetching the image")
}
return fetchingImage
}
}
No offense but your code is a mess.
And there is a big misunderstanding. Core Data records are unordered, there is no index. To update a record you have to fetch it by a known attribute, in your example by name, update it and save it back.
This is a simple method to do that. It searches for a record with the given name. If there is one, update the attribute with newName and save the record.
The code assumes that there is a NSManagedObject subclass UserInfo with implemented class method fetchRequest.
func changeName(_ name: String, to newName: String) {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let request : NSFetchRequest<UserInfo> = UserInfo.fetchRequest()
request.predicate = NSPredicate(format: "name == %#", name)
do {
let records = try context.fetch(request)
guard let foundRecord = records.first else { return }
foundRecord.name = newName
try context.save()
} catch {
print(error)
}
}
Regarding your confusing code:
Create CoreDataHandler as singleton (and it must not be a subclass of NSManagedObject). Move the Core Data related code from AppDelegate and the methods to read and write in this class.

EKEvent event.eventIdentifier not removing

I am trying to remove an event I saved in the calendar with an event id but its removing a different event and sometimes it doesn't remove anything.
I am saving the eventId in a realm database when event is created and reading it back when I want to delete but its not working.
I have tried running it on an actual device, using an array instead of a dictionary, changing the span to .futureEvents but still doesn't work
my code for creating event and saving to realm database
/// function exits in another class
func addEventToCalendar(userName: String, userDate: Date) {
let userDefaults = UserDefaults.standard
let eventStore: EKEventStore = EKEventStore()
eventStore.requestAccess(to: .event) { (granted, error) in
if (granted) && (error == nil) {
print("granted \(granted)")
print("error \(String(describing: error))")
let event: EKEvent = EKEvent(eventStore: eventStore)
event.title = "\(userName) Birthday"
event.startDate = userDate
event.endDate = userDate
event.notes = "Happy Birthday!"
event.isAllDay = true
event.calendar = eventStore.defaultCalendarForNewEvents
let ekrules: EKRecurrenceRule = EKRecurrenceRule.init(recurrenceWith: .yearly, interval: 1, end: nil)
event.recurrenceRules = [ekrules]
//event.addAlarm(EKAlarm(absoluteDate: event.startDate))
//sets alert 00:00 on day of event
event.addAlarm(EKAlarm(relativeOffset: 0))
do {
try eventStore.save(event, span: .thisEvent, commit: true)
} catch let error as NSError {
print("error: \(error)")
}
let eventId = event.eventIdentifier ?? "nil-id"
userDefaults.setValue(eventId, forKey: "eventId")
print(eventId)
} else {
print("error not granted: \(String(describing: error))")
}
}
}
//saving it in a view controller class
#IBAction func okBtnPressed(_ sender: UIButton) {
let eventId = UserDefaults.standard.string(forKey: "eventId") ?? "no-id"
//// saving data to device
let newItem = Item()
newItem.userImageName = String(describing: userImageUrl)
newItem.userName = uName
newItem.isYearPresent = uYearPresent
newItem.userDOB = uDOB
newItem.color = UIColor.init(randomFlatColorOf: .dark).hexValue()
newItem.daysRemaining = daysRemain
newItem.eventId = eventId
self.save(item: newItem)
}
The event id saves succesfully in the realm database.
function for removing the event from calendar
func removeEvent(id: String) {
let store = EKEventStore()
store.requestAccess(to: .event) { (granted, error) in
if !granted { return }
// checking if event exists
if let eventToRemove = store.event(withIdentifier: id) {
do {
print("removing: \(id)")
try store.remove(eventToRemove, span: .thisEvent, commit: true)
print("event removed sucessfully")
} catch let error as NSError {
print("error: \(error)")
}
} else {
print("event doesnt exist.")
}
}
}
This is how I remove it
var eventIDS = [Int: String]()
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// inserting evenIds to dictionary to access it in remove function
// I used an array but it gave me the same problem
if let item = itemsObject?[indexPath.row] {
eventIDS[indexPath.row] = item.eventId
}
}
// then I call remove function when swipe taps on cell
removeEvent(id: self.eventIDS[indexPath.row] ?? "")
Sometimes I get event removed successfully but it removes a different
event, sometimes I get the following errors
Error getting event with identifier 2BD633CA-BBEA-47CD-8410-40BCE6362A5C:98D9EAF2-D5EF-420F-B769-7F02B7795E54: Error Domain=EKCADErrorDomain Code=1010 "(null)"
event doesnt exist.
I figured it out. Because eventStore.requestAccess(to: .event) is asynchronous, I was saving the event id in the database before the id existed.
So I had to declare the function to accept a completion handler and return the value inside the completion handler.
//// adding events to calendar
func addEventToCalendar(userName: String, userDate: Date, completion: #escaping (String?)->()) {
let userDefaults = UserDefaults.standard
var eventId = ""
let eventStore: EKEventStore = EKEventStore()
eventStore.requestAccess(to: .event) { (granted, error) in
if (granted) && (error == nil) {
print("granted \(granted)")
print("error \(String(describing: error))")
let event: EKEvent = EKEvent(eventStore: eventStore)
event.title = "\(userName) \(NSLocalizedString("birthday", comment: "birthday"))"
event.startDate = userDate
event.endDate = userDate
event.notes = NSLocalizedString("happyBirthday", comment: "happyBirthday")
event.isAllDay = true
event.calendar = eventStore.defaultCalendarForNewEvents
let ekrules: EKRecurrenceRule = EKRecurrenceRule.init(recurrenceWith: .yearly, interval: 1, end: nil)
event.recurrenceRules = [ekrules]
//event.addAlarm(EKAlarm(absoluteDate: event.startDate))
//sets alert 00:00 on day of event
event.addAlarm(EKAlarm(relativeOffset: 0))
do {
try eventStore.save(event, span: .futureEvents, commit: true)
eventId = event.eventIdentifier ?? "no-Id"
print("Event has been saved with id \(String(describing: eventId))")
userDefaults.setValue(eventId, forKey: "eventId")
} catch let error as NSError {
print("error: \(error)")
}
completion(eventId)
} else {
print("error not granted: \(String(describing: error))")
completion(nil)
}
}
}
and then use it like so
addEventToCalendar(userName: uName, userDate: uDate) { (eventIdentifier) in
if let eventId = eventIdentifier {
print("Event add birthday id \(eventId)")
//// saving data to device
// run on main thread to avoid 'RLMException', reason: 'Realm accessed from incorrect thread.'
DispatchQueue.main.async {
let newItem = Item()
newItem.userImageName = String(describing: self.userImageUrl)
newItem.userName = uName
newItem.isYearPresent = uYearPresent
newItem.userDOB = uDOB
newItem.color = UIColor.init(randomFlatColorOf: .dark).hexValue()
newItem.daysRemaining = daysRemain
newItem.eventId = eventId
self.save(item: newItem)
// review app
self.review()
}

Fetching core data issue

I have an array with a SQLite with about 2000 records and all are listed on a tableview. When one of the records are selected, it goes to the "speciesDetailViewController" were it displays details of that item, including the common name of that species.
Currently, all displayed fields are not editable.
I am now adding the ability for the user to to change one of the fields, their common name and the ability to add notes per species.
The minor change is saved in CoreData as I have no experience with SQLite (hired someone).
I am fairly certain the data is being stored as I have print commands showing so.
My issue seems to be retrieving the data.
Note that as editing this field is optional, not every species will have a record in coreData, only the species that the user updated their common name.
class SpeciesDetailData: NSManagedObject
{
#NSManaged var speciesName: String
#NSManaged var commonName: String
#NSManaged var commonForeignName: String
#NSManaged var speciesNote: String
}
.
var speciesDetailData : SpeciesDetailData?
var speciesDataObject: [NSManagedObject] = []
var speciesNameToSave = String()
#IBAction func ckSaveCommonNameButton(_ sender: Any) {
speciesNameToSave = speciesLabel.text!
self.saveSpeciesName(speciesName: speciesNameToSave)
let commonNameToSave = ckCommonNameTextField.text
self.saveCommonName(commonName: commonNameToSave!)
}
func saveCommonName (commonName: String) {
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "SpeciesDetailData", in: managedContext)!
let saveEntity = NSManagedObject(entity: entity, insertInto: managedContext)
saveEntity.setValue(commonName, forKey: "commonName")
saveSpeciesName(speciesName: speciesNameToSave)
do {
try managedContext.save()
speciesDataObject.append(saveEntity)
print(commonName)
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
func saveSpeciesName (speciesName: String) {
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "SpeciesDetailData", in: managedContext)!
let saveEntity = NSManagedObject(entity: entity, insertInto: managedContext)
saveEntity.setValue(speciesName, forKey: "speciesName")
do {
try managedContext.save()
speciesDataObject.append(saveEntity)
print(speciesName)
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
Here is the retrieving function
func retrieveCoreDataSpecies () {
let context = (UIApplication.shared.delegate
as! AppDelegate).persistentContainer.viewContext
let entity = NSEntityDescription.entity(
forEntityName: "SpeciesDetailData", in: context)
let request: NSFetchRequest<SpeciesDetailData> = SpeciesDetailData.fetchRequest()
request.entity = entity
let pred = NSPredicate(format: "speciesName = %#", specieDetail.specie)
request.predicate = pred
do {
let results = try context.fetch(request as!
NSFetchRequest<NSFetchRequestResult>)
if (results.count > 0) {
let match = results[0] as! NSManagedObject
if speciesDetailData?.commonName != nil {
ckCommonNameTextField.text = match.value(forKey: "commonName") as? String
} else {
}
if ckNotesTextView.text == "" || ckNotesTextView.text == nil {
} else {
ckNotesTextView.text = match.value(forKey: "speciesNote") as! String
}
}
} catch let error {
print("Count not fetch \(error), \(error.localizedDescription)")
}
}
When it gets to the
if speciesDetailData?.commonName != nil
it thinks the record is empty and skips over the needed lines.
Any help is appreciated
You are creating separate objects in the two save functions. In each case you are setting only one of the properties, so after you call saveSpeciesName you will have created an object with species name set, and after you call saveCommonName you will have created a different object with the common name set. You need to set both the species name and the common name on the same object.

How to set an entities attribute via relationship?

I have the following entities and relationship
I want to be able to set an exercise to have a nil result for its routine name relationship, if that makes sense? so that it can later be set as a routine name when the routine entity is formed.
My question is, how do you set this sort of attribute up? I am trying the following code but it causes a fatal crash:
userExercise.usersroutine?.name = nil
My logic being that i take the exercise and follow the relationship to the name property and set it to nil?
Thanks for any correction and clarification on my logic
EDIT: Added my existing exercise and routine save functions
func createExercise() {
guard let managedObjectContext = managedObjectContext else { return }
if let userExercise = userExercise {
userExercise.name = userExerciseName.text
userExercise.sets = Int64(userSetsCount)
userExercise.reps = Int64(userRepsCount)
userExercise.weight = Double(self.userExerciseWeight.text!)!
userExercise.id = UUID().uuidString
userExercise.routine = nil
}
do {
try managedObjectContext.save()
} catch {
fatalError("Failure to save context: \(error)")
}
}
Routine Creation:
func createRoutine() {
guard let managedObjectContext = managedObjectContext else { return }
let userRoutine = UserRoutine(context: managedObjectContext)
userRoutine.name = workoutNameTextfield.text
do {
try managedObjectContext.save()
} catch {
fatalError("Failure to save context: \(error)")
}
}
Current Fetch Request:
fileprivate lazy var fetchedResultsController: NSFetchedResultsController<UserExercise> = {
let fetchRequest: NSFetchRequest<UserExercise> = UserExercise.fetchRequest()
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "id", ascending: true)]
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.persistentContainer.viewContext, sectionNameKeyPath: nil, cacheName: nil)
fetchedResultsController.delegate = self
return fetchedResultsController
Please check the implementation below I'have created some exercises and routines. Also read comments in code, this will help you figure out how to go about it.
Function to add a new exercise
func createExercise(weight: Int16, respetitions: Int16, name: String, routine: Routine?)->Exercise? {
let context = getMainContext()
let exercise = NSEntityDescription.insertNewObject(forEntityName: "Exercise", into: context) as! Exercise
exercise.setValue(weight, forKey: "weight")
exercise.setValue(name, forKey: "name")
exercise.setValue(respetitions, forKey: "rep")
do {
try context.save()
return exercise
}
catch
{
fatalError("unable to Ssavve")
}
}
Function to add a new routine
func createRoutine(name: String, exercises:[Exercise]) {
let context = getMainContext()
let routine = NSEntityDescription.insertNewObject(forEntityName: "Routine", into: context) as! Routine
routine.name = name
//Iterate over Exercise objects & check if routine is nil.
//Here if routine is not nil it menas your exercise is already assigned to a routine.
//If routine is nil assign routine.addToRelationship(<#T##value: Exercise##Exercise#>) and Also assign routine to the execise.
do {
try context.save()
}
catch
{
fatalError("unable to Ssavve")
}
}
Function to get main NSManagedObjectContext on which we can perform core-data actions
func getMainContext() -> NSManagedObjectContext {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
return appDelegate.persistentContainer.viewContext
}
Below, First I create few exercises without any relationship to routines
"The routine doesnt exist when the exercises are created, it is created afterward and its name is set"
and then create routines by passing some exercises (You can refer to other answer on how to fetch exercises with routine as nil values)
func initializer() {
//I'm adding exercises first without routines
let ex1 = self.createExercise(weight: 10, respetitions: 4, name: "Exercise1", routine: nil)
let ex2 = self.createExercise(weight: 5, respetitions: 10, name: "Exercise2", routine: nil)
let ex3 = self.createExercise(weight: 20, respetitions: 2, name: "Exercise3", routine: nil)
let ex4 = self.createExercise(weight: 5, respetitions: 10, name: "Exercise2", routine: nil)
self.createRoutine(name: "Routine 1", exercises: [ex1!, ex2!]) //You can pass all the exercises or use fetch request to query exercises with routine as nil
self.createRoutine(name: "Routine 2", exercises: [ex3!, ex4!])
self.createRoutine(name: "Routine 3", exercises: [ex1!, ex2!]) //This routine shall not be adding any execises as they are already added to othe routines
}
Updating create routine Function to query results of UserExercise which has usersroutine as nil
func createRoutine() {
guard let managedObjectContext = managedObjectContext else { return }
let userRoutine = UserRoutine(context: managedObjectContext)
userRoutine.name = workoutNameTextfield.text
//Getting nil value User Exercises
let request: NSFetchRequest<UserExercise> = UserExercise.fetchRequest()
request.predicate = NSPredicate(format: "usersroutine == nil")
do {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let queryResults = try context.fetch(request)
//I like to check the size of the returned results!
print ("num of results = \(queryResults.count)")
//You need to convert to NSManagedObject to use 'for' loops
for exercise in queryResults as [NSManagedObject] {
//get the Key Value pairs (although there may be a better way to do that...
print("Exercise NAME: \(exercise.value(forKey: "name"))")
}
} catch {
print("Error with request: \(error)")
}
do {
try managedObjectContext.save()
} catch {
fatalError("Failure to save context: \(error)")
}
}
It doesn't seem that you should need to use the name attribute at all. This attribute should be used for storing the actual name of the UserRoutine and not for anything relationship based.
The relationships between entities in Core Data don't rely on a particular attribute of an entity, but between the entities themselves.
"I want the routine builder to look at the exercises and import all the exercises with nil in the relationship into it"
So...
Create a fetch request to fetch all the entities of UserExercise that don't have a related UserRoutine (i.e. where userroutine is nil).
let orphanedExerciseFetchRequest = NSFetchRequest(entityName: "UserExercises")
orphanedExerciseFetchRequest.predicate = NSPredicate(format: "userroutine == nil)
Execute this fetch request to get an array of UserExercises (with no related routine)
let orphanedExercises = managedObjectContext.executeFetchRequest(orphanedExerciseFetchRequest())
"creating a routine with attributed exercises"
Set the fetched UserExercise entitiy's property userRoutine to your routine (and don't forget to save the changes in your managed object context).
myRoutine.userexercises = orphanedExercises
Later, if you want to get the exercises for a particular routine:
let fetchRequest = NSFetchRequest(entityName: "UserExercises")
fetchRequest.predicate = NSPredicate(format: "userroutine == %#", someUserRoutine)

Array out of index when getting PFFile from Parse

My app gets the images just fine but if I scroll up and down for a while and then pull to refresh, it crashes and says array out of index. This does not happen every time. I believe it has something to do with the fact that I am not using getdatainbackground but when I go to use that instead of getData(), it loops faster than it can actually retrieve the files and they are out of order.
My current code also gives me this error: Break on warnBlockingOperationOnMainThread() to debug.
2016-03-02 00:35:48.630 App Name[1137:96655] Warning: A long-running operation is being executed on the main thread.
if Reachability.isConnectedToNetwork() {
self.skip = 0
//--RESETTING THE ARRAYS FOR DATA--\\
self.contactText.removeAll(keepCapacity: true)
self.names.removeAll(keepCapacity: true)
self.images.removeAll(keepCapacity: true)
self.prices.removeAll(keepCapacity: true)
self.sizes.removeAll(keepCapacity: true)
self.conditions.removeAll(keepCapacity: true)
self.dates.removeAll(keepCapacity: true)
self.ids.removeAll(keepCapacity: true)
self.createdBy.removeAll(keepCapacity: true)
//--RESET THE USERS LOCATION WHEN HE REFRESHES IN CASE OF A DARASTIC MOVE IN LOCATION--\\
let userGeoPoint = PFUser.currentUser()!["location"] as! PFGeoPoint
//--GETTING ALL OF THE OBJECTS WITHIN 60 MILES OF USERS CURRENT LOCATION--\\
let query = PFQuery(className:"Shoes")
query.whereKey("Location", nearGeoPoint: userGeoPoint, withinMiles: 60)
let user = PFUser.currentUser() as PFUser!
let array: AnyObject? = user["blockedUsers"]
if(array != nil){
query.whereKey("createdBy", notContainedIn: array! as! [AnyObject])
}
query.limit = 50
query.orderByDescending("createdAt")
query.skip = self.skip
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
print("Successfully retrieved \(objects!.count) scores.")
for object in objects! {
if let dateCreated = object.createdAt as NSDate? {
self.dates.append(dateCreated)
}
self.contactText.append(object["Contact"] as! String)
self.descriptions.append(object["Description"] as! String)
self.names.append(object["Name"] as! String)
if object["price"] as! String == "" || object["price"] == nil{
self.prices.append("Negotiable")
}else{
self.prices.append(object["price"] as! String)
}
if object["size"] as! String == "" || object["size"] == nil{
self.sizes.append("N/A")
}else{
self.sizes.append(object["size"] as! String)
}
if object["conditionType"] as! String == "" || object["conditionType"] == nil{
self.conditions.append("N/A")
}else{
self.conditions.append(object["conditionType"] as! String)
}
self.ids.append(object.valueForKey("objectId") as! String)
self.createdBy.append(object["createdBy"] as! String)
let imageFile = object["imageFile"] as! PFFile
let imageData = imageFile.getData()
if (imageData != nil) {
let image = UIImage(data:imageData!)
self.images.append(image!)
}
}
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()
})
}
else {
print(error)
}
self.refresher.endRefreshing()
}
self.skip+=50
} else {
print("Internet connection not available")
self.refresher.endRefreshing()
let alert = UIAlertView(title: "No Internet connection", message: "Please ensure you are connected to the Internet", delegate: nil, cancelButtonTitle: "OK")
alert.show()
}
Try adding a check to make sure your arrays aren't empty when assigning data to cells.