You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Xcode14下SwiftUI列表因First Responder未 resign崩溃的解决方法

问题

基于showElements的值条件展示包含ForEach和TextField的List组件,关闭showElements时清空names数组和name字符串。代码在iOS 15/Xcode 13环境下正常运行,但在Xcode 14/iOS 16环境下会崩溃。

崩溃复现步骤

  • 开启Toggle组件
  • 选中TextField组件
  • 关闭Toggle组件(此时触发崩溃)

原代码

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

控制台崩溃日志

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) 

解决方案

崩溃根源是iOS 16中,当包含第一响应者(聚焦状态的TextField)的List被移除时,系统无法正确处理第一响应者的 resign 逻辑,同时同步清空数组的操作加剧了这个冲突。

修复方案1:主动管理TextField聚焦状态

通过@FocusState绑定TextField的聚焦状态,关闭Toggle时先取消聚焦,再清空数据:

struct ContentView: View {
    @State private var names: [String] = []
    @State private var name: String = ""
    @State private var showElements: Bool = false
    @FocusState private var isTextFieldFocused: Bool // 新增聚焦状态管理
    
    var body: some View {
        Form {
            Section {
                Toggle(isOn: $showElements) {
                    Text("ShowElements")
                }
                .onChange(of: showElements) { newValue in
                    if newValue == false {
                        isTextFieldFocused = false // 先取消TextField聚焦
                        name = ""
                        names = []
                    }
                }
                
                if showElements {
                    List {
                        ForEach(names, id: \.self) { name in
                            Text(name)
                        }
                        
                        HStack {
                            TextField("name", text: $name)
                                .focused($isTextFieldFocused) // 绑定聚焦状态
                            Button {
                                names.append(name)
                                name = ""
                                isTextFieldFocused = false // 添加后也取消聚焦
                            } label: {
                                Text("Add")
                            }
                        }
                    }
                }
            }
        }
    }
}

修复方案2:延迟清空数组操作

将清空数组的操作延迟到主线程下一个运行循环,给系统足够时间处理第一响应者的 resign:

.onChange(of: showElements) { newValue in
    if newValue == false {
        name = ""
        DispatchQueue.main.async {
            names = []
        }
    }
}

修复方案3:避免视图被移除

保持List始终存在,通过opacity、disabled和frame控制显示与交互,彻底规避视图移除导致的第一响应者冲突:

// 替换原有的条件List代码
List {
    ForEach(names, id: \.self) { name in
        Text(name)
    }
    
    HStack {
        TextField("name", text: $name)
        Button {
            names.append(name)
            name = ""
        } label: {
            Text("Add")
        }
    }
}
.opacity(showElements ? 1 : 0)
.disabled(!showElements)
.frame(height: showElements ? nil : 0)

内容的提问来源于stack exchange,提问作者Gaspare

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 06:39:37