String Returning Multiple values in Swift - swift

I have a CoreData entity named 'Studio' with an attribute named 'name' with an NSManagedObject subclass created.
My app designed for a simple process, enter a name into a text box, and press 'save' and the name is saved into Studio.name - Press 'Update' and a text label is refreshed to show the value of Studio.name
However, it is not functioning as expected, if I, for example, enter the name 'Stack' and save the update I see 'Stack' in the text label, if i then enter 'Overflow' save/update the label reads 'Overflow', If i update it a third time to 'Swift' save/update the label again reads 'Stack'.
From there updates will give one of the three values seemingly at random.
Force quitting the app and relaunching it shows that the data is being saved to Core Data as pressing the update button will return a random previous value.
My question is, how does this happen with a string? (Shouldn't it only hold one value at a time?)
How can I correct this so it will only hold a single value and any subsequent values simply overwrite the previous value?
My code follows.
import UIKit
import CoreData
class ViewController: UIViewController {
#IBOutlet weak var studioBox: UITextField!
#IBOutlet weak var nameLabel: UILabel!
#IBAction func saveData(sender: AnyObject) {
var studio = writeStudioData()
studio.name = studioBox.text
}
#IBAction func Update(sender: AnyObject) {
var studio = getStudioData()
nameLabel.text = studio.name
}
func getStudioData() -> Studio {
let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let managedContext = appDelegate.managedObjectContext!
let request = NSFetchRequest(entityName: "Studio")
request.returnsObjectsAsFaults = false
let result = managedContext.executeFetchRequest(request, error: nil) as [Studio]
return result[0]
}
func writeStudioData () -> Studio {
let appDelegate =
UIApplication.sharedApplication().delegate as AppDelegate
let managedContext = appDelegate.managedObjectContext!
let entityDescription = NSEntityDescription.entityForName("Studio", inManagedObjectContext: managedContext)
let result = Studio(entity: entityDescription!, insertIntoManagedObjectContext: managedContext)
return result
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}

You don't have one object, you are creating a new object every time. As you aren't including a sort descriptor with your fetch request, the order you get them, and thus the corresponding name, is unspecified, meaning it could be any of them.
You could either perform a fetch first in writeStudioData to see if there's already an object, only creating one if there isn't, or you could create one object and keep it around in a property.

Related

How do I add data from a user in firebase rather than replace what is already there?

The app presents users with a random quote. I want users to be able to save the quotes and see which ones they saved. I can get a single quote to save, however, anytime the save button is clicked again, it overrides the previously saved quote with the new one. I've tried to find the answer elsewhere, but I cannot seem to get it to work. Below is my current code. I've also tried replacing setValue with updateChildValues, but I get the error Cannot convert value of type 'String' to expected argument type '[AnyHashable : Any]'.
import UIKit
import FirebaseDatabase
import FirebaseAuth
class QuotesViewController: UIViewController {
var ref: DatabaseReference?
override func viewDidLoad() {
super.viewDidLoad()
ref = Database.database().reference()
}
#IBAction func backToMain(_ sender: UIButton) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(identifier: "mainHome")
vc.modalPresentationStyle = .overFullScreen
present(vc, animated: true)
}
#IBOutlet weak var quotesLabel: UILabel!
#IBAction func saveButton(_ sender: UIButton) {
guard let user = Auth.auth().currentUser?.uid else { return }
ref!.child("users").child(Auth.auth().currentUser!.uid).child("Quotes").child("quote").setValue(quotesLabel.text!)
}
#IBOutlet weak var nextButtonOutlet: UIButton!
#IBAction func nextQuoteButton(_ sender: UIButton) {
let quotesData = QuotesData()
let randomQuote = quotesData.randomQuote()
quotesLabel.text = randomQuote
}
}
I've also tried:
ref!.child("users").child(Auth.auth().currentUser!.uid).child("Quotes").child("quote").updateChildValues(quotesLabel.text!)
That is expected, you basically are referencing the very same node child("quote") and trying to change its value. But if you want to have multiple quotes, what you need to do is to create multiple nodes under the parent child("Quotes") with different names.
One trivial way of doing so, you might append a different number to each new quote node, for example when you want to add a new quote, define the following path:
child("Quotes").child("quote1").setValue("...")
Path for another quote:
child("Quotes").child("quote2").setValue("...")
And so on.
Alternatively, you can use Firebase Database reference method childByAutoId() to generate unique names. You will use that method after defining the parent node:
ref!.child("users").child(Auth.auth().currentUser!.uid).child("Quotes").childByAutoId().setValue(quotesLabel.text!)
Note:
Try to avoid force unwrapping as much as you can because that makes your app more prone to crashes.
I think you have to add one more field inside your firebase database that must be a unique one
For first time adding data into firebase you can write something like this
let key = ref.child("Quotes").childByAutoId().key
let dict = ["quote": quotesLabel.text!,
"quoteId" : key ?? ""
] as [String: Any]
And whenever you are saving the same quote then you can update the value inside the particular quoteID like this
func updateDatainFirebase(quoteId:String,quote:String){
let dict = ["quote": quotesLabel.text!,
"quoteId" : quoteId ?? ""
] as [String: Any]
self.ref.child(quoteId).updateChildValues(dict)
}

