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

SwiftUI OTP输入框下划线颜色切换动画适配Xcode 12.3问题

问题根因

你当前的写法是给下划线视图添加了无绑定的全局隐式动画,会导致所在视图层级内所有状态变化(包括Text的文本内容更新、TextField宽度变化)都被附加了动画效果,就会出现数字不同步、下划线状态滞后的问题。

修复方案

把下划线的动画绑定到唯一触发源isNextTyped的值上,仅当该值变化时才触发颜色过渡动画,其他状态变更不受影响。

具体修改点

  1. 移除otpText方法里下划线原有无差别的animation(.easeIn)修饰符,替换为带value绑定的动画声明
  2. 可自定义动画时长适配交互体验,不会影响其他控件的状态更新逻辑

修改后完整代码

import SwiftUI

@available(iOS 13.0, *)
class OTPViewModel: ObservableObject {
    
    @Published var otpField = "" {
        didSet {
            isNextTypedArr = Array(repeating: false, count: 6)
            guard otpField.count <= 6,
                  otpField.last?.isNumber ?? true else {
                otpField = oldValue
                return
            }
            if otpField.count < 6 {
                isNextTypedArr[otpField.count] = true
            }
        }
    }
    var otp1: String {
        guard otpField.count >= 1 else {
            return ""
        }
        return String(Array(otpField)[0])
    }
    var otp2: String {
        guard otpField.count >= 2 else {
            return ""
        }
        return String(Array(otpField)[1])
    }
    var otp3: String {
        guard otpField.count >= 3 else {
            return ""
        }
        return String(Array(otpField)[2])
    }
    var otp4: String {
        guard otpField.count >= 4 else {
            return ""
        }
        return String(Array(otpField)[3])
    }
    
    var otp5: String {
        guard otpField.count >= 5 else {
            return ""
        }
        return String(Array(otpField)[4])
    }
    
    var otp6: String {
        guard otpField.count >= 6 else {
            return ""
        }
        return String(Array(otpField)[5])
    }
    
    @Published var isNextTypedArr = Array(repeating: false, count: 6)
    
    @Published var borderColor: Color = .black
    
    @Published var isEditing = false {
        didSet {
            isNextTypedArr = Array(repeating: false, count: 6)
            if isEditing && otpField.count < 6 {
                isNextTypedArr[otpField.count] = true
            }
        }
    }
}

@available(iOS 13.0, *)
struct CXLRPOTPView: View {
    
    @ObservedObject var viewModel = OTPViewModel()
    
    let textBoxWidth = UIScreen.main.bounds.width / 8
    let textBoxHeight = UIScreen.main.bounds.width / 8
    let spaceBetweenLines: CGFloat = 16
    let paddingOfBox: CGFloat = 1
    var textFieldOriginalWidth: CGFloat {
        (textBoxWidth*6)+(spaceBetweenLines*3)+((paddingOfBox*2)*3)
    }
    
    var body: some View {
        VStack {
            ZStack {
                HStack (spacing: spaceBetweenLines){
                    otpText(text: viewModel.otp1, isNextTyped: $viewModel.isNextTypedArr[0])
                    otpText(text: viewModel.otp2, isNextTyped: $viewModel.isNextTypedArr[1])
                    otpText(text: viewModel.otp3, isNextTyped: $viewModel.isNextTypedArr[2])
                    otpText(text: viewModel.otp4, isNextTyped: $viewModel.isNextTypedArr[3])
                    otpText(text: viewModel.otp5, isNextTyped: $viewModel.isNextTypedArr[4])
                    otpText(text: viewModel.otp6, isNextTyped: $viewModel.isNextTypedArr[5])
                }
                
                TextField("", text: $viewModel.otpField) { isEditing in
                    viewModel.isEditing = isEditing
                }
                .frame(width: viewModel.isEditing ? 0 : textFieldOriginalWidth, height: textBoxHeight)
                .textContentType(.oneTimeCode)
                .foregroundColor(.clear)
                .accentColor(.clear)
                .background(Color.clear)
                .keyboardType(.numberPad)
            }
        }
    }
    
    @available(iOS 13.0, *)
    private func otpText(text: String, isNextTyped: Binding<Bool>) -> some View {
        return Text(text)
            .font(Font.custom("GTWalsheim-Regular", size: 34))
            .frame(width: textBoxWidth, height: textBoxHeight)
            .background(VStack{
                Spacer()
                RoundedRectangle(cornerRadius: 1)
                    .frame(height: 2)
                    .foregroundColor(isNextTyped.wrappedValue ? Color(hex: "#367878") : Color(hex: "#BCBEC0"))
                    // 仅当isNextTyped值变化时才触发动画,不影响其他控件状态变更
                    .animation(.easeIn(duration: 0.2), value: isNextTyped.wrappedValue)
            })
            .padding(paddingOfBox)
    }
}

适配说明

该写法完全兼容Xcode12.3版本,动画仅作用于下划线颜色过渡,不会影响数字更新、TextField布局等其他逻辑,不会出现数字不同步、下划线滞后的问题。

内容的提问来源于stack exchange,提问作者Dreiohc

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.05 07:45:04