メルカリQRScannerをSwiftUI × Storyboardで使う

メルカリQRScannerをSwiftUI × Storyboardで使う

実行環境

Swift5.6.1
Xcode14.0
macOS12.6

詳細


QRScannerは、OSSになったメルペイのQRコードスキャン機能です。
下の動画のようにApple標準のようなシンプルなQRスキャンを実装できます。
メルカリのQRScannerのGitHubはこちらから。


また、本記事ではSwiftUI上でStoryboardを扱います。
詳しく知りたい方は、SwiftUIからStoryboardを表示させる方法をご覧ください。

実装

デモ動画

全体のソースコードはこちらのGitHubにあげています。

ライブラリのインストール

Swift Package Managerで、QRScannerをインストールします。
インストール時は、公式の下のリンクをご活用ください。
https://github.com/mercari/QRScanner

ファイルの作成

下のように、必要なファイルを作成します。
内部のコーディングは後述します。

コーディング

コードは、
基本的にメルカリGitHubでの公式リファレンスを参考にしています。

● ContentView

import SwiftUI

struct ContentView: View {
    var body: some View {
        NavigationView {
            NavigationLink(destination: QRView()){
                Text("QRコード起動")
            }
            .navigationBarTitleDisplayMode(.inline)
            .navigationTitle("メイン画面")
        }
    }
}


● QRView

import SwiftUI

struct QRView: View {
    var body: some View {
        QRViewControllerWrapper {
            Text("Hello, World!")
        }.navigationTitle("QRスキャン画面")
    }
}

struct QRViewControllerWrapper<Content: View>: UIViewControllerRepresentable {

    // 表示するView Controllerのタイプ
    typealias UIViewControllerType = QRViewController
    var content: () -> Content

    // ViewControllerのオブジェクトを作成し、初期状態を構成
    func makeUIViewController(context: Context) -> QRViewController {
        let viewControllerWithStoryboard = QRViewController()
        return viewControllerWithStoryboard
    }

    // ViewControllerの状態をSwiftUIからの新しい情報で更新
    func updateUIViewController(_ uiviewController: QRViewController, context: Context) {
    }

}


● QRViewController

import UIKit
import QRScanner
import AVFoundation

class QRViewController: UIViewController {


    @IBOutlet weak var qrScannerView: QRScannerView!
    @IBOutlet weak var flashButton: FlashButton!

    override func viewDidLoad() {
        super.viewDidLoad()
        setupQRScanner()
    }

    private func setupQRScanner() {
        switch AVCaptureDevice.authorizationStatus(for: .video) {
        case .authorized:
            setupQRScannerView()
        case .notDetermined:
            AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in
                if granted {
                    DispatchQueue.main.async { [weak self] in
                        self?.setupQRScannerView()
                    }
                }
            }
        default:
            showAlert()
        }
    }

    private func setupQRScannerView() {
        qrScannerView.configure(delegate: self, input: .init(isBlurEffectEnabled: true))
        qrScannerView.startRunning()
    }

    private func showAlert() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
            let alert = UIAlertController(title: "Error", message: "Camera is required to use in this application", preferredStyle: .alert)
            alert.addAction(.init(title: "OK", style: .default))
            self?.present(alert, animated: true)
        }
    }

    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        qrScannerView.stopRunning()
    }

    @IBAction func tapFlashButton(_ sender: UIButton) {
        qrScannerView.setTorchActive(isOn: !sender.isSelected)
    }

}

// MARK: - QRScannerViewDelegate
extension QRViewController: QRScannerViewDelegate {
    func qrScannerView(_ qrScannerView: QRScannerView, didFailure error: QRScannerError) {
        print(error.localizedDescription)
    }

    func qrScannerView(_ qrScannerView: QRScannerView, didSuccess code: String) {
        if let url = URL(string: code), (url.scheme == "http" || url.scheme == "https") {
            openWeb(url: url)
        } else {
            showAlert(code: code)
        }
    }

    func qrScannerView(_ qrScannerView: QRScannerView, didChangeTorchActive isOn: Bool) {
        flashButton.isSelected = isOn
    }
}

private extension QRViewController {
    func openWeb(url: URL) {
        UIApplication.shared.open(url, options: [:], completionHandler: { [weak self] _ in
            self?.qrScannerView.rescan()
        })
    }

    func showAlert(code: String) {
        let alertController = UIAlertController(title: code, message: nil, preferredStyle: .actionSheet)
        let copyAction = UIAlertAction(title: "Copy", style: .default) { [weak self] _ in
            UIPasteboard.general.string = code
            self?.qrScannerView.rescan()
        }
        alertController.addAction(copyAction)
        let searchWebAction = UIAlertAction(title: "Search Web", style: .default) { [weak self] _ in
            UIApplication.shared.open(URL(string: "https://www.google.com/search?q=\(code)")!, options: [:], completionHandler: nil)
            self?.qrScannerView.rescan()
        }
        alertController.addAction(searchWebAction)
        let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: { [weak self] _ in
            self?.qrScannerView.rescan()
        })
        alertController.addAction(cancelAction)
        present(alertController, animated: true, completion: nil)
    }
}


● FlashButton

import UIKit

final class FlashButton: UIButton {
    override init(frame: CGRect) {
        super.init(frame: frame)
        settings()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        settings()
    }

    // MARK: - Properties
    override var isSelected: Bool {
        didSet {
            let color: UIColor = isSelected ? .gray : .lightGray
            backgroundColor = color.withAlphaComponent(0.7)
        }
    }
}

private extension FlashButton {
    func settings() {
        setTitleColor(.darkGray, for: .normal)
        setTitleColor(.white, for: .selected)
        setTitle("OFF", for: .normal)
        setTitle("ON", for: .selected)
        tintColor = .clear
        titleLabel?.font = .boldSystemFont(ofSize: 16)
        layer.cornerRadius = frame.size.width / 2
        isSelected = false
    }
}


View・Buttonの配置


XIBファイルでViewを配置します。


配置後は、下のようにClassを指定しておきます。


Viewと同様に、
Buttonも配置してClassを指定しておきます。


適当に、AutoLayoutの制約もつけておきます。

Permissionの設定

QRをスキャンするためにカメラを使用するので、
Permissionも許可しておきます。

まとめ

ということで、本記事はメルカリQRScannerをSwiftUI × Storyboardで使う方法をまとめました。
アドバイスや改善などあれば本記事の最後のコメント欄からお願いします。
最後まで読んでいただきありがとうございました!

作業効率がグッと上がるPC道具

間違いなしのSwift書籍2冊



コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です