SwiftUI - 过渡动画错误(已解决:不是错误!)
SwiftUI - Transition animation bug (Resolved: not a bug!)
这是对先前已解决但未解决的问题的跟进。
情况是我在屏幕上有一个文本网格,它是通过另一个视图的转换呈现的。我不能使用 LazyVGrid
来呈现网格,因为一列的宽度需要匹配其中最长的文本。所以我的解决方案是使用 HStack
s 并设置列的宽度。要将该宽度设置为最长文本的宽度,我使用 GeometryReader
来读取文本的大小,然后通过视图树中的 anchorPreference
将该信息发送到 @State
变量,用于设置列中所有标签的宽度。
听起来很复杂,但确实有效。至少......直到我尝试过渡到它。然后返回了一个旧错误,其中 anchorPreference
和 onPreferenceChange(...)
函数的使用似乎改变了带来视图的动画并导致文本滑动得太快。根据此屏幕截图:
目前我不知道如何更正动画以使文本随父视图一起滑动。有什么建议吗?
这是此错误的完整代码:
import SwiftUI
@main
struct TransitionAnimationBug: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
@State var displaySettings = false
var body: some View {
Group {
if displaySettings {
DataView(displaySettings: $displaySettings)
.transition(.slide)
} else {
MainView(displaySettings: $displaySettings)
.transition(.slide)
}
}
.animation(.easeInOut, value: displaySettings)
}
}
struct MainView: View {
let displaySettings: Binding<Bool>
var body: some View {
VStack(spacing: 20) {
Button("Show transition bug") {
displaySettings.wrappedValue.toggle()
}
Text("Watch the text as it animates on. it should slide with the view, but instead moves around independently.")
.padding(20).multilineTextAlignment(.center)
Text("This bug is triggered by the label width update via a @State variable in the onPreferenceChange function.")
.padding(20).multilineTextAlignment(.center)
}
}
}
// The preference key used to advise the parent view of a label's width.
struct LabelWidthPreferenceKey: PreferenceKey {
static var defaultValue = 0.0
static func reduce(value: inout Double, nextValue: () -> Double) {
if value != nextValue() {
value = max(value, nextValue())
}
}
}
struct DataView: View {
let displaySettings: Binding<Bool>
@State private var labelWidth: CGFloat = 0.0
var body: some View {
VStack(spacing: 30) {
row(title: "Short title", desc: "Short title long description")
row(title: "Rather long title", desc: "Rather long title long description")
row(title: "SS", desc: "Super short text")
Button("Close") { displaySettings.wrappedValue.toggle() }
}
.onPreferenceChange(LabelWidthPreferenceKey.self) {
// Updating the label width here triggers the bug.
if [=10=] != labelWidth {
labelWidth = [=10=]
}
}
}
private func row(title: String, desc: String) -> some View {
GeometryReader { geometry in
HStack(alignment: .center) {
Text(title)
.frame(minWidth: labelWidth, alignment: .leading)
.border(.red)
.anchorPreference(key: LabelWidthPreferenceKey.self, value: .bounds) {
geometry[[=10=]].width.rounded(.up)
}
Text(desc)
.border(.red)
}
}
.fixedSize(horizontal: false, vertical: true)
.padding([.leading, .trailing], 20)
}
}
不是 SwiftUI 错误。在下面找到一个修复(使用 Xcode 13.3 / iOS 15.4 测试)
VStack(spacing: 30) {
row(title: "Short title", desc: "Short title long description")
row(title: "Rather long title", desc: "Rather long title long description")
row(title: "SS", desc: "Super short text")
Button("Close") { displaySettings.wrappedValue.toggle() }
}
.animation(nil, value: labelWidth) // << here !!
.onPreferenceChange(LabelWidthPreferenceKey.self) {
这是对先前已解决但未解决的问题的跟进。
情况是我在屏幕上有一个文本网格,它是通过另一个视图的转换呈现的。我不能使用 LazyVGrid
来呈现网格,因为一列的宽度需要匹配其中最长的文本。所以我的解决方案是使用 HStack
s 并设置列的宽度。要将该宽度设置为最长文本的宽度,我使用 GeometryReader
来读取文本的大小,然后通过视图树中的 anchorPreference
将该信息发送到 @State
变量,用于设置列中所有标签的宽度。
听起来很复杂,但确实有效。至少......直到我尝试过渡到它。然后返回了一个旧错误,其中 anchorPreference
和 onPreferenceChange(...)
函数的使用似乎改变了带来视图的动画并导致文本滑动得太快。根据此屏幕截图:
目前我不知道如何更正动画以使文本随父视图一起滑动。有什么建议吗?
这是此错误的完整代码:
import SwiftUI
@main
struct TransitionAnimationBug: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
@State var displaySettings = false
var body: some View {
Group {
if displaySettings {
DataView(displaySettings: $displaySettings)
.transition(.slide)
} else {
MainView(displaySettings: $displaySettings)
.transition(.slide)
}
}
.animation(.easeInOut, value: displaySettings)
}
}
struct MainView: View {
let displaySettings: Binding<Bool>
var body: some View {
VStack(spacing: 20) {
Button("Show transition bug") {
displaySettings.wrappedValue.toggle()
}
Text("Watch the text as it animates on. it should slide with the view, but instead moves around independently.")
.padding(20).multilineTextAlignment(.center)
Text("This bug is triggered by the label width update via a @State variable in the onPreferenceChange function.")
.padding(20).multilineTextAlignment(.center)
}
}
}
// The preference key used to advise the parent view of a label's width.
struct LabelWidthPreferenceKey: PreferenceKey {
static var defaultValue = 0.0
static func reduce(value: inout Double, nextValue: () -> Double) {
if value != nextValue() {
value = max(value, nextValue())
}
}
}
struct DataView: View {
let displaySettings: Binding<Bool>
@State private var labelWidth: CGFloat = 0.0
var body: some View {
VStack(spacing: 30) {
row(title: "Short title", desc: "Short title long description")
row(title: "Rather long title", desc: "Rather long title long description")
row(title: "SS", desc: "Super short text")
Button("Close") { displaySettings.wrappedValue.toggle() }
}
.onPreferenceChange(LabelWidthPreferenceKey.self) {
// Updating the label width here triggers the bug.
if [=10=] != labelWidth {
labelWidth = [=10=]
}
}
}
private func row(title: String, desc: String) -> some View {
GeometryReader { geometry in
HStack(alignment: .center) {
Text(title)
.frame(minWidth: labelWidth, alignment: .leading)
.border(.red)
.anchorPreference(key: LabelWidthPreferenceKey.self, value: .bounds) {
geometry[[=10=]].width.rounded(.up)
}
Text(desc)
.border(.red)
}
}
.fixedSize(horizontal: false, vertical: true)
.padding([.leading, .trailing], 20)
}
}
不是 SwiftUI 错误。在下面找到一个修复(使用 Xcode 13.3 / iOS 15.4 测试)
VStack(spacing: 30) {
row(title: "Short title", desc: "Short title long description")
row(title: "Rather long title", desc: "Rather long title long description")
row(title: "SS", desc: "Super short text")
Button("Close") { displaySettings.wrappedValue.toggle() }
}
.animation(nil, value: labelWidth) // << here !!
.onPreferenceChange(LabelWidthPreferenceKey.self) {