You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 11:15:54