微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

将数据从 SwiftUI 传递到 UIKit

如何解决将数据从 SwiftUI 传递到 UIKit

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

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

enter image description here

我通过将视图中的所有组件提取到单独的视图(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 中发生按钮点击,一直到视图控制器?

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

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

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

Demo project

解决方法

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

对此当然没有绝对正确的答案。您提到的 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 是一个非常简单和轻量级的依赖。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。