Notice
Recent Posts
Recent Comments
Link
๊ด€๋ฆฌ ๋ฉ”๋‰ด

lgvv98

[SwiftUI] QRcodeReader ๋ณธ๋ฌธ

apple/๐Ÿš SwiftUI & Combine

[SwiftUI] QRcodeReader

๐Ÿฅ• ์บ๋Ÿฟ๋งจ 2022. 5. 23. 12:00

QRcodeReader

 

โœ… QRcodeReader๋ฅผ ์•Œ์•„๋ณด์ž.

์ฝ”๋“œ๋Š” ์–ด๋ ต์ง€ ์•Š๋‹ค. 

๋ˆˆ์—ฌ๊ฒจ ๋ณผ ์ ์€ ํ•˜๋‚˜์˜ ํŒŒ์ผ์—์„œ ์—ฌ๋Ÿฌ๊ฐœ์˜ ๋ทฐ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๊ฒ ๋‹ค

 

 

๐ŸŸ  ์•„๋ž˜๋Š” ์˜คํ”ˆ์†Œ์Šค !_!

https://github.com/twostraws/CodeScanner

 

GitHub - twostraws/CodeScanner: A SwiftUI view that is able to scan barcodes, QR codes, and more, and send back what was found.

A SwiftUI view that is able to scan barcodes, QR codes, and more, and send back what was found. - GitHub - twostraws/CodeScanner: A SwiftUI view that is able to scan barcodes, QR codes, and more, a...

github.com

 

 

โœ… QRCodeScannerExampleView

import SwiftUI

struct QRCodeScannerExampleView: View {
    
    @State var isPresentingScanner = false // sheet ๋„์›Œ์ฃผ๊ธฐ ์œ„ํ•œ ๋ณ€์ˆ˜
    @State var scannedCode: String?

    var body: some View {
        
        ZStack{

            if self.scannedCode != nil {
                MyWebview(urlToLoad: self.scannedCode!)
            } else {
                MyWebview(urlToLoad: "https://www.naver.com")
            }
            
            VStack{
                
                Spacer()
                
                Button(action: {
                    self.isPresentingScanner = true
                }){
                    Text("๋กœ๋˜๋ฒˆํ˜ธํ™•์ธ")
                        .font(.system(size: 20))
                        .fontWeight(.bold)
                        .padding()
                        .background(Color.yellow)
                        .cornerRadius(12)
                        .overlay(
                            RoundedRectangle(cornerRadius: 12)
                                .stroke(lineWidth: 5)
                    )
                }
                .sheet(isPresented: $isPresentingScanner) {
                    self.scannerSheet
                }
                
                Spacer().frame(height: 30) // spacer์—๋„ frame์ง€์ • ๊ฐ€๋Šฅ
            }
        }
            
    }

	// ํ•˜๋‚˜์˜ ํŒŒ์ผ์—์„œ view๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ์˜ ๊ตฌ์กฐ
    var scannerSheet: some View { 
        ZStack {
            CodeScannerView(
                codeTypes: [.qr],
                completion: { result in
                    if case let .success(code) = result {
                        self.scannedCode = code
                        self.isPresentingScanner = false
                    }
                }
            )
            QRCodeGuideLineView() // ZStack์œผ๋กœ ๊ฐ€์ด๋“œ๋ผ์ธ ๋ทฐ๋ฅผ ๋งŒ๋“ค๊ธฐ
        }
        
    }
}

 

โœ… QRCodeGuideLineView

import SwiftUI

struct QRCodeGuideLineView: View {
    var body: some View {
        GeometryReader{ geometryProxy in
            RoundedRectangle(cornerRadius: 20)
            	// stroke๋ฅผ ํ†ตํ•ด ๋‚ด๋ถ€๋ฅผ ๋น„๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค, dash: ์ ์„ ์œผ๋กœ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
            	.stroke(style: StrokeStyle(lineWidth: 10, dash: [11])) 
                .frame(
                	width: geometryProxy.size.width / 2,
                    height: geometryProxy.size.height / 3
                )
                
                // geometry ํ”„๋ก์‹œ๋กœ ํฌ์ง€์…˜์„ ์ฃผ๋ฉด ๋œ๋‹ค.
                .position(
                	x: geometryProxy.size.width / 2,
                    y: geometryProxy.size.height / 2
                )
                .foregroundColor(Color.yellow)
        }
    }
}

์ขŒ์ธก๋ถ€ํ„ฐ 1,2,3์ˆœ์„œ

