【问题标题】:SwiftUI withAnimation causes unexpected behaviourSwiftUI withAnimation 导致意外行为
【发布时间】:2020-10-04 20:40:33
【问题描述】:

我注意到使用 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”的结果未使用

事情变得更加混乱:只需事先注释掉 print stamenet withAnimation 块/* print("COMMENT THIS LINE OUT") */ XCode 将不再构建项目:

无法生成表达式诊断;请提交错误报告

这是一个错误还是我的方法完全偏离了道路?

【问题讨论】:

    标签: swift xcode swiftui


    【解决方案1】:

    问题的根源在于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&lt;Block&gt; 函数将返回Block

    如果忽略remove(at:)的返回值,则语句变成没有返回的语句(即返回()Void)。因此,withAnimation 函数变为withAnimation&lt;Void&gt; 并返回Void

    现在,因为BlockRow的闭包在删除print语句时只有一行代码,所以它的返回值就是单条语句的返回值,现在是Void

    BlockRow(block: block) { b in
        withAnimation {
            _ = self.blocks.remove(at: self.blocks.firstIndex(of: b)!)
        }
    }
    

    这与 BlockRow 的闭包所期望的 onDelete 闭包的类型相匹配。

    在您的原始代码中,print 语句导致 BlockRow 的闭包有 2 行代码,从而避免了 Swift 使用单行代码来确定闭包的返回类型的特性。您确实收到了一条警告,表明您没有使用从 withAnimation&lt;Block&gt; 函数返回的 Block。 @Asperi 的回答通过将withAnimation&lt;Block&gt; 返回的Block 分配给_ 来修复该警告。这与我建议的解决方案具有相同的效果,但它处理的是更高一级的问题,而不是问题的根源。


    为什么编译器不抱怨你忽略了remove(at:)的返回值?

    remove(at:) 明确设计为允许您丢弃返回的结果,这就是为什么您不会收到警告说它返回的值是您未处理的值。

    @discardableResult mutating func remove(at i: Self.Index) -> Self.Element
    

    但是如您所见,这会导致您遇到令人困惑的结果。您正在使用remove(at:),就好像它返回了Void,但实际上它返回的是从您的数组中删除的Block。然后,这会导致导致您的问题的整个事件链。

    【讨论】:

      【解决方案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 测试

      【讨论】:

      • 这非常有效。谢谢!你知道它为什么会这样吗?我看到 withAnimation 有一个返回类型,但是为什么它会受到天气的影响,是否有一个打印语句(打印语句可以被任何其他调用替换)。我还注意到,如果 Blocks 是一个结构而不是一个类,那么整个问题就不存在了。
      猜你喜欢
      • 1970-01-01
      • 2017-07-20
      • 2020-01-05
      • 1970-01-01
      • 2013-08-03
      • 1970-01-01
      • 1970-01-01
      • 2021-06-04
      • 1970-01-01
      相关资源
      最近更新 更多