简体   繁体   中英

Attempt to delete item containing first responder that refused to resign

I'm trying to show conditionally a List based on showElements value. This List contain a ForEach to show all the elements of the names array and a TextField to allow users to add a new name. Changing the value of showElements to false should remove all the elements of names array and empty name string. The code above works on iOS 15 and Xcode 13 but if I run the same project with Xcode 14 the app crash. The steps to reproduce the crash are:

  1. Enable the Toggle
  2. Select the TextField
  3. Disable the Toggle (here the crash occur)

Here the code:

struct ContentView: View {
    @State private var names: [String] = []
    @State private var name: String = ""
    @State private var showElements: Bool = false
    
    var body: some View {
        Form {
            Section {
                Toggle(isOn: $showElements) {
                    Text("ShowElements")
                }
                .onChange(of: showElements) { newValue in
                    if newValue == false {
                        name = ""
                        names = []
                    }
                }
                
                if showElements {
                    List {
                        ForEach(names, id: \.self) { name in
                            Text(name)
                        }
                        
                        HStack {
                            TextField("name", text: $name)
                            Button {
                                names.append(name)
                                name = ""
                            } label: {
                                Text("Add")
                            }
                        }
                    }
                }
            }
        }
    }
}

Here the console log:

2022-09-08 23:03:26.516945+0200 test[28915:875267] *** Assertion failure in -[SwiftUI.UpdateCoalescingCollectionView _resignOrRebaseFirstResponderViewWithUpdateItems:indexPathMapping:], UICollectionView.m:11437
2022-09-08 23:03:26.524664+0200 test[28915:875267] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Attempt to delete item containing first responder that refused to resign.
First responder that was asked to resign (returned YES from -resignFirstResponder): <UITextField: 0x7fa25b08aa00; frame = (0 0; 274.333 22); opaque = NO; autoresize = W+H; gestureRecognizers = <NSArray: 0x600002271c80>; placeholder = name; borderStyle = None; background = <_UITextFieldNoBackgroundProvider: 0x600002ec7c10: textfield=<UITextField 0x7fa25b08aa00>>; layer = <CALayer: 0x600002c7f540>> inside containing view: <SwiftUI.ListCollectionViewCell: 0x7fa25b054c00; baseClass = UICollectionViewListCell; frame = (20 79; 353 44); clipsToBounds = YES; layer = <CALayer: 0x600002c61400>> at index path: <NSIndexPath: 0xde7e4e75a67d6979> {length = 2, path = 0 - 1}
Current first responder: <_TtGC7SwiftUI15CellHostingViewGVS_15ModifiedContentVS_14_ViewList_ViewVS_26CollectionViewCellModifier__: 0x7fa25b012400; frame = (0 0; 353 44); autoresize = W+H; gestureRecognizers = <NSArray: 0x6000022750e0>; layer = <CALayer: 0x600002c616a0>> inside containing view: <SwiftUI.ListCollectionViewCell: 0x7fa25b054c00; baseClass = UICollectionViewListCell; frame = (20 79; 353 44); clipsToBounds = YES; layer = <CALayer: 0x600002c61400>> at index path: <NSIndexPath: 0xde7e4e75a67d6979> {length = 2, path = 0 - 1}'
*** First throw call stack:
(
    0   CoreFoundation                      0x00007ff800427378 __exceptionPreprocess + 242
    1   libobjc.A.dylib                     0x00007ff80004dbaf objc_exception_throw + 48
    2   Foundation                          0x00007ff800b876ac _userInfoForFileAndLine + 0
    3   UIKitCore                           0x000000010b195c2f -[UICollectionView _resignOrRebaseFirstResponderViewWithUpdateItems:indexPathMapping:] + 1340
    4   UIKitCore                           0x000000010b1939bc -[UICollectionView _updateWithItems:tentativelyForReordering:propertyAnimator:collectionViewAnimator:] + 226
    5   UIKitCore                           0x000000010b18a71b -[UICollectionView _endItemAnimationsWithInvalidationContext:tentativelyForReordering:animator:collectionViewAnimator:] + 14344
    6   UIKitCore                           0x000000010b19814c -[UICollectionView _performBatchUpdates:completion:invalidationContext:tentativelyForReordering:animator:animationHandler:] + 577
    7   UIKitCore                           0x000000010b197e7d -[UICollectionView performBatchUpdates:completion:] + 34
    8   SwiftUI                             0x000000010ec02c4f block_destroy_helper + 25991
    9   SwiftUI                             0x000000010ec03453 block_destroy_helper + 28043
    10  SwiftUI                             0x000000010f23985e __swift_memcpy56_4 + 159666
    11  SwiftUI                             0x000000010eda93be block_destroy_helper.23 + 66721
    12  SwiftUI                             0x000000010eda93d4 block_destroy_helper.23 + 66743
    13  UIKitCore                           0x000000010c277eff +[UIView(Animation) performWithoutAnimation:] + 84
    14  SwiftUI                             0x000000010f23aaa0 __swift_memcpy56_4 + 164340
    15  SwiftUI                             0x000000010f1298f8 objectdestroy.136Tm + 41411
    16  SwiftUI                             0x000000010ee4a86f block_destroy_helper + 35480
    17  SwiftUI                             0x000000010ee4a1d0 block_destroy_helper + 33785
    18  SwiftUI                             0x000000010eb35a78 __swift_assign_boxed_opaque_existential_1 + 70088
    19  SwiftUI                             0x000000010eb359db __swift_assign_boxed_opaque_existential_1 + 69931
    20  SwiftUI                             0x000000010eb35ad1 __swift_assign_boxed_opaque_existential_1 + 70177
    21  CoreFoundation                      0x00007ff800385fe5 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
    22  CoreFoundation                      0x00007ff800380952 __CFRunLoopDoObservers + 515
    23  CoreFoundation                      0x00007ff800380e9d __CFRunLoopRun + 1161
    24  CoreFoundation                      0x00007ff800380637 CFRunLoopRunSpecific + 560
    25  GraphicsServices                    0x00007ff809c0f28a GSEventRunModal + 139
    26  UIKitCore                           0x000000010bc5d425 -[UIApplication _run] + 994
    27  UIKitCore                           0x000000010bc62301 UIApplicationMain + 123
    28  SwiftUI                             0x000000010f6dbfa3 __swift_memcpy53_8 + 95801
    29  SwiftUI                             0x000000010f6dbe50 __swift_memcpy53_8 + 95462
    30  SwiftUI                             0x000000010edfbafc __swift_memcpy195_8 + 12192
    31  test                                0x000000010a63123e $s4test0A3AppV5$mainyyFZ + 30
    32  test                                0x000000010a6312c9 main + 9
    33  dyld                                0x000000010a87c2bf start_sim + 10
    34  ???                                 0x000000011700252e 0x0 + 4680852782
)
libc++abi: terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Attempt to delete item containing first responder that refused to resign.
First responder that was asked to resign (returned YES from -resignFirstResponder): <UITextField: 0x7fa25b08aa00; frame = (0 0; 274.333 22); opaque = NO; autoresize = W+H; gestureRecognizers = <NSArray: 0x600002271c80>; placeholder = name; borderStyle = None; background = <_UITextFieldNoBackgroundProvider: 0x600002ec7c10: textfield=<UITextField 0x7fa25b08aa00>>; layer = <CALayer: 0x600002c7f540>> inside containing view: <SwiftUI.ListCollectionViewCell: 0x7fa25b054c00; baseClass = UICollectionViewListCell; frame = (20 79; 353 44); clipsToBounds = YES; layer = <CALayer: 0x600002c61400>> at index path: <NSIndexPath: 0xde7e4e75a67d6979> {length = 2, path = 0 - 1}
Current first responder: <_TtGC7SwiftUI15CellHostingViewGVS_15ModifiedContentVS_14_ViewList_ViewVS_26CollectionViewCellModifier__: 0x7fa25b012400; frame = (0 0; 353 44); autoresize = W+H; gestureRecognizers = <NSArray: 0x6000022750e0>; layer = <CALayer: 0x600002c616a0>> inside containing view: <SwiftUI.ListCollectionViewCell: 0x7fa25b054c00; baseClass = UICollectionViewListCell; frame = (20 79; 353 44); clipsToBounds = YES; layer = <CALayer: 0x600002c61400>> at index path: <NSIndexPath: 0xde7e4e75a67d6979> {length = 2, path = 0 - 1}'
terminating with uncaught exception of type NSException
CoreSimulator 857.7 - Device: iPhone 14 Pro (157A86B8-D8A5-4E19-A9B1-B6D9971F2A97) - Runtime: iOS 16.0 (20A360) - DeviceType: iPhone 14 Pro
(lldb) 

