首页 > 解决方案 > *** 由于未捕获的异常“NSInternalInconsistencyException”而终止应用程序,原因:“致命:提供的标识符不是唯一的。” ***

问题描述

大家好 ‍♂️我正在将以下内容解析JSONUITableView使用 aUITableViewDiffableDataSource来获得漂亮的搜索动画。

JSON:https ://www.pathofexile.com/api/trade/data/items

这是回购:https ://github.com/laurentdelorme/PathOfData

从 JSON 中,我可以加载所有 13 个不同的类别,如模型文件中所映射的那样。然后我可以将这些类别中的数据推送到另一个 tableView(使用 的那个UITableViewDiffableDataSource)并很好地显示所有内容。

但是,当我尝试将其内容推送到 DetailViewController 时,有一个类别会使我的应用程序崩溃,这是初始ViewController.

这是我的模型:

struct ItemCategories: Codable {
    var result: [ItemCategory]
}

struct ItemCategory: Codable {
    var label: String
    var entries: [Item]
}

struct Item: Codable, Hashable {
    var name: String?
    var type: String?
    var text: String?
}

这是我的 ViewController :

import UIKit

class ViewController: UITableViewController {

    let urlString = "https://www.pathofexile.com/api/trade/data/items"
    var categories = [ItemCategory]()


    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Path of Data"
        navigationController?.navigationBar.prefersLargeTitles = true
        parseJSON()

        for family: String in UIFont.familyNames
        {
            print(family)
            for names: String in UIFont.fontNames(forFamilyName: family)
            {
                print("== \(names)")
            }
        }
    }

    func parseJSON() {
        guard let url = URL(string: urlString) else { return }
        guard let data = try? Data(contentsOf: url) else { return }

        let decoder = JSONDecoder()

        guard let jsonItemCategories = try? decoder.decode(ItemCategories.self, from: data) else { return }

        categories = jsonItemCategories.result
        tableView.reloadData()
    }


    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return categories.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)

        var categoryName = categories[indexPath.row].label
        if categoryName == "" { categoryName = "Unknown" }
        cell.textLabel?.text = categoryName

        let font = UIFont(name: "Fontin-SmallCaps", size: 30)
        cell.textLabel?.font = font
        cell.textLabel?.textColor = .systemOrange

        let numberOfItemsInCategory = String(categories[indexPath.row].entries.count)
        cell.detailTextLabel?.text = numberOfItemsInCategory + " items"
        return cell
    }

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

        if let vc = storyboard?.instantiateViewController(identifier: "Detail") as? DetailViewController {

            let listLabel: String? = categories[indexPath.row].label
            vc.title = listLabel

            let itemList = categories[indexPath.row].entries
            vc.items = itemList

            print(itemList)

            vc.category = categories[indexPath.row].label

            navigationController?.pushViewController(vc, animated: true)
        }
    }
}

这是 DetailViewController :

import UIKit
import SafariServices

class DetailViewController: UITableViewController {

    enum Section {
        case main
    }

    var category: String!
    var items: [Item] = []
    var transformedItems: [Item] = []
    var filteredItems: [Item] = []

    var isSearching: Bool = false

    var dataSource: UITableViewDiffableDataSource<Section,Item>!

    override func viewDidLoad() {
        super.viewDidLoad()
        navigationController?.navigationBar.prefersLargeTitles = true
        navigationController?.navigationBar.tintColor = .systemOrange
        replacenNilNameFor(items: items)
        configureDataSource()
        updateData(on: items)
        congifureSearchController()
    }

    func replacenNilNameFor(items: [Item]) {
        for item in items {
            if item.name == nil {
                guard item.type != nil else { return }
                let newItem = Item(name: item.type, type: nil, text: nil)
                transformedItems.append(newItem)
            } else {
                transformedItems.append(item)
            }
        }
        self.items = transformedItems
    }

    func configureDataSource() {
        dataSource = UITableViewDiffableDataSource<Section, Item>(tableView: self.tableView, cellProvider: { tableView, indexPath, item -> UITableViewCell? in
            let cell = tableView.dequeueReusableCell(withIdentifier: "Detail", for: indexPath)
            cell.textLabel?.text = item.name
            cell.detailTextLabel?.text = item.type

            let font = UIFont(name: "Fontin-SmallCaps", size: 25)
            cell.textLabel?.font = font
            cell.textLabel?.textColor = self.setLabelColor(for: self.category)

            return cell
        })
    }

    func setLabelColor(for category: String) -> UIColor {
        switch category {
        case "Prophecies":
            return UIColor(red: 0.6471, green: 0.1569, blue: 0.7569, alpha: 1.0)
        default:
            return UIColor(red: 0.6392, green: 0.549, blue: 0.4275, alpha: 1.0)
        }
    }

    func updateData(on items: [Item]) {
        var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
        snapshot.appendSections([.main])
        snapshot.appendItems(items)
        dataSource.apply(snapshot, animatingDifferences: true)
    }

    func congifureSearchController() {
        let searchController = UISearchController()
        searchController.searchResultsUpdater = self
        searchController.searchBar.placeholder = "Search for an item"
        searchController.searchBar.delegate = self
        navigationItem.searchController = searchController
    }

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let endpoint = "https://pathofexile.gamepedia.com/"

        let activeArray = isSearching ? filteredItems : items
        let item = activeArray[indexPath.row]

        let url = URL(string: endpoint + formatNameFor(item: item))
        let sf = SFSafariViewController(url: url!)
        present(sf, animated: true)
    }

    func formatNameFor(item: Item) -> String {
        let name = item.name!
        let firstChange = name.replacingOccurrences(of: " ", with: "_")
        let secondChange = firstChange.replacingOccurrences(of: "'", with: "%27")
        return secondChange
    }
}




extension DetailViewController: UISearchResultsUpdating, UISearchBarDelegate {

    func updateSearchResults(for searchController: UISearchController) {
        guard let filter = searchController.searchBar.text, !filter.isEmpty else { return }
        isSearching = true
        filteredItems = items.filter { ($0.name?.lowercased().contains(filter.lowercased()))! }
        updateData(on: filteredItems)
    }

    func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
        isSearching = false
        updateData(on: items)
    }
}

这是我尝试访问“地图”类别时收到的错误消息:

2020-02-28 14:40:20.470098+0100 PathOfData[2789:224548] *** Assertion failure in -[_UIDiffableDataSourceUpdate initWithIdentifiers:sectionIdentifiers:action:desinationIdentifier:relativePosition:destinationIsSection:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKitCore_Sim/UIKit-3901.4.2/_UIDiffableDataSource.m:1417
2020-02-28 14:40:20.474313+0100 PathOfData[2789:224548] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Fatal: supplied identifiers are not unique.'
*** First throw call stack:
(
    0   CoreFoundation                      0x00000001069f327e __exceptionPreprocess + 350
    1   libobjc.A.dylib                     0x0000000105077b20 objc_exception_throw + 48
    2   CoreFoundation                      0x00000001069f2ff8 +[NSException raise:format:arguments:] + 88
    3   Foundation                          0x0000000104a9fb51 -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 191
    4   UIKitCore                           0x0000000119c4dcdf -[_UIDiffableDataSourceUpdate initWithIdentifiers:sectionIdentifiers:action:desinationIdentifier:relativePosition:destinationIsSection:] + 725
    5   UIKitCore                           0x0000000119c4e04e -[_UIDiffableDataSourceUpdate initWithItemIdentifiers:appendingToDestinationSectionIdentifier:] + 90
    6   UIKitCore                           0x0000000119c43408 -[__UIDiffableDataSource appendItemsWithIdentifiers:intoSectionWithIdentifier:] + 165
    7   libswiftUIKit.dylib                 0x0000000105e9f061 $s5UIKit28NSDiffableDataSourceSnapshotV11appendItems_9toSectionySayq_G_xSgtF + 241
    8   PathOfData                          0x0000000104723b41 $s10PathOfData20DetailViewControllerC06updateC02onySayAA4ItemVG_tF + 369
    9   PathOfData                          0x000000010472231f $s10PathOfData20DetailViewControllerC11viewDidLoadyyF + 767
    10  PathOfData                          0x00000001047223db $s10PathOfData20DetailViewControllerC11viewDidLoadyyFTo + 43
    11  UIKitCore                           0x0000000119e22f01 -[UIViewController _sendViewDidLoadWithAppearanceProxyObjectTaggingEnabled] + 83
    12  UIKitCore                           0x0000000119e27e5a -[UIViewController loadViewIfRequired] + 1084
    13  UIKitCore                           0x0000000119e28277 -[UIViewController view] + 27
    14  UIKitCore                           0x0000000119d773dd -[UINavigationController _startCustomTransition:] + 1039
    15  UIKitCore                           0x0000000119d8d30c -[UINavigationController _startDeferredTransitionIfNeeded:] + 698
    16  UIKitCore                           0x0000000119d8e721 -[UINavigationController __viewWillLayoutSubviews] + 150
    17  UIKitCore                           0x0000000119d6f553 -[UILayoutContainerView layoutSubviews] + 217
    18  UIKitCore                           0x000000011a98c4bd -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 2478
    19  QuartzCore                          0x000000010bbe7db1 -[CALayer layoutSublayers] + 255
    20  QuartzCore                          0x000000010bbedfa3 _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 517
    21  QuartzCore                          0x000000010bbf98da _ZN2CA5Layer28layout_and_display_if_neededEPNS_11TransactionE + 80
    22  QuartzCore                          0x000000010bb40848 _ZN2CA7Context18commit_transactionEPNS_11TransactionEd + 324
    23  QuartzCore                          0x000000010bb75b51 _ZN2CA11Transaction6commitEv + 643
    24  UIKitCore                           0x000000011a4d03f4 _afterCACommitHandler + 160
    25  CoreFoundation                      0x0000000106955867 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
    26  CoreFoundation                      0x00000001069502fe __CFRunLoopDoObservers + 430
    27  CoreFoundation                      0x000000010695097a __CFRunLoopRun + 1514
    28  CoreFoundation                      0x0000000106950066 CFRunLoopRunSpecific + 438
    29  GraphicsServices                    0x0000000109100bb0 GSEventRunModal + 65
    30  UIKitCore                           0x000000011a4a6d4d UIApplicationMain + 1621
    31  PathOfData                          0x000000010471fe6b main + 75
    32  libdyld.dylib                       0x00000001078c5c25 start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb)

我不知道这里发生了什么,所以如果有人有想法,那就太棒了

非常感谢 !

标签: iosjsonswiftuitableviewdiffabledatasource

解决方案


该错误显然与UIDiffableDataSource.

可区分数据源需要项目标识符的唯一哈希值。显然有两个项目具有相同的name,typetext

为了确保哈希值是唯一的,请添加一个uuid属性并仅将此属性用于哈希值(实现协议方法)。要Item正确解码,您必须指定CodingKeys以防止该uuid属性被解码。

struct Item: Codable {
    let uuid = UUID()

    private enum CodingKeys : String, CodingKey { case name, type, text }

    var name: String?
    var type: String?
    var text: String?
}

extension Item : Hashable {
    static func ==(lhs: Item, rhs: Item) -> Bool {
        return lhs.uuid == rhs.uuid
    }

    func hash(into hasher: inout Hasher) {
        hasher.combine(uuid)
    }
}

在 iOS 13+ 中你可以采用Identifiable摆脱Hashable扩展

struct Item: Codable, Identifiable {
    let id = UUID()

    private enum CodingKeys : String, CodingKey { case name, type, text }

    var name: String?
    var type: String?
    var text: String?
}

并且强烈建议您不要与同步加载数据Data(contentsOf:不要那样做。使用异步URLSession


推荐阅读