SwiftUI - 从父视图工具栏调用函数,如何在子视图上也调用函数

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

在我的 SwiftUI 应用程序中,我在父视图中使用自定义工具栏。 该工具栏也适用于子视图。 当用户“完成”编辑时,我调用父级中的函数来执行一些任务。 然而,在孩子中,我不知道如何访问此事件来调用一些逻辑。 使用完成按钮不会调用子项的 onSubmit,因此我无法正确完成编辑。 您只能在创建工具栏的视图中访问工具栏的按钮事件吗?

  • 如果我在子项上使用另一个工具栏,它会在工具栏本身上创建显示错误。

  • 有从父级到子级的绑定,因此我无法将子视图创建为属性。 (即让 child = ChildView(),然后使用 child.alsoFinishedEditing())

  • 我能想到的最接近的解决方案是在父级中创建一个 bool 标志,然后进行切换,监听子级中的 onChange 事件,但这似乎有点老套。

任何解决此问题的帮助将不胜感激。下面是最小的复制代码。

import SwiftUI

// In my parent view I'm handling some logic that needs a toolbar action 
// when 'done'
struct ParentView: View {
    
    // parent logic omitted
    
    var body: some View {
        VStack {
            Text("Parent View")
                .padding()
            
            // child view that handles it's own state
            ChildView()
                .padding()
        }
        .toolbar {
            ToolbarItemGroup(placement: .keyboard) {
                HStack {
                    Button(action: {
                        // how would I also call a function in the child?
                        finishEditing()
                    }, label: {
                        Text("Done").bold()
                    })
                }
            }
        }
    }
    
    // Parent's function that gets called when Done is pressed
    func finishEditing() {
        // Parent logic omitted
        print("Done Editing")
        // How would I call a method in the child view?
    }
}

struct ChildView: View {
    
    @State var inputText: String = ""
    var body: some View {
        VStack { 
            // for this field the 'done' but is also present
            // but when you tap it the onSubmit get's bypassed
            // and we don't correctly finish our editing logic
            TextField("Child Input", text: $inputText)
                .padding()
                .onSubmit {
                    alsoFinishedEditing()
                }

        }
    }
    
    func alsoFinishedEditing() {
        // how can I access the 'done' button
        // event from this child
    }
}
swiftui
1个回答
0
投票

如果我理解正确,您希望每个视图都有自己的方式来处理父视图添加的“完成”按钮的点击。

这可以通过自定义

PreferenceKey
来完成。每个视图都有自己的“点击完成后要做什么”的偏好。

struct DoneActionKey: PreferenceKey {
    static let defaultValue: @MainActor () -> Void = {}
    
    static func reduce(value: inout @MainActor () -> Void, nextValue: () -> @MainActor () -> Void) {
        let curr = value
        let next = nextValue()
        value = {
            curr()
            next()
        }
    }
}

注意

curr()
next()
的顺序。这决定了同级视图的“点击完成后要做什么”首选项的执行顺序。

然后您可以编写一个视图修饰符来更改该首选项,以便每个子视图都可以指定它们想要执行的操作。

extension View {
    func onDone(_ action: @MainActor @escaping () -> Void) -> some View {
        self.onSubmit {
            action()
        }
        .transformPreference(DoneActionKey.self) { value in
            let curr = value
            value = {
                action()
                curr()
            }
        }
    }
}

我也调用了

onSubmit
中的操作。如果这不是您想要的,您可以将其删除。再次注意
action()
curr()
的顺序。这决定了父视图和子视图的首选项的运行顺序。通过将
action()
放在第一位,父视图的操作将在子视图的操作之前运行,因为
value
参数代表 child 的 首选项,而不是父视图的首选项。


用途:

struct ParentView: View {
    var body: some View {
        VStack {
            Text("Parent View")
                .padding()
            
            ChildView()
                .padding()
        }
        .onDone {
            finishEditing()
        }
        // add the toolbar via a backgroundPreferenceValue, so we have access to the preference value
        .backgroundPreferenceValue(DoneActionKey.self) { doneAction in
            Color.clear
                .toolbar {
                    ToolbarItemGroup(placement: .keyboard) {
                        HStack {
                            Button(action: {
                                doneAction()
                            }, label: {
                                Text("Done").bold()
                            })
                        }
                    }
                }
        }
    }
    
    func finishEditing() {
        print("Done Editing")
    }
}

struct ChildView: View {
    
    @State var inputText: String = ""
    var body: some View {
        VStack {
            TextField("Child Input", text: $inputText)
                .padding()
                .onDone {
                    alsoFinishedEditing()
                }

        }
    }
    
    func alsoFinishedEditing() {
        print("Child Finishes Editing")
    }
}

现在点击“完成”将打印

Done Editing
Child Finishes Editing
© www.soinside.com 2019 - 2024. All rights reserved.