Home > Back-end >  How to made a UIViewController class reusable to pass data back to a viewController that calls it
How to made a UIViewController class reusable to pass data back to a viewController that calls it

Time:05-25

I was using the code from the following site

https://www.hackingwithswift.com/example-code/media/how-to-scan-a-qr-code

The code works perfectly, the code can be viewed by accessing the link above.

It was a code that capture a QRCode/BarCode from camera and convert it to string.

The part of the the code that shows the string is:

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

After that the code string is "printed", the code calls "dismiss" and return to the previous UIViewController.

I want to get the "code" string and get the data to the previous UIViewController.

The only way that I am able to do that now is using the following code:

func found(code: String) {
    print("code: \(code)")
    ResenhaEquideoIdentificaAnimal1Controller.shared.microchipAnimalTextField.text = code
}

But this code only works if the code is called by the "ResenhaEquideoIdentificaAnimal1Controller" class.

I use the following code to call the new UIViewController inside the "ResenhaEquideoIdentificaAnimal1Controller" class using a UIButton.

let myScannerViewController = MyScannerViewController()
present(myScannerViewController, animated: true, completion: nil)

How can I made this class reusable to be able to call the "MyScannerViewController" class and send data back to the view that calls it?

CodePudding user response:

You want to use a "delegate patten", that is, when the code is found or something went wrong, you delegate the functionality to some other party to deal with it.

For example, you could modify the existing example to add support for a simple delegate...

import AVFoundation
import UIKit

protocol ScannerDelegate: AnyObject {
    func scanner(_ controller: ScannerViewController, didDiscoverCode code: String)
    func failedToScanner(_ controller: ScannerViewController)
}

class ScannerViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
    var captureSession: AVCaptureSession!
    var previewLayer: AVCaptureVideoPreviewLayer!
    
    weak var scannerDelegate: ScannerDelegate?

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

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

    private func failed() {
        captureSession = nil
        scannerDelegate?.failedToScanner(self)
    }

    private func didFind(code: String) {
        scannerDelegate?.scanner(self, didDiscoverCode: code)
    }

    override var prefersStatusBarHidden: Bool {
        return true
    }

    override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
        return .portrait
    }

    // MARK: AVCaptureMetadataOutputObjectsDelegate
    
    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))
            didFind(code: stringValue)
        }
    }
}

When you want to scan something, your calling view controller could adopt the protocol...

extension ViewController: ScannerDelegate {
    func failedToScanner(_ controller: ScannerViewController) {
        controller.dismiss(animated: true) {
            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))
            self.present(ac, animated: true)
        }
    }
    
    func scanner(_ controller: ScannerViewController, didDiscoverCode code: String) {
        codeLabel.text = code
        controller.dismiss(animated: true)
    }
}

and when you wanted to present the scanner view controller, you would simply set the view controller as the delegate...

let controller = ScannerViewController()
controller.scannerDelegate = self
present(controller, animated: true)

The great thing about this is, you could easily reject the code if you weren't interested in simply by modifying the delegate workflow

  • Related