valid if an object already exists in coreData swift - swift

I have a record of students and grades, where it is saved:
Name
Spanish rating
science grade
math qualification
physical qualification
etc.
My question is how can I check if a student has already been registered, all this is done with CoreData.
I want to avoid duplicate records.
I want to use the name to check if a record with that same name already exists or not.
if it already exists, an alert is launched and if not, it is saved.
I have little time programming.
in this way the data is saved in CoreData.
data register.
private func saveStudent(){
do{
let currentStudent = Student(context: self.context)
currentStudent.name = self.name
currentStudent.spanish = self.spanish
currentStudent.science = self.science
currentStudent.math = self.math
currentStudent.physical = self.physical
currentStudent.average = self.average
try context.save()
SCLAlertView().showSuccess("Registrado", subTitle: "Estudiante registrado.", closeButtonTitle: "Aceptar", animationStyle: .bottomToTop)
}catch{
SCLAlertView().showError("Error", subTitle: "Error al guardar los datos.", closeButtonTitle: "Aceptar", animationStyle: .bottomToTop)
}
}
so the data from Core data is retrieved, an array is filled and the array fills a tableView.
Swift
class ViewController: UIViewController {
//MARK: - Private
private var arrayOfRatings = [Student]()
private let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
recuperarDatos()
}
private func recuperarDatos(){
do{
try self.arrayOfRatings = self.context.fetch(Student.fetchRequest())
DispatchQueue.main.async {
self.tableView.reloadData()
}
}catch{
SCLAlertView().showError("Error", subTitle: "Error al recuperar datos.", closeButtonTitle: "Aceptar", animationStyle: .bottomToTop)
}
}
retrieve data and reload tableView
my problem is that it should not be possible to register 2 students with the same name.
Before registering a new student I want to check if it has not been saved before.

Related

fatal errors with optionals not making sense

