SwiftUI视图标识、ForEach、结构标识和id()



我正试图破译身份在SwiftUI中的工作原理。揭开SwiftUI的神秘面纱会有所帮助,但本质上表明存在显式的或结构化的标识,用于确定状态和转换。

让我们将这个概念应用到一个简单的测试用例中,在这个用例中,我想通过按一个切换按钮来来回移动两个块。下面的示例使用两种可选方法来完成此任务,其中一种方法带有ForEach,另一种方法显式地说明每种方法。奇怪的是,只有ForEach方法似乎允许块在容器中移动时保持身份-用显式身份拼写视图会破坏动画:

struct SimplePreview: PreviewProvider, View {
static var previews = Self()
@State
private var isRunning = false
var body: some View {
VStack {
Button("Toggle is (self.isRunning ? "on" : "off")") {
withAnimation {
self.isRunning.toggle()
}
}
// 1
HStack {
ForEach([self.isRunning, !self.isRunning], id: .self) {
Rectangle()
.fill($0 ? Color.accentColor : Color.gray)
}
}

// 2
HStack {
Rectangle()
.fill(self.isRunning ? Color.blue : Color.gray)
Rectangle()
.fill(!self.isRunning ? Color.blue : Color.gray)
}
// 3
HStack {
Rectangle()
.fill(self.isRunning ? Color.blue : Color.gray)
.id(self.isRunning)
Rectangle()
.fill(!self.isRunning ? Color.blue : Color.gray)
.id(!self.isRunning)
}
}
}
}

我从中得出的结论是:

  1. ForEach允许单个物品保持其完整身份
  2. 单个项目具有单独的结构身份,并且随着其状态的变化,它们单独过渡。
  3. 单个项目具有独立的结构身份,但是当它们的内部.id发生变化时,它们被视为全新的视图,完全破坏动画。

问题显然是:如何在ForEach之外保持完整的视图身份?我们想用类似[2]或[3]的方法达到[1]的效果。

我相信您可以通过matchedGeometryEffect@Namespace实现这一点,例如

@Namespace private var animation
// 2
HStack {
if isRunning {
Rectangle()
.fill(Color.blue)
.matchedGeometryEffect(id: 1, in: animation)
.transition((.scale(scale: 1))) // this is to override the default transition which is opacity that looks bad.
Rectangle()
.fill(Color.gray)
.matchedGeometryEffect(id: 2, in: animation)
.transition(.scale(scale: 1))
}
else {
Rectangle()
.fill(Color.gray)
.matchedGeometryEffect(id: 2, in: animation)
.transition(.scale(scale: 1))
Rectangle()
.fill(Color.blue)
.matchedGeometryEffect(id: 1, in: animation)
.transition(.scale(scale: 1))
}

下面是介绍此特性的教程。

这是另一种实现类似动画的有趣方法,不同之处在于蓝色总是滑到灰色后面。

// 4 
HStack {
Rectangle()
.fill(Color.blue)
Rectangle()
.fill(Color.gray)
}
.environment(.layoutDirection, isRunning ? .leftToRight : .rightToLeft)

最新更新