【问题标题】:Passing data from SwiftUI to UIKit将数据从 SwiftUI 传递到 UIKit
【发布时间】:2021-05-18 03:10:04
【问题描述】:

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

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

我通过将视图中的所有组件提取到单独的视图(LogoView、InputView、ButtonView)来使它们可重用。

struct LogoView: View {
    var body: some View {
        VStack {
            Image("logo")
            Text("Inventory App")
                .foregroundColor(.blue)
                .fontWeight(.bold)
                .font(.system(size: 32))
        }
    }
}

struct InputView: View {
    let title: String
    
    @State private var text: String = ""
    
    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 body: some View {
        Button(title) {
            print(#function)
        }
        .frame(minWidth: 100, idealWidth: 100, maxWidth: .infinity, minHeight: 60, idealHeight: 60)
        .font(.system(size: 24, weight: .bold))
        .foregroundColor(.white)
        .background(Color.blue)
        .cornerRadius(10)
        .padding([.leading, .trailing])
    }
}

我通过将视图嵌入到视图控制器的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 中,一直到 View Controller?

在这个tutorial 中,它使用ObservableObject 将数据传回给视图控制器。尽管在该示例中,整个视图都在一个 SwiftUI 文件中。就我而言,我将视图分解为单独的组件。

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

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

Demo project

【问题讨论】:

    标签: ios swift swiftui observableobject


    【解决方案1】:

    首先,使用绑定到您的输入视图。对于动作,使用闭包将动作从 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])
        }
    }
    
    

    最后,在viewDidLoad()

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

    【讨论】:

    • 谢谢。这正是我一直在寻找的。我需要更多地了解 State 和 Binding 之间的区别。
    • 快速跟进。看来我的 LoginView SwiftUI 预览无法呈现。而罪魁祸首似乎是这条线ButtonView(title: "Proceed", action: observable.onProceedAction)。奇怪的是ButtonView 的预览效果很好。知道为什么会这样吗?
    • 在 ButtonView_Previews 中使用它。 ButtonView(title: "Title", action: {})
    • 将此用于 InputView_Previews :InputView(title: "Title", text: .constant(""))
    • 是的,这也是我已经在做的事情。子视图预览显示没有问题。这个错误仍然出现在 LoginView 预览中,所有子视图都放在一起。诡异的。那么一定是 Xcode 的 bug。
    【解决方案2】:

    对于这个问题,肯定没有一个明确正确的答案。您提到的ObservableObject 解决方案就是其中之一。假设您只需要单向数据流(您的示例似乎有),我可能会想使用一个结构,该结构只有一个简单的委托函数,该函数被操作调用——一种 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 是一个非常简单和轻量级的依赖。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-01-15
      • 2020-06-20
      • 2021-03-05
      • 2021-06-21
      • 2020-10-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多