SwiftUI избегайте повторного звонка по обмену

#ios #swift #swiftui

Вопрос:

В моем коде значение «окончательного» переключателя основано на сложной логике внутри модели представления. Например, пользователь включает переключатель, но логика может отключить его, если определенные условия не выполняются (здесь ниже упрощено сравнение чисел). Проблема: после того, как пользователь включит переключатель, onChange он будет уволен, и если логика обнаружит, что он должен быть выключен, onChange он будет уволен еще раз, потому что есть изменение (на этот раз не внесенное пользователем).

Например, если сгенерированное случайное число равно 5, то консоль выведет следующие инструкции (8 приходит после второго вызова):

     on change called
    shouldUseToggle called
    5
    on change called
    shouldUseToggle called
    8
    onChange(of: Bool) action tried to update multiple times per frame.
 

Я хотел бы избежать этого и заставить onChange реагировать только на изменения пользователя, а не на изменения, которые исходят из модели представления. Есть ли способ сделать это ? Или, может быть, другой способ решить эту проблему ?

 import SwiftUI

struct ContentView: View {
    @StateObject var myViewModel = MyViewModel()
    
    var body: some View {
        VStack(spacing: 0) {
            Toggle("Use xyz ?", isOn: $myViewModel.turnToggleOn).onChange(of: myViewModel.turnToggleOn, perform: { userTurnedOn in
                print("on change called")
                myViewModel.shouldUseToggle(userTurnedOn: userTurnedOn)
            })
        }
    }
}

class MyViewModel: ObservableObject {
    @Published var turnToggleOn = false
    
    func shouldUseToggle(userTurnedOn: Bool) {
        ///some complex logic comes here, for simplicity I use random numbers
        print("shouldUseToggle called")
        let x = Int.random(in: 0..<10)
        print(x)
        if userTurnedOn {
            turnToggleOn = x > 5
        } else {
            turnToggleOn = x > 3
        }
    }
}
 

Комментарии:

1. Это может быть лучше для пользователя, если опция недоступна для включения, этот элемент управления следует отключить. Другими словами, используйте сложную логику, чтобы определить, следует ли включать переключатель, а не запускать его после того, как пользователь попытается его изменить.

2. хороший момент, но для типичного случая — в моем случае я дополнительно показываю предупреждение с указанием причины, по которой оно недоступно, что было бы невозможно, если переключатель отключен

Ответ №1:

Используйте пользовательскую привязку и вызывайте свою логическую функцию непосредственно из привязки.

 struct ContentView: View {
    
    @StateObject var myViewModel = MyViewModel()
    
    var body: some View {
        VStack(spacing: 0) {
            Toggle("Use xyz ?", isOn: Binding(get: {myViewModel.turnToggleOn}, set: {
                myViewModel.turnToggleOn = $0;
                myViewModel.shouldUseToggle(userTurnedOn: $0)
            }))
        }
    }
}

class MyViewModel: ObservableObject {
    var turnToggleOn = false
    
    func shouldUseToggle(userTurnedOn: Bool) {
        ///some complex logic comes here, for simplicity I use random numbers
        print("shouldUseToggle called")
        let x = Int.random(in: 0..<10)
        print(x)
        if userTurnedOn {
            turnToggleOn = x > 5
        } else {
            turnToggleOn = x > 3
        }
    }
}