I'm pretty new to Swift/Xcode and I'm faced with a problem of trying to place CoreData attribute values into an array.
The app is a simple shopping list with an entity with 5 attributes:
entity is "Item"
image Binary data
name String
note String
qty String
status Boolean
The user creates a new shopping item which is displayed in a UITableVIewController (so far I'm fine)
I have to data loading into cells correctly.
I also want to display the total quantity(entity "qty") of items in a label.
I can't seem to put each qty into an array so that I can add them together to display them. There seems to be plenty of resources for Objective C but not much out there for swift.
These are what I've been looking at:
Filling an array with core data attributes
CoreData to Array in Swift
Swift: Fetch CoreData as Array
coredata - fetch one attribute into an array
UITableViewController Code:
let moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
var frc2 : NSFetchedResultsController = NSFetchedResultsController()
func fetchRequest2() -> NSFetchRequest {
let fetchRequest2 = NSFetchRequest(entityName: "Item")
let sortDescriptor2 = NSSortDescriptor(key: "name",ascending:true)
fetchRequest2.sortDescriptors = [sortDescriptor2]
return fetchRequest2
}
override func viewDidLoad() {
super.viewDidLoad()
//set frc
frc2 = getFRC()
frc2.delegate = self
do {
try frc2.performFetch()
} catch {
print("failed to perform initial fetch request")
return
}
self.tableView.reloadData()
}
Any help would be greatly appreciated.
Related
I have a button and below it is the table view. Table view cell has some random data.On button click I am calling the the api(function name is : api.urlRequest(userID: 80, businessUnitID: 2) ) .I have an API that has 35,0000 entries. What I want is to save that data in Realm database. The problem is that, when I am calling the save function, my UI freezes. I am appending the JSON data to Model and then saving it to database. I can get the start index and end index of the the JSON data.
What I tried was to call the API on background thread and when saving function is called, I am calling it on main thread. But this didn't worked.
class ViewController: UIViewController,getAdhocJSONDelegate{
let realm = try! Realm()
#IBOutlet weak var tableViewRef: UITableView!
var array = [NSDictionary]()
var adhocData : [AdhocModel] = []//for appending the JSON data to the model
var adhocDB : Results<AdhocDB>?// for accessing the database
let api = AdhocAPIParamteres()
var adhocJSONDatafromAPI : NSDictionary!
override func viewDidLoad() {
super.viewDidLoad()
adhocDB = realm.objects(AdhocDB.self)
}
#IBAction func buttonPressed(_ sender: Any) {
print("BUtton Tapped")
api.urlRequest(userID: 80, businessUnitID: 2)
api.delegate = self
}
func appTutorialData(json: NSDictionary) {
adhocJSONDatafromAPI = json
let apiData = adhocJSONDatafromAPI.value(forKey: "data") as! [NSDictionary]
print("Start Index of the data : ",apiData.startIndex)
print("End Index of the data : ",apiData.endIndex)
apiData.forEach { (abc) in
let model = AdhocModel()
model.site_id = abc.value(forKey: "site_id") as! Int
model.atm_id = abc.value(forKey: "atm_id") as! String
model.site_address = abc.value(forKey: "site_address") as! String
adhocData.append(model)
print("data appended")
DispatchQueue.main.async {
self.saveToDb(data:model)
}
}
func saveToDb(data: AdhocModel) {
let adhoc = AdhocDB()
try! realm.write {
adhoc.SiteId = data.site_id
adhoc.AtmId = data.atm_id
adhoc.SiteAdress = data.site_address
realm.add(adhoc)
}
}
}
I want to save data in such a way that my UI doesn't freeze.
There are a few issues with the code and writing data to Realm on a background thread is covered in the documentation so I won't address that. Following that design pattern will correct the UI lockup.
This is another issue
func saveToDb(data: AdhocModel) {
**let adhoc = AdhocDB()**
You want to write your populated model to realm, but AdhocDB is a Results object, not a Realm model object. Additionally the realm object created in appTutorialData which is model, is passed to saveToDb, then another object is created and then populated with data from the first object. There's no reason to do that (in this code)
Assuming AdHocModel is a Realm object, this is much cleaner
func appTutorialData(json: NSDictionary) {
adhocJSONDatafromAPI = json
let apiData = adhocJSONDatafromAPI.value(forKey: "data") as! [NSDictionary]
print("Start Index of the data : ",apiData.startIndex)
print("End Index of the data : ",apiData.endIndex)
apiData.forEach { (abc) in
let model = AdhocModel()
model.site_id = abc.value(forKey: "site_id") as! Int
model.atm_id = abc.value(forKey: "atm_id") as! String
model.site_address = abc.value(forKey: "site_address") as! String
try! realm.write {
realm.add(model)
}
}
}
You're going to want to wrap that write within a background thread (again, see the documentation) something like this
DispatchQueue(label: "background").async {
autoreleasepool {
.
.
.
try! realm.write {
realm.add(model)
}
}
}
You may ask about populating your array adhocData.append(model). We don't know what you're doing with it but if you're using it as perhaps a dataSource for a table view or some other UI element, you may want to consider using a Results object instead of an Array.
A significant advantage is, if you have 35,000 objects, that's a pretty sizable array and if you have more, it could overwhelm the device as ALL of that data is stored in memory. However, Results objects are lazily loaded so you could have a much larger dataset without overwhelming the device.
Additionally, when Realm objects are stored in an array, they 'Disconnect' from Realm and loose Realm functionality - they will not auto-update nor will changes to the actual object in Realm be reflected in that array nor can you just update the object - it doesn't appear to have a primary key.
However, if you populate a Results object with those models, they will be live updating - so if for example the atm_id changes in Realm, that object will automatically be updated. If you need to change a property you can change it directly on that object within a write transaction.
So the pattern would be to have a class var of Results and load your objects into those results within viewDidLoad. As you add more models, the results object will automatically be updated.
To keep your UI fresh, you would want to add observers (aka Notifications)to those Results so you can be notified when an object is updated so you can reload your tableView for example.
My goal is
I want to store an array of data into coreData.
Here is my array
let storageArr = ["Teja", "Teja two", "Teja Ri", "Bhanu", "Stack", "Stack over", "Stack over flow"] as NSObject
And if user is typing in the Textfield I need to show them(related to that character) in drop down (tableView).
Let's say user typed "Te". I need to show Teja, Teja two, Teja Ri in table view.
I have done everything. But am unable to fetch only Teja, Teja two, Teja Ri from array.
Here is the code which I tried
override func viewDidLoad() {
super.viewDidLoad()
savingDataToLocalDB()
}
#IBAction func searchBtnClicked(_ sender: Any) {
setUpData(searchKeyword: searchTF.text)
}
func savingDataToLocalDB(){
let saveContext = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer.viewContext
let data: SearchList = NSEntityDescription.insertNewObject(forEntityName: "SearchList",
into: saveContext!) as! SearchList
data.names = storageArr
do {
try saveContext?.save()
print("data saved")
} catch {
print(error)
}
}
func setUpData(searchKeyword: String){
let fetchContext = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer.viewContext
let fetchReq = NSFetchRequest<NSManagedObject>(entityName: "SearchList")
do {
let data = try fetchContext?.fetch(fetchReq)
var filteredData = data as! [SearchList]
print(filteredData[0].names) // Am getting data here
//following line is not working
let wasfilteredData = filteredData.filter { $0.names!.lowercased().contains(searchKeyword.lowercased()) } as NSArray
} catch {
print(error)
}
}
Here in viewDidLoad() am calling savingDataToLocalDB() which means storing in coreData
And in #IBAction am fetching data from coreData.
But in setUpData(searchKeyword: String) method am unable to filer data consists of "Te"(user entered in textfiled)
Please find the following image. That's how I created entity
Where I am doing wrong?
It appears that you have a SINGLE entity in your database, with a transformable property of 'names'. That property is a transformable Array. Which means that it is stored in the database as Data and is encoded and decoded automatically. The database cannot search or sort this array because 1) it is a single property, not collection of entities and 2) it is stored as data. So all management must be done in memory, which means you have none of the benefits of core-data while having all of the cost.
Next, in your code you fetch ALL of the SearchList. Your database contains one so you get back and array of length 1. You then filter that Array of SearchList - NOT the array of names, and you get back the same searchList Array that you started with because that SearchList indeed passes the test.
Perhaps you want:
let wasfilteredData = filteredData.first?.names.filter { $0.lowercased().contains(searchKeyword.lowercased()) } as NSArray
Or perhaps you should consider having each entity contain only term, and then you search using predicates.
I have a custom UITableView that takes care of animations for rearranging cells and works perfectly with standard test array which doesn't use core data.
When trying with an application that uses core data with two entities 'Folder' and 'Item' with a To Many relationships I receive an error.
[(Item)] does not have a member called exchangeObjectAtIndex
for;
tableView.didMoveCellFromIndexPathToIndexPathBlock = {(fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) -> Void in
let delegate:AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let context: NSManagedObjectContext = delegate.managedObjectContext!
self.itemsArray.exchangeObjectAtIndex(toIndexPath.row, withObjectAtIndex: fromIndexPath.row)
var error: NSError? = nil
if !context.save(&error) { abort() }
}
This is because:
The NSSet, NSMutableSet, and NSCountedSet classes declare the
programmatic interface to an unordered collection of objects.
So I tried converting the NSSet to an NSMutableArray to manage the objects order.
func itemsMutableArray() -> NSMutableArray {
return NSMutableArray(array: (item.allObjects as! [Item]).sorted{ $0.date.compare($1.date) == NSComparisonResult.OrderedAscending } )
}
But then I get the following error in my tableview; this was because the mutable array is an AnyObject so swift doesn't believe it has a title property.
cell.textLabel?.text = folderMutableArray[indexPath.row].title
So then I go back to where I started. I am just trying to create a simple list and rearrange the order of objects.
Here is my Folder Class:
class Folder: NSManagedObject {
#NSManaged var date: NSDate
#NSManaged var title: String
#NSManaged var item: NSSet
/// Method 1 - Converting to a NSMutableArray
// func itemsMutableArray() -> NSMutableArray {
// return NSMutableArray(array: (item.allObjects as! [Item]).sorted{ $0.date.compare($1.date) == NSComparisonResult.OrderedAscending } )
// }
// Method 2 - creating an array of type Item
func itemArray() -> [Item] {
let sortByDate = NSSortDescriptor(key: "Date", ascending: true)
return item.sortedArrayUsingDescriptors([sortByDate]) as! [Item]
}
}
Productivity apps do this all the time so I know it's possible but unsure how, Does anyone know where I am going wrong or have any ideas or suggestions ?
This is very easy to achieve. You need to add a property called index to your Item. Set it to an integer type and whenever you add a new Item set this value to the index under which you want an item to appear. This property should be added both in Core Data Model and in your Item's class as NSManaged property.
Next you need to add a transient property to your Folder class called arrayOfItems (you can rename this of course to whatever you want). In Core Data Model set it to Transient and Transformable.
In your Folder class do the following:
class Folder: NSManagedObject {
#NSManaged var date: NSDate
#NSManaged var title: String
#NSManaged var item: NSSet
#NSManaged var arrayOfItems: [Items]
override func awakeFromFetch() {
super.awakeFromFetch()
self.regenerateItems()
}
func regenerateItems() {
let desc = NSSortDescriptor(key: "index", ascending: true)
if let array = item.sortedArrayUsingDescriptors([desc]) as? [Item] {
self.arrayOfItems = array
}
}
}
Now whenever you fetch any instance of Folder you will get a correct sorted and mutable array of Items. There are only two other cases you need to consider.
And they result from the fact that awakeFromFetch is only getting called when you fetch your data from Core Data. So, you have to consider other scenarios.
Adding new Item
When you add new Item you need to either manually append the new Item to arrayOfItems or you need to call regenerateItems() once you are finished adding the new Item. For example, lets assume that somewhere in your code you create your initial data:
var folder = NSEntityDescription.insertNewObjectForEntityForName("Folder", inManagedObjectContext: self.managedObjectContext!) as! Folder
var firstItem = NSEntityDescription.insertNewObjectForEntityForName("Item", inManagedObjectContext: self.managedObjectContext!) as! Item
// assuming that you have an inverse relationship from Item to Folder
// line below will assign it and will add your firstItem to Folder's
// NSSet of items
firstItem.folder = folder
// at this point your firstItem is added to your Folder but
// arrayOfItems is empty. So, you should call
folder.arrayOfItems = [firstItem]
// or you can call folder.regenerateItems()
Code above refers to the situation when you create your initial data. If somewhere in your code you add a new Item to the folder which already has some Items you have the following:
var newItem = NSEntityDescription.insertNewObjectForEntityForName("Item", inManagedObjectContext: self.managedObjectContext!) as! Item
// assuming that you have an inverse relationship from Item to Folder
// line below will assign it and will add your newItem to Folder's
// NSSet of items
newItem.folder = someExistingFolder
// at this point your newItem is added to your Folder but
// arrayOfItems is not yep updated and does not include newItem
// so you should either call
folder.arrayOfItems.append(newItem)
// or you can call folder.regenerateItems()
Rearranging Items
Also, when you move Items in your table view you will need to change their index and order in the arrayOfItems. The easiest way to achieve this would probably be to change the order of items in the arrayOfItems and then to iterate through this array assigning correct new indexes to all items within it:
func tableView(tableView: UITableView, moveRowAtIndexPath sourceIndexPath: NSIndexPath, toIndexPath destinationIndexPath: NSIndexPath) {
let itemThatMoved = currentFolder.arrayOfItems[sourceIndexPath.row]
currentFolder.arrayOfItems.removeAtIndex(sourceIndexPath.row)
currentFolder.arrayOfItems.insert(itemThatMoved, atIndex:destinationIndexPath.row )
var currentIndex = 0
for item in currentFolder.arrayOfItems {
item.index=currentIndex
currentIndex++
}
}
Let me know if this helps or if you have any other questions.
P.S. I strongly encourage you to change the name of your relationship from item to items. Plural name items is much more logical for NSSet.
I'd like to update a CoreData Object.
Backgrund: I made an app which includes a UITableView. In the textLabel of the UITableViewCell is a name. In the detailTextLabel of this cell is a date which can be changed/updated. Now I'd like to change this date.
I wrote the following code:
var people = [NSManagedObject]()
func saveDate(date: NSDate) {
//1
let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let managedContext = appDelegate.managedObjectContext!
//2
let entity = NSEntityDescription.entityForName("Person", inManagedObjectContext:managedContext)
let person = people[dateIndexPath.row]
//3
person.setValue(date, forKey: "datum")
//4
var error: NSError?
if !managedContext.save(&error) {
println("Could not save \(error), \(error?.userInfo)")
}
//5
people.append(person)
tableView.reloadData()
}
Now, if I run this code:
The date is successfully updated but the cell in which the date has been updated is displayed 2 times. For example if I added 3 cells and changed the date in the 3rd cell, I now get 4 cells displayed and 2 of them have the same content/are duplicated.
Does someone knows how to solve this problem?
You're adding an additional object to your array each time. Your updated Person is already in the array and will display the new information when you reload your table data. To fix this, just take out this line:
people.append(person)
You're going to want to associate some kind of unique identifier attribute to your Person class. This allows to retrieve that same object later using it identifier. I would suggest using a UUID string value, called personID, identifier, or something similar.
You can override the awakeFromInsert method on your Person class like so:
// This is called when a new Person is inserted into a context
override func awakeFromInsert()
{
super.awakeFromInsert()
// Automatically assign a randomly-generated UUID
self.identifier = NSUUID().UUIDString
}
When you want to edit an existing Person, you want to retrieve it by the UUID. I suggest a class function like so (in the Person class):
class func personWithIdentifier(identifier: String, inContext context: NSManagedObjectContext) -> Person?
{
let fetchRequest = NSFetchRequest(entityName: "Person")
fetchRequest.predicate = NSPredicate(format: "identifier ==[c] %#", identifier)
fetchRequest.fetchLimit = 1 // Only want the first result
var error : NSError?
let results = context.executeFetchRequest(fetchRequest, error: &error) as [Person]
// Handle error here
return results.first?
}
This way you can use the following function:
let identifier = ...
let context = ...
var person = Person.personWithIdentifier(identifier, inContext: context)
if let person = person
{
// Edit the person
person.value = // change the values as you need
}
else
{
// Person does not exist!
person = // possibly create a person?
}
I'm attempting to set up a UITableView, with all objects in an entity.
However, I'm loading the data from an api. So every time it loads, I'm deleting all the objects in the entity. And adding the new ones.
However, when I do this. It's showing 5 cells with the api data, and every time I load it. It adds 5 empty cells.
The reason it's adding empty cells is because I'm defining numberOfRowsInSection with objects count like so:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return stocks.count
}
Where stocks is: var stocks = [NSManagedObject]()
So from that I assume it's because there's somehow empty objects in my entity?
here's how I'm trying to set up my data:
func loadSuggestions(Formula: Int) {
println("----- Loading Data -----")
// Check for an internet connection
if Reachability.isConnectedToNetwork() == false {
println("ERROR: -> No Internet Connection <-")
} else {
// Delete all the current objects in the entity
let fetchRequest = NSFetchRequest(entityName: formulaEntity)
let a = managedContext.executeFetchRequest(fetchRequest, error: nil) as! [NSManagedObject]
for mo in a {
managedContext.deleteObject(mo)
//println("removed \(mo)")
}
// save the context after removing objects.
managedContext.save(nil)
// Setting up a fetch request to get the api data.
let entity = NSEntityDescription.entityForName("\(formulaEntity)", inManagedObjectContext:managedContext)
var request = NSURLRequest(URL: formulaAPI!)
var data = NSURLConnection.sendSynchronousRequest(request, returningResponse: nil, error: nil)
var formula = JSON(data: data!)
for (index: String, actionable: JSON) in formula["actionable"] {
stockName = actionable["name"].stringValue
ticker = actionable["ticker"].stringValue
action = actionable["action"].stringValue
suggestedPrice = actionable["suggested_price"].floatValue
weight = actionable["percentage_weight"].floatValue
// Set the values in CoreData
let stock = NSManagedObject(entity: entity!,insertIntoManagedObjectContext:managedContext)
stock.setValue(stockName, forKey: "name")
stock.setValue(ticker, forKey: "ticker")
stock.setValue(action, forKey: "action")
stock.setValue(suggestedPrice, forKey: "suggestedPrice")
stock.setValue(weight, forKey: "weight")
// Set up second api fetch
var quoteAPI = NSURL(string: "http://dev.markitondemand.com/Api/v2/Quote/json?symbol=\(ticker)")
var quoteRequest = NSURLRequest(URL: quoteAPI!)
var quoteData = NSURLConnection.sendSynchronousRequest(quoteRequest, returningResponse: nil, error: nil)
if quoteData != nil {
var quote = JSON(data: quoteData!)
betterStockName = quote["Name"].stringValue
lastPrice = quote["LastPrice"].floatValue
// Setting the last values in CoreData
if betterStockName != "" {
stock.setValue(betterStockName, forKey: "name")
}
stock.setValue(lastPrice, forKey: "lastPrice")
} else {
println("ERROR - NO DATA")
}
var error: NSError?
if !managedContext.save(&error) {
println("Could not save \(error), \(error?.userInfo)")
}
// finally add the stockdata to the CoreData entity.
stocks.append(stock)
}
// Reload the tableview with the new data.
self.tableView.reloadData()
}
}
There isn't a lot of information on how to delete all of the objects in an entity. But I tried to print out the objects it was deleting, and that all seemed correct.
But I must be doing something wrong somewhere in the above function. Since it adds 5 more cells every time the above function is called.
Any help figuring out where the 5 empty cells are coming from and how to fix it would be greatly appreciated!
The simple reason is that you only ever add new entries to stocks, you never remove anything. If you never remove anything from the array, it will never get smaller.
You do delete the objects from Core Data, but that doesn't automatically remove them from the array. They don't disappear from memory and an array just because Core Data doesn't have them anymore. You need to do that separately by calling removeAll on the array after deleting the objects from Core Data.