【问题标题】:Pass data from ViewController to Representable SwiftUI将数据从 ViewController 传递到 Representable SwiftUI
【发布时间】:2021-03-05 15:27:52
【问题描述】:

我正在进行对象检测并使用UIViewControllerRepresentable 添加我的视图控制器。问题是我无法将数据从我的ViewController 传递到我的 SwiftUI 视图。我可以打印出来。

有人可以帮助我吗?这是我的代码:

//

import SwiftUI
import AVKit
import UIKit
import Vision
let SVWidth = UIScreen.main.bounds.width

struct MaskDetectionView: View {
    let hasMaskColor = Color.green
    let noMaskColor = Color.red
    let shadowColor = Color.gray
    
    var body: some View {
        VStack(alignment: .center) {
            VStack(alignment: .center) {
                Text("Please place your head inside the bounded box.")
                    .font(.system(size: 15, weight: .regular, design: .default))
                Text("For better result, show your entire face.")
                    .font(.system(size: 15, weight: .regular, design: .default))
            }.padding(.top, 10)
            
            VStack(alignment: .center) {
                SwiftUIViewController()
                    .frame(width: SVWidth - 30, height: SVWidth + 30, alignment: .center)
                    .background(Color.white)
                    .cornerRadius(25)
                    .shadow(color: hasMaskColor, radius: 7, x: 0, y: 0)
                    .padding(.top, 30)
                Spacer()

///      VALUE HERE
            }

        }.padding()
    }
}

struct MaskDetectionView_Previews: PreviewProvider {
    static var previews: some View {
        MaskDetectionView()
        
    }
}


class ViewController: UIViewController, AVCaptureVideoDataOutputSampleBufferDelegate {

    
    var result = String()
    //ALL THE OBJECTS
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 1 - start session
        let capture_session = AVCaptureSession()
        //capture_session.sessionPreset = .vga640x480
        
        // 2 - set the device front & add input
        guard let capture_device = AVCaptureDevice.default(AVCaptureDevice.DeviceType.builtInWideAngleCamera, for: .video, position: .front) else {return}
        
        guard let input = try? AVCaptureDeviceInput(device: capture_device) else { return }
        capture_session.addInput(input)
        
        // 3 - the layer on screen that shows the picture
        let previewLayer = AVCaptureVideoPreviewLayer(session: capture_session)
        view.layer.addSublayer(previewLayer)
        previewLayer.frame.size = CGSize(width: SVWidth, height: SVWidth + 40)
        previewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
        
        // 4 - run the session
        capture_session.startRunning()
        
        // 5 - the produced output aka image or video
        let dataOutput = AVCaptureVideoDataOutput()
        dataOutput.setSampleBufferDelegate(self, queue: DispatchQueue(label: "videoQueue"))
        capture_session.addOutput(dataOutput)
    }
    
    func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection){
        // our model
        
        guard let model = try? VNCoreMLModel(for: SqueezeNet(configuration: MLModelConfiguration()).model) else { return }
        // request for our model
        let request = VNCoreMLRequest(model: model) { (finishedReq, err) in
            if let error = err {
                print("failed to detect faces:", error)
                return
                
            }
            //result
            guard let results = finishedReq.results as? [VNClassificationObservation] else {return}
            guard let first_observation = results.first else {return}
            
            self.result = first_observation.identifier
            print(self.result)
            
        }
        
        guard let pixelBuffer: CVPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {return}
        try? VNImageRequestHandler(cvPixelBuffer: pixelBuffer, options: [:]).perform([request])
    }

}






struct SwiftUIViewController: UIViewControllerRepresentable {
    
    func makeUIViewController(context: Context) -> ViewController{
        return ViewController()
    }
    
    func updateUIViewController(_ uiViewController: ViewController, context: Context) {
        
    }
    
}




