Search code examples
swiftuilabelavfoundationbarcode-scanner

How do I pass a scanned barcode ID from first view controller to second View Controller's UILabel?


This is the barcode scanning tutorial I used in my program, so that you have a lot more context when you read my code: Link

Here is what my program does so far: Essentially, when I scan an item's barcode with my phone, the UIAlert pops up with the barcode ID displayed and a button prompting the user to open the "Results" page. This is all fine and good, but how do I pass that same scanned barcode ID into a label on the Result's page? I have been stuck on this for 2 days now, even though it seems like such an easy task.

Any help is much appreciated <3

Here is my relevant code:

ProductCatalog.plist -> Link to Image

Scanner_ViewController.swift (first View Controller) ->

import UIKit
import AVFoundation

class Scanner_ViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate, ScannerDelegate
{
    private var scanner: Scanner?

    override func viewDidLoad()
    {
        super.viewDidLoad()
    
        self.scanner = Scanner(withDelegate: self)
    
        guard let scanner = self.scanner else
        {
            return
        }
    
        scanner.requestCaptureSessionStartRunning()
    }

    override func didReceiveMemoryWarning()
    {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    // Mark - AVFoundation delegate methods
    public func metadataOutput(_ output: AVCaptureMetadataOutput,
                               didOutput metadataObjects: [AVMetadataObject],
                               from connection: AVCaptureConnection)
    {
        guard let scanner = self.scanner else
        {
            return
        }
        scanner.metadataOutput(output,
                               didOutput: metadataObjects,
                               from: connection)
    }

    // Mark - Scanner delegate methods
    func cameraView() -> UIView
    {
        return self.view
    } 

    func delegateViewController() -> UIViewController
    {
        return self
    }

    func scanCompleted(withCode code: String)
    {
        print(code)
        showAlert_Success(withTitle: (code))
    }
 
    private func showAlert_Success(withTitle title: String)
    {
        let alertController = UIAlertController(title: title, message: "Product has been successfully scanned", preferredStyle: .alert)

        // programatically segue to the next view controller when the UIAlert pops up
        alertController.addAction(UIAlertAction(title:"Get Results", style: .default, handler:{ action in self.performSegue(withIdentifier: "toAnalysisPage", sender: self) }))
    
        present(alertController, animated: true)
    }
}

Scanner.Swift (accompanies Scanner_ViewController.swift)->

import Foundation
import UIKit
import AVFoundation

protocol ScannerDelegate: class
{
    func cameraView() -> UIView
    func delegateViewController() -> UIViewController
    func scanCompleted(withCode code: String)
}

class Scanner: NSObject
{
    public weak var delegate: ScannerDelegate?
    private var captureSession : AVCaptureSession?

    init(withDelegate delegate: ScannerDelegate)
    {
        self.delegate = delegate
        super.init()
        self.scannerSetup()
    }

    private func scannerSetup()
    {
        guard let captureSession = self.createCaptureSession()
    
        else
        {
            return
        }
    
        self.captureSession = captureSession
    
        guard let delegate = self.delegate
    
        else
        {
            return
        }
    
        let cameraView = delegate.cameraView()
        let previewLayer = self.createPreviewLayer(withCaptureSession: captureSession,
                                                   view: cameraView)
        cameraView.layer.addSublayer(previewLayer)
    }

    private func createCaptureSession() -> AVCaptureSession?
    {
        do
        {
            let captureSession = AVCaptureSession()
            guard let captureDevice = AVCaptureDevice.default(for: .video) else
            {
            return nil
            }
        
            let deviceInput = try AVCaptureDeviceInput(device: captureDevice)
            let metaDataOutput = AVCaptureMetadataOutput()
        
            // add device input
            if captureSession.canAddInput(deviceInput) && captureSession.canAddOutput(metaDataOutput)
            {
                captureSession.addInput(deviceInput)
                captureSession.addOutput(metaDataOutput)
            
                guard let delegate = self.delegate,
                    let viewController = delegate.delegateViewController() as? AVCaptureMetadataOutputObjectsDelegate else
                {
                        return nil
                }
            
                metaDataOutput.setMetadataObjectsDelegate(viewController,
                                                          queue: DispatchQueue.main)
                metaDataOutput.metadataObjectTypes = self.metaObjectTypes()
             
                return captureSession
            }
        }
    
        catch
        {
            // handle error
        }
    
        return nil
    }

