【问题标题】:How do you call a function from a subview in SwiftUI?如何从 SwiftUI 的子视图中调用函数?
【发布时间】:2020-07-17 20:10:53
【问题描述】:

我有一个子视图/子视图,它是父视图内实例的模板。这个子视图是一个圆圈,用户可以在屏幕上移动。这个圆圈的初始拖动应该是调用父视图中的一个函数,该函数附加了一个子视图的新实例。基本上,最初将圆从其起始位置移动会在该起始位置创建一个新圆。然后,当您最初移动刚刚创建的新圆时,会​​在该起始位置再次创建另一个新圆,依此类推...

如何从子视图调用 ContentView 中的 addChild() 函数?

谢谢,感谢您的帮助。

import SwiftUI

struct ContentView: View {
    
    @State var childInstances: [Child] = [Child(stateBinding: .constant(.zero))]
    @State var childInstanceData: [CGSize] = [.zero]
    @State var childIndex = 0
    func addChild() {
        self.childInstanceData.append(.zero)
        
        self.childInstances.append(Child(stateBinding: $childInstanceData[childIndex]))
        
        self.childIndex += 1
    }
    
    var body: some View {
        ZStack {
            ForEach(childInstances.indices, id: \.self) { index in
                self.childInstances[index]
            }
            
            ForEach(childInstanceData.indices, id: \.self) { index in
                Text("y: \(self.childInstanceData[index].height) : x: \(self.childInstanceData[index].width)")
                    .offset(y: CGFloat((index * 20) - 300))
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
import SwiftUI

struct Child: View {
    @Binding var stateBinding: CGSize
    
    @State var isInitalDrag = true
    @State var isOnce = true
    
    @State var currentPosition: CGSize = .zero
    @State var newPosition: CGSize = .zero
    
    var body: some View {
        Circle()
            .frame(width: 50, height: 50)
            .foregroundColor(.blue)
            .offset(self.currentPosition)
            .gesture(
                DragGesture()
                    .onChanged { value in
                        
                        if self.isInitalDrag && self.isOnce {
                            
                            // Call function in ContentView here... How do I do it?
                            ContentView().addChild()
                            
                            self.isOnce = false
                        }
                        
                        self.currentPosition = CGSize(
                            width: CGFloat(value.translation.width + self.newPosition.width),
                            height: CGFloat(value.translation.height + self.newPosition.height)
                        )
                        
                        self.stateBinding = self.currentPosition
                    }
                    .onEnded { value in
                        self.newPosition = self.currentPosition
                        
                        self.isOnce = true
                        self.isInitalDrag = false
                    }
            )
    }
}

struct Child_Previews: PreviewProvider {
    static var previews: some View {
        Child(stateBinding: .constant(.zero))
    }
}

【问题讨论】:

    标签: swiftui call parent subview


    【解决方案1】:

    这就是我要做的,我会在第二个视图中创建一个函数并在任何情况下调用它,比如当按下第二个视图中的按钮时调用这个函数。我会在主视图中传递函数的回调。

    这是一个代码来说明我的意思。

    import SwiftUI
    
    struct StackOverflow23: View {
        var body: some View {
            VStack {
                Text("First View")
                
                Divider()
                
                // Note I am presenting my second view here and calling its function ".onAdd"
                SecondView()
                    // Whenever this function is invoked inside `SecondView` then it will run the code in between these brackets.
                    .onAdd {
                        print("Run any function you want here")
                    }
            }
        }
    }
    
    struct StackOverflow23_Previews: PreviewProvider {
        static var previews: some View {
            StackOverflow23()
        }
    }
    
    
    struct SecondView: View {
        // Define a variable and give it default value
        var onAdd = {}
        
        var body: some View {
            Button(action: {
                // This button will invoke the callback stored in the variable, this can be invoked really from any other function. For example, onDrag call self.onAdd (its really up to you when to call this).
                self.onAdd()
            }) {
                Text("Add from a second view")
            }
        }
        
        
        // Create a function with the same name to keep things clean which returns a view (Read note 1 as why it returns view)
        // It takes one argument which is a callback that will come from the main view and it will pass it down to the SecondView
        func onAdd(_ callback: @escaping () -> ()) -> some View { 
            SecondView(onAdd: callback)
        }
    }
    

    注意 1:我们的 onAdd 函数返回视图的原因是因为请记住 swiftui 仅基于视图,并且每个修饰符都返回一个视图本身。例如,当您有一个Text("test") 然后将.foregroundColor(Color.white) 添加到它时,实际上您所做的是您没有修改文本的颜色,而是使用自定义foregroundColor 创建一个新的Text价值。

    这正是我们正在做的,我们正在创建一个可以在调用 SecondView 时初始化的变量,但我们没有在初始化程序中调用它,而是将它创建为一个函数修饰符,它将返回一个新的 @987654328 实例@ 为我们的变量添加自定义值。

    我希望这是有道理的。如果您有任何问题,请不要犹豫。

    这里是你的代码修改:

    ContentView.swift

    
    import SwiftUI
    
    struct ContentView: View {
        
        @State var childInstances: [Child] = [Child(stateBinding: .constant(.zero))]
        @State var childInstanceData: [CGSize] = [.zero]
        @State var childIndex = 0
        func addChild() {
            self.childInstanceData.append(.zero)
            
            self.childInstances.append(Child(stateBinding: $childInstanceData[childIndex]))
            
            self.childIndex += 1
        }
        
        var body: some View {
            ZStack {
                ForEach(childInstances.indices, id: \.self) { index in
                    self.childInstances[index]
                        .onAddChild {
                            self.addChild()
                        }
                }
                
                ForEach(childInstanceData.indices, id: \.self) { index in
                    Text("y: \(self.childInstanceData[index].height) : x: \(self.childInstanceData[index].width)")
                        .offset(y: CGFloat((index * 20) - 300))
                }
            }
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }
    
    

    Child.swift

    import SwiftUI
    
    struct Child: View {
        @Binding var stateBinding: CGSize
        
        @State var isInitalDrag = true
        @State var isOnce = true
        
        @State var currentPosition: CGSize = .zero
        @State var newPosition: CGSize = .zero
        
        var onAddChild = {} // <- The variable to hold our callback
        
        var body: some View {
            Circle()
                .frame(width: 50, height: 50)
                .foregroundColor(.blue)
                .offset(self.currentPosition)
                .gesture(
                    DragGesture()
                        .onChanged { value in
                            
                            if self.isInitalDrag && self.isOnce {
                                
                                // Call function in ContentView here... How do I do it?
                                
                                self.onAddChild() <- // Here is your solution
                                
                                self.isOnce = false
                            }
                            
                            self.currentPosition = CGSize(
                                width: CGFloat(value.translation.width + self.newPosition.width),
                                height: CGFloat(value.translation.height + self.newPosition.height)
                            )
                            
                            self.stateBinding = self.currentPosition
                        }
                        .onEnded { value in
                            self.newPosition = self.currentPosition
                            
                            self.isOnce = true
                            self.isInitalDrag = false
                        }
                )
        }
        
    // Our function which will initialize our variable to store the callback
        func onAddChild(_ callaback: @escaping () -> ()) -> some View {
            Child(stateBinding: self.$stateBinding, isInitalDrag: self.isInitalDrag, isOnce: self.isOnce, currentPosition: self.currentPosition, newPosition: self.newPosition, onAddChild: callaback)
        }
    }
    
    struct Child_Previews: PreviewProvider {
        static var previews: some View {
            Child(stateBinding: .constant(.zero))
        }
    }
    

    【讨论】:

    • 我很高兴能帮上忙!如果您还有其他问题,请不要犹豫。还可以考虑标记答案,以便其他人在有类似问题时可以轻松找到帮助。
    • 哇,穆汉德你太棒了!非常感谢!!!它现在完全有效!近两个月以来,我一直在努力解决这个问题……我有一个大致的了解。我按照你解释的方式理解它,但它的复杂性,例如 @escaping 参数和它是一个函数修饰符,是我需要进一步探索的一些东西。在大多数情况下,我看到您所说的关于使用回调重新创建第二个视图的内容。赞! ~丰富
    • 我自己不是 swift 开发人员,我主要是后端工程师,但 swift 非常简单,我认为通过练习你很快就会掌握它。 SwiftUI 建立在 Swift 之上,因此在 Swift 中学习 Swift 和闭包并没有什么坏处。祝你好运!
    • 嘿@Muhand,我还有另一个问题。从子视图调用父视图中的函数只是一个方向。另一种是创建一个继承自 ObservableObject 的类,它是 @Published 全局变量的中心。当我尝试从任何视图调用函数时都没有问题,但我无法从类中创建双向绑定。我怎样才能做到这一点? $ 在类中不起作用,因为它仅适用于 @State...self.childInstances.append(Child(stateBinding: $childInstanceData[childIndex]))
    • @Rich 你能否提出一个新问题,我会在那里回答,因为它与这个问题有点不同,我们不想混淆与你有类似问题的人.此外,添加的答案将太长。但简而言之,使用EnvironmentObject 是可行的,实际上我已将您的代码转换为使用EnvironmentObject
    【解决方案2】:

    这是您在我的代码中的答案:

    import SwiftUI
    
    struct ContentView: View {
        @State var childInstances: [Child] = []
        @State var childInstanceData: [CGSize] = []
        @State var childIndex = 0
        func addChild() {
            self.childInstanceData.append(.zero)
            
            self.childInstances.append(Child(stateBinding: $childInstanceData[childIndex]))
            
            self.childIndex += 1
        }
        
        var body: some View {
            ZStack {
                ForEach(childInstances.indices , id: \.self) { index in
                    self.childInstances[index]
                        .onAddChild {
                            print(self.childInstances.count)
                            self.addChild()
                        }
                }
                VStack {
                    ForEach(childInstanceData.indices, id: \.self) { index in
                        Text("\(index).  y: \(self.childInstanceData[index].height) : x: \(self.childInstanceData[index].width)")
                    }
                }
                .offset(y: -250)
                
            }
            .onAppear {
                self.addChild()
            }
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }
    
    import SwiftUI
    
    struct Child: View {
        @Binding var stateBinding: CGSize
    
        var onAddChild = {} // <- The variable to hold our callback
        
        @State var isInitalDrag = true
        @State var isOnce = true
        
        @State var currentPosition: CGSize = .zero
        @State var newPosition: CGSize = .zero
        
        var body: some View {
            Circle()
                .frame(width: 50, height: 50)
                .foregroundColor(.blue)
                .offset(self.currentPosition)
                .gesture(
                    DragGesture()
                        .onChanged { value in
                            
                            if self.isInitalDrag && self.isOnce {
                                
                                // Call function in ContentView here:
                                self.onAddChild()
                                
                                self.isOnce = false
                            }
                            
                            self.currentPosition = CGSize(
                                width: CGFloat(value.translation.width + self.newPosition.width),
                                height: CGFloat(value.translation.height + self.newPosition.height)
                            )
                            
                            self.stateBinding = self.currentPosition
                        }
                        .onEnded { value in
                            self.newPosition = self.currentPosition
                            
                            self.isOnce = true
                            self.isInitalDrag = false
                        }
                )
        }
        
        func onAddChild(_ callback: @escaping () -> ()) -> some View {
            Child(stateBinding: self.$stateBinding, onAddChild: callback)
        }
    }
    
    struct Child_Previews: PreviewProvider {
        static var previews: some View {
            Child(stateBinding: .constant(.zero))
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2020-12-19
      • 1970-01-01
      • 2020-02-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-05-12
      • 1970-01-01
      相关资源
      最近更新 更多