SwiftUI 简化许多文本字段的 .onChange 修饰符

use*_*617 14 xcode ios swiftui

我正在寻找一种方法来简化/重构在具有许多文本字段的 SwiftUI 视图中添加 .onChange(of:) 。如果解决方案简洁,我还会将修饰符移至更靠近适当字段的位置,而不是位于 ScrollView 的末尾。在这种情况下,所有 .onChange 修饰符都调用相同的函数。

例子:

.onChange(of: patientDetailVM.pubFirstName) { x in
    changeBackButton()
}
.onChange(of: patientDetailVM.pubLastName) { x in
    changeBackButton()
}
// ten+ more times for other fields
Run Code Online (Sandbox Code Playgroud)

我尝试“oring”字段。这不起作用:

.onChange(of:
            patientDetailVM.pubFirstName ||
            patientDetailVM.pubLastName
) { x in
    changeBackButton()
}
Run Code Online (Sandbox Code Playgroud)

这是我想调用的简单函数:

func changeBackButton() {
    withAnimation {
        showBackButton = false
        isEditing = true
    }
}
Run Code Online (Sandbox Code Playgroud)

任何指导将不胜感激。Xcode 13.2.1 iOS 15

小智 9

为什么不直接使用计算变量呢?

@State private var something: Int = 1
@State private var another: Bool = true
@State private var yetAnother: String = "whatever"

var anyOfMultiple: [String] {[
    something.description,
    another.description,
    yetAnother
]}

var body: some View {
    VStack {
        //
    }
    .onChange(of: anyOfMultiple) { _ in
        //
    }
}
Run Code Online (Sandbox Code Playgroud)


Jer*_*son 2

解决方案概述

我们扩展该Binding类型以创建两个新方法,这两个方法都称为onChange.

这两种onChange方法都适用于每当Binding实例的wrappedValue属性通过其方法更改(而不仅仅是set)时您需要执行某些工作的情况set

第一个onChange方法不会Binding将实例属性的新值传递wrappedValue给提供的更改回调方法,而第二个onChange方法则为其提供新值。

第一种onChange方法允许我们重构它:

bindingToProperty.onChange { _ in
    changeBackButton()
}
Run Code Online (Sandbox Code Playgroud)

对此:

bindingToProperty.onChange(perform: changeBackButton)
Run Code Online (Sandbox Code Playgroud)

解决方案

辅助代码

import SwiftUI

extension Binding {
    public func onChange(perform action: @escaping () -> Void) -> Self where Value : Equatable {
        .init(
            get: {
                self.wrappedValue
            },
            set: { newValue in
                guard self.wrappedValue != newValue else { return }
                
                self.wrappedValue = newValue
                action()
            }
        )
    }
    
    public func onChange(perform action: @escaping (_ newValue: Value) -> Void) -> Self where Value : Equatable {
        .init(
            get: {
                self.wrappedValue
            },
            set: { newValue in
                guard self.wrappedValue != newValue else { return }
                
                self.wrappedValue = newValue
                action(newValue)
            }
        )
    }
}
Run Code Online (Sandbox Code Playgroud)

用法

struct EmployeeForm: View {
    @ObservedObject var vm: VM
    
    private func changeBackButton() {
        print("changeBackButton method was called.")
    }
    
    private func occupationWasChanged() {
        print("occupationWasChanged method was called.")
    }
    
    var body: some View {
        Form {
            TextField("First Name", text: $vm.firstName.onChange(perform: changeBackButton))
            TextField("Last Name", text: $vm.lastName.onChange(perform: changeBackButton))
            TextField("Occupation", text: $vm.occupation.onChange(perform: occupationWasChanged))
        }
    }
}

struct Person {
    var firstName: String
    var surname: String
    var jobTitle: String
}

extension EmployeeForm {
    class VM: ObservableObject {
        @Published var firstName = ""
        @Published var lastName = ""
        @Published var occupation = ""
        
        func load(from person: Person) {
            firstName = person.firstName
            lastName = person.surname
            occupation = person.jobTitle
        }
    }
}

struct EditEmployee: View {
    @StateObject private var employeeForm = EmployeeForm.VM()
    @State private var isLoading = true
    
    func fetchPerson() -> Person {
        return Person(
            firstName: "John",
            surname: "Smith",
            jobTitle: "Market Analyst"
        )
    }
    
    var body: some View {
        Group {
            if isLoading {
                Text("Loading...")
            } else {
                EmployeeForm(vm: employeeForm)
            }
        }
        .onAppear {
            employeeForm.load(from: fetchPerson())
            isLoading = false
        }
    }
}

struct EditEmployee_Previews: PreviewProvider {
    static var previews: some View {
        EditEmployee()
    }
}
Run Code Online (Sandbox Code Playgroud)

解决方案的优点

  1. 帮助程序代码和使用代码都很简单并且非常少。
  2. 它将 onChange 回调保持在非常Binding接近向TextField/TextEditor/其他类型提供实例的位置。
  3. 它是通用的,并且非常通用,因为它可以用于具有符合协议的任何类型的属性的任何Binding实例。wrappedValueEquatable
  4. Binding具有更改回调的实例看起来就像没有Binding更改回调的实例。因此,没有向这些Binding具有更改回调的实例提供任何类型,需要进行特殊修改才能知道如何处理它们。
  5. 帮助程序代码不涉及创建任何新的View@State属性、、、、ObservableObject或任何其他类型。它只是向名为的现有类型添加了几个方法- 这显然是一种已经在代码中使用的类型......EnvironmentKeyPreferenceKeyBinding