How do you set a NSSortDescriptor which will sort by an attribute (but the last character of it?)
For example, if I have the following barcodes...
0000000005353
0000000000224
0000000433355
It should sort using last character, in asc or desc order. So like 3,4,5 in this example. Which would create section headers 3,4,5.
The current code I have gives me an error, sayings the "fetched object at index 7 has an out of order section name '9'. Objects must be sorted by section name. Which tells me I messed up the sort. To understand more please look at the code as I'm using transient properties on the core data model.
The idea is that "numberendsection", should sort from the end of the number as I described previously.
The other two sorts I describe work perfectly right now.
Inventory+CoreDataProperties.swift
import Foundation
import CoreData
extension Inventory {
#NSManaged var addCount: NSNumber?
#NSManaged var barcode: String?
#NSManaged var currentCount: NSNumber?
#NSManaged var id: NSNumber?
#NSManaged var imageLargePath: String?
#NSManaged var imageSmallPath: String?
#NSManaged var name: String?
#NSManaged var negativeCount: NSNumber?
#NSManaged var newCount: NSNumber?
#NSManaged var store_id: NSNumber?
#NSManaged var store: Store?
//This is used for A,B,C ordering...
var lettersection: String? {
let characters = name!.characters.map { String($0) }
return characters.first?.uppercaseString
}
//This is used for 1,2,3 ordering... (using front of barcode)
var numbersection: String? {
let characters = barcode!.characters.map { String($0) }
return characters.first?.uppercaseString
}
//This is used for 0000000123 ordering...(uses back number of barcode)
var numberendsection: String? {
let characters = barcode!.characters.map { String($0) }
return characters.last?.uppercaseString
}
}
InventoryController.swift - (showing only relevant part)
import UIKit
import CoreData
import Foundation
class InventoryController: UIViewController, UISearchBarDelegate, UITableViewDataSource, UITableViewDelegate, NSFetchedResultsControllerDelegate {
//Create fetchedResultsController to handle Inventory Core Data Operations
lazy var fetchedResultsController: NSFetchedResultsController = {
return self.setFetchedResultsController()
}()
func setFetchedResultsController() -> NSFetchedResultsController{
let inventoryFetchRequest = NSFetchRequest(entityName: "Inventory")
var primarySortDescriptor = NSSortDescriptor(key: "name", ascending: true)//by default assume name.
if(g_appSettings[0].indextype=="numberfront"){
primarySortDescriptor = NSSortDescriptor(key: "barcode", ascending: true)
}else if(g_appSettings[0].indextype=="numberback"){
primarySortDescriptor = NSSortDescriptor(key: "barcode", ascending: true)
}
//let secondarySortDescriptor = NSSortDescriptor(key: "barcode", ascending: true)
inventoryFetchRequest.sortDescriptors = [primarySortDescriptor]
let storefilter = g_appSettings[0].selectedStore!
let predicate = NSPredicate(format: "store = %#", storefilter) //This will ensure correct data relating to store is showing
inventoryFetchRequest.predicate = predicate
//default assume letter section
var frc = NSFetchedResultsController(
fetchRequest: inventoryFetchRequest,
managedObjectContext: self.moc,
sectionNameKeyPath: "lettersection",
cacheName: nil)
if(g_appSettings[0].indextype=="numberfront"){
frc = NSFetchedResultsController(
fetchRequest: inventoryFetchRequest,
managedObjectContext: self.moc,
sectionNameKeyPath: "numbersection",
cacheName: nil)
}else if(g_appSettings[0].indextype=="numberback"){
frc = NSFetchedResultsController(
fetchRequest: inventoryFetchRequest,
managedObjectContext: self.moc,
sectionNameKeyPath: "numberendsection",
cacheName: nil)
}
frc.delegate = self
return frc
}
Entity Diagram
Entity + Core Data Screenshot
Screenshot of Error and Code where it occurs
Inventory.swift
** Inventory.swift Entire File **
import UIKit
import CoreData
import Foundation
class InventoryController: UIViewController, UISearchBarDelegate, UITableViewDataSource, UITableViewDelegate, NSFetchedResultsControllerDelegate {
//Create fetchedResultsController to handle Inventory Core Data Operations
lazy var fetchedResultsController: NSFetchedResultsController = {
return self.setFetchedResultsController()
}()
func setFetchedResultsController() -> NSFetchedResultsController{
let inventoryFetchRequest = NSFetchRequest(entityName: "Inventory")
var primarySortDescriptor = NSSortDescriptor(key: "name", ascending: true)//by default assume name.
print("primarySortDescriptor...")
if(g_appSettings[0].indextype=="numberfront"){
primarySortDescriptor = NSSortDescriptor(key: "barcode", ascending: true)
}else if(g_appSettings[0].indextype=="numberback"){
primarySortDescriptor = NSSortDescriptor(key: "barcode", ascending: true)
}
print("set primarySortDescriptor")
//let secondarySortDescriptor = NSSortDescriptor(key: "barcode", ascending: true)
inventoryFetchRequest.sortDescriptors = [primarySortDescriptor]
print("set sort descriptors to fetch request")
var storefilter : Store? = nil
if(g_appSettings[0].selectedStore != nil){
storefilter = g_appSettings[0].selectedStore
let predicate = NSPredicate(format: "store = %#", storefilter!) //This will ensure correct data relating to store is showing
inventoryFetchRequest.predicate = predicate
}
//default assume letter section
var frc = NSFetchedResultsController(
fetchRequest: inventoryFetchRequest,
managedObjectContext: self.moc,
sectionNameKeyPath: "lettersection",
cacheName: nil)
if(g_appSettings[0].indextype=="numberfront"){
frc = NSFetchedResultsController(
fetchRequest: inventoryFetchRequest,
managedObjectContext: self.moc,
sectionNameKeyPath: "numbersection",
cacheName: nil)
}else if(g_appSettings[0].indextype=="numberback"){
frc = NSFetchedResultsController(
fetchRequest: inventoryFetchRequest,
managedObjectContext: self.moc,
sectionNameKeyPath: "numbersection",
cacheName: nil)
}
print("set the frc")
frc.delegate = self
return frc
}
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var inventoryTable: UITableView!
var moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext //convinience variable to access managed object context
// Start DEMO Related Code
var numberIndex = ["0","1","2","3","4","5","6","7","8","9"]
var letterIndex = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"]
var previousNumber = -1 //used so we show A,A, B,B, C,C etc for proper testing of sections
func createInventoryDummyData(number: Int) -> Inventory{
let tempInventory = NSEntityDescription.insertNewObjectForEntityForName("Inventory", inManagedObjectContext: moc) as! Inventory
if(number-1 == previousNumber){
tempInventory.name = "\(letterIndex[number-2])-Test Item # \(number)"
previousNumber = -1//reset it again
}else{
tempInventory.name = "\(letterIndex[number-1])-Test Item # \(number)"
previousNumber = number //set previous letter accordingly
}
tempInventory.barcode = "\(number)00000000\(number)"
tempInventory.currentCount = 0
tempInventory.id = number
tempInventory.imageLargePath = "http://website.tech//uploads/inventory/7d3fe5bfad38a3545e80c73c1453e380.png"
tempInventory.imageSmallPath = "http://website.tech//uploads/inventory/7d3fe5bfad38a3545e80c73c1453e380.png"
tempInventory.addCount = 0
tempInventory.negativeCount = 0
tempInventory.newCount = 0
tempInventory.store_id = 1 //belongs to same store for now
//Select a random store to belong to 0 through 2 since array starts at 0
let aRandomInt = Int.random(0...2)
tempInventory.setValue(g_storeList[aRandomInt], forKey: "store") //assigns inventory to one of the stores we created.
return tempInventory
}
func createStoreDummyData(number:Int) -> Store{
let tempStore = NSEntityDescription.insertNewObjectForEntityForName("Store", inManagedObjectContext: moc) as! Store
tempStore.address = "100\(number) lane, Miami, FL"
tempStore.email = "store\(number)#centraltire.com"
tempStore.id = number
tempStore.lat = 1.00000007
tempStore.lng = 1.00000008
tempStore.name = "Store #\(number)"
tempStore.phone = "123000000\(number)"
return tempStore
}
// End DEMO Related Code
override func viewDidLoad() {
super.viewDidLoad()
print("InventoryController -> ViewDidLoad -> ... starting inits")
// // Do any additional setup after loading the view, typically from a nib.
// print("InventoryController -> ViewDidLoad -> ... starting inits")
//
//First check to see if we have entities already. There MUST be entities, even if its DEMO data.
let inventoryFetchRequest = NSFetchRequest(entityName: "Inventory")
let storeFetchRequest = NSFetchRequest(entityName: "Store")
do {
let storeRecords = try moc.executeFetchRequest(storeFetchRequest) as? [Store]
//Maybe sort descriptor here? But how to organize into sectioned array?
if(storeRecords!.count<=0){
g_demoMode = true
print("No store entities found. Demo mode = True. Creating default store entities...")
var store : Store //define variable as Store type
for index in 1...3 {
store = createStoreDummyData(index)
g_storeList.append(store)
}
//save changes for the stores we added
do {
try moc.save()
print("saved to entity")
}catch{
fatalError("Failure to save context: \(error)")
}
}
let inventoryRecords = try moc.executeFetchRequest(inventoryFetchRequest) as? [Inventory]
//Maybe sort descriptor here? But how to organize into sectioned array?
if(inventoryRecords!.count<=0){
g_demoMode = true
print("No entities found for inventory. Demo mode = True. Creating default entities...")
var entity : Inventory //define variable as Inventory type
for index in 1...52 {
let indexFloat = Float(index/2)+1
let realIndex = Int(round(indexFloat))
entity = createInventoryDummyData(realIndex)
g_inventoryItems.append(entity)
}
//save changes for inventory we added
do {
try moc.save()
print("saved to entity")
}catch{
fatalError("Failure to save context: \(error)")
}
print("finished creating entities")
}
}catch{
fatalError("bad things happened \(error)")
}
//perform fetch we need to do.
do {
try fetchedResultsController.performFetch()
} catch {
print("An error occurred")
}
print("InventoryController -> viewDidload -> ... finished inits!")
}
override func viewWillAppear(animated: Bool) {
print("view appearing")
//When the view appears its important that the table is updated.
//Look at the selected Store & Use the LIST of Inventory Under it.
//Perform another fetch again to get correct data~
do {
//fetchedResultsController. //this will force setter code to run again.
print("attempting fetch again, reset to use lazy init")
fetchedResultsController = setFetchedResultsController() //sets it again so its correct.
try fetchedResultsController.performFetch()
} catch {
print("An error occurred")
}
inventoryTable.reloadData()//this is important to update correctly for changes that might have been made
}
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
print("inventoryItemControllerPrepareForSegueCalled")
if segue.identifier == "inventoryInfoSegue" {
let vc = segue.destinationViewController as! InventoryItemController
if let cell = sender as? InventoryTableViewCell{
vc.inventoryItem = cell.inventoryItem! //sets the inventory item accordingly, passing its reference along.
}else{
print("sender was something else")
}
}
}
func tableView(tableView: UITableView, sectionForSectionIndexTitle title: String, atIndex index: Int) -> Int {
//This scrolls to correct section based on title of what was pressed.
return letterIndex.indexOf(title)!
}
func sectionIndexTitlesForTableView(tableView: UITableView) -> [String]? {
//This is smart and takes the first letter of known sections to create the Index Titles
return self.fetchedResultsController.sectionIndexTitles
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let sections = fetchedResultsController.sections {
let currentSection = sections[section]
return currentSection.numberOfObjects
}
return 0
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("InventoryTableCell", forIndexPath: indexPath) as! InventoryTableViewCell
let inventory = fetchedResultsController.objectAtIndexPath(indexPath) as! Inventory
cell.inventoryItem = inventory
cell.drawCell() //uses passed inventoryItem to draw it's self accordingly.
return cell
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if let sections = fetchedResultsController.sections {
let currentSection = sections[section]
return currentSection.name
}
return nil
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
if let sections = fetchedResultsController.sections {
return sections.count
}
return 0
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
//dispatch_async(dispatch_get_main_queue()) {
//[unowned self] in
print("didSelectRowAtIndexPath")//does not recognize first time pressed item for some reason?
let selectedCell = self.tableView(tableView, cellForRowAtIndexPath: indexPath) as? InventoryTableViewCell
self.performSegueWithIdentifier("inventoryInfoSegue", sender: selectedCell)
//}
}
#IBAction func BarcodeScanBarItemAction(sender: UIBarButtonItem) {
print("test of baritem")
}
#IBAction func SetStoreBarItemAction(sender: UIBarButtonItem) {
print("change store interface")
}
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
print("text is changing")
}
func searchBarCancelButtonClicked(searchBar: UISearchBar) {
print("ended by cancel")
searchBar.text = ""
searchBar.resignFirstResponder()
}
func searchBarSearchButtonClicked(searchBar: UISearchBar) {
print("ended by search")
searchBar.resignFirstResponder()
}
func searchBarTextDidEndEditing(searchBar: UISearchBar) {
print("ended by end editing")
searchBar.resignFirstResponder()
}
#IBAction func unwindBackToInventory(segue: UIStoryboardSegue) {
print("unwind attempt")
let barcode = (segue.sourceViewController as? ScannerViewController)?.barcode
searchBar.text = barcode!
print("barcode="+barcode!)
inventoryTable.reloadData()//reload the data to be safe.
}
}
//Extention to INT to create random number in range.
extension Int
{
static func random(range: Range<Int> ) -> Int
{
var offset = 0
if range.startIndex < 0 // allow negative ranges
{
offset = abs(range.startIndex)
}
let mini = UInt32(range.startIndex + offset)
let maxi = UInt32(range.endIndex + offset)
return Int(mini + arc4random_uniform(maxi - mini)) - offset
}
}
NOTE::
I've cleared phone database also, just in case it was old database by deleting the app (holding down till it wiggles and deleting).
When your persistent store for Core Data is stored in SQLite (which I am assuming here otherwise the other answers would have worked already) you can't use computed properties or transient properties.
However, you can alter your data model so that you are storing the last digit of that bar code in its own property (known as denormalizing) and then sort on that new property. That is the right answer.
You can also do a secondary sort after you have done a fetch. However that means that you are holding a sorted array outside of the NSFetchedResultsController and you will then need to maintain the order of that array as you receive delegate callbacks from the NSFetchedResultsController. This is the second best answer.
If you can change the data model, then add a sort property. Otherwise your view controller code will be more complex because of the second sort.
You can add a comparator to your NSSortDescriptor
example
NSSortDescriptor *sortStates = [NSSortDescriptor sortDescriptorWithKey:#"barcode"
ascending:NO
comparator:^(id obj1, id obj2) {
[obj1 substringFromIndex:[obj1 length] - 1];
[obj2 substringFromIndex:[obj2 length] - 1];
return [obj1 compare: obj2])
}];
I think that you can use transient property in order to achieve what you want:
In order for it to work properly you have to provide implementation of this property in Inventory class.
var lastCharacter: String? {
let characters = barcode!.characters.map { String($0) }
return characters.last?.uppercaseString
}
Having 'lastCharacter' property set up correctly you can create sort descriptor that will allow you to achieve what you want:
NSSortDescriptor(key: "lastCharacter", ascending: true)
So it turns out that my method of trying to sort on a transient property does not work with NSSortDescriptors, the value has to be a real persisted one in the database.
Therefore, my solution was to create a new variable called barcodeReverse in the entity and at the time I enter data into the database for the barcode I also enter a reversed version using this code.
String(tempInventory.barcode!.characters.reverse())
tempInventory is an instance of my coreData class, and barcode a property on it. Simply just use characters.reverse() on the string.
Then you simply do the following:
primarySortDescriptor = NSSortDescriptor(key: "barcodeReverse", ascending: true)
and set frc like so...
frc = NSFetchedResultsController(
fetchRequest: inventoryFetchRequest,
managedObjectContext: self.moc,
sectionNameKeyPath: "numberendsection",
cacheName: nil)
and finally the inventory extension should look like this.
//This is used for 0000000123 ordering...(uses back number of barcode)
var numberendsection: String? {
let characters = barcodeReverse!.characters.map { String($0) }
return characters.first?.uppercaseString
}
This will create the sections and order correctly using the last digit of the barcode.
I am currently learn and creating a basic decision app. The basics of the app is to take user input for a category they would like to do and then all the things that want to fill that category with.
Now I am wanting to display the results on a table view which works but I also what to click on each individual category that they recently used and be able to see the things that they placed under ever category. I was getting everything that was being save to the Core Data but now I am trying to use NSPredicate to filter out what I need. When I run the App there is nothing in the table view.
mainName I have passed in from a different view controller to capture and set what the name of the category was to help filter the data. I was trying to use it in the predicate as a filter.
I don't know if what I am doing is right but help would be great. This is independent study project I am doing to help finish my degree and everything I know is self taught so far. If what I have is completely wrong please tell me. This is just one of the hundreds of different ways I have tried to get this right.
#IBOutlet weak var tableview: UITableView!
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return whats.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell")! as UITableViewCell
let Doing = whats[indexPath.row]
cell.textLabel!.text = Doing.valueForKey("what") as? String
return cell
}
func loadData(){
let appDelegate = UIApplication.sharedApplication().delegate as? AppDelegate
if let context = appDelegate?.managedObjectContext{
let fetchRequest = NSFetchRequest(entityName: "Doing")
let namePredicate = NSPredicate(format: "namesR.noun = '\(mainName)'")
(whats as NSArray).filteredArrayUsingPredicate(namePredicate)
fetchRequest.predicate = namePredicate
do {
let results =
try context.executeFetchRequest(fetchRequest)
whats = results as! [NSManagedObject]
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
}
}
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
}
override func viewDidLoad() {
super.viewDidLoad()
loadData()
}
If you want use Core Data in your tableView based app? You can use NSFetchedResultsController class and their delegate protocol methods. This class specially designed for a tableView!
Official Documentation for NSFetchedResultsController class
From my understanding, you're saying you have a Category entity and an Item entity with many Item for each Category. In that case, in the view controller where you want to display the Items, you need to have a Category variable to use in your predicate so that you only get back the Items associated with that Category.
class ItemsTableVC {
var category: Category!
lazy var fetchedResultsController: NSFetchedResultsController = {
let fetchRequest = NSFetchRequest(entityName: "Item")
fetchRequest.sortDescriptors = []
fetchRequest.predicate = NSPredicate(format: "category == %#", self.category)
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.sharedContext, sectionNameKeyPath: nil, cacheName: nil)
fetchedResultsController.delegate = self
return fetchedResultsController
}()
}
I am trying to group section 0 my tableView by the "category" attribute of an item.
Example:
Drinks: (item.category = header)
Dr. Prepper
Coke
Pepsi
Kitchen: (item.category = header)
Pots
Pans...etc.
CrossOff(header)
items
I still want section1 to be the item.slcross (or the last section if each group has to be their own section...and it doesn't have to be grouped).
When I change the secondarySortDescriptor key from "slitem" to "slcategory" and use the sectionHeader code below, it returns "nil". I also tried using
let sectionHeader2 = "\(item.valueForKeyPath("slcategory"))" but still had the same effect with both "slitem" and "slcategory".
Do I have to use a sort descriptor for each category or is there a way to make it pull the category attribute for the item and group the like categories together?
FRC set up:
let moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
var frc : NSFetchedResultsController = NSFetchedResultsController()
var selectedItem : List?
func itemFetchRequest() -> NSFetchRequest{
let fetchRequest = NSFetchRequest(entityName: "List")
let primarySortDescription = NSSortDescriptor(key: "slcross", ascending: true)
let secondarySortDescription = NSSortDescriptor(key: "slitem", ascending: true)
fetchRequest.sortDescriptors = [primarySortDescription, secondarySortDescription]
fetchRequest.predicate = NSPredicate(format:"slist == true")
return fetchRequest
}
func getFetchRequetController() ->NSFetchedResultsController{
frc = NSFetchedResultsController(fetchRequest: itemFetchRequest(), managedObjectContext: moc, sectionNameKeyPath: "slcross", cacheName: nil)
return frc
}
TableViewHeaders:
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String?{
let entityDescription = NSEntityDescription.entityForName("List", inManagedObjectContext: moc)
let item = List(entity: entityDescription!, insertIntoManagedObjectContext: moc)
let sectionHeader = "\(item.slcategory)"
let sectionHeader1 = "Items in Cart - #\(frc.sections![section].numberOfObjects)"
if (frc.sections!.count > 0) {
let sectionInfo = frc.sections![section]
if (sectionInfo.name == "0") {
return sectionHeader2
} else {
return sectionHeader1
}
} else {
return nil
}
}
There are a few ways to do this, but probably the easiest is to add a new method to your NSManagedObject subclass. The method returns a string which will be used as the title for the section; so if slcross is false, it returns the value of slcategory, and if slcross is true it returns "True":
func sectionIdentifier() -> String {
if (self.slcross) {
return "True"
} else {
return "\(self.slcategory)"
}
}
(Note this code goes in your List class definition, not your view controller).
In the view controller, use this sectionIdentifier as the sectionNameKeyPath for your FRC:
frc = NSFetchedResultsController(fetchRequest: itemFetchRequest(), managedObjectContext: moc, sectionNameKeyPath: "sectionIdentifier", cacheName: nil)
For that to work, it is imperative that the objects are sorted correctly: first by slcross, then by slcategory:
let primarySortDescription = NSSortDescriptor(key: "slcross", ascending: true)
let secondarySortDescription = NSSortDescriptor(key: "slcategory", ascending: true)
Finally, amend your titleForHeaderInSection to use the section name (which the FRC gets from sectionIdentifier), but replacing the "True" with your computed string:
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String?{
if (frc.sections!.count > 0) {
let sectionInfo = frc.sections![section]
if (sectionInfo.name == "True") {
return "Items in Cart - #\(sectionInfo.numberOfObjects)"
} else {
return sectionInfo.name
}
} else {
return nil
}
}
From Apple Docs...
When you initialize the fetch results controller, you provide four parameters: .....
....Optionally, a key path on result objects that returns the section name. The controller uses the key path to split the results into sections (passing nil indicates that the controller should generate a single section).....
After creating an instance, you invoke performFetch: to actually execute the fetch.
If you want to sort the sections by category then you need to make the sectionNameKeyPath argument in the NSFetchedResultsController init to be your "category" property instead of your "slcross" property.
When I've used this in the past I've also included that same property that I've set as the sectionNameKeyPath in my sort descriptors, but not sure if that is actually needed or not.
Hope I have answered your question?
I am a novice Swift developer coming from an Objective-C background.
I have the following code:
class KPCoreDataResultsController: NSObject, NSFetchedResultsControllerDelegate {
var fetchedResultsController: NSFetchedResultsController
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext!
let entityName: NSString
var delegate: KPCoreDataResultsDelegate?
var predicate: NSPredicate {
get {
return fetchedResultsController.fetchRequest.predicate!
}
set {
fetchedResultsController.fetchRequest.predicate = newValue
do {
try fetchedResultsController.performFetch()
} catch {
print(error)
}
}
}
var sortDescriptor: NSSortDescriptor!
var count: NSInteger {
return (fetchedResultsController.sections!.first!).numberOfObjects
}
init(entityName: String, predicate: NSPredicate, sortDescriptor: NSSortDescriptor) {
self.entityName = entityName
let fetchRequest = NSFetchRequest(entityName: entityName)
fetchRequest.predicate = predicate
fetchRequest.sortDescriptors = [sortDescriptor]
fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
super.init()
}
}
Inside of a custom Core Data Results Controller file to simplify accessing CoreData stuff. When I call the results controller's count property from a table view (numberOfRowsInSection), the app crashes saying:
fatal error: unexpectedly found nil while unwrapping an Optional value
Here is the table view code:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return accountController.count
}
Basically, the code above is giving this error - am I using the !'s wrong? Or what is happening here?
Try something like
return fetchedResultsController.sections![section].numberOfObjects
count is called before any data (after viewDidLoad) is fetched and so sections and rows are nil. In swift, you may not access nil! if you do, you get an error
you do call setPredicate but BEFORE you set fetchedResultController -- so performFetch isn't happening ;)
do a fetchedResultsController.performFetch() in init or viewDidLoad
I have a UITableView in swift where the app allows users to re-order the cells, but it keeps crashing during the re-order.
I have this data model:
class Person: NSManagedObject {
#NSManaged var name: String
#NSManaged var mood: String
}
I have setup a bool variable to check when the user hits the edit button to see if the tableview.setEdititing is set to true, because this indicates a change in order is about to happen:
var userDrivenDataChange : Bool = false
Then for each of the fetched results controller delegate methods before I go ahead I always check if there are user driven changees to the object - for example...
func controllerWillChangeContent(controller: NSFetchedResultsController) {
if userDrivenDataChange{
return
}
tblView.beginUpdates()
}
Now, this is the bit I'm really struggling with...
In the moveRowAtIndexPath function, my logic is to get the object the user has just moved, and then get the section that the user wants to move the line to. Then create a new Person NSManagedObject but set its "mood" field to the new section name, then delete the object at the old indexPath.
func tableView(tableView: UITableView, moveRowAtIndexPath sourceIndexPath: NSIndexPath, toIndexPath destinationIndexPath: NSIndexPath) {
userDrivenDataChange = true
var context = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext!
var entity = NSEntityDescription.entityForName("Person", inManagedObjectContext: context)
var secInfo = fetchedResultController.sections![destinationIndexPath.section] as NSFetchedResultsSectionInfo
var personToAmend = fetchedResultController.objectAtIndexPath(sourceIndexPath) as Person
var copyOfPerson = Person(entity: entity!, insertIntoManagedObjectContext: context)
copyOfPerson.name = personToAmend.name
copyOfPerson.mood = secInfo.name!
context.deleteObject(personToAmend)
var err : NSError?
if !context.save(&err){
println(err)
}
userDrivenDataChange = false
}
So this doesn't work, honestly can't figure out why!
Any help would be greatly appreciated!
Thanks,
Jamie
p.s. Just incase you need to see it, my fetched results controller is:
lazy var fetchedResultController : NSFetchedResultsController = {
let fetchRequest = NSFetchRequest(entityName: "Person")
let context = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext!
let sort = NSSortDescriptor(key: "mood", ascending: true)
fetchRequest.sortDescriptors = [sort]
let frc = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: "mood", cacheName: nil)
frc.delegate = self
return frc
}()
----EDIT:
When a user reorders rows from one section to another, the fetchedResultsController always adds the row to the beginning of the section instead of the actual index path represented by destinationIndexPath.
So I guess my question becomes: Is there a function to either edit the index Path of the object I insert, or to add an object at a specific index path?