ios - 如何使用 CoreData 结果快速排序并显示在 UITableview 的正确部分

标签 ios swift uitableview sorting core-data

这个问题的最底部附有我的库存 Controller 文件。我的问题是我在所有部分中都得到重复的结果。我将原因缩小到

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

我在该函数中的代码没有考虑每个部分中有多少行。因此,我只是在每个部分打印出相同的重复结果。

实际问题列在下面的图片后面...

引用下面的图片:

enter image description here enter image description here

我还可以从设置菜单更改索引,以便它可以按数字索引,例如 0-9。引用下图:

enter image description here

也就是说,我目前从 Core Data 加载数据。附件是我使用的实体及其关系的引用图像。

enter image description here

问题:

我的问题是,如何将 coreData 的结果分类为 A、B、C 类型部分或 1、2、3 部分,以便在表格中导航变得简单。

我的直觉是这样一行:let inventoryRecords = try moc.executeFetchRequest(inventoryFetchRequest) as? [Inventory] 需要一个排序描述符来根据我喜欢的方式进行排序,但是我如何获取数据并将其放入正确的数组结构中以分成我需要的部分......我不知道。

globals.swift

import Foundation
import CoreData

//Array of Inventory & Store Core Data Managed Objects
var g_inventoryItems = [Inventory]()
var g_storeList = [Store]()
var g_appSettings = [AppSettings]()
var g_demoMode = false

InventoryController.swift

import UIKit
import CoreData

class InventoryController: UIViewController, UISearchBarDelegate, UITableViewDataSource, UITableViewDelegate {

    @IBOutlet weak var searchBar: UISearchBar!
    @IBOutlet weak var inventoryTable: UITableView!

    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 moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext //convinience variable to access managed object context

    // Start DEMO Related Code
    func createInventoryDummyData(number: Int) -> Inventory{
        let tempInventory = NSEntityDescription.insertNewObjectForEntityForName("Inventory", inManagedObjectContext: moc) as! Inventory

        tempInventory.name = "Test Item # \(number)"
        tempInventory.barcode = "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()

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

            let inventoryRecords = try moc.executeFetchRequest(inventoryFetchRequest) as? [Inventory]
            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...20 {
                    entity = createInventoryDummyData(index)
                    g_inventoryItems.append(entity)
                }

                print("finished creating entities")
            }
        }catch{
            fatalError("bad things happened \(error)")
        }

        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.

        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]? {
        //Use correct index on the side based on settings desired.
        if(g_appSettings[0].indextype=="letter"){
            return letterIndex
        }else{
            return numberIndex
        }
    }

    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

        //TODO: Need to figure out how many rows for ...column A,B,C or 1,2,3 based on indexType using~
        //To do this we need to organize the inventory results into a section'ed array.

        if(g_appSettings[0].selectedStore != nil){
            return (g_appSettings[0].selectedStore?.inventories!.count)! //number of rows is equal to the selected stores inventories count
        }else{
            return g_inventoryItems.count
        }
    }

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("InventoryTableCell", forIndexPath: indexPath) as! InventoryTableViewCell

        if(g_appSettings[0].selectedStore != nil){
            //Get the current Inventory Item & Set to the cell for reference.
            cell.inventoryItem = g_appSettings[0].selectedStore?.inventories?.allObjects[indexPath.row] as! Inventory
        }else{
            //This only happens for DEMO mode or first time.
            cell.inventoryItem = g_inventoryItems[indexPath.row]//create reference to particular inventoryItem this represents.
        }

        cell.drawCell() //uses passed inventoryItem to draw it's self accordingly.

        return cell

    }

    func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        if(g_appSettings[0].indextype == "letter"){
            return letterIndex[section]
        }else{
            return numberIndex[section]
        }

    }

    func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        if(g_appSettings[0].selectedStore != nil){
            if(g_appSettings[0].indextype=="letter"){
                return letterIndex.count
            }else{
                return numberIndex.count
            }
        }else{
            return 1//only one section for DEMO mode.
        }
    }

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

更新::**

所以我环顾四周,发现了这篇文章(我实现了它)。

https://www.andrewcbancroft.com/2015/03/05/displaying-data-with-nsfetchedresultscontroller-and-swift/

我现在真的很接近弄清楚了。唯一的问题是我可以让它自动创建部分,但只能在另一个字段上,例如 store.name,我无法让它将其分为 A、B、C 部分或 1,2,3。

这是我使用该文章中描述的方法的 fetchedResultsController 代码。

//Create fetchedResultsController to handle Inventory Core Data Operations
    lazy var fetchedResultsController: NSFetchedResultsController = {
        let inventoryFetchRequest = NSFetchRequest(entityName: "Inventory")
        let primarySortDescriptor = NSSortDescriptor(key: "name", ascending: true)
        let secondarySortDescriptor = NSSortDescriptor(key: "barcode", ascending: true)
        inventoryFetchRequest.sortDescriptors = [primarySortDescriptor, secondarySortDescriptor]

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

        frc.delegate = self

        return frc
    }()

问题是要为sectionNameKeyPath放置什么:现在这将使其在A B C上进行分区,我得到了这个!

发现一个 stackoverflow 帖子与我的问题非常相似,但需要快速回答。

A-Z Index from NSFetchedResultsController with individual section headers within each letter?

这是另一篇类似的文章,但都是 Objective-C 答案。

NSFetchedResultsController with sections created by first letter of a string

更新::

找到另一篇我认为与我的确切问题有关的文章( How to have a A-Z index with a NSFetchedResultsController )

最佳答案

好吧,我明白了,唷,这太令人困惑了,做了很多研究。

好吧,您要做的第一件事就是在数据模型上创建一个 transient 属性。就我而言,我将其称为 lettersection。要在实体中执行此操作,只需创建一个新属性并将其命名为 lettersection,在图形模式下,如果您选择它(双击它),您将在检查器中看到“ transient ”选项。这意味着它不会保存到数据库中,并且由于内部原因而被更多地使用。

然后,您需要在模型定义的扩展区域中手动设置变量。这就是我的样子。

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?

    var lettersection: String? {
        let characters = name!.characters.map { String($0) }
        return characters[0].uppercaseString
    }

}

完成此操作后,您只需使用 fetchedResultsController 调用这个新的“lettersection”,如下所示...

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

一切都会好起来的!它按我的库存项目的名称排序,但按首字母对它们进行分组,以获得漂亮的 A、B、C 类型列表!

关于ios - 如何使用 CoreData 结果快速排序并显示在 UITableview 的正确部分,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36524208/

相关文章:

ios - 尝试在iOS中使用gestureRecognizer正确移动UIImageView

ios - WKWebView ImagePicker 捕捉iOS选择图片事件

ios - 获取结果 Controller 委托(delegate)在 swift 1.2/xcode 6.3 更新后未调用

ios - 使用 XLPagerTabStrip 在选项卡之间共享数据

iphone - 3.0之后如何使用initWithStyle制作自定义TableViewCell

iOS Objective C - UITableView 性能问题

ios - 如何测试 Firebase DataSnapshot 以模拟对象解析?

ios - AVSpeechSynthesizer 只工作一次

iOS : How to override the button action of UIWebView and call our own Objective C method

swift - 有什么方法可以替换 Swift String 上的字符吗?