Xây dựng ứng dụng quét mã QR Code và Bar Code ...
Vậy QR code là gì? Tôi tin là chắc ai cũng đã từng 1 lần sử dụng QR code rồi đúng không? . Trong trường hợp các bạn chưa biết thì xem hình dưới, nó chính là QRCode, QR là viết tắt của Quick Response
QR Code chính là một loại mã Bar code 2 chiều được phát triển bởi Denso . Không giống như Bar code , QR code có thể chứa thông tin theo cả chiều dọc và chiều dài, vì vậy nó có thể chứa khối lượng lớn dữ liệu dạng kí tự và số. Ở đây tôi không đi sâu vào technical của QR , nếu bạn nào hứng thú thì vào http://www.qrcode.com/en/index.html để đi sâu hơn nhé
Để tăng số người sử dụng QR Code người ta đã tích hợp QR lên những smartphone , vì vậy ở 1 số nước QR code gần như được sử dụng eveywhere.
1. Xây dựng ứng dụng đọc QR đơn giản
Trong ứng dụng này mình sẽ sử dụng 1 third party libaries : AVFoundation Framework , nó sẽ giúp các bạn thực hiện việc đọc QR code rất nhanh và real-time ngoài ra nó còn hỗ trợ đọc nhiều mã code dạng khác nữa.
Hình bên dưới là demo của ứng dụng.
Đầu tiên mình sẽ dựng 2 màn hình trên main.Storyboard , để rút ngắn bài viết, phần dựng này các bạn tự tìm hiểu ở những bài viết khác trong Git book nhé.
Sẽ có 2 màn hình, màn hình đầu tiên sẽ có 1 button , khi nhấn vào button sẽ chuyển sang màn hình 2 để đọc QR code
Màn hình 2 sẽ bao gồm 1 cái view để scan , 1 label để hiện kết quả đã scan và 1 topbar
2. Import AVFoundation Framework
Đầu tiên ta tạo file quản trị cho màn hình Scan là QRScannerController.swift sau đó
import AVFoundation
Tiếp theo ta cần implement AVCaptureMetadataOutputObjectsDelegate protocol . Và các bạn thêm dùm tôi 3 dòng sau, tôi sẽ giải thích sau
var captureSession : AVCaptureSession?
var videoPreviewLayer : AVCaptureVideoPreviewLayer?
var qrCodeFrameView : UIView?
3. Implementing Video Capture
QR code được scan hoàn toàn dựa trên Video Capturing, vì vậy chúng ta phải khai báo đối tượng AVCaptureSession , và AVCaptureDevice để nhận thiết bị đầu vào. Thêm đoạn code bên dưới vào phần viewDidLoad
// Get an instance of the AVCaptureDevice class to initialize a device object and provide the video as the media type parameter.
let captureDevice = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
do {
// Get an instance of the AVCaptureDeviceInput class using the previous device object.
let input = try AVCaptureDeviceInput(device: captureDevice)
// Initialize the captureSession object.
captureSession = AVCaptureSession()
// Set the input device on the capture session.
captureSession?.addInput(input)
} catch {
// If any error occurs, simply print it out and don't continue any more.
print(error)
return
}
đối tượng AVCaptureDevice dùng để config thiết bị capture của các bạn , device ở đây là Camera của điện thoại bạn.
Khi chúng ta đang capture video data thì chúng ta sẽ đi gọi phương thức defaultDevice(withMediaType : ) và truyền cho nó cái AVMediaTypeVideo
Để real-time một cái capture ta phải sử dụng đối tượng AVCapturesession và thêm cái thiết bị thu video vào nó. Thì cái này nó được sử dụng để phối hợp cái flow data từ video device input thành data output của chúng ta
Session ra được set đến một đối tượng AVCaptureMetadataOutput . Đồng thời class AVCaptureMetadataOutput chính là phần lõi của hàm đọc mã QR này. Trong class này nó sẽ kết hợp với AVCaptureMetadataOutputObjectDelegate protocol để nhằm mục đích lấy các metadata từ video của camera.
Đừng lo nếu không hiểu, từ từ xem code các bạn sẽ hiểu rõ, bây giờ thêm đoạn code này vào khối do trong viewDidLoad
// Initialize a AVCaptureMetadataOutput object and set it as the output device to the capture session.
let captureMetadataOutput = AVCaptureMetadataOutput()
captureSession?.addOutput(captureMetadataOutput)
Tiếp theo add thêm đoạn code bên dưới , chúng ta set self như delegate của captureMetadataOutput để có class QRReaderViewController tương thích với AVCaptureMetadataOutputObjectDelegate protocol
// Set delegate and use the default dispatch queue to execute the call back
captureMetadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
captureMetadataOutput.metadataObjectTypes = [AVMetadataObjectTypeQRCode]
Có nghĩa là khi một metadata bắt được, nó sẽ được forward đến đối tượng Delegate, và ở đây mình set DispatchQueue.main , và dispatch queue nó có 2 dạng là Serial (nối tiếp) và Concurrent (đồng thời), thì ở đây mình sẽ dùng serial => DispatchQueue.main
Còn metadatametadataObjectsType là cái dạng code mà chúng ta sẽ quét, ở đây chúng ta quét QRCode nên type của nó là AVMetadataObjectTypeQRCode.
Tiếp theo để hiển thị những gì đang capture từ camera lên màn hình ta dùng AVCaptureVideoPreviewLayer , thì cái previewlayer này nó sẽ được addSubview lên View chính của ta , thêm đoạn code bên dưới vào khối do-catch
// Initialize the video preview layer and add it as a sublayer to the viewPreview view's layer.
videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
videoPreviewLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill
videoPreviewLayer?.frame = view.layer.bounds
view.layer.addSublayer(videoPreviewLayer!)
Cuối cùng , chúng ta bắt đầu Capture Video bằng cách gọi startRunning()
captureSession?.startRunning()
Okay vậy là gần xong rồi đấy, bạn thử Run và xem kết quả đi
.
.
.
Đã crash app với lỗi "This app has crashed because it attempted to access privacy-sensitive data without a usage description."đúng không ? . Bởi vì bạn còn thiếu bước này.
Bạn vào Info.list và add thêm key là Privacy Camera Usage và value là We need to access your camera for scanning QR code
Vì khi camera display lên trên màn hình nó sẽ đè lên 2 cái label và topbar nên ở đây mình phải thêm đoạn này để label và topbar nó hiện lên trên màn hình scan
// Move the message label and top bar to the front
view.bringSubview(toFront: messageLabel)
view.bringSubview(toFront: topbar)
4. Tạo box màu xanh
Khi detect đúng QR code chúng ta sẽ cho nó một cái khung màu xanh để thể hiện là đã cap được, để hightlight nó lên.
// Initialize QR Code Frame to highlight the QR code
qrCodeFrameView = UIView()
if let qrCodeFrameView = qrCodeFrameView {
qrCodeFrameView.layer.borderColor = UIColor.green.cgColor
qrCodeFrameView.layer.borderWidth = 2
view.addSubview(qrCodeFrameView)
view.bringSubview(toFront: qrCodeFrameView)
}
5. Giải mã QR Code
Sau khi có được QR code , ta phải translate nó ra thành chuỗi. Ta sẽ implement hàm ở bên dưới.
func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!) {
// Check if the metadataObjects array is not nil and it contains at least one object.
if metadataObjects == nil || metadataObjects.count == 0 {
qrCodeFrameView?.frame = CGRect.zero
messageLabel.text = "No QR code is detected"
return
}
// Get the metadata object.
let metadataObj = metadataObjects[0] as! AVMetadataMachineReadableCodeObject
if metadataObj.type == AVMetadataObjectTypeQRCode {
// If the found metadata is equal to the QR code metadata then update the status label's text and set the bounds
let barCodeObject = videoPreviewLayer?.transformedMetadataObject(for: metadataObj)
qrCodeFrameView?.frame = barCodeObject!.bounds
if metadataObj.stringValue != nil {
messageLabel.text = metadataObj.stringValue
}
}
}
Ở cái param thứ 2 là 1 cái mảng, thì mảng này sẽ chứa những mã QR code, thì điều kiện xét đầu tiên nó không nil và chứa ít nhất một object. Phần decode QRCode sẽ được MachineReadableCode xử lý.
Khi có được chuỗi QRCode , ta sẽ xuất nó ra Label hiển thị lên.
6. Source Code
Chúc các bạn học tốt
Bên dưới là source code đã test thành công của mình.
@IBOutlet var messageLabel:UILabel!
@IBOutlet var topbar: UIView!
var captureSession:AVCaptureSession?
var videoPreviewLayer:AVCaptureVideoPreviewLayer?
var qrCodeFrameView:UIView?
let supportedCodeTypes = [AVMetadataObjectTypeUPCECode,
AVMetadataObjectTypeCode39Code,
AVMetadataObjectTypeCode39Mod43Code,
AVMetadataObjectTypeCode93Code,
AVMetadataObjectTypeCode128Code,
AVMetadataObjectTypeEAN8Code,
AVMetadataObjectTypeEAN13Code,
AVMetadataObjectTypeAztecCode,
AVMetadataObjectTypePDF417Code,
AVMetadataObjectTypeQRCode]
override func viewDidLoad() {
super.viewDidLoad()
// Get an instance of the AVCaptureDevice class to initialize a device object and provide the video as the media type parameter.
let captureDevice = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
do {
// Get an instance of the AVCaptureDeviceInput class using the previous device object.
let input = try AVCaptureDeviceInput(device: captureDevice)
// Initialize the captureSession object.
captureSession = AVCaptureSession()
// Set the input device on the capture session.
captureSession?.addInput(input)
// Initialize a AVCaptureMetadataOutput object and set it as the output device to the capture session.
let captureMetadataOutput = AVCaptureMetadataOutput()
captureSession?.addOutput(captureMetadataOutput)
// Set delegate and use the default dispatch queue to execute the call back
captureMetadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
captureMetadataOutput.metadataObjectTypes = supportedCodeTypes
// Initialize the video preview layer and add it as a sublayer to the viewPreview view's layer.
videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
videoPreviewLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill
videoPreviewLayer?.frame = view.layer.bounds
view.layer.addSublayer(videoPreviewLayer!)
// Start video capture.
captureSession?.startRunning()
// Move the message label and top bar to the front
view.bringSubview(toFront: messageLabel)
view.bringSubview(toFront: topbar)
// Initialize QR Code Frame to highlight the QR code
qrCodeFrameView = UIView()
if let qrCodeFrameView = qrCodeFrameView {
qrCodeFrameView.layer.borderColor = UIColor.green.cgColor
qrCodeFrameView.layer.borderWidth = 2
view.addSubview(qrCodeFrameView)
view.bringSubview(toFront: qrCodeFrameView)
}
} catch {
// If any error occurs, simply print it out and don't continue any more.
print(error)
return
}
}
// MARK: - AVCaptureMetadataOutputObjectsDelegate Methods
func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!) {
// Check if the metadataObjects array is not nil and it contains at least one object.
if metadataObjects == nil || metadataObjects.count == 0 {
qrCodeFrameView?.frame = CGRect.zero
messageLabel.text = "No QR/barcode is detected"
return
}
// Get the metadata object.
let metadataObj = metadataObjects[0] as! AVMetadataMachineReadableCodeObject
if supportedCodeTypes.contains(metadataObj.type) {
// If the found metadata is equal to the QR code metadata then update the status label's text and set the bounds
let barCodeObject = videoPreviewLayer?.transformedMetadataObject(for: metadataObj)
qrCodeFrameView?.frame = barCodeObject!.bounds
if metadataObj.stringValue != nil {
messageLabel.text = metadataObj.stringValue
}
}
}
Người viết : Nguyễn Khánh Hưng