用于处理独立视图中的独立事件/触发器的 SwiftUI 模式?

问题描述 投票:0回答:1

我有一个独立的

View
,因为它可以处理自己的状态。

View
维护着粒子模型的列表。
View
管理每个粒子的生命周期和渲染。

我想触发视图以一种特殊的方式创建一个新的粒子,例如响应计时器或用户输入。

每个粒子都是独立的。

View
可以管理任意数量的粒子,因此可以调用任意数量的触发器。

目前,我正在使用

@Observable
视图模型。

视图模型有一个标志“articleRequested”。

为了管理此标志,我在视图模型上调用一个便利函数:

func requestParticle() {
    guard particleRequested == false else { return }

    particleRequested = true
    
    // Wait a brief moment, then reset the flag.
    Task { @MainActor [weak self] in
        guard let self = self else { return }
        try await Task.sleep(for: .seconds(0.01))
        self.particleRequested = false
    }
}

在视图本身中,我只观察到标志的更改

.onChange(of: viewModel.particleRequested, { _, newValue in
    if newValue {
        addParticle()  // Modifies View's state.
    }
})

这感觉……很卡顿。整个方法都有气味。有没有更好、更干净的方法?

swiftui
1个回答
0
投票

您的总体想法是正确的 - 使用

onChange
检测视图更新中的更改并执行所需的操作。您当前的代码可以简化很多:

  • 你不需要视图模型类
  • 您不需要触发
    true
    上的操作。每当值改变时触发动作
  • 因此无需在延迟后重置该值
struct Particles<Trigger: Equatable>: View {
    @State private var particles = [String]()
    let newParticleTrigger: Trigger
    
    var body: some View {
        List(particles, id: \.self) {
            Text($0)
        }
        .onChange(of: newParticleTrigger) {
            particles.append(UUID().uuidString)
        }
    }
}

// Example usage:
struct ContentView: View {
    @State private var newParticleTrigger = false
    
    var body: some View {
        Button("Foo") {
            newParticleTrigger.toggle()
        }
        Particles(newParticleTrigger: newParticleTrigger)
    }
}

许多 SwiftUI 内置视图和修饰符中都使用相同的模式,例如

PhaseAnimator
sensoryFeedback
task(id:)
以及任何演示修饰符,如
sheet
confirmationDialog
等。

如果您想要触发多个不同的事物,只需将所有触发器放入

struct

struct Particles: View {
    struct Triggers: Hashable {
        fileprivate var trigger1 = false
        fileprivate var trigger2 = false
        fileprivate var trigger3 = false
        
        mutating func triggerThing1() {
            trigger1.toggle()
        }
        mutating func triggerThing2() {
            trigger2.toggle()
        }
        mutating func triggerThing3() {
            trigger3.toggle()
        }
    }
    
    let triggers: Triggers
    
    var body: some View {
        // ...

        .onChange(of: triggers.trigger1) {
            // ...
        }
        .onChange(of: triggers.trigger2) {
            // ...
        }
        .onChange(of: triggers.trigger3) {
            // ...
        }
    }
}

另一种设计是利用环境:

extension EnvironmentValues {
    @Entry var trigger1: Bool = false
    @Entry var trigger2: Bool = false
    @Entry var trigger3: Bool = false
}

struct OnEnvironmentChange<Trigger: Equatable>: ViewModifier {
    let environmentValueKeyPath: WritableKeyPath<EnvironmentValues, Bool>
    let trigger: Trigger
    @State private var currentState = false
    
    func body(content: Content) -> some View {
        content
            .environment(environmentValueKeyPath, currentState)
            .onChange(of: trigger) {
                currentState.toggle()
            }
    }
}

extension View {
    func thing1<T: Equatable>(trigger: T) -> some View {
        modifier(OnEnvironmentChange(environmentValueKeyPath: \.trigger1, trigger: trigger))
    }
    func thing2<T: Equatable>(trigger: T) -> some View {
        modifier(OnEnvironmentChange(environmentValueKeyPath: \.trigger2, trigger: trigger))
    }
    func thing3<T: Equatable>(trigger: T) -> some View {
        modifier(OnEnvironmentChange(environmentValueKeyPath: \.trigger3, trigger: trigger))
    }
}

struct Particles: View {
    @Environment(\.trigger1) var trigger1
    @Environment(\.trigger2) var trigger2
    @Environment(\.trigger3) var trigger3
    var body: some View {
        // ...

        .onChange(of: trigger1) {
            // ...
        }
        .onChange(of: trigger2) {
            // ...
        }
        .onChange(of: trigger3) {
            // ...
        }
    }
}

这样,当使用

Particles
时,你不需要知道你不想触发的触发器。仅应用与您要触发的内容相对应的视图修饰符。

// suppose this view only wants to trigger thing1
struct ContentView: View {
    @State private var thing1 = false
    
    var body: some View {
        Button("Foo") {
            thing1.toggle()
        }
        Particles()
            // then only apply this modifier
            .thing1(trigger: thing1)
            // don't need to care about thing2 or thing3
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.