【问题讨论】:

  • 我在makeUIViewControllerupdateUIViewController 中没有看到用于实际传递数据的代码。你有没有添加它的原因?
  • 嗨。我不知道该怎么做。我是一名数据科学家,只是 iOS 的初学者。我只需要在我的应用程序中安装摄像头,在它下面我想得到我的 coreML 模型预测的结果。
  • 您能帮我实现吗?我已经浏览了几乎所有堆栈溢出的帖子,但找不到任何东西...... :((
  • 你想传递什么样的数据给视图控制器?这些数据来自哪里?
  • 嗯,基本上我有 SqueezNet 模型。在视图控制器中,我有我的自定义相机,对吧?然后我的 SqueezNet 将预测 ViewController 中的项目,如您所见,然后我希望预测 String 显示在我的视图中。我可以打印该值,但不能将其传递到我的 SwiftUI 视图之外。我认为问题是因为它在函数内部?我不知道

标签: ios swift swiftui viewcontroller avcaptureoutput


【解决方案1】:

简而言之,您需要在 UI 层次结构中循环一个 Binding 实例(这包括 SwiftUI 和 UIKit 代码)。此Binding 将透明地更新与其连接的所有视图上的数据,无论是谁进行了更改。

数据流图可能如下所示:

现在,首先,您需要一个 @State 来将分类标识符存储在 SwiftUI 视图中,这样它就可以连接到您的视图控制器以及另一个将显示它的 UI 元素:

struct MaskDetectionView: View {
    @State var clasificationIdentifier: String = ""

接下来,您需要将它传递给视图控制器和一些 UI 元素:

var body: some View {
    ...
    SwiftUIViewController(identifier: $clasificationIdentifier)
    ...
    // this is the "VALUE HERE" from your question
    Text("Clasification identifier: \(clasificationIdentifier)")

现在,我们正确地注入了绑定,让我们更新代码的 UIKit 端以允许接收绑定。

更新您的可表示视图,使其看起来像这样:

struct SwiftUIViewController: UIViewControllerRepresentable {
    
    // this is the binding that we receive from the SwiftUI side
    let identifier: Binding<String>
    
    // this will be the delegate of the view controller, it's role is to allow
    // the data transfer from UIKit to SwiftUI
    class Coordinator: ViewControllerDelegate {
        let identifierBinding: Binding<String>
        
        init(identifierBinding: Binding<String>) {
            self.identifierBinding = identifierBinding
        }
        
        func clasificationOccured(_ viewController: ViewController, identifier: String) {
            // whenever the view controller notifies it's delegate about receiving a new idenfifier
            // the line below will propagate the change up to SwiftUI
            identifierBinding.wrappedValue = identifier
        }
    }
    
    func makeUIViewController(context: Context) -> ViewController{
        let vc = ViewController()
        vc.delegate = context.coordinator
        return vc
    }
    
    func updateUIViewController(_ uiViewController: ViewController, context: Context) {
        // update the controller data, if needed
    }
    
    // this is very important, this coordinator will be used in `makeUIViewController`
    func makeCoordinator() -> Coordinator {
        Coordinator(identifierBinding: identifier)
    }
}

最后一个难题是编写视图控制器委托的代码,以及使用该委托的代码:

protocol ViewControllerDelegate: AnyObject {
    func clasificationOccured(_ viewController: ViewController, identifier: String)
}

class ViewController: UIViewController {
    
    weak var delegate: ViewControllerDelegate?

    ...

    func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
        ...
  
        print(self.result)

        // let's tell the delegate we found a new clasification
        // the delegate, aka the Coordinator will then update the Binding
        // the Binding will update the State, and this change will be
        // propagate to the Text() element from the SwiftUI view
        delegate?.clasificationOccured(self, identifier: self.result)
    }

【讨论】:

  • swift5 的可表示太复杂了
【解决方案2】:

Swift 有多种方法可以让您在视图和对象之间来回传递数据

例如delegationKey-Value-Observation,或者专门针对SwiftUI的property wrappers,例如@State、@Binding、 @ObservableObject 和 @ObservedObject。但是,在 SwiftUI 视图中显示数据时,您将需要属性包装器。

如果您想以 SwiftUI 方式进行操作,您可能需要查看 @State@Binding 属性包装器以及如何将协调器与 UIViewControllerRepresentable 结构一起使用。将 @State 属性添加到 SwiftUI 视图并将其作为绑定传递给您的 UIViewControllerRepresentable

//Declare a new property in struct MaskDetectionView and pass it to SwiftUIViewController as a binding
@State var string result = ""
...
SwiftUIViewController(resultText: $result)

//Add your new binding as a property in the SwiftUIViewController struct
@Binding var string resultText

这样您可以将 SwiftUI 视图的一部分(例如,您可以在 Text 视图中使用的结果字符串)暴露给 UIViewControllerRepresentable。从那里,您可以将其进一步传递给 ViewController 和/或查看以下有关协调器的文章:https://www.hackingwithswift.com/books/ios-swiftui/using-coordinators-to-manage-swiftui-view-controllers

在我看来,将您的相机工作封装在另一个类 ViewController 中已经过时,并且可以通过使用协调器来完成。以下步骤应该可以帮助您启动并运行视图控制器:

  1. makeUIView 中创建您的视图控制器代码,包括设置 AVKit 对象
  2. 确保将context.coordinator 而非self 作为代表
  3. SwiftUIViewController 中创建一个嵌套类Coordinator 并将该类声明为您的AVCaptureVideoDataOutputSampleBufferDelegate
  4. 向协调器添加一个属性以保存视图控制器对象的实例并实现初始化程序和makeCoordinator 函数以使协调器存储对视图控制器的引用
  5. 如果到目前为止设置正确,您现在可以在协调器类中实现 AVCaptureVideoDataOutputSampleBufferDelegate 委托函数,并在检测到某些内容并返回结果时更新视图控制器的绑定属性

【讨论】:

  • 非常感谢您的回复。问题是我想将数据从func 内部ViewController 传递到MaskDetectionView,这与你所说的相反。反正我能做到吗?
  • 这就是@Binding 的用途。当您将@State 变量作为绑定传递给其他对象时,只要绑定更改,状态就会自动更新,反之亦然。 SwiftUI 在很大程度上依赖于这个设想,因为每次使用状态变量的每个视图都会在状态内容发生变化时自动更新
【解决方案3】:

协议(其他语言的接口)在这样的用例中让生活变得轻松,使用起来也非常简单

1 - 在合适的地方定义协议

2 - 在所需的视图(类,结构)处实现

3 - 将实现的对象引用传递给调用者类或结构

例子->下面

//Protocol
protocol MyDataReceiverDelegte {
  func dataReceived(data:String) //any type of data as your need, I choose String
}

struct MaskDetectionView: View, MyDataReceiverDelegte { // implementer struct
   func dataReceived(data:String){
     //write your code here to process received data
     print(data)
   }
var body: some View {
     //your views comes here
     VStack(alignment: .center) {
         SwiftUIViewController(parent:self)
     }
  }
}

//custom view
struct SwiftUIViewController: UIViewControllerRepresentable {
   let parent:MaskDetectionView
   func makeUIViewController(context: Context) -> ViewController{
    return ViewController(delegate:parent)
   }

   func updateUIViewController(_ uiViewController: ViewController, context: Context) {
    
}
}


//caller class
//i omit your code for simpilicity
class ViewController: UIViewController, AVCaptureVideoDataOutputSampleBufferDelegate {
    let delegate: MyDataReceiverDelegte
  init(delegate d: MyDataReceiverDelegte) {
      self.delegate = d
      super.init(nibName: nil, bundle: nil)
   }
required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
    super.viewDidLoad()
    
    //your code comes here
}

func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection){
        //your rest of code comes here ,
    delegate.dataReceived("Data you want to pass to parent view")
    }
}

【讨论】:

  • 感谢 LOOOTTTTTT!!!!你真是个天才……我很高兴。但还有一个问题,我现在有一个 void 函数中的数据。我怎样才能把它从那里拿出来?我想在我的var body: some View {} 中使用它?
  • 定义一个 @State 属性并在 dataReceived(:) 方法中分配它,并将该属性放在视图中inside body....请阅读文档,有很多关于 SWIFTUI、STATE 变量的文章
  • 委托的东西不起作用,因为:1:委托需要weak,否则你会遇到保留周期; 2:您不能将结构作为委托,分配时会复制结构,因此您不会引用相同的视图
猜你喜欢
  • 2021-01-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-11-08
相关资源
最近更新 更多