I keep getting a fatal error saying how a value was unwrapped and it was nil and I don't understand how. When I instantiate a view controller with specific variables they all show up, but when I perform a segue to the exact VC, the values don't show up.
Take these functions for example...
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
if let displayVC = storyboard?.instantiateViewController(withIdentifier: Constants.Storyboards.TeachStoryboardID) as? SchoolEventDetailsViewController {
displayVC.selectedEventName = events[indexPath.row].eventName
displayVC.selectedEventDate = documentsDate[indexPath.row].eventDate
displayVC.selectedEventCost = documentsCost[indexPath.row].eventCost
displayVC.selectedEventGrade = documentsGrade[indexPath.row].eventGrade
displayVC.selectedEventDocID = documentsID[indexPath.row]?.docID
navigationController?.pushViewController(displayVC, animated: true)
}
}
This combined with this function :
func verifyInstantiation() {
if let dateToLoad = selectedEventDate {
dateEditableTextF.text = dateToLoad
}
if let costToLoad = selectedEventCost {
costEditableTextF.text = costToLoad
}
if let gradesToLoad = selectedEventGrade {
gradesEditableTextF.text = gradesToLoad
}
if let docIDtoLoad = selectedEventDocID {
docIDUneditableTextF.text = docIDtoLoad
}
if let eventNameToLoad = selectedEventName {
eventNameEditableTextF.text = eventNameToLoad
}
}
Helps load the data perfectly, but when I try to perform a segue from a search controller the data is not there.
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = selectedEventName
I set the title of the vc to have the event name , and I also recently added a text field to store it as well for experimental purposes (this question).
Now the issue is I want to do a data transfer from an Algolia Search Controller to that VC and I got all the other fields to show up, except for one and that was the document ID. So I created a completion handler function to get the document ID as a string and have it inserted into the vc when the segue is performed, just like how it's there when the vc is instantiated.
Here is the function :
func getTheEventDocID(completion: #escaping ((String?) -> ())) {
documentListener = db.collection(Constants.Firebase.schoolCollectionName).whereField("event_name", isEqualTo: selectedEventName ?? navigationItem.title).addSnapshotListener(includeMetadataChanges: true) { (querySnapshot, error) in
if let error = error {
print("There was an error fetching the documents: \(error)")
} else {
self.documentsID = querySnapshot!.documents.map { document in
return EventDocID(docID: (document.documentID) as! String)
}
let fixedID = "\(self.documentsID)"
let substrings = fixedID.dropFirst(22).dropLast(3)
let realString = String(substrings)
completion(realString)
}
}
}
I thought either selectedEventName or navigationItem.title would get the job done and provide the value when I used the function in the data transfer function which I will show now :
//MARK: - Data Transfer From Algolia Search to School Event Details
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
otherVC.getTheEventDocID { (eventdocid) in
if let id = eventdocid {
if segue.identifier == Constants.Segues.fromSearchToSchoolEventDetails {
let vc = segue.destination as! SchoolEventDetailsViewController
vc.selectedEventName = self.nameTheEvent
vc.selectedEventDate = self.dateTheEvent
vc.selectedEventCost = self.costTheEvent
vc.selectedEventGrade = self.gradeTheEvent
vc.selectedEventDocID = id
}
}
}
}
But it ends up showing nothing when a search result is clicked which is pretty upsetting, I can't understand why they're both empty values when I declared them in the SchoolEventDetailsVC. I tried to force unwrap selectedEventName and it crashes saying there's a nil value and I can't figure out why. There's actually a lot more to the question but I just tried to keep it short so people will actually attempt to read it and help since nobody ever reads the questions I post, so yeah thanks in advance.
I'm a litte confused what the otherVC is, which sets a property of itself in the getTheEventDocID, whilste in the closure you set the properties of self, which is a different controller. But never mind, I hope you know what you are doing.
Since getTheEventDocID runs asynchronously, the view will be loaded and displayed before the data is available. Therefore, viewDidLoad does not see the actual data, but something that soon will be outdated.
So, you need to inform the details view controller that new data is available, and refresh it's user interface. Something like
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
otherVC.getTheEventDocID { (eventdocid) in
if let id = eventdocid {
if segue.identifier == Constants.Segues.fromSearchToSchoolEventDetails {
let vc = segue.destination as! SchoolEventDetailsViewController
vc.selectedEventName = self.nameTheEvent
vc.selectedEventDate = self.dateTheEvent
vc.selectedEventCost = self.costTheEvent
vc.selectedEventGrade = self.gradeTheEvent
vc.selectedEventDocID = id
vc.updateUI()
}
}
}
}
and in the destination view controller:
class SchoolEventDetailsViewController ... {
override func viewDidLoad() {
super.viewDidLoad()
updateUI()
}
func updateUI () {
navigationItem.title = selectedEventName
// and so on
}
}
Ok so I decided to attempt a workaround and completely ditched the getTheEventDocID() method because it was just causing me stress. So I decided to ditch Firebase generated document IDS and just use 10 digit generated ids from a function I made. I also figured out how to add that exact same 10 digit id in the Algolia record by just storing the random 10 digit id in a variable and using it in both places. So now instead of using a query call to grab a Firebase generated document ID and have my app crash everytime I click a search result, I basically edited the Struct of the Algolia record and just added an eventDocID property that can be used with hits.hitSource(at: indexPath.row).eventDocID.
And now the same way I added the other fields to the vc by segue data transfer, I can now do the same thing with my document ID because everything is matching :).

Download single Object of Firestore and save it into an struct/class object

