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

Swift实现塞尔维亚手机号实时格式化,消除粘贴显示延迟

SwiftUI实现手机号即时格式化消除粘贴延迟

问题背景

需要实现塞尔维亚手机号的实时格式化:输入+381 65 1234 123时自动转为0651234123。已编写normalizeSerbianPhoneNumber函数完成格式转换,但在TextField中粘贴号码时,会短暂显示原始内容再切换为格式化后的值,存在明显延迟,需要实现无延迟的即时格式化。

现有格式化函数

func normalizeSerbianPhoneNumber(_ mobileNumber: String) -> String {
    let allowedCharacterSet = CharacterSet(charactersIn: "+0123456789")
    let trimmedNumber = mobileNumber.trimmingCharacters(in: .whitespacesAndNewlines)
    var strippedNumber = trimmedNumber.trimmingCharacters(in: allowedCharacterSet.inverted)

    // 移除所有空格
    strippedNumber = strippedNumber.replacingOccurrences(of: " ", with: "")

    // 移除前缀国家码或多余的0
    if strippedNumber.hasPrefix("+381") {
        strippedNumber.removeFirst(4)
    } else if strippedNumber.hasPrefix("381") {
        strippedNumber.removeFirst(3)
    } else if strippedNumber.hasPrefix("0") {
        strippedNumber.removeFirst()
    }

    // 验证并返回格式化后号码
    // 塞尔维亚手机号通常以6开头,长度为8或9位
    if (strippedNumber.count == 8 || strippedNumber.count == 9) && strippedNumber.hasPrefix("6") {
        return strippedNumber
    } else {
        return "" // 替换原错误字符串,避免TextField显示无效文本
    }
}

问题根源

原代码使用.onChange(of: phoneNumber)监听值变化后再修改,这会导致TextField先更新为原始输入/粘贴值,再触发格式化修正,因此产生延迟。要解决这个问题,需要在值被绑定到TextField时就直接完成格式化,而非等待值变化后再调整。

解决方案:自定义Binding

通过自定义Binding,在set阶段直接处理输入值的格式化,确保TextField显示的始终是处理后的值,消除延迟:

struct PhoneNumber: View {
    var title: String
    let phoneNumber: Binding<String>
    @State private var showSheet: Bool = false
    var action: () -> ()
    
    // 自定义格式化Binding
    private var formattedPhoneBinding: Binding<String> {
        Binding(
            get: { phoneNumber.wrappedValue },
            set: { newValue in
                let formattedResult = normalizeSerbianPhoneNumber(newValue)
                // 避免重复赋值引发循环更新
                if formattedResult != phoneNumber.wrappedValue {
                    phoneNumber.wrappedValue = formattedResult
                }
            }
        )
    }
    
    var body: some View {
        VStack(alignment:.leading){
            Text(title)
                .font(.ubuntu(name: .ubuntuRegular, size: 12))
            HStack{
                CountrySelectButton(width: UIScreen.main.bounds.width - 290) {
                    showSheet.toggle()
                }
                
                GrayTextField(placeholder: "000 000 0000", text: formattedPhoneBinding)
                    .keyboardType(.phonePad)
            }
        }.sheet(isPresented: $showSheet) {
            SelectCountrySheet()
                .presentationDragIndicator(.hidden)
                .presentationDetents([.medium, .large])
        }
    }
}

额外优化建议

  1. 错误提示优化:不要在格式化函数中返回错误字符串,而是单独添加一个Text组件显示错误信息(比如当格式化结果为空时),避免TextField显示无关文本。
  2. 部分有效输入处理:如果用户输入未完成(比如仅输入了65123),可以返回已输入的有效数字,而非直接返回空,提升输入流畅度。
  3. 长度限制:在自定义Binding的set方法中添加长度限制,防止用户输入过多无效字符。

内容的提问来源于stack exchange,提问作者Luka Šalipur

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 09:33:16