SwiftUI withAnimation导致意外行为

时间:2020-10-04 08:59:15

标签: swift xcode swiftui

我注意到使用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将不再构建项目:

无法产生表达诊断信息;请提交错误报告

这是一个错误还是我的劝告完全脱离了越野?

2 个答案:

答案 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:)的返回值,它将返回BlockBlock,然后成为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。然后,这会导致导致您遇到问题的整个事件链。