I am coding since January 2019 and this is my first post here.
I am using Swift and Firestore. In my App is a tableView where I display events loaded out of a single Document with an array of events inside as [String: [String:Any]]. If the user wants to get more infos about an event he taps on it. In the background the TableViewController will open a new "DetailEventViewController" with a segue and give it the value of the eventID in the tapped cell.
When the user is on the DetailViewController Screen the app will download a new Document with the EventID as key for the document.
I wanna save this Data out of Firestore in a Struct called Event. For this example just with Event(eventName: String).
When I get all the data I can print it directly out but I can't save it in a variable and print it out later. I really don't know why. If I print the struct INSIDE the brackets where I get the data its working but if I save it into a variable and try to use this variable it says its nil.
So how can I fetch data out of Firestore and save in just a Single ValueObject (var currentEvent = Event? -> currentEvent = Event.event(for: data as [String:Any]) )
I search in google, firebaseDoc and stackoverflow but didn't find anything about it so I tried to save all the singe infos of the data inside a singe value.
// Struct
struct Event {
var eventName: String!
static func event(for eventData: [String:Any]) -> Event? {
guard let _eventName = eventData["eventName"] as? String
else {
print("error")
return nil
}
return Event(eventName: _eventName)
}
// TableView VC this should work
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ShowEventDetailSegue" {
if let ShowEvent = segue.destination as? DetailEventViewController, let event = eventForSegue{
ShowEvent.currentEventId = event.eventID
}
}
}
// DetailViewController
var currentEvent = Event()
var currentEventId: String?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
guard let _eventID = currentEventId else {
print("error in EventID")
return}
setupEvent(eventID: _eventID) /* currentEvent should be set here */
setupView(event: currentEvent) /* currentEvent has after "setupEvent" the value of nil */
}
func setupEvent(eventID: String) {
let FirestoreRef = Firestore.firestore().collection("events").document(eventID)
FirestoreRef.getDocument { (document, error) in
if let err = error {
debugPrint("Error fetching docs: \(err)")
SVProgressHUD.showError(withStatus: "Error in Download")
}else {
if let document = document, document.exists {
guard let data = document.data() else {return}
let eventData = Event.event(for: data as [String:Any])
print(eventData)
//here all infos are printed out - so I get them
self.currentEvent = eventData!
//Here is the error.. I can't save the fetched Data in my single current Event
} else {
SVProgressHUD.showError(withStatus: "Error")
}
}
}
}
func setupView(event: Event) {
self.titleLabel.text = event.eventName
}
I expect that the function setupEvents will give the currentEvent in the DetailViewController a SINGLEvalue cause its a SINGLE document not an array. So I can use this single Eventvalue for further actions. Like starting a new segue for a new ViewController and just push the Event there not

How to get Custom Cell to update labels to information from core data

