我有一个可以在屏幕上绘制形状的应用程序。用户可以在屏幕上拖动它们。使用 XCode 15 / Swift 5.x 构建时它可以正常工作。但是,当使用 XCode 16 / Swift 6 构建时,对数据模型的更改并不总是会触发重绘。
数据模型包含一个 @Observable 类,其中包含一个“gadgets”数组,每个“gadgets”都是一个结构体,其中包含应绘制的屏幕位置:
@Observable class GadgetModel: Encodable, Identifiable {
var gadgets = [GadgetNode]()
每个 GadgetNode 都是一个结构体:
struct GadgetNode : Identifiable, Codable, Hashable {
public var gadgetType: GadgetType = .unassigned
public var id: Int = 0
var location: CGPoint = CGPoint(x: 50, y: 50)
数据模型中的每个 GadgetNode 都是由一个视图绘制的,用户可以将其拖动到新位置。这是一个高度简化的视图草图:
struct NodeView<Content: View>: View {
@Environment(GadgetModel.self) private var gadgetModel
@State var gadget: GadgetNode
@GestureState private var gadgetDragGestureState: CGOffset = .zero
init(gadget: GadgetNode) {
self.gadget = gadget
}
var body: some View {
ZStack{
IconBackground(gadget: gadget, drawImage: gadgetModel.redrawGadgets > 0 )
} // ZStack
.frame(width: NodeConstants.nodeWidth, height: NodeConstants.nodeHeight)
.offset(gadgetDragGestureState) // Updates view while drgging
.gesture(gadgetDragGesture) // sets self.position }
// drag the node
var gadgetDragGesture: some Gesture {
DragGesture(minimumDistance: 3)
.updating($gadgetDragGestureState) {
(inMotionDragGestureValue, state, _ ) in
state = inMotionDragGestureValue.translation
}
.onEnded { endingDragGestureValue in
let endLocation = gadgetModel.gadgets[gadget.id].location + endingDragGestureValue.translation
gadget.location = endLocation
gadgetModel.gadgets[gadget.id].location = endLocation
print("drag has ended. GestureDrag = \(gadgetDragGestureState), final position = \(endLocation)")
for gadget in gadgetModel.gadgets {
print("Gadget \(gadget.id) location = \(gadget.location)")
}
}
}
这一切似乎仍然正常工作 - 当用户拖动形状时,形状正确地跟随拖动,并且数据模型正确更新。到目前为止,一切都很好。
有一个封闭的视图绘制了所有形状,这就是事情变得奇怪的地方。如果在屏幕上拖动形状,它们就会恢复到原来的位置。但是,如果用户添加新形状,所有形状都会正确绘制在正确的位置(同样,这对于 Xcode 15 来说没问题)。
封闭视图还查看数据模型:
struct GadgetDocumentView: View {
@Environment(GadgetModel.self) private var gadgetModel
有一个 ViewBuilder 函数返回各种形状,并且每个形状都被绘制到屏幕上:
ForEach(gadgetModel.gadgets) { gadget in
getGadgetView(gadget) // returns a @Viewbuilder view
.zIndex(Double(gadget.id))
.position(gadget.location)
我很抱歉还没有创建这个的最小可执行版本 - 我希望比我更好地理解 @Observable 行为和 @ViewBuilder 行为的人会立即认识到这个问题(我大概在做一些愚蠢的事情 - 也许像制作各个形状处理拖动事件,而不是在封闭视图中处理它们?)。
有什么想法吗?
这是一个 SwiftUI 错误 - 过度优化导致数据模型更改被忽略。
我更换了:
ForEach(gadgetModel.gadgets) { gadget in
getGadgetView(gadget) // returns a @Viewbuilder view
.zIndex(Double(gadget.id))
.position(gadget.location)
与
ForEach(gadgetModel.gadgets) { gadget in
getGadgetView(gadget) // returns a @Viewbuilder view
.zIndex(Double(gadget.id))
.position(gadgetModel.gadgets[gadget.id].location)