How can I solve this problem?

Lose the TextField focus before changing its state

This happens only when you try to change the TextField while it is still focused. If you add new elements and do not click/focus on a TextField and change the Toggle state you will see your code does not crash.

To solve the problem, add a @FocusState to the TextField

@FocusState private var textFieldIsFocused: Bool

TextField("name", text: $name)
    .focused($textFieldIsFocused)

and lose focus before changing the Toggle state. To achieve this I added a custom Binding to the Toggle so we can release the TextField focus before changing the state.

Toggle(
    isOn:
        .init(
            get: { showElements },
            set: {
                textFieldIsFocused = false
                showElements = $0
            }
        )
    ) {
         Text("ShowElements")
      }
      .onChange(of: showElements) { newValue in
          if newValue == false {
              name = ""
              names = []
          }
      }

The complete code

import SwiftUI

struct ContentView: View {
    @State private var names: [String] = []
    @State private var name: String = ""
    @State private var showElements: Bool = false

    @FocusState private var textFieldIsFocused: Bool

    var body: some View {
        Form {
            Section {
                Toggle(
                    isOn:
                    .init(
                        get: { showElements },
                        set: {
                            textFieldIsFocused = false
                            showElements = $0
                        }
                    )
                ) {
                    Text("ShowElements")
                }
                .onChange(of: showElements) { newValue in
                    if newValue == false {
                        name = ""
                        names = []
                    }
                }

                if showElements {
                    List {
                        ForEach(names, id: \.self) { name in
                            Text(name)
                        }

                        HStack {
                            TextField("name", text: $name)
                                .focused($textFieldIsFocused)
                            Button {
                                names.append(name)
                                name = ""
                            } label: {
                                Text("Add")
                            }
                        }
                    }
                }
            }
        }
    }
}

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

Happy Coding,
Kolmar Kafran (:

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM