ios - 如何在主窗口和外部窗口的两个不相关 View Controller 之间传递引用(无segue,不同的SceneDelegate)

标签 ios swift

问题:在两个不相关的 ViewController(例如,在不同时间独立实例化的两个 ViewController)之间传递引用的最“Swiftian”方式是什么?

我正在编写一个 iOS 应用程序,它将有两个窗口:一个位于设备上,一个可选的外部窗口(例如通过 AirPlay 播放到电视或投影仪)。每个窗口都有自己的 SceneDelegate 和 ViewController。外部窗口在用户启动 AirPlay 时设置,或者在启动时设置(如果 AirPlay 已在运行)。这两种设置都是通过 Storyboard和 plist“自动”管理的。

挑战在于两个窗口都需要显示相同的实时摄像头信息。在我当前的实现中,主 ViewController 更新其自己的 View 和外部 View Controller 控制的 View 。

我认为主 ViewController 应该是外部 ViewController 的委托(delegate),但我不知道如何在两个 ViewController 之间传递必要的引用。这通常在转场期间完成,但外部 View 没有转场。

我当前的解决方案有效,但已经过时了。在 AppDelegate 中,我定义了对外部窗口中 UIImageView 的全局引用(如果存在):

class AppDelegate: UIResponder, UIApplicationDelegate {

    var extImageView: UIImageView!

外部窗口的 ViewController 根据需要保持此引用最新:

class ExtViewController: UIViewController {

    @IBOutlet var extImageView: UIImageView!

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        let appDelegate = UIApplication.shared.delegate as! AppDelegate
        appDelegate.extImageView = extImageView
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        let appDelegate = UIApplication.shared.delegate as! AppDelegate
        appDelegate.extImageView = nil
    }

}

然后我在主 ViewController 中实现 AVCaptureVideoDataOutputSampleBufferDelegate 来更新外部窗口:

// MARK: - AVCaptureVideoDataOutputSampleBufferDelegate
extension CameraViewController: AVCaptureVideoDataOutputSampleBufferDelegate {
    func captureOutput(_ captureOutput: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {

        let imageBuffer: CVPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)!
        let ciimage : CIImage = CIImage(cvPixelBuffer: imageBuffer)
        let image : UIImage = self.convert(cmage: ciimage)

        DispatchQueue.main.sync(execute: {() -> Void in
            let appDelegate = UIApplication.shared.delegate as! AppDelegate
            if let imageView = appDelegate.extImageView {imageView.image = image}
        })

    }

    // Convert CIImage to CGImage
    func convert(cmage:CIImage) -> UIImage
    {
        let context:CIContext = CIContext.init(options: nil)
        let cgImage:CGImage = context.createCGImage(cmage, from: cmage.extent)!
        let image:UIImage = UIImage.init(cgImage: cgImage, scale: 1.0, orientation: extOrientation)
        return image
    }
}

这是可行的,但代价是全局引用和非常具体的、可重用性较差的代码。是否有更优雅、可重用的方法,也许使用委托(delegate)?

最佳答案

实现关注点分离的直接解决方案是观察者模式,请检查以下使用发布通知技术重写代码以实现此目的:

class ExtViewController: UIViewController {

    @IBOutlet var extImageView: UIImageView!

    override func viewDidLoad() {
        super.viewDidLoad()
        self.observeImageCaptureNotification()
    }

    func deint() {
        self.removeAllObservers()
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        let appDelegate = UIApplication.shared.delegate as! AppDelegate
        appDelegate.extImageView = extImageView
    }

    func observeImageCaptureNotification() {

        NotificationCenter.default.addObserver(self, selector: #selector(newImageDidCapture(notification:)), name: "NewImageDidCapture", object: nil)

    }

    func removeAllObservers() {

        NotificationCenter.default.removeObserver(self)

    }

    @objc private func newImageDidCapture(notification: Notification) {

        if let newImage = notification.userInfo?["image"] as? UIImage {

            self.extImageView.image = newImage

        }

    }
}


// MARK: - AVCaptureVideoDataOutputSampleBufferDelegate
extension CameraViewController: AVCaptureVideoDataOutputSampleBufferDelegate {
    func captureOutput(_ captureOutput: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {

        let imageBuffer: CVPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)!
        let ciimage : CIImage = CIImage(cvPixelBuffer: imageBuffer)
        let image : UIImage = self.convert(cmage: ciimage)

        DispatchQueue.main.sync(execute: {() -> Void in
           NotificationCenter.default.post(name: Notification.Name(rawValue: "NewImageDidCapture"), object: nil, userInfo: ["image": image])
        })

    }

    // Convert CIImage to CGImage
    func convert(cmage:CIImage) -> UIImage
    {
        let context:CIContext = CIContext.init(options: nil)
        let cgImage:CGImage = context.createCGImage(cmage, from: cmage.extent)!
        let image:UIImage = UIImage.init(cgImage: cgImage, scale: 1.0, orientation: extOrientation)
        return image
    }
}

关于ios - 如何在主窗口和外部窗口的两个不相关 View Controller 之间传递引用(无segue,不同的SceneDelegate),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59923590/

相关文章:

iphone - 外观代理 - iOS 5 的 setShadowImage 替代品?

ios - 在 Xcode 9 之前创建的项目中重新组织组和文件夹

swift - JSTileMap 在 swift 中的使用

ios - UICollectionView selectItemAtIndexPath 不调用 didSelectItemAtIndexPath

ios - MKPolyLineRenderer 改变颜色而不移除覆盖

ios - 从cv::VideoCapture抓取最后一帧

ios - 从我的 iOS 应用程序打开“设置”>“密码和帐户”>“添加帐户”

swift - crc-16 cccitt 问题 - 计算不正确

swift 3 (macOS) : keyDown(with event: NSEvent) doesn't get called

swift - 为什么闭包中的 self 不引用所属的类实例?