Ios 将数据从SwiftUI传递到UIKit

Ios 将数据从SwiftUI传递到UIKit,ios,swift,swiftui,observableobject,Ios,Swift,Swiftui,Observableobject,我是SwiftUI新手,我一直在尝试如何在同一个应用程序中集成SwiftUI和UIKit。我用SwiftUI做了一个简单的登录屏幕 struct LoginView: View { var body: some View { VStack { LogoView() InputView(title: "Company Code") ButtonView(title: &qu

我是SwiftUI新手,我一直在尝试如何在同一个应用程序中集成SwiftUI和UIKit。我用SwiftUI做了一个简单的登录屏幕

struct LoginView: View {
    
    var body: some View {
        VStack {
            LogoView()
            InputView(title: "Company Code")
            ButtonView(title: "Proceed")
        }
    }
}

我将该视图中的所有组件提取到单独的视图(LogoView、InputView、ButtonView),从而使其可重用

我通过将视图嵌入视图控制器中的
UIHostingController
来显示视图

class LoginViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let controller = UIHostingController(rootView: LoginView(observable: observable))
        controller.view.translatesAutoresizingMaskIntoConstraints = false
        addChild(controller)
        view.addSubview(controller.view)
        controller.didMove(toParent: self)
        
        NSLayoutConstraint.activate([
            controller.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            controller.view.topAnchor.constraint(equalTo: view.topAnchor),
            controller.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            controller.view.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
    }
    
}
我的问题是如何在InputView中输入文本,并在ButtonView中点击按钮,一直到视图控制器

在这种情况下,它使用
observateObject
将数据传递回视图控制器。尽管在该示例中,整个视图位于单个SwiftUI文件中。在我的例子中,我将视图分解为单独的组件

所以我想知道,
observateObject
仍然是这样做的吗?因为我的视图是子视图,所以我觉得创建多个可观察对象来将值传播到子视图链上并不理想

有没有更好的方法来实现这一点

对于这个问题,肯定没有一个绝对正确的答案。您提到的
observegeobject
解决方案就是其中之一。假设您只需要单向数据流(您的示例中似乎有),我可能会尝试使用一种结构,其中只包含一个通过操作调用的简单委托函数——一种Redux式的方法:

enum AppAction {
    case buttonPress(value: String)
    case otherButtonPress(value: Int)
}

typealias DispatchFunction = (AppAction) -> Void

struct ContentView : View {
    var dispatch : DispatchFunction = { action in
        print(action)
    }
    
    var body: some View {
        VStack {
            SubView(dispatch: dispatch)
            SubView2(dispatch: dispatch)
        }
    }
}

struct SubView : View {
    var dispatch : DispatchFunction
    
    var body: some View {
        Button(action: { dispatch(.buttonPress(value: "Test")) }) {
            Text("Press me")
        }
    }
}

struct SubView2 : View {
    var dispatch : DispatchFunction
    
    var body: some View {
        Button(action: { dispatch(.otherButtonPress(value: 2)) }) {
            Text("Press me 2")
        }
    }
}

class ViewController : UIViewController {
    func dispatch(_ action: AppAction) {
        print("Action: \(action)")
    }
    
    override func viewDidLoad() {
        let controller = UIHostingController(rootView: ContentView(dispatch: self.dispatch))
        //...
    }
}

这样,您仍然在向所有子视图传递某些内容,但是像这样传递
DispatchFunction
是一种非常简单和轻松的依赖关系。

首先,使用绑定到输入视图。对于动作,使用闭包从SwiftUI到UIKit获取动作

这里有一个可能的解决办法

class LoginViewObservable: ObservableObject {
    @Published var code: String = ""
    var onLoginAction: (()->Void)! //<-- Button action closure
}

struct LoginView: View {
    @ObservedObject var observable: LoginViewObservable
    
    var body: some View {
        VStack {
            LogoView()
            InputView(title: "Company Code", text: $observable.code) //<- Binding text
            ButtonView(title: "Proceed", action: observable.onLoginAction) //<- Pass action
        }
    }
}

struct InputView: View {
    let title: String
    @Binding var text: String //<- Binding
    
    var body: some View {
        VStack(alignment: .leading) {
            Text(title)
                .foregroundColor(.gray)
                .fontWeight(.medium)
                .font(.system(size: 18))
            
            TextField("", text: $text)
                .frame(height: 54)
                .textFieldStyle(PlainTextFieldStyle())
                .padding([.leading, .trailing], 10)
                .cornerRadius(10)
                .overlay(RoundedRectangle(cornerRadius: 10).stroke(Color.gray))
        }
        .padding()
    }
}

struct ButtonView: View {
    let title: String
    var action: () -> Void
    
    var body: some View {
        Button(title) {
            action() //<- Send action
        }
        .frame(minWidth: 100, idealWidth: 100, maxWidth: .infinity, minHeight: 60, idealHeight: 60)
        .font(.system(size: 24, weight: .bold))
        .foregroundColor(.white)
        .background(Color(hex: "4980F3"))
        .cornerRadius(10)
        .padding([.leading, .trailing])
    }
}


非常感谢。这正是我想要的。我需要更多地了解状态和绑定之间的差异。快速跟进。看起来我的LoginView SwiftUI预览无法渲染。罪魁祸首似乎是这一行
按钮视图(标题:“继续”,动作:observable.onProceedAction)
。奇怪的是,
按钮的预览效果很好。你知道为什么吗?用这个按钮查看预览<代码>按钮视图(标题:“标题”,操作:{})
将此用于InputView\u预览:
InputView(标题:“标题”,文本:。常量(“”)
是的,这也是我正在做的。子视图预览显示时不会出现问题。对于LoginView预览,此错误仍然存在,其中所有子视图都放在一起。奇怪的那一定是一个Xcode错误。
class LoginViewObservable: ObservableObject {
    @Published var code: String = ""
    var onLoginAction: (()->Void)! //<-- Button action closure
}

struct LoginView: View {
    @ObservedObject var observable: LoginViewObservable
    
    var body: some View {
        VStack {
            LogoView()
            InputView(title: "Company Code", text: $observable.code) //<- Binding text
            ButtonView(title: "Proceed", action: observable.onLoginAction) //<- Pass action
        }
    }
}

struct InputView: View {
    let title: String
    @Binding var text: String //<- Binding
    
    var body: some View {
        VStack(alignment: .leading) {
            Text(title)
                .foregroundColor(.gray)
                .fontWeight(.medium)
                .font(.system(size: 18))
            
            TextField("", text: $text)
                .frame(height: 54)
                .textFieldStyle(PlainTextFieldStyle())
                .padding([.leading, .trailing], 10)
                .cornerRadius(10)
                .overlay(RoundedRectangle(cornerRadius: 10).stroke(Color.gray))
        }
        .padding()
    }
}

struct ButtonView: View {
    let title: String
    var action: () -> Void
    
    var body: some View {
        Button(title) {
            action() //<- Send action
        }
        .frame(minWidth: 100, idealWidth: 100, maxWidth: .infinity, minHeight: 60, idealHeight: 60)
        .font(.system(size: 24, weight: .bold))
        .foregroundColor(.white)
        .background(Color(hex: "4980F3"))
        .cornerRadius(10)
        .padding([.leading, .trailing])
    }
}

override func viewDidLoad() {
        super.viewDidLoad()
        // Other code----
        observable.onLoginAction = { [weak self] in //<-- Get login action
            print(self?.observable.code ?? "")
        }
    }