SwiftUI弹窗Offset动画中文本延迟显示问题排查
问题:弹窗动画中文本延迟显示的原因及解决办法

我在PopupView中使用PopupType枚举的title和description计算属性,执行offset动画时,为何Text文本会延迟显示?
相关代码
MainView代码
VStack(spacing: 30) { Spacer() Button { authManager.popupType = .warning(title: "Are you sure ?", desc: "Do you wanna sign out ?") } label: { Text("Sign Out") } } .frame(maxWidth: .infinity, maxHeight: .infinity) .padding(.bottom, 16) .overlay { PopupView( firstButtonTitle: Constant.okButtonTitle, actionButtonTitle: Constant.goBackBtnTitle, status: $authManager.popupType) }
PopupType代码
enum PopupType: Equatable { case none case warning(title: String, desc: String) case success(String) case error(String) var icon: String { switch self { case .none: return "circle" case .warning: return "exclamationmark.circle" case .success: return "checkmark.circle" case .error: return "xmark.circle" } } var title: String { switch self { case .none: return "" case .warning(let title, _): return title case .success: return "Başarılı" case .error: return "Hata" } } var description: String { switch self { case .none: return "" case .warning(_, let desc): return desc case .success(let desc): return desc case .error(let desc): return desc } } var iconColor: Color { switch self { case .none: return .clear case .warning: return Color(uiColor: .orange) case .success: return Color(uiColor: .green) case .error: return Color(uiColor: .red) } } }
PopupView代码
struct PopupView: View { enum ActionType { case dismiss case custom } var showActionButton: Bool = true var firstButtonTitle: String = "" var actionButtonTitle: String = "" var actionType: ActionType = .custom @Binding var status: PopupType var onAction: (() -> ())? = { } @Environment(\.dismiss) var dismiss var body: some View { VStack { Spacer() VStack(spacing: 16) { Image(systemName: status.icon) .resizable() .frame(width: 50, height: 50) .fontWeight(.thin) .colorMultiply(status.iconColor) Text(status.title) .bold() Text(status.description) .font(.system(size: 16, weight: .light)) .multilineTextAlignment(.center) HStack { Button(action: { status = .none }, label: { Text(firstButtonTitle) .bold() .frame(maxWidth: .infinity) .frame(height: 50) .foregroundStyle(Color(uiColor: .systemBlue)) .background(.ultraThinMaterial) .cornerRadius(8.0) }) if showActionButton { Button(action: { switch actionType { case .dismiss: dismiss() case .custom: onAction?() } status = .none }, label: { Text(actionButtonTitle) .bold() .frame(maxWidth: .infinity) .frame(height: 50) .foregroundStyle(Color(uiColor: .white)) .background(Color(uiColor: .systemBlue)) .cornerRadius(8.0) }) } } } .padding(16) .background(.ultraThinMaterial) .cornerRadius(8.0) .padding(16) } .background(Color.white.opacity(0.001)) .offset(y: status == .none ? UIScreen.main.bounds.height : .zero) .animation(.easeInOut, value: status) } }
原因分析
问题根源在于:当status从.none切换到.warning时,文本内容从空字符串变为实际文本,SwiftUI对文本内容的变化默认会添加隐式淡入动画,而你给整个弹窗加的.offset动画是另一个独立的动画。这两个动画的时序不同步,就导致文本看起来比弹窗的位移动画慢半拍,出现延迟显示的效果。
解决办法
方法1:禁用文本的隐式动画
直接给两个Text组件添加.animation(nil, value: status),让它们跳过自身的内容变化动画,和弹窗的位移动画同步:
Text(status.title) .bold() .animation(nil, value: status) // 禁用文本自身的隐式动画 Text(status.description) .font(.system(size: 16, weight: .light)) .multilineTextAlignment(.center) .animation(nil, value: status) // 禁用文本自身的隐式动画
方法2:统一用withAnimation控制动画范围
把设置popupType的代码用withAnimation包裹,同时去掉PopupView里的.animation修饰符,让整个状态变化的动画由withAnimation统一驱动,这样所有元素的变化都会同步:
修改MainView中的按钮代码:
Button { withAnimation(.easeInOut) { authManager.popupType = .warning(title: "Are you sure ?", desc: "Do you wanna sign out ?") } } label: { Text("Sign Out") }
修改PopupView,移除.animation修饰符:
// 去掉这一行:.animation(.easeInOut, value: status)
两种方法都能解决文本延迟的问题,你可以根据自己的代码习惯选择。
内容的提问来源于stack exchange,提问作者Ufuk Köşker
相关产品推荐
相关产品推荐

