save increase int as multiple core data entities - swift

In my swift code below I am trying to save ints to core data. Every time a user hits a button a new int is created. So if the user hits the button twice there are know 2 int entities in core data. My code below is having a runtime error and I dont know how to solve it.
pic
var pageNumber = 0
var itemName : [NSManagedObject] = []
func enterData() {
let appDeldeaget = UIApplication.shared.delegate as! AppDelegate
let context = appDeldeaget.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "Player", in: context)
let theTitle = NSManagedObject(entity: entity!, insertInto: context)
theTitle.setValue(pageNumber, forKey: "positon")
do {
try context.save()
itemName.append(theTitle)
pageNumber += 1
}
catch {
}
self.theScores.reloadData()
positionTextField.text = ""
positionTextField.resignFirstResponder()
}

You are introducing a few complications that might be causing the issue.
First, if I understood your purpose, the itemName should not be an array of NSManagedObject, but rather an array of Player. Also, creating the theTitle can be simplified.
Try this instead of the code you proposed:
var pageNumber = 0
// If I understand correctly, you should have an array of Players
var itemName : [Player] = []
func enterData() {
let appDeldeaget = UIApplication.shared.delegate as! AppDelegate
let context = appDeldeaget.persistentContainer.viewContext
// Simpler way to create a new Core Data object
let theTitle = Player(context: context)
// Simpler way to set the position attribute
theTitle.position = pageNumber // pageNumber must be of type Int64, otherwise use Int64(pageNumber)
do {
try context.save()
itemName.append(theTitle)
pageNumber += 1
} catch {
// handle errors
}
// rest of the code
}

Related

Why is this Label not showing the correct value? Swift

I wrote a code to take data from my CoreDate Entity to show the highest Integer as the value at a Highscore label. I don't understand why it is not working? I tried it with or without a extra function...
func loadHighscore() {
//Kontext identifizieren
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
//Anfrage stellen
let context = appDelegate.persistentContainer.viewContext
let entityName = "PushUps"
let request = NSFetchRequest<NSFetchRequestResult>(entityName: entityName)
do {
let results = try context.fetch(request)
for result in results {
guard let count = (result as! NSManagedObject).value(forKey: "highScore") as? Int16 else {
return
}
}
if count > highScore {
highscoreLabel.text = "Highscore: \(count)"
highScoreChanged(newHighScore: Int16(count))
// Console statement:
print("New Highscore: \(count)")
}
} catch {
print("error")
}
}
func highScoreChanged(newHighScore: Int16) {
highscoreLabel.text = "Highscore: \(newHighScore)"
}
}
Your approach is a bit strange.
A better approach is to load the data sorted descending by highScore so the first item is the item with the highest value.
It's highly recommended to take advantage of the generic Core Data types and to use dot notation rather the KVC value(forKey
func loadHighscore() {
//Kontext identifizieren
// delegate can be forced unwrapped. The app doesn't even launch if AppDelegate doesn't exist
let appDelegate = UIApplication.shared.delegate as! AppDelegate
//Anfrage stellen
let context = appDelegate.persistentContainer.viewContext
let entityName = "PushUps"
// Use a specific fetch request
let request = NSFetchRequest<PushUps>(entityName: entityName)
// add a sort descriptor to sort the items by highScore descending
request.sortDescriptors = [NSSortDescriptor(key: "highScore", ascending: false)]
do {
// results is an array of PushUps instances, no type cast needed
let results = try context.fetch(request)
if let result = results.first, result.highScore > highScore {
highScore = result.highScore
print("New Highscore: \(highScore)")
}
} catch {
print(error)
}
highscoreLabel.text = "Highscore: \(highScore)"
}
The function highScoreChanged is not needed either. If the saved highscore is higher than the current value (property highScore) the property is updated and at the end of the method the text field is updated with the value of the property highScore.
Be sure to execute the label update in main queue. In other way it may not be done.

swift element is empty

