在Xcode 11中展示UIViewController的问题,SwiftUI

3

我有一个嵌入在UIViewController中的二维码扫描器。我已经查看了其他帖子和博客,但所有这些都是单页应用程序,它们将UIViewController呈现为应用程序启动时的主要页面。我只需要在按钮操作内打开它,但出于某种原因,我无法正确地呈现扫描器?

以下是QR扫描器代码:

class ScannerViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
    var captureSession: AVCaptureSession!
    var previewLayer: AVCaptureVideoPreviewLayer!

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = UIColor.black
        captureSession = AVCaptureSession()

        guard let videoCaptureDevice = AVCaptureDevice.default(for: .video) else { return }
        let videoInput: AVCaptureDeviceInput

        do {
            videoInput = try AVCaptureDeviceInput(device: videoCaptureDevice)
        } catch {
            return
        }

        if (captureSession.canAddInput(videoInput)) {
            captureSession.addInput(videoInput)
        } else {
            failed()
            return
        }

        let metadataOutput = AVCaptureMetadataOutput()

        if (captureSession.canAddOutput(metadataOutput)) {
            captureSession.addOutput(metadataOutput)

            metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
            metadataOutput.metadataObjectTypes = [.qr]
        } else {
            failed()
            return
        }

        previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
        previewLayer.frame = view.layer.bounds
        previewLayer.videoGravity = .resizeAspectFill
        view.layer.addSublayer(previewLayer)

        captureSession.startRunning()
    }

    func failed() {
        let ac = UIAlertController(title: "Scanning not supported", message: "Your device does not support scanning a code from an item. Please use a device with a camera.", preferredStyle: .alert)
        ac.addAction(UIAlertAction(title: "OK", style: .default))
        present(ac, animated: true)
        captureSession = nil
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        if (captureSession?.isRunning == false) {
            captureSession.startRunning()
        }
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)

        if (captureSession?.isRunning == true) {
            captureSession.stopRunning()
        }
    }

    func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
        captureSession.stopRunning()

        if let metadataObject = metadataObjects.first {
            guard let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject else { return }
            guard let stringValue = readableObject.stringValue else { return }
            AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))
            found(code: stringValue)
        }

        dismiss(animated: true)
    }

    func found(code: String) {
        print(code)
    }

    override var prefersStatusBarHidden: Bool {
        return true
    }

    override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
        return .portrait
    }
}

这是我(非常简单)尝试按下按钮后打开它的方法:

struct ScanView: View{
    
    var body: some View{
        VStack {
            Button(action: {
                ScannerViewController()
            }) {
                Text("Scan QR Code").foregroundColor(Color.white).padding()
            }.frame(width: 150)
            .background(Color.blue)
            .cornerRadius(25)
        }
    }
}

我尝试查看了几篇关于这些问题的StackOverflow文章,但有些文章使用了已弃用的扫描二维码的方法,或者与我面临的问题无关。

我确信这是一件相当简单的事情,但我的Swift / Xcode专业知识大多涉及基本的API调用设计和样式,但我并不一定理解不同的视图层次结构和调用方法的工作方式。

我当然想修复这个问题,但我也想知道问题所在,这样下次就能知道怎么做了。


你曾经试图了解 viewDidLoad 有什么作用吗? - El Tomato
既然你这么不太明显地指出来,我会确保去看一下的。虽然那是个修辞问题,但我也可以回答一下我的当前知识,以便日后进行比较;我原本认为viewDidLoad是在整个视图加载后运行的(即contentview加载后),所以在这种情况下,viewDidLoad是无用的,如果不是导致在实际按下按钮之前就拉起扫描仪。 - methodical
@ElTomato 我在上次评论以来的时间里一直很苦恼,找不到足够的信息来解决我的问题,你能给我一个已解决的代码示例,这样我就可以看到我哪里出了错吗?我明白为什么viewDidLoad在这种情况下不合适,但我不知道如何调整我的代码以删除那些引用行。我刚刚意识到我在另一个帐户上发布了这个评论,但我们是同一个人。 - Nathan
2个回答

1
我们无法直接在SwiftUI中调用UIViewController。需要使用UIViewControllerRepresentable来呈现一个UIViewController。
您可以在这里阅读相关信息:

试一试这个,它可以正常工作,我可以通过相机看到内容:

import SwiftUI
import UIKit
import AVFoundation

struct ScannerView: UIViewControllerRepresentable {
    typealias UIViewControllerType = ScannerViewController

    func updateUIViewController(_ uiViewController: ScannerViewController, context: Context) {
        print("update")
    }

    func makeUIViewController(context: Context) -> ScannerViewController {
        return ScannerViewController()
    }

}

在你的struct ScanView中:
struct ContentView: View {
    @State private var showingScanner = false
    var body: some View {
        VStack {
            Button(action: {
                self.showingScanner = true
            }) {
                Text("Scan QR Code").foregroundColor(Color.white).padding()
            }.frame(width: 150)
            .background(Color.blue)
            .cornerRadius(25)
        }.sheet(isPresented: $showingScanner) {
            ScannerView()
        }
    }
}

摄像机视图,我已经将摄像机视图拖到下面,这样您就可以看到背景中的按钮屏幕了:

enter image description here

提示:别忘了在 info.plist 中添加 Privacy - Camera Usage Description


这个解决方案可以用于呈现视图,现在我面临的问题是,它会拉起视图,但相机视图为空白?图片参考在这里:https://imgur.com/a/gFw596A - methodical
我一段时间前添加了那个权限,但相机视图仍未出现。问题可能是我正在尝试从contentview下的嵌套视图中拉起视图吗?我尝试制作一个可观察对象和环境对象,以便能够从contentview中拉起扫描仪,但这段代码根本不起作用。我真的很困惑问题出在哪里,我将创建一个空白项目并使用此代码来查看问题是否基于视图嵌套的事实。 - Nathan
我刚刚使用你提供的代码和我提供的代码创建了一个项目,它按预期工作。这让我感到困惑,为什么它在我的原始项目中不起作用?我已经在原始项目中添加了相机访问权限到info.plist,但它没有像在空白项目中那样弹出请求访问相机的提示? - Nathan
由于我尝试了更多(不成功的)解决方案,以下是更多上下文信息:我使用一个名为viewRouter的可观察对象来导航到我的视图的不同子树,因为有5-6个不同的离散导航树适用于不同的用户类型,最初将其分解为不同的树是占据我的contentview的。我尝试添加另一个专门用于QR扫描仪的分支,认为可能与它嵌套在contentview之外有关,但即使直接放在contentview中,相同的问题仍然存在。是否有任何我可以提供的信息以提供更多帮助? - Nathan

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接