Good morning Ladies and Gentleman, in my production app I noticed what I think is a bug when altering a view (For example triggering a toggle) if a UITextField is Focused at the moment the app will just crash. I m able to reproduce this error. Is this supposed to be so? Do I really need to defensive develop this?
Basically I click on "AddCustomSource" and the textfield appears to add the custom source. If I click on cancel or trigger one of the toggle while this textfield has focus, the app will just crash. 100% reproducible.
Here the error that the console shows: " *** 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: 0x13411bc00; frame = (0 0; 290 22); opaque = NO; autoresize = W+H; gestureRecognizers = <NSArray: 0x600002d66880>; placeholder = NewspaperTitle; borderStyle = None; background = <UITextFieldNoBackgroundProvider: 0x6000021e5190: textfield=<UITextField 0x13411bc00>>; layer = <CALayer: 0x6000023568e0>> inside containing view: <SwiftUI.ListCollectionViewCell: 0x134151a00; baseClass = UICollectionViewListCell; frame = (20 82.6667; 358 44); clipsToBounds = YES; layer = <CALayer: 0x60000235d220>> at index path: <NSIndexPath: 0xae4395876dfb5f2e> {length = 2, path = 0 - 1} Current first responder: <TtGC7SwiftUI15CellHostingViewGVS_15ModifiedContentVS_14_ViewList_ViewVS_26CollectionViewCellModifier: 0x134152000; frame = (0 0; 358 44); autoresize = W+H; gestureRecognizers = <NSArray: 0x600002d62f70>; layer = <CALayer: 0x60000235dc20>> inside containing view: <SwiftUI.ListCollectionViewCell: 0x134151a00; baseClass = UICollectionViewListCell; frame = (20 82.6667; 358 44); clipsToBounds = YES; layer = <CALayer: 0x60000235d220>> at index path: <NSIndexPath: 0xae4395876dfb5f2e> {length = 2, path = 0 - 1}' terminating with uncaught exception of type NSException CoreSimulator 857.14 - Device: iPhone 14 Pro Max (9E3E3FA5-4FCC-47D7-9D91-E1E5E48286FE) - Runtime: iOS 16.2 (20C52) - DeviceType: iPhone 14 Pro Max " And here the code that leads to this behaviour:
// ContentView.swift
// BLE
//
// Created by Entwicklung Help Tech GmbH on 22.12.22.
//
import SwiftUI
struct ContentView: View {
@State var categories: [NewsSource] = []
@State var isEditing = false
@State var newSourceName = ""
var body: some View {
VStack {
List($categories, id:\.self ) { $category in
Section {
ForEach($category.sources, id: \.self ) { $source in
Toggle(source.name, isOn: $source.active)
.foregroundColor(Color.black)
.accessibilityLabel(String(localized: "s"))
.onChange(of: categories, perform: { newValue in
})
.tint(Color.blue)
if !isEditing {
Button { [self] in
withAnimation(.spring()) {
isEditing.toggle()
}
} label: {
HStack {
Text(String(localized:"AddCustomSource"))
}
}
}
else {
AddCell
HStack {
Button { [self] in
withAnimation(.spring()) {
//isFocused = false
isEditing.toggle()
}
} label: {
HStack {
Text(String(localized: "Cancel"))
}
}
Spacer()
}
}
}
}
header: {
Text(category.name)
}
}
.padding()
}
.onAppear {
for index in 1...10 {
categories.append(NewsSource(name: "\(index)", feeds: [NewsFeed(name: "News feed \(index)", url: URL(string: "https://google.de")!, active: true)]))
}
}
}
@ViewBuilder var AddCell: some View {
VStack {
HStack {
Button { [self] in
if newSourceName != "" {
withAnimation(.spring()) {
isEditing.toggle()
}
}
} label: {
Image(systemName: "plus")
.background(.green)
.clipShape(Circle())
.foregroundColor(.white)
.frame(width: 20.0, height: 20.0)
}
TextField(String(localized: "NewspaperTitle"),text: $newSourceName)
.onSubmit { [self] in
if newSourceName != ""{
newSourceName = ""
withAnimation(.spring()) {
isEditing.toggle()
}
}
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct NewsSource: Codable, Hashable, Identifiable, Equatable {
var id: UUID { UUID() }
var name: String
var sources: [NewsFeed]
init(name: String, feeds: [NewsFeed]) {
self.name = name
self.sources = feeds
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
self.sources = try container.decode([NewsFeed].self, forKey: .sources)
}
func hash(into hasher: inout Hasher) {
hasher.combine(name)
}
static func ==(lhs: NewsSource, rhs: NewsSource) -> Bool {
return lhs.name == rhs.name && lhs.sources == rhs.sources
}
}
struct Newspaper: Codable, Hashable {
var categories : [NewsSource]
}
struct NewsFeed: Codable, Equatable, Hashable, Identifiable {
var id: UUID? {
UUID()
}
let name: String
let url: URL
var active: Bool
var image: String?
}