1

I created a barcode scanner control in Swift. The scanner works fine and I am able to draw a scan area box overlay. I am trying to make the box flash green or red depending on the success of the scan. I've added an animation with a delay but it does not work properly. The color of the box does change but there isn't a slow animation for the duration period. it just changes immediately. I. would like the box to flash green and then revert back to the original color.

I am wondering if it has something to due with how I am drawing the box in the first place. Perhaps the CAShapeLayer that I am modifying isn't compatible with animations?

class BarcodeScanner: UIView, AVCaptureMetadataOutputObjectsDelegate {
       
    var scanBoxLayer: CAShapeLayer!
    
    func preparePreviewLayer() {
        self.layer.sublayers?.removeAll()
        
        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 {
            return
        }

        let metadataOutput = AVCaptureMetadataOutput()
        
        if (captureSession.canAddOutput(metadataOutput)) {
            captureSession.addOutput(metadataOutput)

            metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
            metadataOutput.metadataObjectTypes = [.ean8, .ean13, .pdf417, .qr, .code39, .code128]
        } else {
            return
        }

        scanPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
        scanPreviewLayer.frame = self.layer.bounds
        scanPreviewLayer.videoGravity = .resizeAspectFill
                    
        self.layer.addSublayer(scanPreviewLayer)

        // Here is where I draw the scan box
        drawScanBox()
    }

    func drawScanBox() {
        let path = UIBezierPath(rect: scanBox())
        
        scanBoxLayer = CAShapeLayer()
                    
        scanBoxLayer.path = path.cgPath
        scanBoxLayer.strokeColor = UIColor.systemYellow.cgColor
        scanBoxLayer.lineWidth = 2
        scanBoxLayer.fillColor = UIColor.clear.cgColor
        scanBoxLayer.shadowColor = UIColor.systemYellow.cgColor
        scanBoxLayer.shadowOpacity = 1.0
        scanBoxLayer.shadowRadius = 5.0
        
        self.layer.addSublayer(scanBoxLayer)
    }
    
    func scanBox() -> CGRect {
        let height: CGFloat = self.scanPreviewLayer.frame.size.height
        let width: CGFloat = self.scanPreviewLayer.frame.size.width
        
        return self.bounds.insetBy(dx: width / 5, dy: height / 3)
    }

    func flash() {        
        UIView.animate(
            withDuration: 3.0,
            delay: 0.0,
            options: [.repeat, .autoreverse],
            animations: {
                self.scanBoxLayer.strokeColor = UIColor.systemGreen.cgColor
                self.scanBoxLayer.shadowColor = UIColor.systemGreen.cgColor
            },
            completion: nil
        )
    }
    
    func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
         if let metadataObject = metadataObjects.first {
            guard let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject else { return }
            
            if let barcode = readableObject.stringValue {
                // Here is where I want to make the scan box flash
                flash()
                
                self.delegate?.scanComplete(code: barcode)
            }
        }
    }
    
}

Screenshot

Can anyone please tell me if I am missing something?

Thanks!

Rob
  • 415,655
  • 72
  • 787
  • 1,044
Joel S.
  • 41
  • 5
  • Thanks Rob. First time poster on SO :) – Joel S. Apr 29 '22 at 16:05
  • https://stackoverflow.com/questions/15651717/how-to-animate-cashapelayer-path-and-fillcolor – Harry J May 02 '22 at 06:20
  • 1
    Thanks Harry. Working through the link you posted made me finally realize I was simply adding the animation to the wrong layer. User error :) Appreciate the help. – Joel S. May 02 '22 at 11:16

0 Answers0