swift - 通过 UIViewControllerRepresentable 实现复杂的 UIKit + SwiftUI 界面

标签 swift swiftui uikit

我正在构建一个相机应用程序,其所有 UI 都在 SwiftUI(父级)中,其中包含一个包含所有录制功能的 UIKit Controller 。用户界面非常复杂,因此希望尽可能保留项目的这种结构。

UIKit 类有一些函数,例如 startRecord() stopRecord(),我希望从 SwiftUI View 触发这些函数。因此,我想从 SwiftUI View “调用”UIKit 函数。

我正在尝试 UIViewControllerRepresentable,能够对全局变量更改执行更新,但我仍然无法从 SwiftUI 父级调用我想要触发的各个函数。

这是 SwiftUI 文件:

init(metalView: MetalViewController?) {
    self.metalView = MetalViewController(appStatus: appStatus)
}

var body: some View {
    
    ZStack {
        
        // - Camera view
        metalView
            .edgesIgnoringSafeArea(.top)
            .padding(.bottom, 54)
        
        VStack {
            
            
            LateralMenuView(appStatus: appStatus, filterTooltipShowing: $_filterTooltipShowing)
            
            Button("RECORD", action: {
                print("record button pressed")
                metalView?.myMetalDelegate.switchRecording(). // <-- Not sure about this
            })

这是 MetalViewController:

protocol MetalViewControllerDelegate {
    func switchRecording()
}

// MARK: - The secret sauce for loading the MetalView (UIKit -> SwiftUI)
struct MetalViewController: UIViewControllerRepresentable {

var appStatus: AppStatus
typealias UIViewControllerType = MetalController
var myMetalDelegate: MetalViewControllerDelegate!

func makeCoordinator() -> Coordinator {
    Coordinator(metalViewController: self)
}

func makeUIViewController(context: UIViewControllerRepresentableContext<MetalViewController>) -> MetalController {
    let controller = MetalController(appStatus: appStatus)
    return controller
}

func updateUIViewController(_ controller: MetalController, context: UIViewControllerRepresentableContext<MetalViewController>) {
    controller.changeFilter()
}

class Coordinator: NSObject, MetalViewControllerDelegate {
    var controller: MetalViewController

    init(metalViewController: MetalViewController) {
        controller = metalViewController
    }
    func switchRecording() {
        print("just testing")
    }
}

}

和 UIKit Controller ...

class MetalController: UIViewController {

var _mydelegate: MetalViewControllerDelegate?
...
override func viewDidLoad() {
 ...
    self._mydelegate = self
}

extension MetalController: MetalViewControllerDelegate {
    func switchRecording() {
        print("THIS SHOULD BE WORKING, BUT ITS NOT")
    }
}

最佳答案

我喜欢使用组合通过 ObservableObject 将消息传递到 UIKit View 。这样,我就可以命令式地调用它们。我没有尝试解析您的代码,而是做了一个这个概念的小例子:

import SwiftUI
import Combine

enum MessageBridgeMessage {
    case myMessage(parameter: Int)
}

class MessageBridge : ObservableObject {
    @Published var result = 0
    
    var messagePassthrough = PassthroughSubject<MessageBridgeMessage, Never>()
}

struct ContentView : View {
    @StateObject private var messageBridge = MessageBridge()
    
    var body: some View {
        VStack {
            Text("Result: \(messageBridge.result)")
            Button("Add 2") {
                messageBridge.messagePassthrough.send(.myMessage(parameter: messageBridge.result))
            }
            VCRepresented(messageBridge: messageBridge)
        }
    }
}

struct VCRepresented : UIViewControllerRepresentable {
    var messageBridge : MessageBridge
    
    func makeUIViewController(context: Context) -> CustomVC {
        let vc = CustomVC()
        context.coordinator.connect(vc: vc, bridge: messageBridge)
        return vc
    }
    
    func updateUIViewController(_ uiViewController: CustomVC, context: Context) {
        
    }
    
    func makeCoordinator() -> Coordinator {
        return Coordinator()
    }
    
    class Coordinator {
        private var cancellable : AnyCancellable?
        
        func connect(vc: CustomVC, bridge: MessageBridge) {
            cancellable = bridge.messagePassthrough.sink(receiveValue: { (message) in
                switch message {
                case .myMessage(let parameter):
                    bridge.result = vc.addTwo(input: parameter)
                }
            })
        }
    }
}

class CustomVC : UIViewController {
    func addTwo(input: Int) -> Int {
        return input + 2
    }
}

在示例中,MessageBridge 有一个 PassthroughSubject,可以从 UIKit View (或在本例中为 UIViewController)订阅。它属于 ContentView,并通过参数传递给 VCRepresented

VCRepresented 中,Coordinator 上有一个方法用于订阅发布者 (messagePassthrough) 并对消息进行操作。您可以通过枚举 (MessageBridgeMes​​sage) 上的关联属性传递参数。如果需要,返回值可以存储在 MessageBridge 上的 @Published 属性中(或者,您可以设置另一个发布者以相反的方向)。

它有点冗长,但似乎是一个非常可靠的模式,可以与您需要的树的任何级别(SwiftUI View 、可表示 View 、UIKit View 等)进行通信。

关于swift - 通过 UIViewControllerRepresentable 实现复杂的 UIKit + SwiftUI 界面,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66619751/

相关文章:

ios - 获取本地存储数据时出现问题 - Swift

swift - 无法以函数作为参数执行选择器

html - Canvas 外的 CSS 固定位置

ios - 根据 subview 大小调整 XIB 中 UIView 的大小

swift - 元组未按正确顺序排序(swift3)

ios - 如何使用用户输入在 Firebase 中为 child 命名?

multithreading - DispatchQueue.main.async 不返回主线程 - SwiftUI

swiftui - SwiftUI 上下文菜单是否使用 LayoutConstraints?

SwiftUI 在 NavigationLink 上滑动/拖动

ios - 生成不重复的随机 UIImage