首页 > 解决方案 > 为什么 iOS 在尝试以相反的顺序显示排序结果时会崩溃?

问题描述

我有一个按部分显示信息的 SwiftUI 应用程序。当按下按钮时,我想对节标题以及每个节的内容进行升序或降序排序(即,按钮反转当前的排序顺序。

按下按钮时,应用程序立即崩溃并出现以下错误。我已经简化了代码以轻松重现问题。在真实的应用程序中,除非您滚动数据然后排序,否则应用程序不会崩溃。

我怎样才能在没有崩溃的情况下达到预期的结果?任何帮助或指示将不胜感激。

Catalina 10.15.2
Xcode 11.3
iOS 13.3
2020-01-29 22:31:07.837639-0800 sections[33051:9858192] *** Assertion failure in -[_UITableViewUpdateSupport _setupAnimationsForNewlyInsertedCells], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKitCore_Sim/UIKit-3901.4.2/UITableViewSupport.m:1311
2020-01-29 22:31:07.847803-0800 sections[33051:9858192] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Attempt to create two animations for cell'
*** First throw call stack:
(
    0   CoreFoundation                      0x00007fff23c7127e __exceptionPreprocess + 350
    1   libobjc.A.dylib                     0x00007fff513fbb20 objc_exception_throw + 48
    2   CoreFoundation                      0x00007fff23c70ff8 +[NSException raise:format:arguments:] + 88
    3   Foundation                          0x00007fff256e9b51 -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 191
    4   UIKitCore                           0x00007fff482c3979 -[_UITableViewUpdateSupport(Private) _setupAnimationsForNewlyInsertedCells] + 1575
    5   UIKitCore                           0x00007fff482ce773 -[_UITableViewUpdateSupport _setupAnimations] + 118
    6   UIKitCore                           0x00007fff48265026 -[UITableView _updateWithItems:updateSupport:] + 2892
    7   UIKitCore                           0x00007fff4825cfd4 -[UITableView _endCellAnimationsWithContext:] + 17188
    8   UIKitCore                           0x00007fff48276e0b -[UITableView endUpdatesWithContext:] + 112
    9   UIKitCore                           0x00007fff48276fb7 -[UITableView _performBatchUpdates:withContext:completion:] + 253
    10  UIKitCore                           0x00007fff482770cf -[UITableView performBatchUpdates:completion:] + 98
    11  SwiftUI                             0x00007fff2c24ea26 $s7SwiftUI25UpdateCoalescingTableView33_BFB370BA5F1BADDC9D83021565761A49LLC19performBatchUpdates_10completionyyycSg_ySbcSgtF + 470
    12  SwiftUI                             0x00007fff2c24ebaf $s7SwiftUI25UpdateCoalescingTableView33_BFB370BA5F1BADDC9D83021565761A49LLC19performBatchUpdates_10completionyyycSg_ySbcSgtFTo + 175
    13  SwiftUI                             0x00007fff2c2510cc $s7SwiftUI19ListCoreCoordinator33_BFB370BA5F1BADDC9D83021565761A49LLC17updateUITableView_4from2toySo0mN0C_xxtF + 1692
    14  SwiftUI                             0x00007fff2c24f9ed $s7SwiftUI9_ListCore33_BFB370BA5F1BADDC9D83021565761A49LLV12updateUIView_7contextyAA12TableWrapperACLLC_AA0L20RepresentableContextVyADyxq_GGtF + 973
    15  SwiftUI                             0x00007fff2c6149c1 $s7SwiftUI32PlatformViewRepresentableAdaptor33_19642D833A8FE469B137699ED1426762LLV06updateD8Provider_7contexty10UIViewTypeQz_AA0cdE7ContextVyADyxGGtF + 289
    16  SwiftUI                             0x00007fff2c2c6afb $s7SwiftUI17PlatformViewChild33_A513612C07DFA438E70B9FA90719B40DLLV6update7contexty14AttributeGraph0O7ContextVyADyxGGz_tFyyXEfU_ + 2315
    17  SwiftUI                             0x00007fff2c2c07f6 $s7SwiftUI17PlatformViewChild33_A513612C07DFA438E70B9FA90719B40DLLV6update7contexty14AttributeGraph0O7ContextVyADyxGGz_tF + 310
    18  SwiftUI                             0x00007fff2c2c7e60 $s7SwiftUI17PlatformViewChild33_A513612C07DFA438E70B9FA90719B40DLLVyxG14AttributeGraph07UntypedM0AafGP7_update_5graph9attributeySv_So10AGGraphRefaSo11AGAttributeatFZTW + 32
    19  AttributeGraph                      0x00007fff2f8c0c69 $sTA + 25
    20  AttributeGraph                      0x00007fff2f8a8ac5 _ZN2AG5Graph11UpdateStack6updateEv + 1111
    21  AttributeGraph                      0x00007fff2f8a8d83 _ZN2AG5Graph16update_attributeEjb + 377
    22  AttributeGraph                      0x00007fff2f8ad9a1 _ZN2AG8Subgraph6updateEj + 929
    23  SwiftUI                             0x00007fff2c180405 $s7SwiftUI9ViewGraphC13updateWrapperyyyyXEcvgyyyXEcfU_yACXEfU_ + 53
    24  SwiftUI                             0x00007fff2c185c11 $s7SwiftUI9ViewGraphCIgg_ACytIeggr_TRTA + 17
    25  SwiftUI                             0x00007fff2c4cbdd7 $s7SwiftUI16ViewRendererHostPAAE06updateC5Graph4bodyqd__qd__AA0cG0CXE_tlF + 71
    26  SwiftUI                             0x00007fff2c4cbd83 $s7SwiftUI14_UIHostingViewCyqd__GAA0D13GraphDelegateA2aEP06updatedE04bodyqd__qd__AA0dE0CXE_tlFTW + 19
    27  SwiftUI                             0x00007fff2c1803a5 $s7SwiftUI9ViewGraphC13updateWrapperyyyyXEcvgyyyXEcfU_ + 181
    28  AttributeGraph                      0x00007fff2f8ad890 _ZN2AG8Subgraph6updateEj + 656
    29  SwiftUI                             0x00007fff2c1826c0 $s7SwiftUI9ViewGraphC14runTransaction33_D63C4EB7F2B205694B6515509E76E98BLL2inySo10AGGraphRefa_tF + 224
    30  SwiftUI                             0x00007fff2c182490 $s7SwiftUI9ViewGraphC17flushTransactionsyyFySo10AGGraphRefaXEfU_ + 256
    31  SwiftUI                             0x00007fff2c18212f $s7SwiftUI9ViewGraphC17flushTransactionsyyF + 223
    32  SwiftUI                             0x00007fff2c1822af $s7SwiftUI9ViewGraphC16asyncTransaction_8mutation5styleyAA0F0V_xAA01_D14Mutation_StyleOtAA0dI0RzlFyycfU_yACXEfU_ + 15
    33  SwiftUI                             0x00007fff2c180439 $s7SwiftUI9ViewGraphCIgg_ACytIeggr_TR03$s7a3UI9cD92C16asyncTransaction_8mutation5styleyAA0F0V_xAA01_D14Mutation_StyleOtAA0dI0RzlFyycfU_yACXEfU_Tf3nnpf_n + 9
    34  SwiftUI                             0x00007fff2c4cbdd7 $s7SwiftUI16ViewRendererHostPAAE06updateC5Graph4bodyqd__qd__AA0cG0CXE_tlF + 71
    35  SwiftUI                             0x00007fff2c4cbd83 $s7SwiftUI14_UIHostingViewCyqd__GAA0D13GraphDelegateA2aEP06updatedE04bodyqd__qd__AA0dE0CXE_tlFTW + 19
    36  SwiftUI                             0x00007fff2c18228a $s7SwiftUI9ViewGraphC16asyncTransaction_8mutation5styleyAA0F0V_xAA01_D14Mutation_StyleOtAA0dI0RzlFyycfU_ + 122
    37  SwiftUI                             0x00007fff2c1a078c $sIeg_ytIegr_TR + 12
    38  SwiftUI                             0x00007fff2bfe3261 $sIeg_ytIegr_TRTA + 17
    39  SwiftUI                             0x00007fff2bfe3187 $sSo9NSRunLoopC7SwiftUIE14flushObserversyyFZ + 119
    40  SwiftUI                             0x00007fff2bfe3109 $sSo9NSRunLoopC7SwiftUIE11addObserveryyyycFZySo05CFRunbF3RefaSg_So0gB8ActivityVSvSgtcfU_ + 9
    41  SwiftUI                             0x00007fff2bfe31fb $sSo9NSRunLoopC7SwiftUIE11addObserveryyyycFZySo05CFRunbF3RefaSg_So0gB8ActivityVSvSgtcfU_To + 43
    42  CoreFoundation                      0x00007fff23bd3867 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
    43  CoreFoundation                      0x00007fff23bce2fe __CFRunLoopDoObservers + 430
    44  CoreFoundation                      0x00007fff23bce97a __CFRunLoopRun + 1514
    45  CoreFoundation                      0x00007fff23bce066 CFRunLoopRunSpecific + 438
    46  GraphicsServices                    0x00007fff384c0bb0 GSEventRunModal + 65
    47  UIKitCore                           0x00007fff48092d4d UIApplicationMain + 1621
    48  sections                            0x000000010af50bbb main + 75
    49  libdyld.dylib                       0x00007fff5227ec25 start + 1
    50  ???                                 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb) 
//
//  ContentView.swift
//  sections
//

import SwiftUI

struct ContentView: View {
    @State private var ascending: Bool = true

    var body: some View {


               VStack {
                   Button(action: {
                       self.ascending.toggle()
                   }) {
                       if self.ascending {
                           Text("Sort Descending")
                       } else {
                           Text("Sort Ascending")
                       }
                   }

                   SectionView(boats: [Boat(name: "B1", number: "1"),
                                       Boat(name: "B2", number: "2"),
                                       Boat(name: "B3", number: "3"),
                                       Boat(name: "B22", number: "22"),
                                       Boat(name: "B24", number: "24"),
                                       Boat(name: "B333", number: "333"),
                                       Boat(name: "B4", number: "3"),
                                       Boat(name: "B5", number: "3"),

                   ], ascending: ascending)
               }
           }
       }

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
//
//  SectionView.swift
//  sections
//


import SwiftUI

struct Boat: Hashable {
    var id = UUID()
    var name: String
    var number: String
}

struct SectionView: View {

    var boats = [Boat]()
    var ascending: Bool

    var body: some View {
        NavigationView {
            List {
                ForEach (self.classDictionary(ascending: ascending), id: \.0) { key, boats in
                    Section(header: Text("\(key)").font(.caption).bold()) {

                        ForEach (boats, id: \.self) { boat in
                            NavigationLink(destination: DetailView(boat: boat)) {
                                Text(boat.name)
                            }
                        }
                    }
                }
            }
        }
    }

    //
    func classDictionary(ascending: Bool) ->  [(key: String, boats: [Boat])] {

        var returnTuple = [(key: String, boats: [Boat])]()

        let groupedDictionary = Dictionary(grouping: self.boats, by: {String(numberOfDigits(in: Int($0.number)!))})

        for (key, boats) in groupedDictionary {
            returnTuple.append((key, boats))
        }

        if ascending {
            returnTuple = returnTuple.sorted(by: { $0.boats.map { Int($0.number)!}.min() ?? 0 < $1.boats.map { Int($0.number)!}.min() ?? 0 })
        } else {
            returnTuple = returnTuple.sorted(by: { $0.boats.map { Int($0.number)!}.max() ?? 0 > $1.boats.map { Int($0.number)!}.max() ?? 0 })
        }

        return returnTuple
    }

    // private recursive method for counting digits
    private func numberOfDigits(in number: Int) -> Int {
        if number < 10 && number >= 0 || number > -10 && number < 0 {
            return 1
        } else {
            return 1 + numberOfDigits(in: number/10)
        }
    }
}



struct SectionView_Previews: PreviewProvider {

    static var ascending: Bool = true

    static var previews: some View {

        VStack {
            Button(action: {
                self.ascending.toggle()
            }) {
                if self.ascending {
                    Text("Sort Descending")
                } else {
                    Text("Sort Ascending")
                }
                //                Text("Change Sort")
            }

            SectionView(boats: [Boat(name: "B1", number: "1"),
                                Boat(name: "B2", number: "2"),
                                Boat(name: "B3", number: "3"),
                                Boat(name: "B22", number: "22"),
                                Boat(name: "B24", number: "24"),
                                Boat(name: "B333", number: "333"),
                                Boat(name: "B4", number: "3"),
                                Boat(name: "B5", number: "3"),

            ], ascending: ascending)
        }
    }
}


struct DetailView: View {

    var boat: Boat

    var body: some View {
        Text(boat.name)
    }
}

标签: iosswiftui

解决方案


问题的根源在于内容标识符......因此更改以下几行可修复崩溃

1)

ForEach (self.classDictionary(ascending: ascending), id: \.1) { key, boats in

注意:出于性能目的,我建议改为使用自己的 UUID 作为 id 为 Section 数据创建显式结构。

2)

ForEach (boats, id: \.id) { boat in

推荐阅读