最近在自学 SwiftUI ,现在还是超级新手,写了如下一小段代码(我已将问题简化):
struct ani_repeat: View {
@State private var xv = false
var body: some View {
VStack {
Text("Hello, World!")
.scaleEffect(xv ? 1.5 : 1)
Text("\(xv ? "T" : "F")")
}
.onTapGesture {
withAnimation(Animation.spring().repeatForever()){
self.xv.toggle()
}
}
}
}
当我第一次点击时,第一行 Text 循环缩放,第二行在 T 和 F 之间来回切换。
第二次点击后,第一行不再循环缩放,第二行仍然在 T 和 F 之间来回切换。
我不理解为什么 xv 仍然在切换,但是第一行不再缩放了。毕竟 xv 是一个 @State 。
我问了 ChatGPT ,但还是没能理解,有时它也跟我胡说八道。买了 99 美金的 SwiftUI 入门书籍,里面暂时没看到答案。
另外,因为愤青之前发了政治相关的帖子,现在发帖需要三位数的币,回帖也很贵,签到 1 币左右。我以后不会在这里讨论政治了,希望管理员能调整账号,现在回复讨论的成本高了许多。我还想在这个社区长期使用下去。
1
hstdt 2023-03-21 01:04:21 +08:00 via iPhone
我用 Xcode 14.3 beta3 运行这个代码没问题
|
2
jwautumn 2023-03-21 10:29:20 +08:00
在切换 xv 后,动画停止而文字逐渐变化的原因是因为:
动画在不断地重复 scaleEffect 的值随着 xv 的变化而变化,因此会立即更新文本的缩放大小 而在另一方面,Text("\(xv ? "T" : "F")") 中的字符串只在切换 xv 值时才会更新,因此在动画完成后仍将继续反复更新字符串的标签。 为了避免这种情况,您可以将两个 Text 视图放在同一个 Group 中,并在 Group 的条件语句中同时更新它们。这将确保您修改的不仅仅是标签的值,而是整个 Group 的内容。以下是更新后的代码示例,可以解决您描述的问题: |
3
jwautumn 2023-03-21 10:34:54 +08:00
struct ContentView: View {
@State private var xv = false var body: some View { VStack { Text("Hello, World!") .scaleEffect(xv ? 1.5 : 1) Group { if xv { Text("T") } else { Text("F") } } } .onTapGesture { withAnimation(Animation.spring().repeatForever()){ self.xv.toggle() } } } } 在这个示例中,我们将 if 条件语句与包含 Text 视图的 Group 组合在一起,在框架布局的条件块中同时更新了两个文本。这将确保在切换 xv 值时更新整个分组,而不是只更新字符串。 现在,当你点击屏幕时,你会发现 scaleEffect 的值仍然会通过弹簧动画进行调整,但是文本颜色仅在新动画开始时更改,并在之后保持静止,直到新动画开始时再次更新。 ~~~ 能详细解释下具体是为什么吗? ~~~ 当您单独更新一个 Text 视图的内容时,在 SwiftUI 中,只有该视图及其子视图会重新渲染。但是,在您切换 if 条件分句中的语句时,将重新构建容器视图 Group 。这是因为 SwiftUI 会在任何视图更改后检查详细信息,例如视图类型、颜色、位置等,以确定它是否需要重新构建它的子树。在本例中,每次选择不同的条件语句时,SwiftUI 都会发现“Group”视图的内容和结构发生了变化,因此它将该视图标记为需要更新,并从头开始执行构建过程。 因此,通过在 Group 中同时更新两个具有相同数据源的 Text 视图,您可以确保视图不仅仅只是可变数据源的容器,而是由数据源本身完全驱动的视图。这有利于优化性能并减少不必要的重新构建。 ~~~~~ 以上是我咨询 Chatgpt 后的结果(包括上面的一条回复)。 大概就是:“更新文字” 和 “更新布局” 的问题。 我按照你的代码实现后也是一样。 我也挺纳闷,查询无果后还是问了 gpt 。也是学到了。 当然还有一种处理方式是: ``` Text("\(xv ? "T" : "F")") .animation(xv ? .spring().repeatForever() : .default, value: xv) ``` 想来,应该跟上面的意思差不多。更新了整体布局,而不是单纯的变化了文字。 希望对你有帮助 |
4
jwautumn 2023-03-21 10:44:02 +08:00
顺便也问了一下 为何 if-else 和 三木运算法则 的区别:
当 SwiftUI 首次创建视图时,它会捕捉该视图的所有属性及其组成视图的子视图。在如果语句中,当变量更改发生时,SwiftUI 会检查新的变量状态,并根据使用该变量的创建视图时所捕获的视图结构和子视图的所有属性来更新该视图。但是,如果 if-else 中两个不同情况下使用了相同的视图,那么 SwiftUI 不知道要更新哪一种视图。 在你的示例中,if-else 语句中的两个不同情况的视图都是相同的 Text 视图。 当变量更改发生时,SwiftUI 会检测该变量的新状态,并发现两个分支都包含相同的 Text 视图。 因此,SwiftUI 决定不更新任何视图,因此动画也将停止。 但是在三目运算符中,每个条件分支都处理了不同的视图。在真值分支中,它是一个 Text 视图,而在假值分支中则是另一个 Text 视图。 当变量更改发生时,SwiftUI 会检测该变量的新状态,并发现两个分支都包含不同的视图。因此,SwiftUI 决定用新的 Text 视图替换旧的 Text 视图,因此动画将继续进行。 希望这个解释能够帮助您理解问题的根源。 |
6
fatbobxu 2023-05-08 20:53:06 +08:00
因为你碰到了一个 SwiftUI 4 的 Bug 。
这个代码在 iOS 15 ( SwiftUI 3 )中运行时,下方面的 Text 是不会反复变化的。因为 Text 并不符合 Animatable 协议,无法创建差值。 在 SwiftUI 4 中,Text 在内部实现了对文字变化的动态处理,通过 contentTransition 可以设置差值的处理方式,目前看来,在 opactity 和 interpolate 这两种状态下,对 animation 为 repeatForever 或 repeatCount 的处理有问题,没有及时响应 animation 的停止。 仅此而已。建议你给苹果提交 FB,让他们在下个版本中改善该问题。 如果,你确实要实现这个效果,可以采用 @jwautumn 回答中提到的方式,使用 if else 来创建两个不同的 Text 分支。 之所以在这种情况下,可以响应 animation ,是因为 transition 的原因。默认情况下,transition 为 opactity ,因此 SwiftUI 会为转场的透明度创建差值,反复在两个视图中交替。 需要注意的是,当 animation 为 repeat 时,状态( xv )并不会发生变化(只会在 toggle 执行时改变一次),所有的动画效果,都是在反应差值的状态,并非当前 xv 的状态。 |