简体   繁体   English

SwiftUI - 响应父视图中的点击

[英]SwiftUI - Responding to taps in parent View

TL;DR: TL;博士:

I want to trigger an action when (parent) state changes.我想在(父)状态改变时触发一个动作 This seems difficult in a declarative context.这在声明性上下文中似乎很困难。

Remarks评论

The challenge here is that I am not trying to make a property of one view dependent on another.这里的挑战是我不想让一个视图的属性依赖于另一个视图。 That's well-covered territory.那是覆盖良好的领域。 I could (and have) read all day about sharing state changes.我可以(并且已经)整天阅读有关共享状态更改的信息。 But this is an event .但这是一个事件

The best I have come across is by arsenius .我遇到的最好的是arsenius His approach does work;他的方法确实有效。 I am wondering if there is a more Reactive way to do it.我想知道是否有更被动的方式来做到这一点。 Maybe a one-shot Publisher ?也许是一次性Publisher者? Seems sketchy.看起来很粗略。

Code代码

"Event" is not always a dirty word in FRP. “事件”在 FRP 中并不总是一个肮脏的词。 I can start a View 's animation by handling an event in the same View :我可以通过处理同一View中的事件来启动View的动画:

import SwiftUI

struct MyReusableSubview : View {
    @State private var offs = CGFloat.zero  // Animate this.

    var body: some View {
        Rectangle().foregroundColor(.green).offset(y: self.offs)

        // A local event triggers the action...
        .onTapGesture { self.simplifiedAnimation() }
        // ...but we want to animate when parent view says so.
    }

    private func simplifiedAnimation() {
        self.offs = 200
        withAnimation { self.offs = 0 }
    }
}

But I want this View to be composable and reusable.但我希望这个View是可组合和可重用的。 It seems reasonable to want to plug this into a larger hierarchy, which will have its own idea of when to run the animation.将其插入到更大的层次结构中似乎是合理的,它对何时运行动画有自己的想法。 All my "solutions" either change state during View update, or won't even compile.我所有的“解决方案”要么在View更新期间更改状态,要么甚至无法编译。

struct ContentView: View {
    var body: some View {
        VStack {
            Button(action: {
                // Want this to trigger subview's animation.
            }) {
                Text("Tap me")
            }
            MyReusableSubview()
        }.background(Color.gray)
    }
}

Surely SwiftUI is not going to force me not to decompose my hierarchy? SwiftUI 肯定不会强迫我不要分解我的层次结构吗?

Solution解决方案

Here is arsenius' suggestion .这是arsenius 的建议 Is there a more Swifty-UI way?有没有更 Swifty-UI 的方式?

struct MyReusableSubview : View {
    @Binding var doIt : Bool // Bound to parent

    // ... as before...

    var body: some View {
        Group {
            if self.doIt {
                ZStack { EmptyView() }
                .onAppear { self.simplifiedAnimation() }
                // And call DispatchQueue to clear the doIt flag.
            }

            Rectangle()
            .foregroundColor(.green)
            .offset(y: self.offs)
        }
    }
}

Here is possible approach with optional external event generator, so if one provided reusable view reacts on external provider as well as on local (if local is not needed, then it can be just removed).这是使用可选外部事件生成器的可能方法,因此,如果提供的可重用视图对外部提供者以及本地做出反应(如果不需要本地,则可以将其删除)。

Tested with Xcode 11.4 / iOS 13.4使用 Xcode 11.4 / iOS 13.4 测试

演示

Full module code:完整的模块代码:

import SwiftUI
import Combine

struct MyReusableSubview : View {
    private let publisher: AnyPublisher<Bool, Never>
    init(_ publisher: AnyPublisher<Bool, Never> = 
                      Just(false).dropFirst().eraseToAnyPublisher()) {
        self.publisher = publisher
    }

    @State private var offs = CGFloat.zero  // Animate this.

    var body: some View {
        Rectangle().foregroundColor(.green).offset(y: self.offs)

        // A local event triggers the action...
        .onTapGesture { self.simplifiedAnimation() }
        .onReceive(publisher) { _ in self.simplifiedAnimation() }
        // ...but we want to animate when parent view says so.
    }

    private func simplifiedAnimation() {
        self.offs = 200
        withAnimation { self.offs = 0 }
    }
}

struct TestParentToChildEvent: View {
    let generator = PassthroughSubject<Bool, Never>()
    var body: some View {
        VStack {
            Button("Tap") { self.generator.send(true) }
            Divider()
            MyReusableSubview(generator.eraseToAnyPublisher())
                .frame(width: 300, height: 200)
        }
    }
}

struct TestParentToChildEvent_Previews: PreviewProvider {
    static var previews: some View {
        TestParentToChildEvent()
    }
}

This demo seems simplest for me, also it is possible indirect dependency injection of external generator via Environment instead of constructor.这个演示对我来说似乎最简单,也可以通过环境而不是构造函数间接依赖注入外部生成器。

backup 备份

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM