*** 由于未捕获的异常 'NSInternalInconsistencyException' 而终止应用程序,原因:'Fatal: supplied identifiers are not unique.' ***
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Fatal: supplied identifiers are not unique.' ***
大家好 ♂️我正在使用 UITableViewDiffableDataSource
将以下 JSON
解析为 UITableView
以获得漂亮的搜索动画。
JSON : https://www.pathofexile.com/api/trade/data/items
这是回购协议:https://github.com/laurentdelorme/PathOfData
从 JSON,我能够加载模型中映射的所有 13 个不同类别 file.I 然后能够将这些类别中的数据推送到另一个 tableView(一个使用 UITableViewDiffableDataSource
) 并很好地显示所有内容。
但是,当我尝试将其内容推送到 DetailViewController 时,有一个类别会导致我的应用程序崩溃,即初始 ViewController
上的 "Maps" 类别。
这是我的模型:
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)
}
}
}
这是详细信息ViewController:
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 { ([=14=].name?.lowercased().contains(filter.lowercased()))! }
updateData(on: filteredItems)
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
isSearching = false
updateData(on: items)
}
}
这是我尝试访问 "Maps" 类别时收到的错误消息:
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)
我不知道这里发生了什么,所以如果有人有想法,那就太棒了
非常感谢!
您的故事板中 UIViewController
的标识符值重复。
Search for text Detail
在你的故事板中为不同的 UIViewControllers 设置不同的标识符。
例如Detail1
和Detail2
错误显然与 UIDiffableDataSource
有关。
可区分数据源需要项目标识符的唯一哈希值。显然有两个item相同name
, type
and text
.
为了确保散列值是唯一的,添加一个 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
大家好 ♂️我正在使用 UITableViewDiffableDataSource
将以下 JSON
解析为 UITableView
以获得漂亮的搜索动画。
JSON : https://www.pathofexile.com/api/trade/data/items
这是回购协议:https://github.com/laurentdelorme/PathOfData
从 JSON,我能够加载模型中映射的所有 13 个不同类别 file.I 然后能够将这些类别中的数据推送到另一个 tableView(一个使用 UITableViewDiffableDataSource
) 并很好地显示所有内容。
但是,当我尝试将其内容推送到 DetailViewController 时,有一个类别会导致我的应用程序崩溃,即初始 ViewController
上的 "Maps" 类别。
这是我的模型:
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)
}
}
}
这是详细信息ViewController:
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 { ([=14=].name?.lowercased().contains(filter.lowercased()))! }
updateData(on: filteredItems)
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
isSearching = false
updateData(on: items)
}
}
这是我尝试访问 "Maps" 类别时收到的错误消息:
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)
我不知道这里发生了什么,所以如果有人有想法,那就太棒了
非常感谢!
您的故事板中 UIViewController
的标识符值重复。
Search for text Detail
在你的故事板中为不同的 UIViewControllers 设置不同的标识符。
例如Detail1
和Detail2
错误显然与 UIDiffableDataSource
有关。
可区分数据源需要项目标识符的唯一哈希值。显然有两个item相同name
, type
and text
.
为了确保散列值是唯一的,添加一个 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