如何在不中断删除的情况下在 SwiftUI 中实现 TextFields 列表
How to implement a List of TextFields in SwiftUI without breaking deletion
当我
- 在 XCode
中创建主从应用程序
- 使用核心数据
- 向事件模型添加一个新字段(假设
title
为 String
)
- 将
MasterView
实现更改为此
struct MasterView: View {
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Event.timestamp, ascending: true)],
animation: .default)
var events: FetchedResults<Event>
@Environment(\.managedObjectContext)
var viewContext
var body: some View {
List {
ForEach(events, id: \.self) { event in
NavigationLink(
destination: DetailView(event: event)
) {
TextField("Title", text: Binding(ObservedObject<Event>(wrappedValue: event).projectedValue.title)!)
}
}.onDelete { indices in
self.events.delete(at: indices, from: self.viewContext)
}
}
}
}
,即,将 Text
换成 TextField
,并适当绑定到 event
对象的 title
属性,XCode 会愉快地编译只要我不尝试从列表中删除,应用程序就会以预期的行为运行。
当我尝试删除 Event
时,它因堆栈跟踪而崩溃
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
#0: [33m`SwiftUI.BindingOperations.ForceUnwrapping.get(base: Swift.Optional<A>) -> A[0m
#1: [33m`protocol witness for SwiftUI.Projection.get(base: A.Base) -> A.Projected in conformance SwiftUI.BindingOperations.ForceUnwrapping<A> : SwiftUI.Projection in SwiftUI[0m
#2: [33m`SwiftUI.(ProjectedLocation in _5A9440699EF65619D724050F1A6941EE).update(context: AttributeGraph.AttributeContext<SwiftUI.VoidAttribute>, state: inout Swift.Optional<Any>) -> (B.Projected, Swift.Bool)[0m
#3: [33m`SwiftUI.LocationBox.update(context: AttributeGraph.AttributeContext<SwiftUI.VoidAttribute>, state: inout Swift.Optional<Any>) -> (A.Value, Swift.Bool)[0m
#4: [33m`SwiftUI.Binding.(ScopedLocation in _5436F2B399369BE3B016147A5F8FE9F2).update(context: AttributeGraph.AttributeContext<SwiftUI.VoidAttribute>, state: inout Swift.Optional<Any>) -> (A, Swift.Bool)[0m
#5: [33m`protocol witness for SwiftUI.Location.update(context: AttributeGraph.AttributeContext<SwiftUI.VoidAttribute>, state: inout Swift.Optional<Any>) -> (A.Value, Swift.Bool) in conformance SwiftUI.Binding<A>.(ScopedLocation in _5436F2B399369BE3B016147A5F8FE9F2) : SwiftUI.Location in SwiftUI[0m
#6: [33m`SwiftUI.LocationBox.update(context: AttributeGraph.AttributeContext<SwiftUI.VoidAttribute>, state: inout Swift.Optional<Any>) -> (A.Value, Swift.Bool)[0m
#7: [33m`SwiftUI.Binding.(Box in _5436F2B399369BE3B016147A5F8FE9F2).update(property: inout SwiftUI.Binding<A>, context: AttributeGraph.AttributeContext<SwiftUI.VoidAttribute>) -> Swift.Bool[0m
#8: [33m`static SwiftUI.(BoxVTable in _68550FF604D39F05971FE35A26EE75B0).update(ptr: Swift.UnsafeMutableRawPointer, property: Swift.UnsafeMutableRawPointer, context: AttributeGraph.AttributeContext<SwiftUI.VoidAttribute>) -> Swift.Bool[0m
#9: [33m`SwiftUI._DynamicPropertyBuffer.update(container: Swift.UnsafeMutableRawPointer, context: AttributeGraph.AttributeContext<SwiftUI.VoidAttribute>) -> Swift.Bool[0m
#10: [33m`SwiftUI.(DynamicPropertyBody in _9F92ACD17B554E8AB7D29ABB1E796415).update(context: inout AttributeGraph.AttributeContext<SwiftUI.(DynamicPropertyBody in _9F92ACD17B554E8AB7D29ABB1E796415)<A>>) -> ()[0m
#11: [33m`protocol witness for static AttributeGraph.UntypedAttribute._update(_: Swift.UnsafeMutableRawPointer, graph: __C.AGGraphRef, attribute: __C.AGAttribute) -> () in conformance SwiftUI.(DynamicPropertyBody in _9F92ACD17B554E8AB7D29ABB1E796415)<A> : AttributeGraph.UntypedAttribute in SwiftUI[0m
#12: [33m`partial apply forwarder[0m
#13: [33m`AG::Graph::UpdateStack::update[0m
#14: [33m`AG::Graph::update_attribute[0m
#15: [33m`AG::Subgraph::update[0m
#16: [33m`SwiftUI.ViewGraph.(runTransaction in _D63C4EB7F2B205694B6515509E76E98B)(in: __C.AGGraphRef) -> ()[0m
#17: [33m`closure #1 (__C.AGGraphRef) -> (prefs: Swift.Bool, idealSize: Swift.Bool, outputs: SwiftUI.ViewGraph.Outputs) in SwiftUI.ViewGraph.updateOutputs(at: SwiftUI.Time) -> ()[0m
#18: [33m`SwiftUI.ViewGraph.updateOutputs(at: SwiftUI.Time) -> ()[0m
#19: [33m`closure #1 () -> () in closure #1 () -> () in (extension in SwiftUI):SwiftUI.ViewRendererHost.render(interval: Swift.Double, updateDisplayList: Swift.Bool) -> ()[0m
#20: [33m`closure #1 () -> () in (extension in SwiftUI):SwiftUI.ViewRendererHost.render(interval: Swift.Double, updateDisplayList: Swift.Bool) -> ()[0m
#21: [33m`(extension in SwiftUI):SwiftUI.ViewRendererHost.render(interval: Swift.Double, updateDisplayList: Swift.Bool) -> ()[0m
#22: [33m`closure #1 () -> () in SwiftUI._UIHostingView.requestImmediateUpdate() -> ()[0m
#23: [33m`reabstraction thunk helper from @escaping @callee_guaranteed () -> () to @escaping @callee_unowned @convention(block) () -> ()[0m
#24: [33m`_dispatch_call_block_and_release[0m
#25: [33m`_dispatch_client_callout[0m
#26: [33m`_dispatch_main_queue_callback_4CF[0m
#27: [33m`__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__[0m
#28: [33m`__CFRunLoopRun[0m
#29: [33m`CFRunLoopRunSpecific[0m
#30: [33m`GSEventRunModal[0m
#31: [33m`UIApplicationMain[0m
* #32: [33m`main[0m at AppDelegate.swift:13
#33: [33m`start[0m
我试过这个: 但没有用。我还尝试将文本字段提取到单独的视图中 class 并使用适当的属性包装器。
我想我以某种方式滥用了基础设施(绑定、可观察对象等)。我应该如何使用这些来实现列表中的文本字段?
对此的一些想法:
选项 1
(内联绑定)
(我猜可能会有更简洁的实现,但这段代码工作正常)
// Somewhere in MasterView...
// A function that returns a binding for the title of an event
func titleBindingFor(_ event: Event) -> Binding<String> {
Binding<String>(get: { () -> String in
event.title ?? ""
}) { (title) in
event.title = title
}
}
然后
// Bind it to the textfield
TextField("Title", text: self.titleBindingFor(event))
选项 2
(单独查看)
extension Optional where Wrapped == String {
var safe: String {
get { self ?? "" }
set { self = newValue }
}
}
struct TitleEditor: View {
@ObservedObject var event: Event
var body: some View {
TextField("Title", text: $event.title.safe)
}
}
然后
TitleEditor(event: event)
注意:我还必须在 DetailView 中处理可选的时间戳强制展开(真的是 Apple?!),因为我在删除时崩溃:
struct DetailView: View {
@ObservedObject var event: Event
var body: some View {
let timestamp = event.timestamp ?? Date()
return Text("\(timestamp, formatter: dateFormatter)")
.navigationBarTitle(Text("Detail"))
}
}
请注意,如果标题为 nil,我将返回一个空字符串,这由文本字段优雅地处理(它在新创建的项目上显示占位符)
而不是
TextField("Title", text: Binding(ObservedObject(wrappedValue:
event).projectedValue.title)!)
使用
TextField("Title", text: Binding<String>(
get: {event.title ?? "<none>"}, set: {event.title = [=10=]}))
已测试并适用于 Xcode 11.2、iOS 13.2
当我
- 在 XCode 中创建主从应用程序
- 使用核心数据
- 向事件模型添加一个新字段(假设
title
为String
) - 将
MasterView
实现更改为此
struct MasterView: View {
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Event.timestamp, ascending: true)],
animation: .default)
var events: FetchedResults<Event>
@Environment(\.managedObjectContext)
var viewContext
var body: some View {
List {
ForEach(events, id: \.self) { event in
NavigationLink(
destination: DetailView(event: event)
) {
TextField("Title", text: Binding(ObservedObject<Event>(wrappedValue: event).projectedValue.title)!)
}
}.onDelete { indices in
self.events.delete(at: indices, from: self.viewContext)
}
}
}
}
,即,将 Text
换成 TextField
,并适当绑定到 event
对象的 title
属性,XCode 会愉快地编译只要我不尝试从列表中删除,应用程序就会以预期的行为运行。
当我尝试删除 Event
时,它因堆栈跟踪而崩溃
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
#0: [33m`SwiftUI.BindingOperations.ForceUnwrapping.get(base: Swift.Optional<A>) -> A[0m
#1: [33m`protocol witness for SwiftUI.Projection.get(base: A.Base) -> A.Projected in conformance SwiftUI.BindingOperations.ForceUnwrapping<A> : SwiftUI.Projection in SwiftUI[0m
#2: [33m`SwiftUI.(ProjectedLocation in _5A9440699EF65619D724050F1A6941EE).update(context: AttributeGraph.AttributeContext<SwiftUI.VoidAttribute>, state: inout Swift.Optional<Any>) -> (B.Projected, Swift.Bool)[0m
#3: [33m`SwiftUI.LocationBox.update(context: AttributeGraph.AttributeContext<SwiftUI.VoidAttribute>, state: inout Swift.Optional<Any>) -> (A.Value, Swift.Bool)[0m
#4: [33m`SwiftUI.Binding.(ScopedLocation in _5436F2B399369BE3B016147A5F8FE9F2).update(context: AttributeGraph.AttributeContext<SwiftUI.VoidAttribute>, state: inout Swift.Optional<Any>) -> (A, Swift.Bool)[0m
#5: [33m`protocol witness for SwiftUI.Location.update(context: AttributeGraph.AttributeContext<SwiftUI.VoidAttribute>, state: inout Swift.Optional<Any>) -> (A.Value, Swift.Bool) in conformance SwiftUI.Binding<A>.(ScopedLocation in _5436F2B399369BE3B016147A5F8FE9F2) : SwiftUI.Location in SwiftUI[0m
#6: [33m`SwiftUI.LocationBox.update(context: AttributeGraph.AttributeContext<SwiftUI.VoidAttribute>, state: inout Swift.Optional<Any>) -> (A.Value, Swift.Bool)[0m
#7: [33m`SwiftUI.Binding.(Box in _5436F2B399369BE3B016147A5F8FE9F2).update(property: inout SwiftUI.Binding<A>, context: AttributeGraph.AttributeContext<SwiftUI.VoidAttribute>) -> Swift.Bool[0m
#8: [33m`static SwiftUI.(BoxVTable in _68550FF604D39F05971FE35A26EE75B0).update(ptr: Swift.UnsafeMutableRawPointer, property: Swift.UnsafeMutableRawPointer, context: AttributeGraph.AttributeContext<SwiftUI.VoidAttribute>) -> Swift.Bool[0m
#9: [33m`SwiftUI._DynamicPropertyBuffer.update(container: Swift.UnsafeMutableRawPointer, context: AttributeGraph.AttributeContext<SwiftUI.VoidAttribute>) -> Swift.Bool[0m
#10: [33m`SwiftUI.(DynamicPropertyBody in _9F92ACD17B554E8AB7D29ABB1E796415).update(context: inout AttributeGraph.AttributeContext<SwiftUI.(DynamicPropertyBody in _9F92ACD17B554E8AB7D29ABB1E796415)<A>>) -> ()[0m
#11: [33m`protocol witness for static AttributeGraph.UntypedAttribute._update(_: Swift.UnsafeMutableRawPointer, graph: __C.AGGraphRef, attribute: __C.AGAttribute) -> () in conformance SwiftUI.(DynamicPropertyBody in _9F92ACD17B554E8AB7D29ABB1E796415)<A> : AttributeGraph.UntypedAttribute in SwiftUI[0m
#12: [33m`partial apply forwarder[0m
#13: [33m`AG::Graph::UpdateStack::update[0m
#14: [33m`AG::Graph::update_attribute[0m
#15: [33m`AG::Subgraph::update[0m
#16: [33m`SwiftUI.ViewGraph.(runTransaction in _D63C4EB7F2B205694B6515509E76E98B)(in: __C.AGGraphRef) -> ()[0m
#17: [33m`closure #1 (__C.AGGraphRef) -> (prefs: Swift.Bool, idealSize: Swift.Bool, outputs: SwiftUI.ViewGraph.Outputs) in SwiftUI.ViewGraph.updateOutputs(at: SwiftUI.Time) -> ()[0m
#18: [33m`SwiftUI.ViewGraph.updateOutputs(at: SwiftUI.Time) -> ()[0m
#19: [33m`closure #1 () -> () in closure #1 () -> () in (extension in SwiftUI):SwiftUI.ViewRendererHost.render(interval: Swift.Double, updateDisplayList: Swift.Bool) -> ()[0m
#20: [33m`closure #1 () -> () in (extension in SwiftUI):SwiftUI.ViewRendererHost.render(interval: Swift.Double, updateDisplayList: Swift.Bool) -> ()[0m
#21: [33m`(extension in SwiftUI):SwiftUI.ViewRendererHost.render(interval: Swift.Double, updateDisplayList: Swift.Bool) -> ()[0m
#22: [33m`closure #1 () -> () in SwiftUI._UIHostingView.requestImmediateUpdate() -> ()[0m
#23: [33m`reabstraction thunk helper from @escaping @callee_guaranteed () -> () to @escaping @callee_unowned @convention(block) () -> ()[0m
#24: [33m`_dispatch_call_block_and_release[0m
#25: [33m`_dispatch_client_callout[0m
#26: [33m`_dispatch_main_queue_callback_4CF[0m
#27: [33m`__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__[0m
#28: [33m`__CFRunLoopRun[0m
#29: [33m`CFRunLoopRunSpecific[0m
#30: [33m`GSEventRunModal[0m
#31: [33m`UIApplicationMain[0m
* #32: [33m`main[0m at AppDelegate.swift:13
#33: [33m`start[0m
我试过这个:
我想我以某种方式滥用了基础设施(绑定、可观察对象等)。我应该如何使用这些来实现列表中的文本字段?
对此的一些想法:
选项 1 (内联绑定)
(我猜可能会有更简洁的实现,但这段代码工作正常)
// Somewhere in MasterView...
// A function that returns a binding for the title of an event
func titleBindingFor(_ event: Event) -> Binding<String> {
Binding<String>(get: { () -> String in
event.title ?? ""
}) { (title) in
event.title = title
}
}
然后
// Bind it to the textfield
TextField("Title", text: self.titleBindingFor(event))
选项 2 (单独查看)
extension Optional where Wrapped == String {
var safe: String {
get { self ?? "" }
set { self = newValue }
}
}
struct TitleEditor: View {
@ObservedObject var event: Event
var body: some View {
TextField("Title", text: $event.title.safe)
}
}
然后
TitleEditor(event: event)
注意:我还必须在 DetailView 中处理可选的时间戳强制展开(真的是 Apple?!),因为我在删除时崩溃:
struct DetailView: View {
@ObservedObject var event: Event
var body: some View {
let timestamp = event.timestamp ?? Date()
return Text("\(timestamp, formatter: dateFormatter)")
.navigationBarTitle(Text("Detail"))
}
}
请注意,如果标题为 nil,我将返回一个空字符串,这由文本字段优雅地处理(它在新创建的项目上显示占位符)
而不是
TextField("Title", text: Binding(ObservedObject(wrappedValue: event).projectedValue.title)!)
使用
TextField("Title", text: Binding<String>(
get: {event.title ?? "<none>"}, set: {event.title = [=10=]}))
已测试并适用于 Xcode 11.2、iOS 13.2