将数据从SwiftUI传递到UIKit

yvt65v4c  于 2023-03-16  发布在  Swift
关注(0)|答案(3)|浏览(217)

我是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中点击按钮,一直到视图控制器?
在这个tutorial中,它使用ObservableObject将数据传回视图控制器,尽管在那个示例中,整个视图都在一个SwiftUI文件中,但在我的示例中,我将视图分解为单独的组件。
所以我想知道,ObservableObject仍然是这样做的吗?因为我的视图是子视图,我觉得创建多个可观察对象来传播子视图链上的值并不理想。
有没有更好的方法来实现这一目标?
Demo project

e5nqia27

e5nqia271#

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

svujldwt2#

对于这个问题,肯定没有一个 * 绝对 * 正确的答案。您提到的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是一个非常简单和轻微的依赖。

aelbi1ox

aelbi1ox3#

首先使用NavigationLink和UIViewControllerRepresentable将动作数据(点击列表)从SwiftUI传递到UIKit。

struct LessonsListView: View {
    @StateObject var viewModel = LessonsViewModel()
    var body: some View {
        NavigationView {
            List {
                ForEach(self.viewModel.lessonList,id: \.self) { lesson in
                    ZStack(alignment: .leading) {
                        NavigationLink(destination: LessonVideoPlayerRepresentable(thumbnail: lesson.thumbnail ?? "", videoURL: lesson.video_url ?? "", lessonDesc: lesson.description ?? "", name: lesson.name ?? "")) {
                            EmptyView()
                        }
                        LessonView(lesson: lesson)
                    }.listRowBackground(Color.black)
                    
                }
                .padding([.top, .leading, .bottom], 0.0)
                .foregroundColor(.clear).navigationBarTitleDisplayMode(.large).navigationTitle("Lessons").edgesIgnoringSafeArea([.all])
            }
            .listStyle(.plain).scrollContentBackground(.hidden).navigationBarBackButtonHidden()
            
        }.background(Color.black).onAppear {
            self.viewModel.getLessonsList{ success in
                print(success)
            }
        }.edgesIgnoringSafeArea(.all)
        
    }
}
struct LessonsListView_Previews: PreviewProvider {
    static var previews: some View {
        LessonsListView()
    }
}
struct LessonView: View {
    
    var lesson: LessonsModel?
    @StateObject var viewModel = LessonsViewModel()
    var body: some View {
        HStack(spacing: 10) {
            
            LessonImageView(
                url: URL(string: lesson?.thumbnail ?? "")!,
                placeholder: { Text("Loading ...") }
            ).frame(width: 100, height: 60).aspectRatio(contentMode: .fit)
            Text(lesson?.name ?? "").font(.headline).foregroundColor(.white)
            Spacer()
            Image("Forward")
                .renderingMode(.original)
                .padding(/*@START_MENU_TOKEN@*/.all, 10.0/*@END_MENU_TOKEN@*/)
        }
    }
}
struct LessonView_Previews: PreviewProvider {
    static var previews: some View {
        LessonView()
    }
}

struct LessonImageView<Placeholder: View>: View {
    @StateObject private var loader: ImageLoader
    private let placeholder: Placeholder
    private let image: (UIImage) -> Image
    
    init(
        url: URL,
        @ViewBuilder placeholder: () -> Placeholder,
        @ViewBuilder image: @escaping (UIImage) -> Image = Image.init(uiImage:)
    ) {
        self.placeholder = placeholder()
        self.image = image
        _loader = StateObject(wrappedValue: ImageLoader(url: url, cache: Environment(\.imageCache).wrappedValue))
    }
    
    var body: some View {
        content
            .onAppear(perform: loader.load)
    }
    
    private var content: some View {
        Group {
            if loader.image != nil {
                image(loader.image!).resizable()
            } else {
                placeholder
            }
        }
    }
}

设计将看起来像屏幕截图。

其次,您需要在SwiftUI视图中添加UIViewControllerRepresentable。

struct LessonVideoPlayerRepresentable: UIViewControllerRepresentable {
    var thumbnail: String
    var videoURL: String
    var lessonDesc: String
    var name: String
    func makeUIViewController(context: UIViewControllerRepresentableContext<LessonVideoPlayerRepresentable>) -> LessonVideoPlayer {
        
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let detailsVC = storyboard.instantiateViewController(withIdentifier: "LessonVideoPlayer") as! LessonVideoPlayer
        detailsVC.name = name
        detailsVC.thumbnail = thumbnail
        detailsVC.videoURL = videoURL
        detailsVC.lessonDescription = lessonDesc
        detailsVC.view.backgroundColor = .black
        return detailsVC
    }
    func updateUIViewController(_ uiViewController: UIViewControllerType, context: UIViewControllerRepresentableContext<LessonVideoPlayerRepresentable>) {
        uiViewController.view.backgroundColor = .black
        uiViewController.navigationItem.largeTitleDisplayMode = .never
    }
}

现在创建一个UIKit视图控制器,您将从SwiftUi获取数据并将其发送到UIKit类。

class LessonVideoPlayer: UIViewController {
    
    var thumbnail: String = ""
    var videoURL: String = ""
    var name: String = ""
    var lessonDescription: String = "" 

override func viewDidLoad() {
        super.viewDidLoad()
    
        
        print("Name:\(name)")
        print("Thumbnail:\(thumbnail)")
        print("Video URL:\(videoURL)")
        print("Description:\(lessonDescription)")
         
}

}

相关问题