xcode started complaining about the following code (worked fine w/o any issues for quite a few months):
let request = NSFetchRequest(entityName: entity)
request.returnsDistinctResults = true
let nsArr: NSArray = ["int_field", "date_field"]
request.propertiesToFetch = nsArr as [AnyObject]
request.resultType = .DictionaryResultType
let predicate = NSPredicate(format: "(val1 == %d) AND (val2 == %#)", argumentArray: [anIntegerValue, aStringValue])
request.predicate = predicate
do {
let results: NSArray = try moc.executeFetchRequest(request)
for result in results {
Up to this point all is fine. But when I try to extract data from a result, I get an 'Ambiguous use of 'subscript' error on the following:
let val1 = result["int_field"] as! Int
let val2 = result["date_field"] as! NSDate
Not sure what is happening, but if I replace the above syntax with:
let val1 = (result as! NSDictionary) ["int_field"] as! Int
let val2 = (result as! NSDictionary)["date_field"] as! NSDate
it works. What I am not clear about is why declaring the following is not sufficient (or what happened in ios 9.2 to cause this):
request.resultType = .DictionaryResultType
Thoughts?
Please use Swift native collection types, they are so much easier to maintain.
First of all, assign the array directly to propertiesToFetch. No NSArray, no [AnyObject]
request.propertiesToFetch = ["int_field", "date_field"]
Your result is an array of dictionaries which have String keys and Int / NSDate values. Multiple types must be represented by AnyObject.
do {
let results = try moc.executeFetchRequest(request) as! [[String:AnyObject]]
Now, since the compiler knows that the array contains dictionaries this will smoothly compile
for result in results {
let val1 = result["int_field"] as! Int
let val2 = result["date_field"] as! NSDate
}
}
Foundation collection types don't contain the type information and can cause those Ambiguous use messages.
After a close look, the proposed solution by Vadian did not work for me.
The only thing that worked was my original solution:
let val1 = (result as! NSDictionary) ["int_field"] as! Int
let val2 = (result as! NSDictionary)["date_field"] as! NSDate
It seems that the trigger was importing Realm framework (to address another dependency). All I can say is that when I removed Realm, the error/issue goes away.
This occurs despite me not actually using Realm at the moment.
Related
I know how to fetch from core data with predicates using a
let predicate = NSPredicate(format: "MyEntityAttribute == %#", "Matching Value"). I want to know if it's possible to fetch all of the values for a particular attribute without using a Matching Value. I want to get a count of the total number of values for a particular attribute.
This is what I got so far, but I am only getting back what is matching the name attribute.
let filter = "wayne"
let fetchRequest = NSFetchRequest<Likes>(entityName: "Likes")
let predicate = NSPredicate(format: "name == %#", filter)
fetchRequest.predicate = predicate
do {
let nameCount = try context.fetch(fetchRequest)
if nameCount.count >= 0 {
print("name exist")
}
} catch{
print(error.localizedDescription)
}
I took a different approach and use NSFetchRequestResult to get back the result of the single attribute.
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Likes")
fetchRequest.resultType = .dictionaryResultType
fetchRequest.propertiesToFetch = ["name"] // Single attribute I wanted to fetch
fetchRequest.returnsDistinctResults = true
do {
let result = try context.fetch(fetchRequest)
let resultDic = result as! [[String:String]]
print(resultDic.count)
print(resultDic)
} catch{
print(error.localizedDescription)
}
I am trying to fetch data grouped by a given column. It works well when I have data. I want to handle the case when I have no data, because it raises an NS error that I could not catch in swift do catch block. I've seen the answers on creating an ObjC wrapper but I it does not apply to my case because I need to return an Array of String.
let request = self.fetchRequest()
request.propertiesToGroupBy = [attribute]
request.propertiesToFetch = [attribute]
request.resultType = NSFetchRequestResultType.dictionaryResultType
request.returnsDistinctResults = true
if let results = try? context().fetch(request), // raises exception in testing, but runs fine when run on simulator.
let dics = results as? [NSDictionary] {
var resultsArray: [Any] = []
for dic in dics {
if let propValue = dic[attribute] {
resultsArray.append(propValue)
}
}
return resultsArray
}
How might I do this?
It's recommended to wrap Core Data fetch lines always in a do - catch block because on success it returns reliably a non-optional array of the specified return type.
Swift has got a strong type system. Casting to unspecified Any or Foundation type NSDictionary doesn't help the compiler and could cause unnecessary compiler errors.
Assuming both key and value of the dictionary are String cast the dictionary to Swift type [String:String]. To forced unwrap the dictionary is absolutely safe because the Core Data model cannot be changed at runtime.
flatMap returns a non-optional array of all values which are not nil.
var resultsArray = [String]()
do {
let results = try context().fetch(request) as! [[String:String]]
resultsArray = results.flatMap {$0[attribute] as? String}
} catch {
print(error)
}
return resultsArray
Based on this answer
It was not immediately obvious to me that it can be done like this:
let request = self.fetchRequest()
request.propertiesToGroupBy = [attribute]
request.propertiesToFetch = [attribute]
request.resultType = NSFetchRequestResultType.dictionaryResultType
request.returnsDistinctResults = true
var result: [Any] = []
do {
try ObjC.catchException {
let results = try? context().fetch(request)
if let dics = results as? [NSDictionary] {
var resultsArray: [Any] = []
for dic in dics {
if let propValue = dic[attribute] {
resultsArray.append(propValue)
}
}
result = resultsArray
}
}
} catch {}
return result
Ideally I wanted the array to be returned by ObjC.catchException unfortunately I have no solid grasp of ObjC yet. The scope of the result var looks too wide, I welcome any suggestion to improve it.
I wanted to keep everything in swift for uniformity but I guess I am stuck with this solution for now.
Ok, I was searching and trying in that case for the last 1-2 weeks and I didn't get it work. I would be able to achieve what I want without NSFRC but for performance reasons and convienience I would like to do it with the NSFRC.
So, I have a DataModel with 2 Entities - see the picture
There is one Account and one account can have many accountchanges - which is quite obvious.
So I want to be able to choose an Account and then show all AccountChanges for that specific Account.
So far I was able to get the Account and also accessing the NSSet in cellForRow Function but I am not getting the correct sections and numberOfRowsInSection - this is the main issue.
Here is some code:
func numberOfSections(in tableView: UITableView) -> Int {
print("Sections : \(self.fetchedResultsController.sections?.count)")
if (self.fetchedResultsController.sections?.count)! <= 0 {
print("There are no objects in the core data - do something else !!!")
}
return self.fetchedResultsController.sections?.count ?? 0
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("Section Name")
print(self.fetchedResultsController.sections![section].name)
let sectionInfo = self.fetchedResultsController.sections![section]
print("Section: \(sectionInfo) - Sections Objects: \(sectionInfo.numberOfObjects)")
return sectionInfo.numberOfObjects
}
There are some print statements which are only for information!
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let myCell = myTable.dequeueReusableCell(withIdentifier: "myCell")! as UITableViewCell
let accountBalanceChanges = self.fetchedResultsController.object(at: indexPath)
print("AccountBalanceChanges from cell....")
print(accountBalanceChanges)
let details = accountBalanceChanges.accountchanges! as NSSet
print("Print out the details:")
print(details)
let detailSet = details.allObjects
let detailSetItem = detailSet.count // Just for information!
let myPrint = detailSet[indexPath.row] as! AccountChanges
let myVal = myPrint.category
myCell.textLabel?.text = myVal
return myCell
}
So, I am able to get the data but always only one item and not the whole set - I guess due to the fact that the sections/ numberOfRows are wrong.
Here is my NSFRC
var fetchedResultsController: NSFetchedResultsController<Accounts> {
if _fetchedResultsController != nil {
return _fetchedResultsController!
}
let fetchRequest: NSFetchRequest<Accounts> = Accounts.fetchRequest()
// Set the batch size to a suitable number.
fetchRequest.fetchBatchSize = 20
// Edit the sort key as appropriate.
let sortDescriptor = NSSortDescriptor(key: "aName", ascending: false)
fetchRequest.sortDescriptors = [sortDescriptor]
let predicate = NSPredicate(format: "(ANY accountchanges.accounts = %#)", newAccount!)
fetchRequest.predicate = predicate
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.coreDataStack.context, sectionNameKeyPath: nil, cacheName: nil)
aFetchedResultsController.delegate = self
_fetchedResultsController = aFetchedResultsController
do {
try _fetchedResultsController!.performFetch()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
return _fetchedResultsController!
}
I am assuming it is the SortDescriptor or the predicate - or maybe both?
Any help or at least directions are well appreciated.
I already tried many different approaches but none was giving me the correct results.
I would do the opposite, I mean using the FRC to fetch all the changes for an account with a certain Id, and use the following predicate:
let predicate = NSPredicate(format: "accounts.aId = %#", ACCOUNTID)
or
let predicate = NSPredicate(format: "accounts = %#", account.objectID)
I would rename Accounts entity to Account and same for the relationship since it's a to-one relationship.
That's assuming you have a table view with all the accounts and when you click on one it gives you back its changes.
var fetchedResultsController: NSFetchedResultsController<AccountChanges> {
if _fetchedResultsController != nil {
return _fetchedResultsController!
}
let fetchRequest: NSFetchRequest<AccountChanges> = AccountChanges.fetchRequest()
// Set the batch size to a suitable number.
fetchRequest.fetchBatchSize = 20
// Edit the sort key as appropriate.
let sortDescriptor = NSSortDescriptor(key: "aName", ascending: false)
fetchRequest.sortDescriptors = [sortDescriptor]
let predicate = NSPredicate(format: "accounts.aId = %#", ACCOUNTID)
fetchRequest.predicate = predicate
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.coreDataStack.context, sectionNameKeyPath: nil, cacheName: nil)
aFetchedResultsController.delegate = self
_fetchedResultsController = aFetchedResultsController
do {
try _fetchedResultsController!.performFetch()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
return _fetchedResultsController!
}
Cheers
i optimize my code to swift 2
now my core data doesn't work correctly.
i think, that my entrie will save, but my tableview do not show any data.
can you find any mistake in the code below?:
#IBOutlet weak var Table: UITableView!
#IBOutlet weak var name: UILabel!
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
var lm = [LM_ITEMS]()
/*************** DATEN ABRUFEN ***************/
func DatenAbrufen() {
let fetchRequest = NSFetchRequest(entityName: "LM_ITEMS")
let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
do {
try managedObjectContext.executeFetchRequest(fetchRequest) as? [LM_ITEMS]
} catch { print("Error")}
Table.reloadData()
}
override func viewWillAppear(animated: Bool) {
self.DatenAbrufen()
}
/*************** ANZAHL DER ZELLEN ERMITTELN ***************/
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return lm.count
}
/*************** ZELLEN MIT INHALT FÜLLEN ***************/
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("lmCell") as! ModifyCells
let LM_ITEM = lm[indexPath.row]
cell.name.text = LM_ITEM.name
return cell
}
}
Yes, some pretty serious problems.
DatenAbrufen executes a fetch request but does not assign the result to anything. You call executeFetchRequest but you don't do anything with the result. So, the results are lost, and the fetch serves no purpose.
You declare lm as an array but you never put anything into the array. So it's empty, which means that tableView(_:numberOfRowsInSection:) will always return 0-- since lm.count will always be 0.
It looks like you probably intended to put the results of the fetch into lm.
You should move away from using arrays to populate your table view and embrace the NSFetchedResultsController API. Check out the Xcode templates. It is the best way to populate a Core Data backed UITableView.
I am trying to get the string for "content" but when I execute the code tha application crash... when I try with self.content.text = "\(theText)" it works but I want to encode to UTF8 because I use cyrilic and I get back kind of these \U0421\U044a\U0434\U044a\U0440\U0436\U0430\U043d\U0438\U0435 \U043d\U0430 \U043f\U0438\U0449\U043e\U0432\U0430 Anyone who can fix the issue?
Here is the code:
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let context: NSManagedObjectContext = appDel.managedObjectContext
let theReq = NSFetchRequest(entityName: "Entity1")
theReq.returnsObjectsAsFaults = false
let myPredicate = NSPredicate(format: "objectId == \"\(contentID)\"")
theReq.predicate = myPredicate
let executed:AnyObject = context.executeFetchRequest(theReq, error: nil) as Array
let theText : AnyObject = executed.valueForKey("content")
self.content.text = theText
That has nothing to do with encoding problems.
executeFetchRequest() returns an array of managed objects.
Applying valueForKey() to that array returns an array of values.
Printing the description of an array uses \Unnnn escape sequences for all
non-ASCII characters.
So the solution should be simple: Select a single element of the fetched array:
let executed = context.executeFetchRequest(theReq, error: nil)[0] as NSManagedObject
let theText = executed.valueForKey("content") as String
self.content.text = theText
Of course should also check if the fetch was successful or failed, and if it returned
any object. A more detailed version would look like this:
var error : NSError?
let result = context.executeFetchRequest(theReq, error: &error)
if !result {
println("fetch failed: \(error)")
} else if result.count == 0 {
println("nothing found")
} else {
let obj = result[0] as NSManagedObject
let theText = obj.valueForKey("timeStamp") as String
self.content.text = theText
}