I am trying to get the first or current exercise from my core data but swift keeps telling me that the element is empty. When i run the app and set the break points the debugger shows that the element is empty but no errors. here are the functions i am using to get the element data.
func currentWorkout() -> Workout? {
let client = currentClient()
return (appointment?.workouts as? Set<Workout>)?.first(where: { $0.client == client })
}
private func currentCard() -> Card? {
return currentWorkout()?.card
}
private func currentClientPlannedExercises() -> [ExerciseInfo] {
if let currentCard = currentCard(), let template = currentCard.template, let exerciseSets = template.exerciseSets?.array as? [ExerciseSet] {
let numCardsWithThis = (template.cardsWithThisTemplate as? Set<Card>)?.filter { $0.client != currentClient() }.count ?? 0
let exercsiseSetNumber = numCardsWithThis % exerciseSets.count
if let result = exerciseSets[exercsiseSetNumber].exercises?.array as? [ExerciseInfo] {
return result
}
}
return [ExerciseInfo]()
}
private func currentExercise() -> Exercise? {
// we can't have an exercise without a selection
guard let selectedExercise = currentExerciseInfo(), let currentCard = currentCard() else{
return nil
}
// get the first exercise on the current card that has the same exercise info as the one selected
if let exercises = currentWorkout()?.exercises as? Set<Exercise>{
return exercises.first(where: { $0.exerciseInfo == selectedExercise })
}
let exercise = Exercise(context: context)
exercise.workout = currentWorkout()
exercise.exerciseInfo = selectedExercise
//TODO: Set Seat
return exercise
}
private func currentExerciseInfo() -> ExerciseInfo? {
guard let selectedRow = exercisesTableView.indexPathForSelectedRow else {
return nil
}
return currentClientPlannedExercises()[selectedRow.row]
}
if the Issue is fetching then You can use this Code:
For Fetching the data from Core Data
var tasks: [Task] = [] //Your Entity Name in Bracket
func getData() {
do {
tasks = try context.fetch(Task.fetchRequest()) //Instead of Task your Entity Name
} catch {
print("Fetching Failed")
}
}
And use it like:
for data in tasks{
print(data.name) // All the Attributes name after data.attributename
print(data.address)
}
If it is in tableView:
let data = tasks[indexPath.row]
print(data.name)
You will get the data if it is there.
Edit to Check if Data entered or not
Print the Path like this:
let paths = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)
print(paths[0])
Go to sqlite file and open and check if there is Data or not inside that.
Edit If you are facing the issue in Adding Data to Core Data
Simple code to add Data
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let task = Task(context: context) //Entity Name here instead of Task
task.name = taskTextField.text! // Attribute name here after task.attributename
// Save the data to coredata
(UIApplication.shared.delegate as! AppDelegate).saveContext()
Hope this help.
I found the issue was in the currentExercise function it wasn't calling the first exercise until the it had an exercise. I fixed by rewriting the function
private func currentExercise() -> Exercise? {
// we can't have an exercise without a selection
guard let selectedExercise = currentExerciseInfo() else{
return nil
}
// get the first exercise on the current card that has the same exercise info as the one selected
if let exercises = currentWorkout()?.exercises as? Set<Exercise>, let firstExercise = exercises.first(where: { $0.exerciseInfo == selectedExercise }) {
return firstExercise
}
let exercise = Exercise(context: context)
exercise.workout = currentWorkout()
exercise.exerciseInfo = selectedExercise
//TODO: Set Seat
return exercise
}

Swift - How to fetch next data if value changes

