skip to Main Content

I’m trying to use a Swift class from hackingswift.com that reads QR codes. This code has apparently been used in the past by posters to this forum, but it doesn’t work now in Swift 5.5. I get an error ‘expression failed to parse, unknown error’ on the line

    view.layer.addSublayer(previewLayer)

in the following. Any help would be appreciated.

//: A UIKit based Playground for presenting user interface
  
import AVFoundation
import UIKit
import PlaygroundSupport

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
    }
}

// Present the view controller in the Live View window
PlaygroundPage.current.liveView = ScannerViewController()

2

Answers


  1. .addSublayer is working as expected.

    You can try out the sample code below in the playground for testing.

    let view = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
    view.backgroundColor = .cyan
    
    let layer = CALayer()
    
    layer.bounds = CGRect(x: 0, y: 0, width: 100, height: 100)
    layer.position = CGPoint(x: 200/2, y: 200/2)
    layer.backgroundColor = UIColor.magenta.cgColor
    layer.borderWidth = 5
    layer.borderColor = UIColor.black.cgColor
    
    view.layer.addSublayer(layer)
    PlaygroundPage.current.liveView = view
    

    I rapidly tried the sample code you provided in Playgrounds and AVCaptureDevice.default(for: .video) seems to fail.

    Login or Signup to reply.
  2. This is an annoying bug in playgrounds. Leaving aside for a moment that there is no capture device available in playgrounds or the simulator, so none of this will work anyway, the fix is to treat view as an optional:

    view?.layer.addSublayer(previewLayer)
    

    I have no idea why this is the case, and nor do some other people

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search