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

SwiftUI双TextField时间互转触发无限循环的解决方法咨询

解决SwiftUI时间转换TextField无限循环问题

问题根源

你遇到的无限循环是因为:当用户修改localTime时,onChange触发updateUTCTime更新utcTime;而utcTime的变化又会触发它的onChange,调用updateLocalTime重新修改localTime,如此往复形成闭环。原有的shouldUpdate标志位没有在更新过程中正确切换状态,无法阻断循环。

解决思路

核心是区分用户输入触发的更新和代码自动更新的触发,确保只有用户主动输入的操作才会触发转换,代码自动更新的文本变化不会触发反向转换。以下是两种可行实现方式:

方式1:优化标志位控制

在更新对方文本前,暂时禁用对方的更新触发开关,更新完成后恢复开关状态,避免反向触发循环。同时修正日期格式化的时区逻辑,确保转换准确:

import SwiftUI

struct TimeView: View {
    @Binding var localDate : Date?
    @Binding var utcDate : Date?
    
    /// 存储HH:mm格式的本地时间
    @Binding var localTime : String
    @Binding var utcTime : String
    
    @Binding var selectedDate: Date
    
    @State var shouldUpdateUTCTime = true
    @State var shouldUpdateLocalTime = true
   
    @FocusState var isFocused: Bool

    var body: some View {
        HStack {
            TimeTextField(time: $localTime, description: "Local Time")
                .multilineTextAlignment(.center)
                .keyboardType(.numbersAndPunctuation)
                .onChange(of: localTime) { oldValue, newValue in
                    if shouldUpdateUTCTime {
                        updateUTCTime()
                    }
                }
                
            
            TimeTextField(time: $utcTime, description: "UTC")
                .multilineTextAlignment(.center)
                .keyboardType(.numbersAndPunctuation)
                .onChange(of: utcTime) { oldValue, newValue in
                    if shouldUpdateLocalTime {
                        updateLocalTime()
                    }
                }
                
        }
    }
    
    func updateUTCTime() {
        // 先禁用本地时间的更新触发,避免循环
        shouldUpdateLocalTime = false
        
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "HH:mm"
        dateFormatter.timeZone = TimeZone.current // 用当前时区解析用户输入的本地时间
        
        if let localDate = dateFormatter.date(from: localTime) {
            dateFormatter.timeZone = TimeZone(identifier: "UTC")
            utcTime = dateFormatter.string(from: localDate)
        }
        
        // 延迟恢复开关,确保当前更新周期完成后再允许下次用户输入触发
        DispatchQueue.main.async {
            shouldUpdateLocalTime = true
        }
    }
    
    func updateLocalTime() {
        // 先禁用UTC时间的更新触发,避免循环
        shouldUpdateUTCTime = false
        
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "HH:mm"
        dateFormatter.timeZone = TimeZone(identifier: "UTC") // 用UTC时区解析用户输入的UTC时间
        
        if let utcDate = dateFormatter.date(from: utcTime) {
            dateFormatter.timeZone = TimeZone.current
            localTime = dateFormatter.string(from: utcDate)
        }
        
        // 延迟恢复开关
        DispatchQueue.main.async {
            shouldUpdateUTCTime = true
        }
    }
}

// 假设TimeTextField的实现如下(如果未定义)
struct TimeTextField: View {
    @Binding var time: String
    let description: String
    
    var body: some View {
        TextField(description, text: $time)
            .frame(width: 100)
            .border(Color.gray)
    }
}

方式2:结合FocusState判断(更精准)

利用FocusState判断当前哪个TextField处于激活状态,只有激活状态的TextField输入变化才触发转换,代码更新的非激活TextField变化不会触发反向转换:

import SwiftUI

struct TimeView: View {
    @Binding var localDate : Date?
    @Binding var utcDate : Date?
    
    /// 存储HH:mm格式的本地时间
    @Binding var localTime : String
    @Binding var utcTime : String
    
    @Binding var selectedDate: Date
    
    // 用枚举标记当前焦点所在的TextField
    enum FocusedField {
        case local, utc
    }
    @FocusState private var focusedField: FocusedField?
   
    var body: some View {
        HStack {
            TimeTextField(time: $localTime, description: "Local Time")
                .multilineTextAlignment(.center)
                .keyboardType(.numbersAndPunctuation)
                .focused($focusedField, equals: .local)
                .onChange(of: localTime) { oldValue, newValue in
                    // 只有当前焦点在本地TextField时,才触发UTC更新
                    if focusedField == .local {
                        updateUTCTime()
                    }
                }
                
            
            TimeTextField(time: $utcTime, description: "UTC")
                .multilineTextAlignment(.center)
                .keyboardType(.numbersAndPunctuation)
                .focused($focusedField, equals: .utc)
                .onChange(of: utcTime) { oldValue, newValue in
                    // 只有当前焦点在UTCTextField时,才触发本地时间更新
                    if focusedField == .utc {
                        updateLocalTime()
                    }
                }
                
        }
    }
    
    func updateUTCTime() {
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "HH:mm"
        dateFormatter.timeZone = TimeZone.current
        
        if let localDate = dateFormatter.date(from: localTime) {
            dateFormatter.timeZone = TimeZone(identifier: "UTC")
            utcTime = dateFormatter.string(from: localDate)
        }
    }
    
    func updateLocalTime() {
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "HH:mm"
        dateFormatter.timeZone = TimeZone(identifier: "UTC")
        
        if let utcDate = dateFormatter.date(from: utcTime) {
            dateFormatter.timeZone = TimeZone.current
            localTime = dateFormatter.string(from: utcDate)
        }
    }
}

struct TimeTextField: View {
    @Binding var time: String
    let description: String
    
    var body: some View {
        TextField(description, text: $time)
            .frame(width: 100)
            .border(Color.gray)
    }
}

关键说明

  • 两种方式都能阻断循环,方式2通过焦点判断更符合用户交互逻辑:只有用户正在输入的文本框才会触发转换,避免不必要的状态切换。
  • 修正了日期格式化的时区逻辑:解析用户输入时使用对应的时区(本地时间用当前时区,UTC时间用UTC时区),转换时切换到目标时区生成字符串。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 18:20:20