1. ์Šค์บ๋„ˆ๋ฅผ ์ฐ๊ธฐ์ „์— ๊ธฐ๋ณธ url์„ ๋„ค์ด๋ฒ„๋กœ ์„ธํŒ…

2. ์Šค์บ๋„ˆ ๊ฐ€์ด๋“œ๋ผ์ธ๊ณผ ์นด๋ฉ”๋ผ๋กœ ์ฐ๋Š”๋‹ค.

3. ์ธ์‹๋˜๋ฉด ํ•ด๋‹น url๋กœ ์ด๋™

 

 ๐ŸŸ  ์ฃผ์˜์‚ฌํ•ญ

 ์นด๋ฉ”๋ผ๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด info.plist์— ํ•ด๋‹น ๊ถŒํ•œ์„ ๋„ฃ์–ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.

 

 

โœ… ์˜คํ”ˆ์†Œ์Šค์—์„œ ์ œ๊ณตํ•˜๋Š” ์ฝ”๋“œ!

//
//  CodeScannerView.swift
//
//  Created by Paul Hudson on 10/12/2019.
//  Copyright © 2019 Paul Hudson. All rights reserved.
//
import AVFoundation
import SwiftUI

/// A SwiftUI view that is able to scan barcodes, QR codes, and more, and send back what was found.
/// To use, set `codeTypes` to be an array of things to scan for, e.g. `[.qr]`, and set `completion` to
/// a closure that will be called when scanning has finished. This will be sent the string that was detected or a `ScanError`.
/// For testing inside the simulator, set the `simulatedData` property to some test data you want to send back.
public struct CodeScannerView: UIViewControllerRepresentable {
    public enum ScanError: Error {
        case badInput, badOutput
    }

    public class ScannerCoordinator: NSObject, AVCaptureMetadataOutputObjectsDelegate {
        var parent: CodeScannerView
        var codeFound = false

        init(parent: CodeScannerView) {
            self.parent = parent
        }

        public func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
            if let metadataObject = metadataObjects.first {
                guard let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject else { return }
                guard let stringValue = readableObject.stringValue else { return }
                guard codeFound == false else { return }

                AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))
                found(code: stringValue)