NSManagedObject collection properties become nil after a while

I'm working in a subclass of NSViewController where in viewDidLoad I fetch my entities from CoreData as below:
let delegate = AppDelegate.init()
let context = delegate.persistentContainer.viewContext
let fetch = PicPathEntity.entityFetchRequest()
var tempEntities: [PicPathEntity] = []
context.performAndWait {
tempEntities = try! fetch.execute()
}
entities = tempEntities
Then during the itemForRepresentedObjectAt function, it appears that the count of entities is the same, but the property values have become nil/empty.
I did some searching and found similar problems with no clear answers (at least not for Swift)
I can't seem to find information on how to properly fetch and retain the data in my entities variable.
I figured out how to solve this after reading the how the relationship between objects and context work.
Turns out all I needed to make this work, was make the context into a property of the ViewController instead of a local variable of viewDidLoad.
var entities: [PicPathEntity] = []
var context: NSManagedObjectContext!
override func viewDidLoad() {
super.viewDidLoad()
let delegate = AppDelegate.init()
context = delegate.persistentContainer.viewContext
let fetch = PicPathEntity.entityFetchRequest()
context.performAndWait {
entities = try! fetch.execute()
}
}
Due to helpful comments to this answer, I have now updated the code to:
var entities: [PicPathEntity] = []
var context: NSManagedObjectContext!
override func viewDidLoad() {
super.viewDidLoad()
let delegate = NSApplication.shared.delegate as! AppDelegate
context = delegate.persistentContainer.viewContext
let fetch = PicPathEntity.entityFetchRequest()
context.perform {
self.entities = try! fetch.execute()
self.picFinderEntityCollectionView.reloadData()
}
configureCollectionView()
}

How do I change a switch in CoreData using Dynamic Tables (Swift)