I am trying to fetch next question from server after the previous one is played by users.
When arrayNum is changed, can't fetch the next question. I am thinking to use switch method to write same code over and over again. Is there a better way?
func retriveRoom(){
let idRef = db.collection("ROOMS_Collection").document(GameData.shared.documentID)
idRef.getDocument { (docSnapshot, error) in
guard let docSnapshot = docSnapshot, docSnapshot.exists else {return}
guard let data = docSnapshot.data() else { return }
let questionID = data["questionID"] as? Array<Int>
let questionRef = self.db.collection("QUESTIONS_Collection").document("\(questionID![self.arrayNum])")
questionRef.getDocument{ (docSnapshot, error) in
guard let docSnapshot = docSnapshot, docSnapshot.exists else { return }
let data = docSnapshot.data()
self.questionArray = (docSnapshot.data()?.compactMap({_ in Question(dictionary: data!)}))!
let question = self.questionArray[0]
self.answer = Int(question.answer)!
self.questionText.fontColor = UIColor.white
self.questionText.fontName = "Verdana"
self.questionText.text = ("\(question.question)")
self.questionText.fontSize = 24.0
self.questionText.numberOfLines = 0
self.questionText.lineBreakMode = NSLineBreakMode.byWordWrapping
self.questionText.zPosition = 5
self.questionText.position = CGPoint(x: (self.questionBackground?.frame.midX)! - 5, y: (self.questionBackground?.frame.midY)! - 5)
self.questionText.preferredMaxLayoutWidth = (self.questionBackground?.frame.width)! - 10
self.addChild(self.questionText)
}
}
}
If I understand what you are trying to do correctly, you want to fetch another question from your database when arrayNum changes. I think your best bet is to try a property observer function on your arrayNum variable. Something like this:
In your class declaration where you declare arrayNum:
var arrayNum : Int {
didSet {
// Implement retrieveRoom() here
retrieveRoom()
}
}
What is not clear from your code is exactly when and how arrayNum changes. As an alternative to didSet you can also use willSet, which, as the name suggests, is called when a parameter value is about to be reassigned. You can read up on this here:
https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html
(And by the way, it might serve your user experience better if you were to fetch a batch of questions in one go rather than repeat the database call every time a new question is required. But that is not strictly relevant to your question.)
Hope that helps.

how to read firebase database