                // make sure we only trigger scans once per use
                codeFound = true
            }
        }

        func found(code: String) {
            parent.completion(.success(code))
        }

        func didFail(reason: ScanError) {
            parent.completion(.failure(reason))
        }
    }

    #if targetEnvironment(simulator)
    public class ScannerViewController: UIViewController,UIImagePickerControllerDelegate,UINavigationControllerDelegate{
        var delegate: ScannerCoordinator?
        override public func loadView() {
            view = UIView()
            view.isUserInteractionEnabled = true
            let label = UILabel()
            label.translatesAutoresizingMaskIntoConstraints = false
            label.numberOfLines = 0

            label.text = "์—๋ฎฌ๋ ˆ์ดํ„ฐ๋กœ ๋Œ๋ฆฌ์…จ๊ตฐ์š” - ์‹ค์ œ ๊ธฐ๊ธฐ๋กœ ํ…Œ์ŠคํŠธ ๋ถ€ํƒ๋ฐ”๋ž๋‹ˆ๋‹ค."
            label.textAlignment = .center
//            let button = UIButton()
//            button.translatesAutoresizingMaskIntoConstraints = false
//            button.setTitle("Or tap here to select a custom image", for: .normal)
//            button.setTitleColor(UIColor.systemBlue, for: .normal)
//            button.setTitleColor(UIColor.gray, for: .highlighted)
//            button.addTarget(self, action: #selector(self.openGallery), for: .touchUpInside)
            let stackView = UIStackView()
            stackView.translatesAutoresizingMaskIntoConstraints = false
            stackView.axis = .vertical
            stackView.spacing = 50
            stackView.addArrangedSubview(label)
//            stackView.addArrangedSubview(button)
            view.addSubview(stackView)

            NSLayoutConstraint.activate([
//                button.heightAnchor.constraint(equalToConstant: 50),
                stackView.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
                stackView.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor),
                stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
            ])
        }

        override public func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
            guard let simulatedData = delegate?.parent.simulatedData else {
                print("Simulated Data Not Provided!")
                return
            }

            delegate?.found(code: simulatedData)
        }

        @objc func openGallery(_ sender: UIButton){
            let imagePicker = UIImagePickerController()
            imagePicker.delegate = self
            self.present(imagePicker, animated: true, completion: nil)
        }

        public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]){
            if let qrcodeImg = info[.originalImage] as? UIImage {
                let detector:CIDetector=CIDetector(ofType: CIDetectorTypeQRCode, context: nil, options: [CIDetectorAccuracy:CIDetectorAccuracyHigh])!
                let ciImage:CIImage=CIImage(image:qrcodeImg)!
                var qrCodeLink=""

                let features=detector.features(in: ciImage)
                for feature in features as! [CIQRCodeFeature] {
                    qrCodeLink += feature.messageString!
                }

                if qrCodeLink=="" {
                    delegate?.didFail(reason: .badOutput)
                }else{
                    delegate?.found(code: qrCodeLink)
                }
            }
            else{
                print("Something went wrong")
            }
            self.dismiss(animated: true, completion: nil)
        }
    }
    #else
    public class ScannerViewController: UIViewController {
        var captureSession: AVCaptureSession!
        var previewLayer: AVCaptureVideoPreviewLayer!
        var delegate: ScannerCoordinator?

        override public func viewDidLoad() {
            super.viewDidLoad()


            NotificationCenter.default.addObserver(self,
                                                   selector: #selector(updateOrientation),
                                                   name: Notification.Name("UIDeviceOrientationDidChangeNotification"),
                                                   object: nil)

            view.backgroundColor = UIColor.black
            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 {
                delegate?.didFail(reason: .badInput)
                return
            }

            let metadataOutput = AVCaptureMetadataOutput()

            if (captureSession.canAddOutput(metadataOutput)) {
                captureSession.addOutput(metadataOutput)

                metadataOutput.setMetadataObjectsDelegate(delegate, queue: DispatchQueue.main)
                metadataOutput.metadataObjectTypes = delegate?.parent.codeTypes
            } else {
                delegate?.didFail(reason: .badOutput)
                return
            }
        }

        override public func viewWillLayoutSubviews() {
            previewLayer?.frame = view.layer.bounds
        }

        @objc func updateOrientation() {
            guard let orientation = UIApplication.shared.windows.first?.windowScene?.interfaceOrientation else { return }
            guard let connection = captureSession.connections.last, connection.isVideoOrientationSupported else { return }
            connection.videoOrientation = AVCaptureVideoOrientation(rawValue: orientation.rawValue) ?? .portrait
        }

        override public func viewDidAppear(_ animated: Bool) {
            super.viewDidAppear(animated)
            previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
            previewLayer.frame = view.layer.bounds
            previewLayer.videoGravity = .resizeAspectFill
            view.layer.addSublayer(previewLayer)
            updateOrientation()
            captureSession.startRunning()
        }

        override public func viewWillAppear(_ animated: Bool) {
            super.viewWillAppear(animated)

            if (captureSession?.isRunning == false) {
                captureSession.startRunning()
            }
        }

        override public func viewWillDisappear(_ animated: Bool) {
            super.viewWillDisappear(animated)

            if (captureSession?.isRunning == true) {
                captureSession.stopRunning()
            }

            NotificationCenter.default.removeObserver(self)
        }

        override public var prefersStatusBarHidden: Bool {
            return true
        }

        override public var supportedInterfaceOrientations: UIInterfaceOrientationMask {
            return .all
        }
    }
    #endif

    public let codeTypes: [AVMetadataObject.ObjectType]
    public var simulatedData = ""
    public var completion: (Result<String, ScanError>) -> Void

    public init(codeTypes: [AVMetadataObject.ObjectType], simulatedData: String = "", completion: @escaping (Result<String, ScanError>) -> Void) {
        self.codeTypes = codeTypes
        self.simulatedData = simulatedData
        self.completion = completion
    }

    public func makeCoordinator() -> ScannerCoordinator {
        return ScannerCoordinator(parent: self)
    }

    public func makeUIViewController(context: Context) -> ScannerViewController {
        let viewController = ScannerViewController()
        viewController.delegate = context.coordinator
        return viewController
    }

    public func updateUIViewController(_ uiViewController: ScannerViewController, context: Context) {

    }
}

struct CodeScannerView_Previews: PreviewProvider {
    static var previews: some View {
        CodeScannerView(codeTypes: [.qr]) { result in
            // do nothing
        }
    }
}

'apple > ๐Ÿš SwiftUI & Combine' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

[SwiftUI] TextField, SecureField  (0) 2022.05.23
[SwiftUI] ButtonStyle  (0) 2022.05.23
[SwiftUI 3.0] State/ Binding / EnvironmentObject  (0) 2022.05.19
[SwiftUI] TabView + CustomTabView  (0) 2022.05.19
[SwiftUI] GeometryReader  (0) 2022.05.19
Comments