I'm not getting any errors, just not the result I am looking for.
Here is where I gather data from the user
(If this is not relevant please let me know so I can declutter the post)
I want my custom cell to display the core data record once it's added, but it keeps displaying placeholder labels instead of updating.
class ViewController: UIViewController,UITableViewDataSource, UITableViewDelegate
{
var parties: [NSManagedObject] = []
#IBOutlet weak var tableView: UITableView!
#IBAction func addParty(_ sender: UIBarButtonItem)
{
/*init alert controller with title, message & .alert style*/
let alert = UIAlertController(title: "New Name",
message: "Add a new name",
preferredStyle: .alert)
/*create a name text field, with placeholder "name"*/
alert.addTextField(configurationHandler: { (textFieldName) in
textFieldName.placeholder = "name"
})
/*create a ssn text field, with placeholder "ssn"*/
alert.addTextField(configurationHandler: { (textFieldSize) in
textFieldSize.placeholder = "size"
})
/*create a ssn text field, with placeholder "ssn"*/
alert.addTextField(configurationHandler: { (textFieldContact) in
textFieldContact.placeholder = "contact"
})
/*create a ssn text field, with placeholder "ssn"*/
alert.addTextField(configurationHandler: { (textFieldLocation) in
textFieldLocation.placeholder = "location"
})
/*create a save action*/
let saveAction = UIAlertAction(title: "Save", style: .default) { [unowned self] action in
/*find textfield's text (name) guard let way to get unwrap value otherwise return early*/
guard let textField = alert.textFields?.first,
let nameToSave = textField.text else {
return
}
/*find textfield's text (ssn) guard let way to get unwrap value otherwise return early*/
guard let textFieldSize = alert.textFields?[1],
let sizeToSave = textFieldSize.text else {
return
}
/*find textfield's text (ssn) guard let way to get unwrap value otherwise return early*/
guard let textFieldContact = alert.textFields?[2],
let contactToSave = textFieldContact.text else {
return
}
/*find textfield's text (ssn) guard let way to get unwrap value otherwise return early*/
guard let textFieldLocation = alert.textFields?[3],
let locationToSave = textFieldLocation.text else {
return
}
/*call save method by passing nameToSave and SSNToSave*/
self.save(name: nameToSave, size: sizeToSave, contact: contactToSave, location: locationToSave)
self.tableView.reloadData()
}
let cancelAction = UIAlertAction(title: "Cancel",
style: .default)
alert.addAction(saveAction)
alert.addAction(cancelAction)
present(alert, animated: true)
}
// Save core data function
func save(name: String, size : String, contact: String, location: String)
{
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
/*1.
Before you can save or retrieve anything from your Core Data store, you first need to get your hands on an NSManagedObjectContext. You can consider a managed object context as an in-memory “scratchpad” for working with managed objects.
Think of saving a new managed object to Core Data as a two-step process: first, you insert a new managed object into a managed object context; then, after you’re happy with your shiny new managed object, you “commit” the changes in your managed object context to save it to disk.
Xcode has already generated a managed object context as part of the new project’s template. Remember, this only happens if you check the Use Core Data checkbox at the beginning. This default managed object context lives as a property of the NSPersistentContainer in the application delegate. To access it, you first get a reference to the app delegate.
*/
let managedContext = appDelegate.persistentContainer.viewContext
/*
An NSEntityDescription object is associated with a specific class instance
Class
NSEntityDescription
A description of an entity in Core Data.
Retrieving an Entity with a Given Name here person
*/
let entity = NSEntityDescription.entity(forEntityName: "Party",
in: managedContext)!
/*
Initializes a managed object and inserts it into the specified managed object context.
init(entity: NSEntityDescription,
insertInto context: NSManagedObjectContext?)
*/
let party = NSManagedObject(entity: entity,
insertInto: managedContext)
/*
With an NSManagedObject in hand, you set the name attribute using key-value coding. You must spell the KVC key (name in this case) exactly as it appears in your Data Model
*/
party.setValue(name, forKeyPath: "name")
party.setValue(size, forKeyPath: "size")
party.setValue(contact, forKeyPath: "contact")
party.setValue(location, forKeyPath: "location")
/*
You commit your changes to person and save to disk by calling save on the managed object context. Note save can throw an error, which is why you call it using the try keyword within a do-catch block. Finally, insert the new managed object into the people array so it shows up when the table view reloads.
*/
do {
try managedContext.save()
parties.append(party)
tableView.reloadData()
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
// TABLE VIEW CODE
func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int
{
return parties.count
}
//NEED TO FIX WHY CUSTOM CELL NOT DISPLAYING INFO
func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
(print(tableView.dequeueReusableCell(withIdentifier: "PartyCell", for: indexPath)))
let party = parties[indexPath.row] as NSManagedObject
let cell = tableView.dequeueReusableCell(withIdentifier: "PartyCell",
for: indexPath) as! PartyCell
cell.nameLabel?.text = party.value(forKeyPath: "name") as? String
cell.sizeLabel.text = party.value(forKeyPath: "size") as? String
cell.contactLabel.text = party.value(forKeyPath: "contact") as? String
cell.locationLabel.text = party.value(forKeyPath: "location") as? String
return cell
}
override func viewDidLoad()
{
super.viewDidLoad()
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}

Ignoring duplicated records in picker view swift 3

I am trying to do dynamic picker view which shows extra data whenever I will get new input in data base.
At this moment I am using notification and observer to take care of real time updates. Also I am removing all items from string array to get just one newest record - it is not what i want.
override func viewDidLoad() {
super.viewDidLoad()
pickerView.delegate = self
pickerView.dataSource = self
pickerView.sizeToFit()
NotificationCenter.default.addObserver(forName: SEARCH_RESULT, object: nil, queue: nil, using: catchNotificationForSearchResult)
}
func catchNotificationForSearchResult(_notification: Notification) {
let _resultSearch = _notification.object as! [Class]
for _result in _resultSearch {
_stringArray.removeAll()
_stringArray.append("AAA : \(_result.aaa), BBB \(_result.bbb), CCC : \(_result.ccc)")
pickerView.reloadAllComponents()
}
}
AppDelegate:
fun test() {
let _fetchRequest:NSFetchRequest<Class> = Class.fetchRequest()
do {
let _searchResults = try DataBaseController.getContext().fetch(_fetchRequest)
NotificationCenter.default.post(name: SEARCH_RESULT, object: _searchResults)
} catch {
print("Error \(error)")
}
}
Thanks in advance!

Realm causing program to behave unexpectedly. (OS X and Swift)

So I am creating a fairly simple program using Realm as my database. I am fairly new to programing in Swift (or any OS X or iOS environment.) In my program when a button is pressed IBAction func createInvoice I want a few things to happen, I want to count the previous rows in the database and create an invoice number, I want to write new data to the database and I want to call a new view and view controller and pass along the invoice number. My code works except for one thing when using Realm the new view controller is called (override func prepareForSegue) before the invoice number is created so a 0 value is passed along to the new view controller.
If I create a dummy invoice number value such as let invoicenumber = 42 everything works perfectly. It seems that Realm is causing things to happen 'out of order' How can I make the veiwcontroller wait for a value before loading?
#IBAction func createInvoice(sender: AnyObject) {
let realm = Realm()
let invoicepull = Invoice()
let invoicecount = realm.objects(Invoice)
let invoicenraw = invoicecount.count
let a = 100
let invoicenumber = a + invoicenraw
var invoicefile = Invoice()
invoicefile.inumber = invoicenumber
invoicefile.cnumber = clientcombo.stringValue
invoicefile.cost = owed.doubleValue
invoicefile.paid = paid.doubleValue
invoicefile.sevicecode = service.stringValue
invoicefile.dateofservice = NSDate()
// Save your object
realm.beginWrite()
realm.add(invoicefile)
realm.commitWrite()
//Sent notification
performSegueWithIdentifier("cinvoiceseuge", sender: nil)
println("Inside Action")
println(invoicenumber)
dismissViewController(self)
}
override func prepareForSegue(segue: NSStoryboardSegue, sender: AnyObject!) {
if (segue.identifier == "cinvoiceseuge") {
//Checking identifier is crucial as there might be multiple
// segues attached to same view
var detailVC = segue.destinationController as! invociegenerator;
detailVC.toPass = invoicenumber
println("Inside Sugue")
println(invoicenumber)
}
}
If createInvoice is happening on a different thread than prepareForSegue, you'll have to refresh the realm (Realm().refresh()) before accessing your invoicenumber variable (which I assume is of type RealmSwift.Object).
I have solved this issue, thanks to the help of #Shmidt by using Realm's built in notification center. To use the notifications you can use this basic structure.
var notificationToken: NotificationToken?
deinit{
let realm = Realm()
if let notificationToken = notificationToken{
realm.removeNotification(notificationToken)
}
}
override func viewDidLoad() {
super.viewDidLoad()
let realm = Realm()
notificationToken = realm.addNotificationBlock { note, realm in
println("The realm is complete")
}
...
}
One small other error in my code was let invoicenumber = a + invoicenraw I needed to drop the let as it is a variable and not a constant.