I have an app that I am converting from objective-c to Swift and am also changing it to use dynamic (rather than static) tables. I can load the cells with entity rows but I have been unable to figure out how to reference the UISwitch value in the #IBAction function in order to save it to CoreData.
Can anyone point me to simple example of how to do this?
In storyboard, link the UISwitch in your prototype cell to the #IBAction handler in the view controller. In the handler, determine the core data object and manipulate as desired.
Assuming you have a fetched results controller (recommended):
#IBAction didFlipSwitch(sender: UISwitch) {
let point = sender.convertPoint(CGPointZero, toView:tableView)
let indexPath = tableView.indexPathForRowAtPoint(point)
let object = fetchedResultsController.objectAtIndexPath(indexPath) as! Thing
thing.flag = sender.on
}
Addition:
Pursuant to your question: If you are not using a fetched results controller (though this is not recommended), you are presumably using an Array like [Thing]. You would replace the one line with something like:
let object = dataArray[indexPath.row] // no need to cast to Thing
Second, if you need access to other values in the cell, you can get to the cell and its elements with
let cell = tableView.cellForRowAtIndexPath(indexPath) as! CustomCell
let textToRetrieve = cell.textField?.text
However, this is not a good method, because you are getting data from your UI elements. Instead you should always store the data in your model, not in a table view row!
Thus, the proper text attribute of your Thing should already have been set by the UITextFieldDelegate implementation. (You can get the indexPath and thence the desired object in pretty much the same way as above, with convertPoint and indexPathForRowAtPoint.)
Consequently, when you flip the switch and retrieve the Core Data object as shown above, the text attribute be readily available (though you will probably not need it).
Ultimately I resolved this by using both cellForRowAtIndexPath to load the cells from my data model in my custom UITableViewController class and the following code in my custom UITableViewCell class. (Probably this is what Mundi was suggesting but if so I did not understand him.)
class CREWTableViewCell: UITableViewCell {
#IBOutlet weak var myTextView: UITextView!
#IBOutlet weak var mySwitch: UISwitch!
#IBAction func changedSwitch(sender: UISwitch) {
var newDescription = self.myTextView.text // value from cell
var newSwitchValue = self.mySwitch.on // value from cell
var fetchRequest = NSFetchRequest(entityName: "Switch")
var pred1 = NSPredicate(format: "(viewName = %#)",viewName)
var pred2 = NSPredicate(format: "(switchDescription = %#)",newDescription)
fetchRequest.predicate = NSCompoundPredicate(type: NSCompoundPredicateType.AndPredicateType, subpredicates: [pred1, pred2])
// Execute the fetch request
var error: NSError? = nil
if let fetchResults = context.executeFetchRequest(fetchRequest, error: &error) as? [Switch]
{
var recordCount = 0
recordCount = fetchResults.count
if recordCount == 1 {
var appConfig = fetchResults [0]
appConfig.switchValue = newSwitchValue
if !managedObjectContext!.save(nil) {
NSLog("Unresolved error ")
abort()
}
}
}
}

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.

Return value from CoreData with entity.attribute?

I am new to swift and have been having trouble working with CoreData, I'm looking for a way to simplify fetching and writing to values, specifically being able to write them with something similar to:
entity.attribute = variable
and retrieve them with something similar to:
variable = entity.attribute
I have an entity called 'Studio' with an attribute called 'name' (string).
I've created an NSManagedObject Subclass (Studio.swift) and have updated the class to 'testapp.Studio'.
In my example code below the IBAction saveData will write studioBox.text into CoreData, however the IBAction update is throwing some errors.
var Studio = getStudioData()
Pattern Variable Binding Cannot Appear in an Expression
nameLabel.text = Studio.name
'Studio.Type' does not have a member named 'name'
and finally in the getStudioData function:
return studio
'NSArray' is not a subtype of 'Studio'
Below is my full code:
import UIKit
import CoreData
class ViewController: UIViewController {
#IBOutlet weak var studioBox: UITextField!
#IBOutlet weak var nameLabel: UILabel!
#IBAction func saveData(sender: AnyObject) {
var info: String = studioBox.text!
var Studio = dbconnect()
Studio.name = info as String
}
#IBAction func Update(sender: AnyObject) {
var info: String =
var Studio = getStudioData()
nameLabel.text = Studio.name
}
func getStudioData() -> Studio {
let appDelegate =
UIApplication.sharedApplication().delegate as AppDelegate
let managedContext = appDelegate.managedObjectContext!
let request = NSFetchRequest(entityName: "Studio")
request.returnsObjectsAsFaults = false
let result = managedContext.executeFetchRequest(request, error: nil)!
let studio = result
return studio
}
func dbconnect () -> Studio {
let appDelegate =
UIApplication.sharedApplication().delegate as AppDelegate
let managedContext = appDelegate.managedObjectContext!
let entityDescription = NSEntityDescription.entityForName("Studio", inManagedObjectContext: managedContext)
let studio = Studio(entity: entityDescription!, insertIntoManagedObjectContext: managedContext)
return studio
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
First, the fetch request returns an array of objects. So if you want to return one Studio you should return one object of this array (e.g. the first one). Don't forget to cast your fetch result as the right type array.
let result = managedContext.executeFetchRequest(request, error: nil) as [Studio]
// check if result.count > 0
return result[0]
This explains the first and third errors. The second one seems to be caused by the fact that with the capitalized Studio you are referring to the class, not an instance, i.e. a managed object. While it is "legal" to use variable names with capitalized first letters, it is bad practice because it makes your code less readable. Stick with names like Studio for class names, and studio for variable names.
nameLabel.text = studio.name