ios - NSFetchedResultsController 字符串最后一个字符的排序描述符

标签 ios swift core-data nsfetchedresultscontroller nsfetchrequest

你如何设置一个 NSSortDescriptor 将按属性排序(但它的最后一个字符?)

例如,如果我有以下条形码...

0000000005353

0000000000224

0000000433355

它应该使用最后一个字符按升序或降序排序。所以就像这个例子中的 3,4,5。这将创建节标题 3、4、5。

我当前的代码给我一个错误,说“在索引 7 处获取的对象有一个乱序的部分名称‘9’。对象必须按部分名称排序。这告诉我我搞砸了排序。到了解更多请查看代码,因为我在核心数据模型上使用 transient 属性。

想法是“numberendsection”应该按照我之前描述的那样从数字末尾开始排序。

我描述的其他两种类型现在工作得很好。

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 -(仅显示相关部分)

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
    }

实体图

enter image description here

enter image description here

实体 + 核心数据截图

enter image description here

出现错误的截图和代码

enter image description here

Inventory.swift

enter image description here

** Inventory.swift 整个文件 **

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
    }
}

注意:

我还清除了电话数据库,以防万一它是旧数据库,方法是删除应用程序(按住直到它摆动并删除)。

最佳答案

当您的核心数据持久存储存储在 SQLite 中时(我在这里假设,否则其他答案已经有效)您不能使用计算属性或 transient 属性。

但是,您可以更改您的数据模型,以便将该条形码的最后一位存储在它自己的属性中(称为非规范化),然后根据该新属性进行排序。这是正确的答案。

您还可以在完成提取后进行二次排序。然而,这意味着您在 NSFetchedResultsController 之外持有一个已排序的数组,然后您需要在从 NSFetchedResultsController 接收委托(delegate)回调时维护该数组的顺序。这是第二好的答案。

如果可以更改数据模型,则添加排序属性。否则你的 View Controller 代码会因为第二种而变得更复杂。

关于ios - NSFetchedResultsController 字符串最后一个字符的排序描述符,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36527093/

相关文章:

swift - 如何修复: QuickLook shows black screen instead of the desired picture

ios - 自定义 UITableviewcell 在 swift 中显示 "fatal error: Can' t unwrap Optional.None"问题

core-data - 核心数据: How to write "generic" fetch/delete function in swift 3?

ios - 在后台线程上初始化 NSManagedObjectContext 和 NSPersistentStoreCoordinator

"DerInputStream.getLength(): lengthTag=109, too big."的 Java APNS 证书错误

ios - clipsToBounds 如何工作?

ios - Polidea/ios-class-guard - 如何排除外部类

ios - (Swift) 如何在 UITableView 和 UITableViewCell 之间来回传递数据?

ios - onesignal 可以在 ios10 以下的 ios 上运行吗?

ios - 使用 NSPersistentContainer 在后台保存托管对象的更改