我注意到使用swiftUI的withAnimation{}
时XCode的异常行为。
我创建了以下工作示例:
import SwiftUI
class Block: Hashable, Identifiable {
// Note: this needs to be a class rather than a struct
var id = UUID()
static func == (lhs: Block, rhs: Block) -> Bool {
lhs.id == rhs.id
}
func hash(into hasher: inout Hasher) {
hasher.combine(self.id)
}
}
struct ContentView: View {
@State private var blocks: [Block]
init() {
var blocks = [Block]()
/// generate some blocks here
blocks.append(Block())
blocks.append(Block())
blocks.append(Block())
self._blocks = State(initialValue: blocks)
}
var body: some View {
VStack {
ForEach(blocks, id: \.self) { block in
BlockRow(block: block) { b in
print("COMMENT THIS LINE OUT") // try commenting out this line -> XCODE won't build anymore
withAnimation {
self.blocks.remove(at: self.blocks.firstIndex(of: b)!)
}
}
}
}
}
}
struct BlockRow: View {
@State var block: Block
var onDelete: (Block) -> Void = {_ in}
var body: some View {
Text("<Block>")
.onTapGesture {
print("Tap block \(block.id)")
self.onDelete(block)
}
}
}
上面的示例按预期方式工作,如果单击其中一个块,则该块将被删除,随后的块将在自由位置滑动。
但是XCode在这里产生警告,我不明白:
调用'withAnimation'的结果未使用
事情变得更加令人困惑:仅通过预先注释打印stamenet,withAnimation块/* print("COMMENT THIS LINE OUT") */
XCode将不再构建项目:
无法产生表达诊断信息;请提交错误报告
这是一个错误还是我的劝告完全脱离了越野?
答案 0 :(得分:2)
使用以下内容
var body: some View {
VStack {
ForEach(blocks, id: \.self) { block in
BlockRow(block: block) { b in
_ = withAnimation {
self.blocks.remove(at: self.blocks.firstIndex(of: b)!)
}
}
}
}
}
通过Xcode 12.0 / iOS 14测试
答案 1 :(得分:0)
问题的根源是self.blocks.remove(at:)
返回一个您未处理的值的事实。如果您显式忽略该值,则无论是否存在print
语句,您的代码都会按预期工作。
withAnimation {
_ = self.blocks.remove(at: self.blocks.firstIndex(of: b)!)
}
Swift具有一个功能,即只有一行代码的闭包将返回该语句的值作为闭包的返回。因此,在这种情况下,如果您不忽略remove(at:)
的返回值,它将返回Block
和Block
,然后成为withAnimation
闭包的返回类型。>
withAnimation
的定义如下:
func withAnimation<Result>(_ animation: Animation? = .default, _ body: () throws -> Result) rethrows -> Result
这是一个泛型函数,它需要关闭。该闭包的返回类型确定了通用占位符的类型,后者又确定了withAnimation
本身的返回类型。
因此,如果您不忽略remove(at:)
的返回类型,则withAnimation<Block>
函数将返回Block
。
如果忽略remove(at:)
的返回值,该语句将成为没有返回值的语句(即,它返回()
或Void
)。因此,withAnimation
函数变为withAnimation<Void>
,并返回Void
。
现在,由于BlockRow
的闭包在删除打印语句时只有一行代码,因此它的返回值是该语句的返回值,现在为Void
:< / p>
BlockRow(block: block) { b in
withAnimation {
_ = self.blocks.remove(at: self.blocks.firstIndex(of: b)!)
}
}
,这与BlockRow
的闭包期望的onDelete
闭包类型相同。
在您的原始代码中,print
语句导致BlockRow
的闭包有两行代码,因此避免了Swift的使用单行代码确定闭包的返回类型的功能。 。您确实收到警告,提示您没有使用Block
函数返回的withAnimation<Block>
。 @Asperi的答案通过将Block
返回的withAnimation<Block>
分配给_
来解决了该警告。这与我建议的解决方案具有相同的效果,但它是在更高一级而不是从根本上解决问题。
为什么编译器不会抱怨您忽略了remove(at:)
的返回值?
remove(at:)
专门用于允许您丢弃返回的结果,这就是为什么您不会收到警告它返回您未处理的值的原因。
@discardableResult mutating func remove(at i: Self.Index) -> Self.Element
但是,正如您所看到的,这导致您遇到令人困惑的结果。您使用remove(at:)
就好像它返回了Void
,但实际上它返回的是从数组中删除的Block
。然后,这会导致导致您遇到问题的整个事件链。