【问题标题】:NSFetchedResultsController Sort Descriptor for Last Character Of StringNSFetchedResultsController 字符串最后一个字符的排序描述符
【发布时间】:2016-04-10 07:30:12
【问题描述】:

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

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

0000000005353

0000000000224

0000000433355

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

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

这个想法是“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
    }

实体图

实体 + 核心数据截图

出现错误的屏幕截图和代码

Inventory.swift

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

注意::

我也清除了手机数据库,以防万一它是旧数据库,通过删除应用程序(按住直到它摆动并删除)。

【问题讨论】:

    标签: ios swift core-data nsfetchedresultscontroller nsfetchrequest


    【解决方案1】:

    当您的 Core Data 持久存储存储在 SQLite 中时(我在这里假设其他答案已经有效),您不能使用计算属性或瞬态属性。

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

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

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

    【讨论】:

    • 似乎我们想出了与唯一方法相同的解决方案,直到现在我发布时才看到您的答案,但这是最好的答案,所以我会接受。我的解决方案效率低下,因为它存储了整个反向而不仅仅是字符。所以你的更好。
    【解决方案2】:

    您可以将比较器添加到您的 NSSortDescriptor

    例子

    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]) 
    }];
    

    【讨论】:

    • 如果你能快速给出答案,我可以接受! :),进一步研究。
    • CoreData 不支持带块的排序描述符 - 这个答案不起作用。
    • 哦,真可惜……然后 Rafal 有什么想法吗?
    • 我能想到的唯一技巧是将条形码的数据与真实数据相反? (然后当我显示时反转?)虽然感觉很脏。
    【解决方案3】:

    我认为您可以使用transient 属性来实现您想要的:

    为了使其正常工作,您必须在 Inventory 类中提供此属性的实现。

    var lastCharacter: String? {
        let characters = barcode!.characters.map { String($0) }
        return characters.last?.uppercaseString
    }
    

    正确设置“lastCharacter”属性后,您可以创建排序描述符,以实现您想要的:

    NSSortDescriptor(key: "lastCharacter", ascending: true)
    

    【讨论】:

    • 这不是我现在用 var numberendsection: String 做的吗? (我想我尝试了您所描述的内容并遇到了一个奇怪的错误,让我报告一下以确保)
    • 是的,几乎一样。你能把你的错误贴在这里吗?
    • 是的一秒钟...我遇到了无关的崩溃错误...给我 5 分钟修复
    • 它启动的那一刻,我得到了 NSException 类型的未捕获异常终止(让我为您提供有关错误的更多详细信息......)
    • 2016-04-10 05:19:45.243 Inventory Counter[815:559618] *** 由于未捕获的异常“NSInvalidArgumentException”而终止应用程序,原因:在实体 '
    【解决方案4】:

    事实证明,我尝试对瞬态属性进行排序的方法不适用于 NSSortDescriptors,该值必须是数据库中真正持久的值。

    因此,我的解决方案是在实体中创建一个名为barcodeReverse 的新变量,并且在我将数据输入到条形码数据库时,我还使用此代码输入了一个反向版本。

    String(tempInventory.barcode!.characters.reverse())
    

    tempInventory 是我的 coreData 类的一个实例,并且对它的属性进行了条形码化。只需在字符串上使用 characters.reverse() 即可。

    然后您只需执行以下操作:

    primarySortDescriptor = NSSortDescriptor(key: "barcodeReverse", ascending: true)
    

    然后像这样设置 frc...

    frc = NSFetchedResultsController(
                    fetchRequest: inventoryFetchRequest,
                    managedObjectContext: self.moc,
                    sectionNameKeyPath: "numberendsection",
                    cacheName: nil)
    

    最后,广告资源扩展应该如下所示。

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

    这将使用条形码的最后一位正确创建部分和顺序。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-02-12
      • 2015-10-14
      • 1970-01-01
      • 2017-12-28
      • 2016-10-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多