QRCodeReaderTutorial-iOS
๋ชฉ์ฐจ
์์ฑ
Main.storyboard
ํ๋ฉด์ ๋ค์๊ณผ ๊ฐ์ด ๊ตฌ์ฑํ๋ค. ์ผ์ชฝ ์คํ ๋ฆฌ๋ณด๋๋ถํฐ 123 ์์.
- ViewController : (1) ๋ฉ์ธ ๋ทฐ์ปจํธ๋กค๋ฌ
- QRCodeModalViewController : (2) qr ์ฝ๋ ๋ฅผ ๋ง๋ค์ด์ ๋ชจ๋ฌ์ฐฝ์ผ๋ก ๋์ฐ๊ธฐ
- QRCodeReaderViewController : (3) qr ์ฝ๋ ๋ฆฌ๋๊ธฐ๋ฅผ ๋ง๋ค์ด์ ํ๋ฉด์ ํ
QR์ฝ๋ ๋ง๋ค๊ธฐ
CIQRCodeGenerator CIFilter ๋ฅผ ํตํด์ qr code ๋ฅผ ๋ง๋ค ๊ฒ์ด๋ค.
๊ฐ๋ฐ์ ๋ฌธ์๋ฅผ ์ดํด๋ณด์
Apple Developer - Core Image Filter Reference(CIQRCodeGenerator)
์ด ํํฐ๋ ๋๊ฐ์ง ํ๋ผ๋ฏธํฐ๋ฅผ ๊ฐ์ง๋ค.
- inputMessage : QR์ฝ๋๋ก ์ธ์ฝ๋ฉํ ๋ฐ์ดํฐ์ ๋๋ค. NSData ๊ฐ์ฒด์ ๋๋ค.
- inputCorrectionLevel : ์ค๋ฅ ์์ ํ์์ ์ง์ ํ๋ ๋จ์ผ ๋ฌธ์์ ๋๋ค. NSString ๊ฐ์ฒด์ ๋๋ค. ๊ธฐ๋ณธ๊ฐ์ M.
String ๋๋ URL์์ QR ์ฝ๋๋ฅผ ์์ฑํ๋ ค๋ฉด NSISOLatin1StringEncoding ๋ฌธ์์ด ์ธ์ฝ๋ฉ์ ์ฌ์ฉํ์ฌ ์ด๋ฅผ NSData ๊ฐ์ฒด๋ก ๋ณํํฉ๋๋ค.
inputCorrectionLevel ๋งค๊ฐ๋ณ์๋ ์ค๋ฅ์์ ์ ์ํด์ ์ถ๋ ฅ์ด๋ฏธ์ง์ ์ธ์ฝ๋ฉ๋ ์ถ๊ฐ ๋ฐ์ดํฐ์์ ์ ์ดํฉ๋๋ค.
(QR์ฝ๋๋ ์ฝ๋์ ์ค์ผ์ด๋ ์์์๋ ์ฝ๋ ์์ฒด์ ๋ฐ์ดํฐ๋ฅผ ๋ณต์ํ๋ ๊ธฐ๋ฅ์ด ์์ต๋๋ค. ๋ ๋ฒจ์ ์ฌ๋ฆฌ๋ฉด ์ค๋ฅ๋ณต์ ๋ฅ๋ ฅ์ ํฅ์๋์ง๋ง ๋ฐ์ดํฐ๊ฐ ์ฆ๊ฐ๋์ด ์ฝ๋์ ํฌ๊ธฐ๊ฐ ์ปค์ง๋๋ค.)
L
: 7%M
: 15%(๊ธฐ๋ณธ๊ฐ)Q
: 25%H
: 30%
qr code ์ ์ค๋ฅ ๋ณต์ ๊ธฐ๋ฅ์ ๋ํด์๋ ์๋ ์ฌ์ดํธ๋ฅผ ์ฐธ๊ณ ํด๋ณด์
์ค๋ฅ ๋ณต์ ๊ธฐ๋ฅ์ ๋ํด
- QRCodeView.swift : QR code ๋ฅผ ๋ง๋๋ ํด๋์ค
import Foundation
import UIKit
class QRCodeView: UIView {
// โ
CIQRCodeGenerator : QR code ์์ฑ ํํฐ๋ฅผ ์๋ณํ๊ธฐ ์ํ ์์ฑ.
var filter = CIFilter(name: "CIQRCodeGenerator")
// โ
QRCode CIImage ๋ฅผ ๋ง๋ค์ด์ ์ถ๊ฐํ UIImageView.
var imageView = UIImageView()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(imageView)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
imageView.frame = bounds
}
// โ
QRCode ์ด๋ฏธ์ง๋ฅผ ๋ง๋ค ๋ ๋ค์ํ ์์ผ๋ก ๋ง๋ค ์ ์๋๋ก parameter ๋ฅผ ๋ฐ์๋ค.
func generateCode(_ string: String, foregroundColor: UIColor = .black, backgroundColor: UIColor = .white) {
// โ
์ฃผ์ด์ง ์ธ์ฝ๋ฉ์(=using) ์ฌ์ฉํด์ NSData ๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ค.
guard let filter = filter, let data = string.data(using: .isoLatin1, allowLossyConversion: false) else {
return
}
// โ
๋๊ฐ์ง ํ๋ผ๋ฏธํฐ ์ค์ .
filter.setValue(data, forKey: "inputMessage")
filter.setValue("M", forKey: "inputCorrectionLevel")
// โ
.outputImage : ํํฐ์ ๊ตฌ์ฑ๋ ์์
์ ์บก์ํํ๋ CIImage ๊ฐ์ฒด์ด๋ค. ์ฆ, ๊ฒฐ๊ณผ๋ฌผ
guard let ciImage = filter.outputImage else {
return
}
// โ๏ธ ์ด๋ ๊ฒ ๋๋ด๋ฉด qr code ๊ฐ ์ ๋ช
ํ์ง ์๊ฒ ๋์จ๋ค.
//imageView.image = UIImage(ciImage: ciImage, scale: 2.0, orientation: .up)
// โ
๋ค์์ ์ด๋ฏธ์ง ์ ๋ช
ํ๊ฒ ๋ณํํ๋ ๊ณผ์ ์ด๋ค.
// โ
์๋ ์ด๋ฏธ์ง์ affine transform(by ํ๋ผ๋ฏธํฐ๋ฅผ ์๋ฏธ.) ์ ์ ์ฉํ ์ ์ด๋ฏธ์ง๋ฅผ ๋ฐํ. ์ด๋ฏธ์ง์ ๋์ด์ ๋์ด๋ฅผ 10๋ฐฐ ์ฆ๊ฐ์ํด.
let transformed = ciImage.transformed(by: CGAffineTransform.init(scaleX: 10, y: 10))
// โ
๋ค์์ QR code ์ ์ปค์คํ
์ค์ ํ๋ ๊ณผ์ ์ด๋ค. ํํฐ ์์ฑํ๊ณ ์ด๋ฏธ์ง ์ ์ฉ.
// โ
CIColorInvert : ์์์ ๋ฐ์ ์ํค๊ธฐ ์ํ ํํฐ์ด๋ค.
let invertFilter = CIFilter(name: "CIColorInvert")
invertFilter?.setValue(transformed, forKey: kCIInputImageKey)
// โ
CIMaskToAlpha : grayscale ๋ก ๋ณํ๋ ์ด๋ฏธ์ง๋ฅผ alpha ๋ก ๋ง์คํน๋ ํฐ์์ด๋ฏธ์ง๋ก ๋ณํ.
let alphaFilter = CIFilter(name: "CIMaskToAlpha")
alphaFilter?.setValue(invertFilter?.outputImage, forKey: kCIInputImageKey)
// โ
๋ฐ์ ํ๋ผ๋ฏธํฐ๋ก imageView ์ ์์ฑ์ ์ค์ .
if let ouputImage = alphaFilter?.outputImage {
imageView.tintColor = foregroundColor
imageView.backgroundColor = backgroundColor
// โ
withRenderingMode(.alwaysTemplate) : ์๋ณธ ์ด๋ฏธ์ง์ ์ปฌ๋ฌ์ ๋ณด๊ฐ ์ฌ๋ผ์ง๊ณ ๋ถํฌ๋ช
ํ ๋ถ๋ถ์ tintColor ๋ก ์ค์ .
imageView.image = UIImage(ciImage: ouputImage, scale: 2.0, orientation: .up).withRenderingMode(.alwaysTemplate)
} else {
return
}
}
}
-
โ CIColorInvert : ์์์ ๋ฐ์ ์ํค๊ธฐ ์ํ ํํฐ์ด๋ค.
-
โ CIMaskToAlpha : grayscale ๋ก ๋ณํ๋ ์ด๋ฏธ์ง๋ฅผ alpha ๋ก ๋ง์คํน๋ ํฐ์์ด๋ฏธ์ง๋ก ๋ณํ.
- QRCodeModalViewController.swift
QRCodeView ํด๋์ค๋ฅผ ํตํด์ qr code ๋ฅผ ๋ง๋ค์ด๋ณด์.
class QRCodeModalViewController: UIViewController {
@IBOutlet weak var qrcodeView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let frame = CGRect(origin: .zero, size: qrcodeView.frame.size)
let qrcode = QRCodeView(frame: frame)
// โ
๋ด ๊นํ๋ธ ์ฃผ์ ๋ฌธ์์ด์ data ๋ก ๊ฐ์ง๋ qr code.
qrcode.generateCode("https://github.com/hyun99999", foregroundColor: #colorLiteral(red: 0.2745098174, green: 0.4862745106, blue: 0.1411764771, alpha: 1), backgroundColor: #colorLiteral(red: 0.721568644, green: 0.8862745166, blue: 0.5921568871, alpha: 1))
qrcodeView.addSubview(qrcode)
}
}
๊ฒฐ๊ณผ
์ฐธ๊ณ :
iOS์์ QR ์ฝ๋๋ฅผ ์์ฑํ๋ ๋ฐฉ๋ฒ
Apple Developer - Core Image Filter Reference(CIFilter)
QR์ฝ๋ Reader ๋ง๋ค๊ธฐ
์นด๋ฉ๋ผ ๊ถํ ์ป๊ธฐ
- info.plist ์ ์ถ๊ฐํ๋ค.
AVCaptureSession
๊ฐ๋ฐ์ ๋ฌธ์๋ฅผ ์ดํด๋ณด์
์บก์ฒ ํ๋์ ๊ด๋ฆฌํ๊ณ ์ ๋ ฅ ์ฅ์น์ ๋ฐ์ดํฐ ํ๋ฆ์ ์กฐ์ ํ์ฌ ์ถ๋ ฅ์ ์บก์ฒํ๋ ๊ฐ์ฒด์ ๋๋ค.
overview
์ค์๊ฐ ์บก์ฒ๋ฅผ ์ํํ๋ ค๋ฉด AVCaptureSession ๊ฐ์ฒด๋ฅผ ์ธ์คํด์คํํ๊ณ ์ ์ ํ inputs ๋ฐ outputs ๋ฅผ ์ถ๊ฐํฉ๋๋ค.
startRunning()
์ ํธ์ถํ์ฌ input ์์ output ์ผ๋ก์ ๋ฐ์ดํฐ ํ๋ฆ์ ์์ํ๊ณ stopRunning()
์ ํธ์ถํ์ฌ ํ๋ฆ์ ์ค์งํฉ๋๋ค.
startRunning()
๋ฉ์๋๋ ์๊ฐ์ด ๊ฑธ๋ฆฌ๋ blocking call ์ด๋ฏ๋ก main queue ๊ฐ ์ฐจ๋จ๋์ง ์๋๋ก serial queue ์์ ์ธ์
์ค์ ์ ์ํํด์ผ ํฉ๋๋ค. (UI ๋ฅผ ๋ฐ์์ ์ผ๋ก ์ ์งํ๊ฒ ํด์ค๋ค.)
seesionPreset
์์ฑ์ ์ฌ์ฉํด์ ์ถ๋ ฅ์ ๋ํ ํ์ง ์์ค, ๋นํธ ์ ์ก๋ฅ ๋๋ ๊ธฐํ ์ค์ ์ ์ฌ์ฉ์ ์ง์ ํฉ๋๋ค.(๊ธฐ๋ณธ๊ฐ์ high ์
๋๋ค.
- ์ ๋ฐ์ ์ธ ์์์ metadata ์ ๋ํด์ ๋จผ์ ์์๋ณด์
startRunning()
๊ณผ stopRunning()
๋ก ํ๋ฆ ํต์
metadata : ์ฌ์ง ํ์ผ output ์ ํฌํจํ metadata keys ์ values ์ ๋์ ๋๋ฆฌ.
์ฆ, ์ฌ๊ธฐ์๋ photo ๋ฐ์ดํฐ์ ๋ํ ๋ฐ์ดํฐ๋ฅผ ์๋ฏธํ๋ค.
- QRCodeReaderViewController : QR์ฝ๋ reader ๋ฅผ ์ถ๊ฐํ๊ณ ์ฝ์ ์ ๋ณด๋ฅผ ๋ค๋ฃจ๋ ๋ทฐ์ปจํธ๋กค๋ฌ
import UIKit
import AVFoundation
class QRCodeReaderViewController: UIViewController {
// 1๏ธโฃ ์ค์๊ฐ ์บก์ฒ๋ฅผ ์ํํ๊ธฐ ์ํด์ AVCaptureSession ๊ฐ์ฒด๋ฅด ์ธ์คํด์คํ.
private let captureSession = AVCaptureSession()
override func viewDidLoad() {
super.viewDidLoad()
basicSetting()
}
}
extension QRCodeReaderViewController {
private func basicSetting() {
// โ
AVCaptureDevice : capture sessions ์ ๋ํ ์
๋ ฅ(audio or video)๊ณผ ํ๋์จ์ด๋ณ ์บก์ฒ ๊ธฐ๋ฅ์ ๋ํ ์ ์ด๋ฅผ ์ ๊ณตํ๋ ์ฅ์น.
// โ
์ฆ, ์บก์ฒํ ๋ฐฉ์์ ์ ํ๋ ์ฝ๋.
guard let captureDevice = AVCaptureDevice.default(for: AVMediaType.video) else {
// โ
์๋ฎฌ๋ ์ดํฐ์์๋ ์นด๋ฉ๋ผ๋ฅผ ์ฌ์ฉํ ์ ์๊ธฐ ๋๋ฌธ์ ์๋ฎฌ๋ ์ดํฐ์์ ์คํํ๋ฉด ์๋ฌ๊ฐ ๋ฐ์ํ๋ค.
fatalError("No video device found")
}
do {
// 2๏ธโฃ ์ ์ ํ inputs ์ค์
// โ
AVCaptureDeviceInput : capture device ์์ capture session ์ผ๋ก media ๋ฅผ ์ ๊ณตํ๋ capture input.
// โ
์ฆ, ํน์ device ๋ฅผ ์ฌ์ฉํด์ input ๋ฅผ ์ด๊ธฐํ.
let input = try AVCaptureDeviceInput(device: captureDevice)
// โ
session ์ ์ฃผ์ด์ง input ๋ฅผ ์ถ๊ฐ.
captureSession.addInput(input)
// 3๏ธโฃ ์ ์ ํ outputs ์ค์
// โ
AVCaptureMetadataOutput : capture session ์ ์ํด์ ์์ฑ๋ ์๊ฐ์ ํ metadata ๋ฅผ ์ฒ๋ฆฌํ๊ธฐ ์ํ capture output.
// โ
์ฆ, ์์์ผ๋ก ์ดฌ์ํ๋ฉด์ ์ง์์ ์ผ๋ก ์์ฑ๋๋ metadata ๋ฅผ ์ฒ๋ฆฌํ๋ output ์ด๋ผ๋ ๋ง.
let output = AVCaptureMetadataOutput()
// โ
session ์ ์ฃผ์ด์ง output ๋ฅผ ์ถ๊ฐ.
captureSession.addOutput(output)
// โ
AVCaptureMetadataOutputObjectsDelegate ํฌ๋กํ ์ฝ์ ์ฑํํ๋ delegate ์ dispatch queue ๋ฅผ ์ค์ ํ๋ค.
// โ
queue : delegate ์ ๋ฉ์๋๋ฅผ ์คํํ ํ์ด๋ค. ์ด ํ๋ metadata ๊ฐ ๋ฐ์ ์์๋๋ก ์ ๋ฌ๋๋ ค๋ฉด ๋ฐ๋์ serial queue(์ง๋ ฌํ) ์ฌ์ผ ํ๋ค.
output.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
// โ
๋ฆฌ๋๊ธฐ๊ฐ ์ธ์ํ ์ ์๋ ์ฝ๋ ํ์
์ ์ ํ๋ค. ์ด ํ๋ก์ ํธ์ ๊ฒฝ์ฐ qr.
output.metadataObjectTypes = [AVMetadataObject.ObjectType.qr]
// โ
์นด๋ฉ๋ผ ์์์ด ๋์ค๋ layer ์ + ๋ชจ์ ๊ฐ์ด๋ ๋ผ์ธ์ ๋ทฐ์ ์ถ๊ฐํ๋ ํจ์ ํธ์ถ.
setVideoLayer()
setGuideCrossLineView()
// 4๏ธโฃ startRunning() ๊ณผ stopRunning() ๋ก ํ๋ฆ ํต์
// โ
input ์์ output ์ผ๋ก์ ๋ฐ์ดํฐ ํ๋ฆ์ ์์
captureSession.startRunning()
}
catch {
print("error")
}
}
// โ
์นด๋ฉ๋ผ ์์์ด ๋์ค๋ layer ๋ฅผ ๋ทฐ์ ์ถ๊ฐ
private func setVideoLayer() {
// ์์์ ๋ด์ ๊ณต๊ฐ.
let videoLayer = AVCaptureVideoPreviewLayer(session: captureSession)
// ์นด๋ฉ๋ผ์ ํฌ๊ธฐ ์ง์
videoLayer.frame = view.layer.bounds
// ์นด๋ฉ๋ผ์ ๋น์จ์ง์
videoLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
view.layer.addSublayer(videoLayer)
}
// โ
+ ๋ชจ์ ๊ฐ์ด๋๋ผ์ธ์ ๋ทฐ์ ์ถ๊ฐ
private func setGuideCrossLineView() {
let guideCrossLine = UIImageView()
guideCrossLine.image = UIImage(systemName: "plus")
guideCrossLine.tintColor = .green
guideCrossLine.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(guideCrossLine)
NSLayoutConstraint.activate([
guideCrossLine.centerXAnchor.constraint(equalTo: view.centerXAnchor),
guideCrossLine.centerYAnchor.constraint(equalTo: view.centerYAnchor),
guideCrossLine.widthAnchor.constraint(equalToConstant: 30),
guideCrossLine.heightAnchor.constraint(equalToConstant: 30)
])
}
}
// โ
metadata capture ouput ์์ ์์ฑ๋ metatdata ๋ฅผ ์์ ํ๋ ๋ฉ์๋.
// โ
์ด ํ๋กํ ์ฝ์ ์์์์ฒ๋ผ AVCaptureMetadataOutput object ๊ฐ ์ฑํํด์ผ๋ง ํ๋ค. ๋จ์ผ ๋ฉ์๋๊ฐ ์๊ณ ์ต์
์ด๋ค.
// โ
์ด ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ฉด capture metadata ouput object ๊ฐ connection ์ ํตํด์ ๊ด๋ จ๋ metadata objects ๋ฅผ ์์ ํ ๋ ์๋ตํ ์ ์๋ค.(์๋ ๋ฉ์๋์ ํ๋ผ๋ฏธํฐ๋ฅผ ํตํด ๋ค๋ฃฐ ์ ์๋ค.)
// โ
์ฆ, ์ด ํ๋กํ ์ฝ์ ํตํด์ metadata ๋ฅผ ์์ ํด์ ๋ฐ์ํ ์ ์๋ค.
extension QRCodeReaderViewController: AVCaptureMetadataOutputObjectsDelegate {
// โ
caputure output object ๊ฐ ์๋ก์ด metadata objects ๋ฅผ ๋ณด๋์ ๋ ์๋ฆฐ๋ค.
func metadataOutput(_ captureOutput: AVCaptureMetadataOutput,
didOutput metadataObjects: [AVMetadataObject],
from connection: AVCaptureConnection) {
// โ
metadataObjects : ์๋ก ๋ด๋ณด๋ธ AVMetadataObject ์ธ์คํด์ค ๋ฐฐ์ด์ด๋ค.
if let metadataObject = metadataObjects.first {
// โ
AVMetadataObject ๋ ์ถ์ ํด๋์ค์ด๋ฏ๋ก ์ด ๋ฐฐ์ด์ object ๋ ํญ์ ๊ตฌ์ฒด์ ์ธ ํ์ ํด๋์ค์ ์ธ์คํด์ค์ฌ์ผ ํ๋ค.
// โ
AVMetadataObject ๋ฅผ ์ง์ ์๋ธํด๋์ฑํด์ ์๋๋ค. ๋์ AVFroundation ํ๋ ์์ํฌ์์ ์ ๊ณตํ๋ ์ ์๋ ํ์ ํด๋์ค ์ค ํ๋๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค.
// โ
AVMetadataMachineReadableCodeObject : ๋ฐ์ฝ๋์ ๊ธฐ๋ฅ์ ์ ์ํ๋ AVMetadataObject ์ ๊ตฌ์ฒด์ ์ธ ํ์ ํด๋์ค. ์ธ์คํด์ค๋ ์ด๋ฏธ์ง์์ ๊ฐ์ง๋ ํ๋
๊ฐ๋ฅํ ๋ฐ์ฝ๋์ ๊ธฐ๋ฅ๊ณผ payload ๋ฅผ ์ค๋ช
ํ๋ immutable object ๋ฅผ ๋ํ๋ธ๋ค.
// โ
(์ฐธ๊ณ ๋ก ์ด์ธ์๋ AVMetadataFaceObject ๋ผ๋ ๊ฐ์ง๋ ๋จ์ผ ์ผ๊ตด์ ๊ธฐ๋ฅ์ ์ ์ํ๋ subclass ๋ ์๋ค.)
guard let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject, let stringValue = readableObject.stringValue else {
return
}
// โ
qr์ฝ๋๊ฐ ๊ฐ์ง ๋ฌธ์์ด์ด URL ํํ๋ฅผ ๋๋ค๋ฉด ์ถ๋ ฅ.(์๋ฌด๋ฐ qr์ฝ๋๋ ์ฐ๋๋ค๊ณ ์ถ๋ ฅ์ํค๋ฉด ์๋๋๊น ์ฌ๊ธฐ์ ๋ถ๊ธฐ์ฒ๋ฆฌ ๊ฐ๋ฅ. )
if stringValue.hasPrefix("http://") || stringValue.hasPrefix("https://") {
print(stringValue)
// 4๏ธโฃ startRunning() ๊ณผ stopRunning() ๋ก ํ๋ฆ ํต์
// โ
input ์์ output ์ผ๋ก์ ํ๋ฆ ์ค์ง
self.captureSession.stopRunning()
self.dismiss(animated: true, completion: nil)
}
}
}
}
๊ฒฐ๊ณผ
์๊ฐํด๋ณผ ์
์์ QR์ฝ๋ ํด๋์ค๋ฅผ ๋ง๋ค์ด์ ์ฌ์ฉํ๋ ๋ฐ๋ฉด QR์ฝ๋ reader ๋ ์ ๋ณด๋ฅผ ์ฝ๊ณ ๋ค๋ค์ผ ํ๋ ๊ธฐ๋ฅ์ ์ข ๋ ์ง์คํด๋ณด๊ธฐ ์ํด์ ํธ์์ ๋ทฐ์ปจํธ๋กค๋ฌ์์ ๋ค๋ฃจ์ด ์ฃผ์๋ค.
์๋์ ๋ธ๋ก๊ทธ๋ฅผ ์ฐธ๊ณ ํ๋ฉด reader ๋ฅผ ํด๋์ค๋ก ๋ง๋ค ์ ์๋ค. ํด๋์ค๋ก ๋ง๋ค์์ ๋ ์ฐ๋ฆฌ๊ฐ ๊ณ ๋ คํด์ผํ๋ ์ ์ ๋ทฐ์ปจํธ๋กค๋ฌ์ QR์ฝ๋๋ฅผ ์ฝ์ ๊ฒฐ๊ณผ๋ฅผ ์ ๋ฌํ delegate ๋ง๋ค๊ณ ๋ทฐ์ปจํธ๋กค๋ฌ์์ ๊ทธ delegate ๋ฅผ ์ฒ๋ฆฌํ๋ ๋ถ๋ถ์ ๊ตฌํํด์ผ ํ๋ ๊ฒ์ด๋ค.
QR, Barcode ๋ฆฌ๋๊ธฐ ๋ง๋ค๊ธฐ - ๋๋(swieeft)์ ๊ฐ๋ฐ์๋ฐ๊ธฐ
์ฐธ๊ณ :
[iOS] QR Code Scanner ๋ง๋ค๊ธฐ - AvFoundation ์ด์ฉ
IOS) QRCode ๋ฆฌ๋๊ธฐ ๋ง๋ค๊ธฐ
์ํ๋ ์์ญ์์๋ง QR์ฝ๋ ์ฝ๊ธฐ
์ฐ๋ฆฌ๊ฐ ์ ํ๋ QR์ฝ๋ ๋ฆฌ๋๊ธฐ๋ ํน์ ์์ญ์์์ QR์ฝ๋๊ฐ ์ฝํ๋ค. ๊ทธ ์ด์ ๋ก ๋๋ ๋ง์ QR์ฝ๋๊ฐ ์นด๋ฉ๋ผ์ ์กํ์ง ์๋๋ก ์ฌ์ฉ์๋ฅผ ์ ๋ํจ๊ณผ ๋์์ ์ฌ์ฉ์๊ฐ ๋ณธ์ธ์ QR์ฝ๋๋ฅผ ์ ๋ ฅํ๋ ์ธ์์ ์ฃผ๊ธฐ ์ํจ์ด๋ผ๊ณ ์๊ฐ์ด ๋ ๋ค. ํธ๋ํฐ์ ์ ๋๋ก ๊ฐ์ ธ๋ค ๋์ง๋ ์์๋๋ฐ ์กฐ๊ธ์ด๋ผ๋ ์นด๋ฉ๋ผ์ ๋ ธ์ถ๋ QR์ฝ๋๊ฐ ๋ฐ๋ก ์ฝํ๋ค๋ฉด ์ฌ์ฉ์๋ ๋ถ๋ช ๋นํฉ์ค๋ฌ์ธ ๊ฒ์ด๋ค.
๊ทธ๋์ ์ด๋ฐ ์ด์ ๋ก QR์ฝ๋ ์์ญ์ ์ง์ ํ๋ ๊ฒ์ ํด๋ณด๋ ค ํ๋ค.
AVCaputureMetadataOutput
์rectOfInterest
์์ฑ์ ์ด์ฉํ๋ฉด ๋๋ค. ๋จผ์ ๊ฐ๋ฐ์ ๋ฌธ์๋ฅผ ์ดํด๋ณด์.
rectOfInterest
- ์๊ฐ์ metatdata ์ ๊ฒ์ ์์ญ์ ์ ํํ๊ธฐ ์ํ ์ฌ๊ฐํ์ ๊ฒฐ์ ํ๋
CGRect
๊ฐ์ด๋ค. - ์ฌ๊ฐํ์ origin(์์ ) ์ ์ผ์ชฝ ์๋จ์ด๊ณ metatdat ๋ฅผ ์ ๊ณตํ๋ ์ฅ์น์ ์ขํ๊ณต๊ฐ์ ๊ธฐ์ค์ผ๋ก ํ๋ค.
- rectangle of interest ๋ฅผ ์ง์ ํ๋ฉด ํน์ ์ ํ์ metadata ์ ๋ํ ๊ฐ์ง ๋ฅ๋ ฅ์ด ํฅ์๋ ์ ์๋ค.
rectOfInterest
๋ฅผ ๊ฐ๋ก์ง๋ฅด๋ metadata ojects ์ bounds ๊ฐ ์๋๋ฉด ๋ฆฌํดํ์ง ์๋๋ค.(์ฆ ์ฌ๊ฐํ์์ ์์ง ์์ผ๋ฉด ์ธ์ํ์ง ์๋๋ค๋ ๋ง.) - ๊ธฐ๋ณธ๊ฐ์ (0.0, 0.0, 1.0, 1.0) ์ด๋ค.
๊ธฐ๋ณธ๊ฐ์ด....? ์กฐ๊ธ ์ด์ํ๋ค. width ์ height ์ ๊ธฐ๋ณธ๊ฐ์ด 1.0 ์ด๋ผ.. ์ค์ ๋ก ๊ฐ์ ๋์ค์ ์ถ๋ ฅํด๋ณด๋ฉด ์๊ฒ ์ง๋ง 0~100% ๊น์ง ๋น์จ์ ํ๋ ์์ ์ฑ์ด๋ค๊ณ ํ๋ค. ๊ทธ๋์ ์๋ฌด๋ฐ ์์ฑ์ค์ ์๋ ๊ธฐ๋ณธ๊ฐ์์๋ ์ ์ฒด ํ๋ ์์์ qr์ฝ๋๋ฅผ ์ธ์ํ์๋ค.
๊ทธ๋์ ์ฐ๋ฆฌ๋ metadataOutputRectConverted
๋ฅผ ์ฌ์ฉํด์ ๋น์จ๋ก ๋ณํํด์ฃผ์ด์ผ ํ๋ค.
metadataOutputRectConverted
- preview layer(์ฌ๊ธฐ์๋ AVCaptureVideoPreviewLayer ๊ฐ์ฒด์ ํด๋น) ์ metadata ouputs ์ ์ฌ์ฉ๋๋ ์ขํ๊ณ์ ์ฌ๊ฐํ์ผ๋ก ๋ณํ.
+
๋ชจ์์ด์๋ ๊ฐ์ด๋ ๋ผ์ธ์์ ์ ์ฌ๊ฐํ์ ๊ฐ์ด๋๋ผ์ธ์ ๋ณด์ฌ์ฃผ๊ณ ๊ฑฐ๊ธฐ์๋ง QR์ฝ๋๊ฐ ์ฝํ๋๋ก ํด๋ณด์.
๊ฐ์ด๋๋ผ์ธ์ ๊ทธ๋ฆฌ๋ ์์ ์ ์ต๊ทผ์ ์ฝ์๋ zedd ๋์ UIBezierPath ์ ๊ดํ ๊ธ์ ์ฝ์ด๋ดค๊ณ ํ์ฉํด๋ณด๊ธฐ๋ก ํ๋ค.
iOS ) UIBezierPath (3) - Triangle, Circle
์์ฑ
์ฝ๋
- QRCodeReaderViewController.swift
extension QRCodeReaderViewController {
private func basicSetting() {
guard let captureDevice = AVCaptureDevice.default(for: AVMediaType.video) else {
fatalError("No video device found")
}
do {
// โ
์ ํํ๊ณ ์ถ์ ์์ญ
let rectOfInterest = CGRect(x: (UIScreen.main.bounds.width - 200) / 2 , y: (UIScreen.main.bounds.height - 200) / 2, width: 200, height: 200)
let input = try AVCaptureDeviceInput(device: captureDevice)
captureSession.addInput(input)
let output = AVCaptureMetadataOutput()
captureSession.addOutput(output)
output.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
output.metadataObjectTypes = [AVMetadataObject.ObjectType.qr]
// โ
preview layer ์์์ ์์ญ ๋ณํ๊ฐ์ ๋ฆฌํด๋ฐ์ ์ฌ์ฉํ๊ธฐ ์ํด์ ๊ธฐ์กด ์ฝ๋์์ ์์ ํด์ฃผ์๋ค.
let rectConverted = setVideoLayer(rectOfInterest: rectOfInterest)
// โ
rectOfInterest ๋ฅผ ์ค์ (=์ ํ์์ญ ์ค์ ์๋ฃ)
output.rectOfInterest = rectConverted
// โ
์ ์ฌ๊ฐํ ๊ฐ์ด๋ ๋ผ์ธ ์ถ๊ฐ
setGuideCrossLineView(rectOfInterest: rectOfInterest)
captureSession.startRunning()
}
catch {
print("error")
}
}
// โ
preview layer ์์์ ์์ญ ๋ณํ๊ฐ์ ๋ฆฌํด๋ฐ์ ์ฌ์ฉํ๊ธฐ ์ํด์ ๊ธฐ์กด ์ฝ๋์์ ์์ ํด์ฃผ์๋ค.
private func setVideoLayer(rectOfInterest: CGRect) -> CGRect{
// ์์์ ๋ด์ ๊ณต๊ฐ.
let videoLayer = AVCaptureVideoPreviewLayer(session: captureSession)
//์นด๋ฉ๋ผ์ ํฌ๊ธฐ ์ง์
videoLayer.frame = view.layer.bounds
//์นด๋ฉ๋ผ์ ๋น์จ์ง์
videoLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
view.layer.addSublayer(videoLayer)
return videoLayer.metadataOutputRectConverted(fromLayerRect: rectOfInterest)
}
private func setGuideCrossLineView(rectOfInterest: CGRect) {
//...
}
}
extension QRCodeReaderViewController {
// ...
private func setGuideCrossLineView(rectOfInterest: CGRect) {
// ์๋ต๋ ์ฝ๋๋ + ๋ชจ์ ๊ฐ์ด๋๋ผ์ธ ์ถ๊ฐ ์ฝ๋์ด๋ค.
// ...
let cornerLength: CGFloat = 20
let cornerLineWidth: CGFloat = 5
// โ
๊ฐ์ด๋๋ผ์ธ์ ๊ฐ ๋ชจ์๋ฆฌ point
let upperLeftPoint = CGPoint(x: rectOfInterest.minX, y: rectOfInterest.minY)
let upperRightPoint = CGPoint(x: rectOfInterest.maxX, y: rectOfInterest.minY)
let lowerRightPoint = CGPoint(x: rectOfInterest.maxX, y: rectOfInterest.maxY)
let lowerLeftPoint = CGPoint(x: rectOfInterest.minX, y: rectOfInterest.maxY)
// โ
๊ฐ ๋ชจ์๋ฆฌ๋ฅผ ์ค์ฌ์ผ๋ก ํ Edge๋ฅผ ๊ทธ๋ฆผ.
let upperLeftCorner = UIBezierPath()
upperLeftCorner.lineWidth = cornerLineWidth
upperLeftCorner.move(to: CGPoint(x: upperLeftPoint.x + cornerLength, y: upperLeftPoint.y))
upperLeftCorner.addLine(to: CGPoint(x: upperLeftPoint.x, y: upperLeftPoint.y))
upperLeftCorner.addLine(to: CGPoint(x: upperLeftPoint.x, y: upperLeftPoint.y + cornerLength))
let upperRightCorner = UIBezierPath()
upperRightCorner.lineWidth = cornerLineWidth
upperRightCorner.move(to: CGPoint(x: upperRightPoint.x - cornerLength, y: upperRightPoint.y))
upperRightCorner.addLine(to: CGPoint(x: upperRightPoint.x, y: upperRightPoint.y))
upperRightCorner.addLine(to: CGPoint(x: upperRightPoint.x, y: upperRightPoint.y + cornerLength))
let lowerRightCorner = UIBezierPath()
lowerRightCorner.lineWidth = cornerLineWidth
lowerRightCorner.move(to: CGPoint(x: lowerRightPoint.x, y: lowerRightPoint.y - cornerLength))
lowerRightCorner.addLine(to: CGPoint(x: lowerRightPoint.x, y: lowerRightPoint.y))
lowerRightCorner.addLine(to: CGPoint(x: lowerRightPoint.x - cornerLength, y: lowerRightPoint.y))
let lowerLeftCorner = UIBezierPath()
lowerLeftCorner.lineWidth = cornerLineWidth
lowerLeftCorner.move(to: CGPoint(x: lowerLeftPoint.x + cornerLength, y: lowerLeftPoint.y))
lowerLeftCorner.addLine(to: CGPoint(x: lowerLeftPoint.x, y: lowerLeftPoint.y))
lowerLeftCorner.addLine(to: CGPoint(x: lowerLeftPoint.x, y: lowerLeftPoint.y - cornerLength))
upperLeftCorner.stroke()
upperRightCorner.stroke()
lowerRightCorner.stroke()
lowerLeftCorner.stroke()
// โ
layer ์ ์ถ๊ฐ
let upperLeftCornerLayer = CAShapeLayer()
upperLeftCornerLayer.path = upperLeftCorner.cgPath
upperLeftCornerLayer.strokeColor = UIColor.black.withAlphaComponent(0.5).cgColor
upperLeftCornerLayer.fillColor = UIColor.clear.cgColor
upperLeftCornerLayer.lineWidth = cornerLineWidth
let upperRightCornerLayer = CAShapeLayer()
upperRightCornerLayer.path = upperRightCorner.cgPath
upperRightCornerLayer.strokeColor = UIColor.black.withAlphaComponent(0.5).cgColor
upperRightCornerLayer.fillColor = UIColor.clear.cgColor
upperRightCornerLayer.lineWidth = cornerLineWidth
let lowerRightCornerLayer = CAShapeLayer()
lowerRightCornerLayer.path = lowerRightCorner.cgPath
lowerRightCornerLayer.strokeColor = UIColor.black.withAlphaComponent(0.5).cgColor
lowerRightCornerLayer.fillColor = UIColor.clear.cgColor
lowerRightCornerLayer.lineWidth = cornerLineWidth
let lowerLeftCornerLayer = CAShapeLayer()
lowerLeftCornerLayer.path = lowerLeftCorner.cgPath
lowerLeftCornerLayer.strokeColor = UIColor.black.withAlphaComponent(0.5).cgColor
lowerLeftCornerLayer.fillColor = UIColor.clear.cgColor
lowerLeftCornerLayer.lineWidth = cornerLineWidth
view.layer.addSublayer(upperLeftCornerLayer)
view.layer.addSublayer(upperRightCornerLayer)
view.layer.addSublayer(lowerRightCornerLayer)
view.layer.addSublayer(lowerLeftCornerLayer)
}
}
**์ฐธ๊ณ : ** [iOS] Swift QRCode ์ฝ๊ธฐ