SwiftUI:将视图从一个堆栈移动到另一个堆栈?
Posted
技术标签:
【中文标题】SwiftUI:将视图从一个堆栈移动到另一个堆栈?【英文标题】:SwiftUI: animate movement of a view from one stack to another? 【发布时间】:2019-11-20 22:45:04 【问题描述】:背景
我正在使用 SwiftUI 实现类似纸牌的纸牌游戏,其中包括堆叠纸牌以及将纸牌从一个堆栈移动到另一个堆栈的能力,例如:
在此处的示例中,当我点击“移动卡片”按钮时,♠️9 从第 1 列移动到第 2 列,位于❤️10 的顶部,动画是♠️9 从第一列,而新的 ♠️9 同时在第二列淡入。
我想要的是让 ♠️9 视图本身在 ❤️10 顶部动画从其原始位置到其最终位置。
(在我的实际应用中,卡片也可以在其他位置,但实现类似于ColumnView
。)
代码
我有一个由Column
类组成的数据模型,该类包含Card
对象的数组,以及对应的ColumnView
和CardView
结构:
public class Column: Identifiable, ObservableObject
public let id: Int
@Published var stack = [Card]()
public init(id: Int, cards: [Card]? = nil)
self.id = id
self.stack = cards ?? []
public func push(_ card: Card)
stack.append(card)
public func pop() -> Card
guard !stack.isEmpty else fatalError()
return stack.removeLast()
public func orderIndex(for card: Card) -> Int
return stack.firstIndex(of: card) ?? -1
public struct Card: Equatable, Identifiable
public let suit: Suit
public let rank: Rank
public var id: String return displayTitle
public init(suit: Suit, rank: Rank)
self.suit = suit
self.rank = rank
public var displayTitle: String
return "\(suit.displayTitle)\(rank.displayTitle)"
public struct ColumnView: View
@ObservedObject var column: Column
public init(column: Column)
self.column = column
public var body: some View
ZStack
ForEach(column.stack) card in
CardView(card: card)
.offset(x: 0, y: 35*CGFloat(self.column.orderIndex(for: card)))
.frame(width: 125, height: 187)
public struct CardView: View
let card: Card
public init(card: Card)
self.card = card
public var body: some View
// Implementation that draws the card
最后是一个包装Column
对象的Board
对象,以及一个要显示的对应BoardView
(本例中的主屏幕):
struct Board
let column1: Column
let column2: Column
init()
// Convenience method for Card.ten.ofDiamonds, etc not shown.
column1 = Column(id: 0, cards: [
Card.ten.ofDiamonds,
Card.nine.ofSpades
])
column2 = Column(id: 1, cards: [
Card.king.ofSpades,
Card.queen.ofDiamonds,
Card.jack.ofClubs,
Card.ten.ofHearts
])
func moveCard()
let card = column1.pop()
column2.push(card)
struct BoardView: View
let board = Board()
var body: some View
ZStack(alignment: .center)
Rectangle()
.foregroundColor(.green)
.frame(maxWidth: .infinity, maxHeight: .infinity)
HStack(spacing: 20)
ColumnView(column: board.column1)
ColumnView(column: board.column2)
Button(action:
withAnimation
self.board.moveCard()
)
Text("Move Card")
.font(.system(size: 20, weight: .bold, design: .default))
.foregroundColor(.white)
.offset(x: 0, y: 300)
.edgesIgnoringSafeArea(.all)
问题
有道理为什么会发生淡出/淡入 - 从第一个 ColumnView
的角度来看,column1
丢失了一张卡片(卡片已从 column1
数组中移除),因此它会以淡出方式过渡;从第二个ColumnView
的角度来看,column2
获得了一张卡片(卡片已添加到column2
数组中),因此它会以淡入方式过渡。
问题是,有没有一种方法可以实现我想要的,而不必完全重新构建我设置的数据模型和视图层次结构,Card
s 在Column
s 和CardView
s 内在ColumnView
s 里面?让数据模型保持原来的样子真的很有帮助,而且将数据模型直接映射到视图层次结构也很有帮助。
我可以想象某种替代视图层次结构,其中只有 52 个CardView
s,并根据它们所在的列或位置对位置进行某种修饰,但这感觉很hacky。我考虑过某种自定义transition
,但转换是用于插入或删除的视图。有没有办法告诉 SwiftUI Card
结构没有被删除和重新添加,而是 moved,因此执行匹配的移动动画?
【问题讨论】:
很想知道除了下面的答案之外,您是否找到了任何相关信息! 遗憾的是,我向 Apple 提交了 DTS 票证,他们说目前无法在 SwiftUI 中做我想做的事情。我最终将“所有 52 张卡片”数组公开为计算属性,并使用锚首选项和偏移量来设置卡片的位置以允许它们进行动画处理。 (换句话说,基本上是我希望避免的解决方案。) 当...谢谢你让我知道,至少很高兴听到 Apple 的确认(建议你自己用那个答案回答这个问题,也许指定当前的 Xcode 版本,因为希望它会在未来改变!) 我想知道是否有可能实现你在 UIKit 中可能做的事情,当一张卡片需要离开一个视图时让它出现在 zstack 的顶部,动画它移动到它的目标视图和然后将它插入那里......估计它可能会变得混乱,但我可能会试一试! 如何将 VStacks 用于“堆栈”(列),然后锚定到它的底部,将“9”卡移动到那里,然后对用户没有动画和跟踪,移除初始卡从视图层次结构中并在其位置创建一个新的“9”卡,位于 VStack 内?您不必再暴露所有卡片,只需暴露 VStacks。下次你在这里移动一张卡片时,VStack 的底部锚点会更低。我认为给出了列数,这有助于解决此问题。 【参考方案1】:我认为对于您的情况,您需要为这种行为计算每张卡与某些 @State var
(或更多变量)的偏移量。我拿了你的代码并更改了BoardView
。当您点击按钮时,每张卡片偏移都会发生变化。这只是显示卡片如何移动到新位置并可能为您提供正确方向的示例:
struct BoardView: View
let board = Board()
@State var cards = [
Card.ten.ofDiamonds,
Card.nine.ofSpades,
Card.king.ofSpades,
Card.queen.ofDiamonds,
Card.jack.ofClubs,
Card.ten.ofHearts
]
@State var column1: Int = 2
var body: some View
ZStack(alignment: .center)
Rectangle()
.foregroundColor(.green)
.frame(maxWidth: .infinity, maxHeight: .infinity)
ZStack
ForEach(0..<self.cards.count, id: \.self) cardIndex in
CardView(card: self.cards[cardIndex])
.offset(x: cardIndex < self.column1 ? 0 : 135,
y: 35*CGFloat(cardIndex >= self.column1 ? cardIndex - self.column1 : cardIndex))
.frame(width: 125, height: 187)
.offset(x: -80, y: 0)
Button(action:
withAnimation
self.column1 -= 1
)
Text("Move Card")
.font(.system(size: 20, weight: .bold, design: .default))
.foregroundColor(.white)
.offset(x: 0, y: 300)
.edgesIgnoringSafeArea(.all)
所以也许正确的解决方案是将卡片从一个数组移动到另一个数组,而不是改变它的偏移量。
【讨论】:
谢谢 - 这就是我所说的“我可以想象某种替代视图层次结构,其中只有 52 个 CardViews,并根据它们所在的列或位置对位置进行某种修饰,但这感觉很hacky。”将 52 张卡片放在一个没有组织的巨型视图中,感觉不是很 Swift-y 或 SwiftUI-y。此外,我的实际使用类似于纸牌,并且卡片位置可能比仅在列中更多 - 卡片也可以在其他位置 - 所以即使仅使用偏移量单独处理列也无济于事。【参考方案2】:(复制自对该问题的评论)
从 Xcode 11.3、ios 13.3 开始:
遗憾的是,我向 Apple 提交了 DTS 请求,他们说目前无法在 SwiftUI 中完成我想做的事情。我最终将“所有 52 张卡片”数组公开为计算属性,并使用锚首选项和偏移量来设置卡片的位置以允许它们进行动画处理。 (换句话说,基本上是我希望避免的解决方案。)希望这在 iOS 14 / Xcode 12 中得到改进。
【讨论】:
以上是关于SwiftUI:将视图从一个堆栈移动到另一个堆栈?的主要内容,如果未能解决你的问题,请参考以下文章