This is my data structure: many clubs, each club has address. I tried to make the database flat.
Now I want to load a few club info on table view. When I swipe down iPhone screen, it will load next a few club info.
This is my code. But it loads all club info. How can I load only a few club, and load next a few club when user swipe down?
func loadClubs() {
ref = Database.database().reference()
ref.child("club").observe(DataEventType.value, with: { (snapshot) in
//print("clubs: \(snapshot)")
let array:NSArray = snapshot.children.allObjects as NSArray
for obj in array {
let snapshot:DataSnapshot = obj as! DataSnapshot
if let childSnapshot = snapshot.value as? [String: AnyObject] {
if let clubName = childSnapshot["name"] as? String {
print(clubName)
}
}
}
})
}
Firebase's queries support pagination, but it's slightly different from what you're used to. Instead of working with offsets, Firebase uses so-called anchor values to determine where to start.
Getting the items for the first page is easy, you just specify a limit to the number of items to retrieve:
ref = Database.database().reference()
query = ref.child("club").queryOrderedByKey().limitToFirst(10)
query.observe(DataEventType.value, with: { (snapshot) in
Now within the block, keep track of the key of the last item you've shown to the user:
for obj in array {
let snapshot:DataSnapshot = obj as! DataSnapshot
if let childSnapshot = snapshot.value as? [String: AnyObject] {
lastKey = childSnapshot.key
if let clubName = childSnapshot["name"] as? String {
print(clubName)
}
}
}
Then to get the next page, construct a query that starts at the last key you've seen:
query = ref.child("club").queryOrderedByKey().startAt(lastKey).limitToFirst(11)
You'll need to retrieve one more item than your page size, since the anchor item is retrieve in both pages.
i think the right way make reference to array of elements,
and make variable for index
var i = 0;
var club = null;
club = loadClubs(index); // here should return club with specified index;
// and increment index in loadClubs func,
// also if you need steped back --- and one more
// argument to function, for example
// loadClubs(index, forward) // where forward is
// boolean that says in which way we should
// increment or decrement our index
so in your example will be something like this:
func loadClubs(index, forward) {
ref = Database.database().reference()
ref.child("club").observe(data => {
var club = data[index];
if(forward){
index ++
}else{
index--
}
return club;
})
}

How to update attribute with core data

I'm trying to write and read with core data in SWIFT. When it comes to writing new data and reading existing data everything is ok. But now I want to update a record (lets say where "pagina" is "1") and suddenly I feel completely lost! I've looked into batch update, since it seems that's the best way to do it. Unfortunately none of the tutorials I've found seem to be basic enough for me.
I've created the entity "Stories" and added three attributes: "pagina" (String), "plaats" (String) and "naam" (String). The way I was going to do this was by retrieving the Page number from txtPagina.text and storing it in the variable Pagina.
Then updating the records with the UITextField contents of txtPlaats.text and txtNaam.text where pagina = Pagina. It seems simple enough. Unfortunately I can't seem figure out batch updating.
Can anyone help me? I'm totally lost here.
#IBAction func btnSave(){
var Pagina = txtPagina.text
var appDel:AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
var context:NSManagedObjectContext = appDel.managedObjectContext!
//check if data exists
var request = NSFetchRequest(entityName: "Stories")
var results:NSArray = context.executeFetchRequest(request, error:nil)!
if(results.count > 0) {
Pagina = "\(results.count)"
txtPagina.text = Pagina
//code to update plaats and naam where pagina = Pagina
} else {
var newStory = NSEntityDescription.insertNewObjectForEntityForName("Stories", inManagedObjectContext: context) as NSManagedObject
newStory.setValue(Pagina, forKey: "pagina")
newStory.setValue("\(txtPlaats.text)", forKey: "plaats")
newStory.setValue("\(txtNaam.text)", forKey: "naam")
context.save(nil)
println(newStory)
println("Object Saved.")
}
}
If the only problem you're having is updating a record, it looks as though your problem is that you aren't making a call to context.save() after updating it. This should solve that problem.
I've got something working right now, but I'm not sure it's the most efficient way:
I've created a class called Stories:
import UIKit
import CoreData
#objc(Stories)
class Stories: NSManagedObject {
#NSManaged var pagina:String
#NSManaged var plaats:String
#NSManaged var naam:String
}
Now my btnSave action is this:
#IBAction func brnSave(sender: AnyObject) {
var appDel:AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
var context:NSManagedObjectContext = appDel.managedObjectContext!
var request = NSFetchRequest(entityName: "Stories")
var results:NSArray = context.executeFetchRequest(request, error: nil) as [Stories]
if (results.count > 0) {
println(results.count)
if (results.count >= txtPagina.text.toInt()) {
for res in results{
var data = res as Stories
println(data.plaats)
if (data.pagina == txtPagina.text) {
data.plaats = txtPlaats.text
data.naam = txtNaam.text
context.save(nil)
println("Page \(data.pagina) updated")
}
}
} else {
// Save new Story
var ent = NSEntityDescription.entityForName("Stories", inManagedObjectContext: context)
var newStory = Stories(entity: ent!, insertIntoManagedObjectContext: context)
newStory.pagina = txtPagina.text
newStory.plaats = txtPlaats.text
newStory.naam = txtNaam.text
context.save(nil)
println("New story saved")
// Save new Story end
}
} else {
// Save new Story
var ent = NSEntityDescription.entityForName("Stories", inManagedObjectContext: context)
var newStory = Stories(entity: ent!, insertIntoManagedObjectContext: context)
newStory.pagina = txtPagina.text
newStory.plaats = txtPlaats.text
newStory.naam = txtNaam.text
context.save(nil)
println(newStory)
println("New story saved")
// Save new Story end
}
}
The code works, but doesn't seem perfect.
Is there a way to make this more efficient? When updating the array I'm basically rewriting the whole coredata table instead of just updating the changed data. I'm can only imagine what this does performance wise when there's a lot more data!