如何防止SwiftUI TextField的onChange之间的循环依赖?

vsdwdz23  于 2023-08-02  发布在  Swift
关注(0)|答案(3)|浏览(109)

我正在处理一个SwiftUI视图,其中包含两个TextField,一个用于金额,另一个用于百分比。两个TextField都使用$符号绑定到各自的@State变量。

struct ContentView2: View {
    @State private var amount: String = ""
    @State private var percentage: String = ""
    
    var body: some View {
        VStack {
            TextField("enter value here", text: $amount)
            TextField("enter percentage here", text: $percentage)
            
                .onChange(of: amount, perform: onAmountChange(_:))
                .onChange(of: percentage, perform: onPercentageChange(_:))
        }
        .padding()
    }
    
    func onAmountChange(_ newValue: String) {
        // while setting percentage, `percentage` is a state property that triggers onPercentageChange(_:)
        percentage = newValue
        // Is there a way to stop triggering the onPercentageChange(_:) method here?
    }
    
    func onPercentageChange(_ newValue: String) {
        // while setting amount, `amount` is a state property that triggers onAmountChange(_:)
        amount = newValue
        // Is there a way to stop triggering the onAmountChange(_:) method here?
    }
}

字符串
问题:
我面临的问题是,当我更新金额时,它会触发onAmountChange(:)方法,该方法设置百分比。但是,设置百分比也会触发onPercentageChange(:)方法,从而创建循环依赖项
问:
有没有一种方法可以防止这种循环依赖,并避免在设置百分比时触发onAmountChange(_:)方法,反之亦然?我想在不调用第二个函数的情况下更新金额和百分比的值。任何指导或替代方法将不胜感激!谢谢你,谢谢

wvyml7n5

wvyml7n51#

解决这个问题并避免循环依赖的一种方法是使用一个“模型”,在其中执行逻辑,并仅使用视图来呈现状态和发送用户交互。
下面的解决方案接近于MVI模式(模型视图意图),它基本上是一种单向和事件驱动的方法。在这种模式中,视图严格地是状态的函数,逻辑在其他地方执行,即在“模型”中。顺便说一句,它不仅解决了循环依赖,而且还提供了一些非常理想的原则,使您的代码可测试,可靠和可理解。
SwiftUI非常适合实现它,我们只需要弄清楚绑定。见下文:

import SwiftUI
import Combine

// User Interactions
enum Intent {
    case amount(String)
    case percentage(String)
}

// The state of the view
struct ViewState {
    var amount: String = ""
    var percentage: String = ""
}

struct ContentView: View {
    @StateObject var model = Model()
    
    var body: some View {
        let amountBinding = Binding(
            get: { model.viewState.amount },
            set: { value in model.send(.amount(value)) }
        )
        
        let percentageBinding = Binding(
            get: { model.viewState.percentage },
            set: { value in model.send(.percentage(value)) }
        )

        VStack {
            TextField("enter value here", text: amountBinding)
            TextField("enter percentage here", text: percentageBinding)
        }
        .padding()
    }
    
}

@MainActor
final class Model: ObservableObject {
    @Published private(set) var viewState: ViewState = .init()
    
    
    func send(_ intent: Intent) {
        self.viewState = Model.update(self.viewState, intent: intent)
    }

    // This function performs the whole logic of your view. It's only 
    // taking the current state and an intent that happened and computes
    // a new state.
    static func update(_ state: ViewState, intent: Intent) -> ViewState {
        switch intent {
        case .amount(let value):
            guard let x = Double(value) else {
                return .init(amount: value, percentage: "")
            }
            return .init(amount: value, percentage: "\(x * 100)")
        
        case .percentage(let value):
            guard let x = Double(value) else {
                return .init(amount: "", percentage: value)
            }
            return .init(amount: "\(x / 100)", percentage: value)
        
        } 
    }

}

字符串
这里需要注意的是,整个逻辑将在 one 函数中执行,并且该函数是 pure,因此可以轻松测试。

4uqofj5v

4uqofj5v2#

您不需要在SwiftUI的TextField中手动设置文本更改的新值。TextField视图将提供的绑定用作双向绑定,允许它自动读取和写入数据。
只需删除两个onChange修饰符,TextField就可以正常工作,无需任何额外的配置。
有关SwiftUI中双向绑定的更多信息和详细说明,您可以参考以下资源:https://www.hackingwithswift.com/quick-start/swiftui/two-way-bindings-in-swiftui

jgovgodb

jgovgodb3#

使用@FocusState修饰符跟踪哪个textField是焦点
如果我用TextField 1编写,则计算中会聚焦(百分比方法检查TextField 1是否聚焦,如果是,则返回else continue同样用于TextField 2

struct ContentView: View {
    
    @FocusState private var isTextField1Focused: Bool
    @FocusState private var isTextField2Focused: Bool

    @State private var textField1Text: String = ""
    @State private var textField2Text: String = ""
    
    var body: some View {
        VStack {
            TextField("TextField 1", text: $textField1Text)
                .focused($isTextField1Focused)

            TextField("TextField 2", text: $textField2Text)
                .focused($isTextField2Focused)
        }
        .padding()
        .onChange(of: textField1Text, perform: calculate(cost:))
        .onChange(of: textField2Text, perform: calculate(percentage:))
    }
    
    func calculate(cost: String) {
        // here calcuate percentage
        guard !isTextField2Focused else { return }
        textField2Text = cost
        
    }
   
    func calculate(percentage: String) {
        // here calcuate percentage
        guard !isTextField1Focused else { return }
        textField1Text = percentage
    }
}

字符串

相关问题