https://github.com/hyun99999/qrcodereadertutorial-ios
๐ make QRcode and QRcode Reader Tutorial
https://github.com/hyun99999/qrcodereadertutorial-ios
ios qrcode-generator qrcode-reader
Last synced: about 2 months ago
JSON representation
๐ make QRcode and QRcode Reader Tutorial
- Host: GitHub
- URL: https://github.com/hyun99999/qrcodereadertutorial-ios
- Owner: hyun99999
- Created: 2021-08-11T02:24:26.000Z (almost 4 years ago)
- Default Branch: main
- Last Pushed: 2021-08-12T16:57:26.000Z (almost 4 years ago)
- Last Synced: 2025-03-25T11:49:19.764Z (2 months ago)
- Topics: ios, qrcode-generator, qrcode-reader
- Language: Swift
- Homepage:
- Size: 1.53 MB
- Stars: 5
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# QRCodeReaderTutorial-iOS
๐ make QRcode and QRcode Reader Tutorial
QR์ฝ๋์ ๋ฆฌ๋๊ธฐ๋ฅผ ๋ง๋๋ ์คํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์์ง๋ง ์์ฒด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํ์ฉํด์ ๋ง๋ค์ด๋ณด๊ธฐ๋ก ํ๋ค.# ๋ชฉ์ฐจ
- [QR์ฝ๋ ๋ง๋ค๊ธฐ](#qr์ฝ๋-๋ง๋ค๊ธฐ)
- [QR์ฝ๋ Reader ๋ง๋ค๊ธฐ](#qr์ฝ๋-reader-๋ง๋ค๊ธฐ)
- [์ํ๋ ์์ญ์์๋ง QR์ฝ๋ ์ฝ๊ธฐ](#์ํ๋-์์ญ์์๋ง-qr์ฝ๋-์ฝ๊ธฐ)### ์์ฑ
### Main.storyboard
ํ๋ฉด์ ๋ค์๊ณผ ๊ฐ์ด ๊ตฌ์ฑํ๋ค. ์ผ์ชฝ ์คํ ๋ฆฌ๋ณด๋๋ถํฐ 123 ์์.
- ViewController : (1) ๋ฉ์ธ ๋ทฐ์ปจํธ๋กค๋ฌ
- QRCodeModalViewController : (2) qr ์ฝ๋ ๋ฅผ ๋ง๋ค์ด์ ๋ชจ๋ฌ์ฐฝ์ผ๋ก ๋์ฐ๊ธฐ
- QRCodeReaderViewController : (3) qr ์ฝ๋ ๋ฆฌ๋๊ธฐ๋ฅผ ๋ง๋ค์ด์ ํ๋ฉด์ ํ
---
# QR์ฝ๋ ๋ง๋ค๊ธฐ
CIQRCodeGenerator CIFilter ๋ฅผ ํตํด์ qr code ๋ฅผ ๋ง๋ค ๊ฒ์ด๋ค.
๊ฐ๋ฐ์ ๋ฌธ์๋ฅผ ์ดํด๋ณด์
[Apple Developer - Core Image Filter Reference(CIQRCodeGenerator)](https://developer.apple.com/library/archive/documentation/GraphicsImaging/Reference/CoreImageFilterReference/index.html#//apple_ref/doc/filter/ci/CIQRCodeGenerator)
์ด ํํฐ๋ ๋๊ฐ์ง ํ๋ผ๋ฏธํฐ๋ฅผ ๊ฐ์ง๋ค.
- inputMessage : QR์ฝ๋๋ก ์ธ์ฝ๋ฉํ ๋ฐ์ดํฐ์ ๋๋ค. NSData ๊ฐ์ฒด์ ๋๋ค.
- inputCorrectionLevel : ์ค๋ฅ ์์ ํ์์ ์ง์ ํ๋ ๋จ์ผ ๋ฌธ์์ ๋๋ค. NSString ๊ฐ์ฒด์ ๋๋ค. ๊ธฐ๋ณธ๊ฐ์ M.String ๋๋ URL์์ QR ์ฝ๋๋ฅผ ์์ฑํ๋ ค๋ฉด NSISOLatin1StringEncoding ๋ฌธ์์ด ์ธ์ฝ๋ฉ์ ์ฌ์ฉํ์ฌ ์ด๋ฅผ NSData ๊ฐ์ฒด๋ก ๋ณํํฉ๋๋ค.
inputCorrectionLevel ๋งค๊ฐ๋ณ์๋ ์ค๋ฅ์์ ์ ์ํด์ ์ถ๋ ฅ์ด๋ฏธ์ง์ ์ธ์ฝ๋ฉ๋ ์ถ๊ฐ ๋ฐ์ดํฐ์์ ์ ์ดํฉ๋๋ค.
(QR์ฝ๋๋ ์ฝ๋์ ์ค์ผ์ด๋ ์์์๋ ์ฝ๋ ์์ฒด์ ๋ฐ์ดํฐ๋ฅผ ๋ณต์ํ๋ ๊ธฐ๋ฅ์ด ์์ต๋๋ค. ๋ ๋ฒจ์ ์ฌ๋ฆฌ๋ฉด ์ค๋ฅ๋ณต์ ๋ฅ๋ ฅ์ ํฅ์๋์ง๋ง ๋ฐ์ดํฐ๊ฐ ์ฆ๊ฐ๋์ด ์ฝ๋์ ํฌ๊ธฐ๊ฐ ์ปค์ง๋๋ค.)
- `L`: 7%
- `M`: 15%(๊ธฐ๋ณธ๊ฐ)
- `Q`: 25%
- `H`: 30%qr code ์ ์ค๋ฅ ๋ณต์ ๊ธฐ๋ฅ์ ๋ํด์๋ ์๋ ์ฌ์ดํธ๋ฅผ ์ฐธ๊ณ ํด๋ณด์
[์ค๋ฅ ๋ณต์ ๊ธฐ๋ฅ์ ๋ํด](https://www.qrcode.com/ko/about/error_correction.html)
- QRCodeView.swift : QR code ๋ฅผ ๋ง๋๋ ํด๋์ค
```swift
import Foundation
import UIKitclass 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 ๋ฅผ ๋ง๋ค์ด๋ณด์.
```swift
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)
}
}
```### ๊ฒฐ๊ณผ
---
์ฐธ๊ณ :
[Swift QRcode make](https://kodaewon.github.io/ios/2018/10/15/ios-qrcode/)
[iOS์์ QR ์ฝ๋๋ฅผ ์์ฑํ๋ ๋ฐฉ๋ฒ](https://ichi.pro/ko/ioseseo-qr-kodeuleul-saengseonghaneun-bangbeob-266413765452800)
[Apple Developer - Core Image Filter Reference(CIFilter)](https://developer.apple.com/library/archive/documentation/GraphicsImaging/Reference/CoreImageFilterReference/index.html#//apple_ref/doc/filter/ci)
# QR์ฝ๋ Reader ๋ง๋ค๊ธฐ
### ์นด๋ฉ๋ผ ๊ถํ ์ป๊ธฐ
- info.plist ์ ์ถ๊ฐํ๋ค.
### [AVCaptureSession](https://developer.apple.com/documentation/avfoundation/avcapturesession/)
๊ฐ๋ฐ์ ๋ฌธ์๋ฅผ ์ดํด๋ณด์
์บก์ฒ ํ๋์ ๊ด๋ฆฌํ๊ณ ์ ๋ ฅ ์ฅ์น์ ๋ฐ์ดํฐ ํ๋ฆ์ ์กฐ์ ํ์ฌ ์ถ๋ ฅ์ ์บก์ฒํ๋ ๊ฐ์ฒด์ ๋๋ค.
**overview**
์ค์๊ฐ ์บก์ฒ๋ฅผ ์ํํ๋ ค๋ฉด AVCaptureSession ๊ฐ์ฒด๋ฅผ ์ธ์คํด์คํํ๊ณ ์ ์ ํ inputs ๋ฐ outputs ๋ฅผ ์ถ๊ฐํฉ๋๋ค.
`startRunning()` ์ ํธ์ถํ์ฌ input ์์ output ์ผ๋ก์ ๋ฐ์ดํฐ ํ๋ฆ์ ์์ํ๊ณ `stopRunning()`์ ํธ์ถํ์ฌ ํ๋ฆ์ ์ค์งํฉ๋๋ค.
`startRunning()` ๋ฉ์๋๋ ์๊ฐ์ด ๊ฑธ๋ฆฌ๋ blocking call ์ด๋ฏ๋ก main queue ๊ฐ ์ฐจ๋จ๋์ง ์๋๋ก serial queue ์์ ์ธ์ ์ค์ ์ ์ํํด์ผ ํฉ๋๋ค. (UI ๋ฅผ ๋ฐ์์ ์ผ๋ก ์ ์งํ๊ฒ ํด์ค๋ค.)
[`seesionPreset`](https://developer.apple.com/documentation/avfoundation/avcapturesession/1389696-sessionpreset) ์์ฑ์ ์ฌ์ฉํด์ ์ถ๋ ฅ์ ๋ํ ํ์ง ์์ค, ๋นํธ ์ ์ก๋ฅ ๋๋ ๊ธฐํ ์ค์ ์ ์ฌ์ฉ์ ์ง์ ํฉ๋๋ค.(๊ธฐ๋ณธ๊ฐ์ high ์ ๋๋ค.
- ์ ๋ฐ์ ์ธ ์์์ metadata ์ ๋ํด์ ๋จผ์ ์์๋ณด์
1๏ธโฃ AVCaptureSession ๊ฐ์ฒด๋ฅผ ์ธ์คํด์คํ
2๏ธโฃ ์ ์ ํ inputs ์ค์
3๏ธโฃ ์ ์ ํ ouputs ์ค์
4๏ธโฃ `startRunning()` ๊ณผ `stopRunning()` ๋ก ํ๋ฆ ํต์
[metadata](https://developer.apple.com/documentation/avfoundation/avcapturephotosettings/2875951-metadata) : ์ฌ์ง ํ์ผ output ์ ํฌํจํ metadata keys ์ values ์ ๋์ ๋๋ฆฌ.
์ฆ, ์ฌ๊ธฐ์๋ photo ๋ฐ์ดํฐ์ ๋ํ ๋ฐ์ดํฐ๋ฅผ ์๋ฏธํ๋ค.
- QRCodeReaderViewController : QR์ฝ๋ reader ๋ฅผ ์ถ๊ฐํ๊ณ ์ฝ์ ์ ๋ณด๋ฅผ ๋ค๋ฃจ๋ ๋ทฐ์ปจํธ๋กค๋ฌ
```swift
import UIKit
import AVFoundationclass 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)์ ๊ฐ๋ฐ์๋ฐ๊ธฐ](https://swieeft.github.io/2020/02/25/QRCodeAndBarcodeReader.html)
์ฐธ๊ณ :
[[iOS] QR Code Scanner ๋ง๋ค๊ธฐ - AvFoundation ์ด์ฉ](https://dongminyoon.tistory.com/19)
[IOS) QRCode ๋ฆฌ๋๊ธฐ ๋ง๋ค๊ธฐ](https://hururuek-chapchap.tistory.com/34)
---
# ์ํ๋ ์์ญ์์๋ง QR์ฝ๋ ์ฝ๊ธฐ์ฐ๋ฆฌ๊ฐ ์ ํ๋ QR์ฝ๋ ๋ฆฌ๋๊ธฐ๋ ํน์ ์์ญ์์์ QR์ฝ๋๊ฐ ์ฝํ๋ค. ๊ทธ ์ด์ ๋ก ๋๋ ๋ง์ QR์ฝ๋๊ฐ ์นด๋ฉ๋ผ์ ์กํ์ง ์๋๋ก ์ฌ์ฉ์๋ฅผ ์ ๋ํจ๊ณผ ๋์์ ์ฌ์ฉ์๊ฐ ๋ณธ์ธ์ QR์ฝ๋๋ฅผ ์ ๋ ฅํ๋ ์ธ์์ ์ฃผ๊ธฐ ์ํจ์ด๋ผ๊ณ ์๊ฐ์ด ๋ ๋ค. ํธ๋ํฐ์ ์ ๋๋ก ๊ฐ์ ธ๋ค ๋์ง๋ ์์๋๋ฐ ์กฐ๊ธ์ด๋ผ๋ ์นด๋ฉ๋ผ์ ๋ ธ์ถ๋ QR์ฝ๋๊ฐ ๋ฐ๋ก ์ฝํ๋ค๋ฉด ์ฌ์ฉ์๋ ๋ถ๋ช ๋นํฉ์ค๋ฌ์ธ ๊ฒ์ด๋ค.
๊ทธ๋์ ์ด๋ฐ ์ด์ ๋ก QR์ฝ๋ ์์ญ์ ์ง์ ํ๋ ๊ฒ์ ํด๋ณด๋ ค ํ๋ค.
- `AVCaputureMetadataOutput` ์ `rectOfInterest` ์์ฑ์ ์ด์ฉํ๋ฉด ๋๋ค. ๋จผ์ ๊ฐ๋ฐ์ ๋ฌธ์๋ฅผ ์ดํด๋ณด์.
### [rectOfInterest](https://developer.apple.com/documentation/avfoundation/avcapturemetadataoutput/1616291-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](https://developer.apple.com/documentation/avfoundation/avcapturevideopreviewlayer/1623495-metadataoutputrectconverted)
- preview layer(์ฌ๊ธฐ์๋ AVCaptureVideoPreviewLayer ๊ฐ์ฒด์ ํด๋น) ์ metadata ouputs ์ ์ฌ์ฉ๋๋ ์ขํ๊ณ์ ์ฌ๊ฐํ์ผ๋ก ๋ณํ.
`+` ๋ชจ์์ด์๋ ๊ฐ์ด๋ ๋ผ์ธ์์ **์ ์ฌ๊ฐํ์ ๊ฐ์ด๋๋ผ์ธ์ ๋ณด์ฌ์ฃผ๊ณ ๊ฑฐ๊ธฐ์๋ง QR์ฝ๋๊ฐ ์ฝํ๋๋ก ํด๋ณด์.**
๊ฐ์ด๋๋ผ์ธ์ ๊ทธ๋ฆฌ๋ ์์ ์ ์ต๊ทผ์ ์ฝ์๋ zedd ๋์ UIBezierPath ์ ๊ดํ ๊ธ์ ์ฝ์ด๋ดค๊ณ ํ์ฉํด๋ณด๊ธฐ๋ก ํ๋ค.
[iOS ) UIBezierPath (3) - Triangle, Circle](https://zeddios.tistory.com/822?category=682195)
### ์์ฑ
### ์ฝ๋
- QRCodeReaderViewController.swift
โ **์์ญ ์ ํ**
```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) {
//...
}
}
```โ **๊ฐ์ด๋๋ผ์ธ ์ถ๊ฐ**
```swift
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 ์ฝ๊ธฐ](https://nebori.tistory.com/28)