An open API service indexing awesome lists of open source software.

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

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 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 : ์ƒ‰์ƒ์„ ๋ฐ˜์ „์‹œํ‚ค๊ธฐ ์œ„ํ•œ ํ•„ํ„ฐ์ด๋‹ค.

2-1

- โœ… CIMaskToAlpha : grayscale ๋กœ ๋ณ€ํ™˜๋œ ์ด๋ฏธ์ง€๋ฅผ alpha ๋กœ ๋งˆ์Šคํ‚น๋œ ํฐ์ƒ‰์ด๋ฏธ์ง€๋กœ ๋ณ€ํ™˜.

2

- 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 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)์˜ ๊ฐœ๋ฐœ์ƒˆ๋ฐœ๊ธฐ](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)