    private func createPreviewLayer(withCaptureSession captureSession: AVCaptureSession,
                                    view: UIView) -> AVCaptureVideoPreviewLayer
    {
        let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
        previewLayer.frame = view.layer.bounds
        previewLayer.videoGravity = .resizeAspectFill
    
        return previewLayer
    }

    private func metaObjectTypes() -> [AVMetadataObject.ObjectType]
    {
        return [.qr,
                .code128,
                .code39,
                .code39Mod43,
                .code93,
                .ean13,
                .ean8,
                .interleaved2of5,
                .itf14,
                .pdf417,
                .upce
        ]
    }

    public func metadataOutput(_ output: AVCaptureMetadataOutput,
                         didOutput metadataObjects: [AVMetadataObject],
                         from connection: AVCaptureConnection)
    {
        self.requestCaptureSessionStopRunning()
    
        guard let metadataObject = metadataObjects.first,
            let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject,
            let scannedValue = readableObject.stringValue,
            let delegate = self.delegate
    
        else
        {
                return
        }
        
        delegate.scanCompleted(withCode: scannedValue)
    }

    public func requestCaptureSessionStartRunning()
    {
        self.toggleCaptureSessionRunningState()
    }

    public func requestCaptureSessionStopRunning()
    {
        self.toggleCaptureSessionRunningState()
    }

    private func toggleCaptureSessionRunningState()
    {
        guard let captureSession = self.captureSession
    
        else
        {
            return
        }
    
        if !captureSession.isRunning
        {
            captureSession.startRunning()
        }
    
        else
        {
            captureSession.stopRunning()
        }
    }
}

Analysis_ViewController.swift (second view controller) ->

Right now, the forKey: has been hard-coded to item ID 8710908501708 because I have no idea how to actually pass camera-scanned ID's into the second View Controller :/

import UIKit

class Analysis_ViewController: UIViewController
{
    @IBOutlet weak var productTitle: UILabel!

    func getData()
    {
        let path = Bundle.main.path(forResource:"ProductCatalog", ofType: "plist")

        let dict:NSDictionary = NSDictionary(contentsOfFile: path!)!
    
        if (dict.object(forKey: "8710908501708" as Any) != nil)
        {
            if let levelDict:[String : Any] = dict.object(forKey: "8710908501708" as Any) as? [String : Any]
            {
                // use a for loop to iterate through all the keys and values in side the "Levels" dictionary
                for (key, value) in levelDict
                {
                    // if we find a key named whatever we care about, we can print out the value
                    if (key == "name")
                    {
                        productTitle.text = (value as! String)
                    }
                }
            }
        }
    }

    // listing the better options that are safer in comparison to the scanned product image
    override func viewDidLoad()
    {
        super.viewDidLoad()

        getData()
    }
}

Solution

  • Do you have a variable to hold the scanned ID in your view controllers? If not, you can add var itemID: String? to both Scanner_ViewController and Analysis_ViewController.

    Then in your func where you get the scanned code, you can set it to the variable.

    func scanCompleted(withCode code: String) {
        print(code)
        itemID = code // Saves the scanned code to your var
        showAlert_Success(withTitle: (code))
    }
    

    For passing data to another view controller via segue, you might want to look into this UIViewController method for segues: documentation here. This answer also might help.

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "toAnalysisPage" {
            if let viewController = segue.destination as? Analysis_ViewController {
                viewController.itemID = itemID    
            }
        }
    }