SwiftUI - 条件 onChange

SwiftUI - conditional onChange

我有 2 个文本字段: __ x2 __

我想进行简单的计算:string1 x 2 = string2。我正在使用 .onChange 修饰符,因此如果您键入第一个数字,它会乘以 2,结果会打印在第二个 TextField 中。您可以反过来输入 string2,它将除以 2,结果将打印在第一个 TextField 中。

现在因为两个 TextField 都有 .onChange,它被触发了几次(在本例中为 3 次)。更改 string1 后,string2 得到更新。随着它的变化,触发了 string2 的 .onChange,之后与 string1 的 .onChange 相同。

请运行此示例代码并检查控制台中打印的内容:

import SwiftUI

struct ContentView: View {
    @State private var string1: String = ""
    @State private var int1: Int = 0
    
    @State private var string2: String = ""
    @State private var int2: Int = 0
    
    let multiplier: Int = 2
    
    var body: some View {
            VStack {
                HStack {
                    TextField("0", text: $string1)
                        .keyboardType(.decimalPad)
                        .onChange(of: string1, perform: { value in
                            string1 = value
                            int1 = Int(string1) ?? 0
                            int2 = int1 * multiplier
                            string2 = "\(int2)"
                            print("int1: \(int1)")
                        })
                }
                .multilineTextAlignment(.trailing)
                .font(.largeTitle)
                .background(Color(UIColor.systemGray5))
                
                HStack {
                    Spacer()
                    Text("x2")
                }
                
                HStack {
                    TextField("0", text: $string2)
                        .keyboardType(.decimalPad)
                        .onChange(of: string2, perform: { value in
                            string2 = value
                            int2 = Int(string2) ?? 0
                            int1 = int2 / multiplier
                            string1 = ("\(int1)")
                            print("int2: \(int2)")
                        })
                }
                .multilineTextAlignment(.trailing)
                .font(.largeTitle)
                .background(Color(UIColor.systemGray5))
            }
            .padding()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

问题:

如何使 .onChange 有条件地 运行 只执行一次?准确地说,我只想在编辑第一个输入时对第一个输入执行 .onChange。并且仅当我编辑第二个输入时才对第二个输入执行 .onChange。

在 iOS 15 中使用 .onFocus 可能会很容易。但是如何在 iOS 14 中做到这一点?

我已经弄明白了。我需要两个变量,每个 TextField 一个:isFocused1、isFocused2。它们中的每一个都通过 onEditingChanged 变为 true。并且每个 onChange 都有 if 条件来检查此 TextField 的 isFocused 是否为真。

现在onChange只有在编辑每个TextField时才会触发。我添加了不断变化的背景颜色以可视化焦点变化。

工作代码:

import SwiftUI

struct ContentView: View {
    @State private var string1: String = ""
    @State private var int1: Int = 0
    
    @State private var string2: String = ""
    @State private var int2: Int = 0
    
    let multiplier: Int = 2
    
    @State private var isFocused1 = false
    @State private var isFocused2 = false
    
    var body: some View {
        VStack {
            HStack {
                TextField("0", text: $string1, onEditingChanged: { (changed) in
                    isFocused1 = changed
                })
                .keyboardType(.decimalPad)
                .onChange(of: string1, perform: { value in
                    if isFocused1 {
                        int1 = Int(string1) ?? 0
                        int2 = int1 * multiplier
                        string2 = "\(int2)"
                        print("int1: \(int1)")
                    }
                })
                .background(isFocused1 ? Color.yellow : Color.gray)
            }
            .multilineTextAlignment(.trailing)
            .font(.largeTitle)
            
            HStack {
                Spacer()
                Text("x2")
            }
            
            HStack {
                TextField("0", text: $string2, onEditingChanged: { (changed) in
                    isFocused2 = changed
                })
                    .keyboardType(.decimalPad)
                    .onChange(of: string2, perform: { value in
                        if isFocused2 {
                            int2 = Int(string2) ?? 0
                            int1 = int2 / multiplier
                            string1 = ("\(int1)")
                            print("int2: \(int2)")
                        }
                    })
                    .background(isFocused2 ? Color.yellow : Color.gray)
            }
            .multilineTextAlignment(.trailing)
            .font(.largeTitle)
        }
        .padding()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

根据@JoakimDanielson 的反馈,我制作了一个带有枚举而不是 2 个单独变量的版本:

import SwiftUI

struct ContentView: View {
    
    enum Focus {
        case input1
        case input2
    }
    
    @State private var isFocused: Focus?
    
    @State private var string1: String = ""
    @State private var int1: Int = 0
    
    @State private var string2: String = ""
    @State private var int2: Int = 0
    
    let multiplier: Int = 2
    
    var body: some View {
        VStack {
            HStack {
                TextField("0", text: $string1, onEditingChanged: { (changed) in
                    if changed {
                        isFocused = Focus.input1
                    }
                })
                .keyboardType(.decimalPad)
                .onChange(of: string1, perform: { value in
                    if isFocused == .input1 {
                        int1 = Int(string1) ?? 0
                        int2 = int1 * multiplier
                        string2 = "\(int2)"
                        print("int1: \(int1)")
                    }
                })
                .background(isFocused == .input1 ? Color.yellow : Color.gray)
            }
            .multilineTextAlignment(.trailing)
            .font(.largeTitle)
            
            HStack {
                Spacer()
                Text("x2")
            }
            
            HStack {
                TextField("0", text: $string2, onEditingChanged: { (changed) in
                    if changed {
                        isFocused = Focus.input2
                    }
                })
                    .keyboardType(.decimalPad)
                    .onChange(of: string2, perform: { value in
                        if isFocused == .input2 {
                            int2 = Int(string2) ?? 0
                            int1 = int2 / multiplier
                            string1 = ("\(int1)")
                            print("int2: \(int2)")
                        }
                    })
                    .background(isFocused == .input2 ? Color.yellow : Color.gray)
            }
            .multilineTextAlignment(.trailing)
            .font(.largeTitle)
        }
        .padding()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}