SwiftUI:视图从一个堆栈到另一个堆栈的动画移动?

Ube*_*son 5 ios swiftui

背景

\n\n

我正在使用 SwiftUI 实现一款类似纸牌游戏的纸牌游戏,其中包括堆叠纸牌以及将纸牌从一叠移到另一叠的功能,例如:

\n\n

卡牌运动

\n\n

在此示例中,当我点击“移动卡”按钮时,\xe2\x99\xa0\xef\xb8\x8f9 从第 1 列移动到第 2 列,位于 \xe2\x9d\xa4\xef 的顶部\xb8\x8f10,动画是 \xe2\x99\xa0\xef\xb8\x8f9 从第一列淡出,而新的 \xe2\x99\xa0\xef\xb8\x8f9 同时在第一列淡入第二栏。

\n\n

我想要的是 \xe2\x99\xa0\xef\xb8\x8f9 视图本身直接从其原始位置动画到\xe2\x9d\xa4\xef\xb8\x8f10 顶部的最终位置。

\n\n

(在我的实际应用程序中,卡片还可以处于其他位置,但实现方式与 类似ColumnView。)

\n\n

代码

\n\n

我有一个数据模型,由一个Column包含对象数组的类Card以及相应的ColumnViewCardView结构组成:

\n\n
public class Column: Identifiable, ObservableObject {\n    public let id: Int\n    @Published var stack = [Card]()\n\n    public init(id: Int, cards: [Card]? = nil) {\n        self.id = id\n        self.stack = cards ?? []\n    }\n\n    public func push(_ card: Card) {\n        stack.append(card)\n    }\n\n    public func pop() -> Card {\n        guard !stack.isEmpty else { fatalError() }\n        return stack.removeLast()\n    }\n\n    public func orderIndex(for card: Card) -> Int {\n        return stack.firstIndex(of: card) ?? -1\n    }\n}\n\npublic struct Card: Equatable, Identifiable {\n    public let suit: Suit\n    public let rank: Rank\n\n    public var id: String { return displayTitle }\n\n    public init(suit: Suit, rank: Rank) {\n        self.suit = suit\n        self.rank = rank\n    }\n\n    public var displayTitle: String {\n        return "\\(suit.displayTitle)\\(rank.displayTitle)"\n    }\n}\n\n
Run Code Online (Sandbox Code Playgroud)\n\n
public struct ColumnView: View {\n    @ObservedObject var column: Column\n\n    public init(column: Column) {\n        self.column = column\n    }\n\n    public var body: some View {\n        ZStack {\n            ForEach(column.stack) { card in\n                CardView(card: card)\n                    .offset(x: 0, y: 35*CGFloat(self.column.orderIndex(for: card)))\n                    .frame(width: 125, height: 187)\n            }\n        }\n    }\n}\n\n\npublic struct CardView: View {\n    let card: Card\n\n    public init(card: Card) {\n        self.card = card\n    }\n\n    public var body: some View {\n        // Implementation that draws the card\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

最后是一个Board包裹对象的对象Column,以及一个对应的BoardView显示(本例中的主屏幕):

\n\n
struct Board {\n    let column1: Column\n    let column2: Column\n\n    init() {\n        // Convenience method for Card.ten.ofDiamonds, etc not shown.\n        column1 = Column(id: 0, cards: [\n            Card.ten.ofDiamonds,\n            Card.nine.ofSpades\n        ])\n\n        column2 = Column(id: 1, cards: [\n            Card.king.ofSpades,\n            Card.queen.ofDiamonds,\n            Card.jack.ofClubs,\n            Card.ten.ofHearts\n        ])\n    }\n\n    func moveCard() {\n        let card = column1.pop()\n        column2.push(card)\n    }\n}\n\nstruct BoardView: View {\n    let board = Board()\n\n    var body: some View {\n        ZStack(alignment: .center) {\n            Rectangle()\n                .foregroundColor(.green)\n                .frame(maxWidth: .infinity, maxHeight: .infinity)\n\n            HStack(spacing: 20) {\n                ColumnView(column: board.column1)\n                ColumnView(column: board.column2)\n            }\n\n            Button(action: {\n                withAnimation {\n                    self.board.moveCard()\n                }\n            }) {\n                Text("Move Card")\n                    .font(.system(size: 20, weight: .bold, design: .default))\n                    .foregroundColor(.white)\n            }\n            .offset(x: 0, y: 300)\n        }.edgesIgnoringSafeArea(.all)\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

问题

\n\n

淡出/淡入发生的原因是有道理的ColumnView- 从第一个角度来看,column1丢失了一张卡片(卡片已从column1阵列中删除),因此它会以淡出的方式过渡出去;从第二个ColumnView角度来看,column2获得了一张卡片(卡片已添加到阵列中column2),因此它会以淡入的方式过渡。

\n\n

问题是,有没有一种方法可以实现我想要的,而不必完全重新架构我已经设置的数据模型和视图层次结构,其中Cards 位于Columns 内,CardViews 位于ColumnViews 内?按原样使用该数据模型确实很有帮助,并且将数据模型直接映射到视图层次结构也很有帮助。

\n\n

我可以想象某种替代视图层次结构,其中只有 52CardView个,并且根据它们所在的列或位置对位置进行某种修饰符,但这感觉非常老套。我考虑过某种自定义transition,但过渡是用于插入或删除视图的。有没有办法告诉 SwiftUI 该Card结构不是被删除和重新添加,而是被移动,从而执行匹配的移动动画?

\n

Ube*_*son 0

(从对该问题的评论复制)

\n\n

从 Xcode 11.3、iOS 13.3 开始:

\n\n

遗憾的是,我向 Apple 提交了 DTS 票证,他们说 \xe2\x80\x99 目前无法在 SwiftUI 中执行我想要执行的操作。我最终将 \xe2\x80\x9call 52 卡\xe2\x80\x9d 数组公开为计算属性,并使用锚点首选项和偏移量来设置卡\xe2\x80\x99 位置以允许它们动画。(换句话说,本质上是我\xe2\x80\x99d 希望避免的解决方案。)希望这在 iOS 14 / Xcode 12 中得到改进。

\n