ソースコード source code

下記アプリの主要なソースコードを公開しています。アプリ開発の参考になれば幸いです。

画像等が別途必要ですので下記情報のみでアプリが完成するものではありません。 アプリは少しずつ機能拡張していますのでストア公開されているアプリと内容が異なる場合があります。 コードはコピーして自由にお使いいただけます。ただし著作権は放棄しておりませんので全部の再掲載はご遠慮ください。部分的に再掲載したり、改変して再掲載するのは構いません。 自身のアプリ作成の参考として個人使用・商用問わず自由にお使いいただけます。 コード記述のお手本を示すものではありません。ミニアプリですので変数名などさほど気遣いしていない部分も有りますし間違いも有るかと思いますので参考程度にお考え下さい。 他の賢者の皆様が公開されているコードを参考にした箇所も含まれます。iOSアプリ開発の熟練者が書いたコードではありません。 エンジニア向け技術情報共有サービスではありませんので説明は省いています。ご了承ください。 GitHubなどへの公開は予定しておりません。

下記コードの最終ビルド日: 2021-04-19

BarcodeScannerApp.swift

//
//  BarcodeScannerApp.swift
//  BarcodeScanner
//
//  Created by akira ohmachi on 2021/04/17.
//

import SwiftUI
import UIKit
import GoogleMobileAds

// AppDelegateクラスを定義する
class AppDelegate: NSObject, UIApplicationDelegate {
    func application(_ application: UIApplication,didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {

        // Mobile Ads SDKを初期化する
        GADMobileAds.sharedInstance().start(completionHandler: nil)
        
        return true
    }
}


@main
struct BarcodeScannerApp: App {
    // SwiftUI AppライフサイクルにAppDelegateクラスを注入する
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    var body: some Scene {
        WindowGroup {
            ContentView().environmentObject(PublicManager())
        }
    }
}

CodeScannerView.swift

//
//  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 enum ScanMode {
        case once, oncePerCode, continuous
    }

    public class ScannerCoordinator: NSObject, AVCaptureMetadataOutputObjectsDelegate {
        var parent: CodeScannerView
        var codesFound: Set<String>
        var isFinishScanning = false
        var lastTime = Date(timeIntervalSince1970: 0)

        init(parent: CodeScannerView) {
            self.parent = parent
            self.codesFound = Set<String>()
        }

        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 isFinishScanning == false else { return }

                switch self.parent.scanMode {
                case .once:
                    found(code: stringValue)
                    // make sure we only trigger scan once per use
                    isFinishScanning = true
                case .oncePerCode:
                    if !codesFound.contains(stringValue) {
                        codesFound.insert(stringValue)
                        found(code: stringValue)
                    }
                case .continuous:
                    if isPastScanInterval() {
                        found(code: stringValue)
                    }
                }
            }
        }

        func isPastScanInterval() -> Bool {
            return Date().timeIntervalSince(lastTime) >= self.parent.scanInterval
        }
        
        func found(code: String) {
            lastTime = Date()
            AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))
            parent.completion(.success(code))
        }

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

    #if targetEnvironment(simulator)
    public class ScannerViewController: UIViewController,UIImagePickerControllerDelegate,UINavigationControllerDelegate{
        var delegate: ScannerCoordinator?
        private let showViewfinder: Bool

        public init(showViewfinder: Bool = false) {
            self.showViewfinder = showViewfinder
            super.init(nibName: nil, bundle: nil)
        }

        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }

        override public func loadView() {
            view = UIView()
            view.isUserInteractionEnabled = true
            let label = UILabel()
            label.translatesAutoresizingMaskIntoConstraints = false
            label.numberOfLines = 0

            label.text = "You're running in the simulator, which means the camera isn't available. Tap anywhere to send back some simulated data."
            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?
        let videoCaptureDevice = AVCaptureDevice.default(for: .video)

        private let showViewfinder: Bool

        private lazy var viewFinder: UIImageView? = {
            guard let image = UIImage(named: "viewfinder", in: .none,  with: nil) else {
                return nil
            }

            let imageView = UIImageView(image: image)
            imageView.translatesAutoresizingMaskIntoConstraints = false
            return imageView
        }()

        public init(showViewfinder: Bool) {
            self.showViewfinder = showViewfinder
            super.init(nibName: nil, bundle: nil)
        }

        public required init?(coder: NSCoder) {
            self.showViewfinder = false
            super.init(coder: coder)
        }

        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 = videoCaptureDevice else {
                return
            }

            guard let videoInput = try? AVCaptureDeviceInput(device: videoCaptureDevice) else { 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)
            updateOrientation()
        }

        override public func viewWillAppear(_ animated: Bool) {
            super.viewWillAppear(animated)
            
            if previewLayer == nil {
                previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
            }
            previewLayer.frame = view.layer.bounds
            previewLayer.videoGravity = .resizeAspectFill
            view.layer.addSublayer(previewLayer)
            addviewfinder()

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

        private func addviewfinder() {
            guard showViewfinder, let imageView = viewFinder else { return }
            
            view.addSubview(imageView)
            NSLayoutConstraint.activate([
                imageView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
                imageView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
                imageView.widthAnchor.constraint(equalToConstant: 200),
                imageView.heightAnchor.constraint(equalToConstant: 200),
            ])
        }

        override public func viewDidDisappear(_ animated: Bool) {
            super.viewDidDisappear(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
        }
        
        /** Touch the screen for autofocus */
        public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
            guard touches.first?.view == view,
                  let touchPoint = touches.first,
                  let device = videoCaptureDevice
            else { return }
            
            let videoView = view
            let screenSize = videoView!.bounds.size
            let xPoint = touchPoint.location(in: videoView).y / screenSize.height
            let yPoint = 1.0 - touchPoint.location(in: videoView).x / screenSize.width
            let focusPoint = CGPoint(x: xPoint, y: yPoint)
            
            do {
                try device.lockForConfiguration()
            } catch {
                return
            }
            
            // Focus to the correct point, make continiuous focus and exposure so the point stays sharp when moving the device closer
            device.focusPointOfInterest = focusPoint
            device.focusMode = .continuousAutoFocus
            device.exposurePointOfInterest = focusPoint
            device.exposureMode = AVCaptureDevice.ExposureMode.continuousAutoExposure
            device.unlockForConfiguration()
        }
    }
    #endif

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

    public init(codeTypes: [AVMetadataObject.ObjectType], scanMode: ScanMode = .once, showViewfinder: Bool = false, scanInterval: Double = 2.0, simulatedData: String = "", completion: @escaping (Result<String, ScanError>) -> Void) {
        self.codeTypes = codeTypes
        self.scanMode = scanMode
        self.showViewfinder = showViewfinder
        self.scanInterval = scanInterval
        self.simulatedData = simulatedData
        self.completion = completion
    }

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

    public func makeUIViewController(context: Context) -> ScannerViewController {
        let viewController = ScannerViewController(showViewfinder: showViewfinder)
        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,.aztec,.code128,.code39,.code39Mod43,.code93,.dataMatrix,.ean13,.ean8,.itf14,.pdf417,.upce]) { result in
            // do nothing
        }
    }
}

ConstValue.swift

//
//  ConstValue.swift
//  BarcodeScanner
//
//  Created by akira ohmachi on 2021/04/17.
//

import SwiftUI

class ConstValue: ObservableObject {

    //background
    static let colorBg: Color = Color.init(red: 241 / 255, green: 226 / 255, blue: 244 / 255)
    
    //button color
    /*
    static let colorTitle: Color = Color.init(red: 61 / 255, green: 196 / 255, blue: 99 / 255)
    static let colorScan: Color = Color.init(red: 101 / 255, green: 196 / 255, blue: 74 / 255)
    static let colorContinuousScan: Color = Color.init(red: 138 / 255, green: 196 / 255, blue: 74 / 255)
    static let colorShareAll: Color = Color.init(red: 172 / 255, green: 196 / 255, blue: 74 / 255)
    static let colorClearAll: Color = Color.init(red: 196 / 255, green: 180 / 255, blue: 74 / 255)
    static let colorShareLatest: Color = Color.init(red: 196 / 255, green: 164 / 255, blue: 74 / 255)
    static let colorBrowserLatest: Color = Color.init(red: 196 / 255, green: 142 / 255, blue: 74 / 255)
    static let colorClearLatest: Color = Color.init(red: 196 / 255, green: 117 / 255, blue: 74 / 255)
    */
    static let colorTitle: Color          = Color.init(red:  66 / 255, green:  46 / 255, blue: 195 / 255)
    static let colorScan: Color           = Color.init(red:  79 / 255, green:  46 / 255, blue: 195 / 255)
    static let colorContinuousScan: Color = Color.init(red:  96 / 255, green:  46 / 255, blue: 195 / 255)
    static let colorShareAll: Color       = Color.init(red: 114 / 255, green:  46 / 255, blue: 195 / 255)
    static let colorClearAll: Color       = Color.init(red: 131 / 255, green:  47 / 255, blue: 195 / 255)
    static let colorShareLatest: Color    = Color.init(red: 151 / 255, green:  47 / 255, blue: 195 / 255)
    static let colorBrowserLatest: Color  = Color.init(red: 165 / 255, green:  47 / 255, blue: 195 / 255)
    static let colorClearLatest: Color    = Color.init(red: 180 / 255, green:  47 / 255, blue: 195 / 255)

}

ContentView.swift

//
//  ContentView.swift
//  BarcodeScanner
//
//  Created by akira ohmachi on 2021/04/17.
//

import SwiftUI
import GoogleMobileAds
//import CodeScanner
//import MobileCoreServices   //clipboard access

struct ContentView: View {
    @EnvironmentObject var pub: PublicManager
    @State private var destroyFlag: Bool = false
    @State private var isShowingScanner: Bool = false   //barcode
    @State private var isShowingScannerContinuous: Bool = false   //barcode
    private let dateFormatter: DateFormatter = DateFormatter()
    private let dateFormatString: String = "yyyyMMdd_HHmmss"
    @State private var resultTextPadding: CGFloat = 5.0
    @State private var isAlertClipboardAll: Bool = false
    @State private var isShareAll: Bool = false
    @State private var isAlertClearAll: Bool = false
    @State private var isAlertClipboardLatest: Bool = false
    @State private var isShareLatest: Bool = false
    @State private var isAlertBrowserLatest: Bool = false
    @State private var isAlertClearLatest: Bool = false
    @State private var buttonHeightDiv1: CGFloat = 8
    @State private var buttonHeightDiv2: CGFloat = 14
    //
    var body: some View {
        NavigationView {
            GeometryReader { bodyView in
                ZStack(alignment: .bottom) {
                    ScrollView {
                        VStack(spacing: 0) {
                            VStack(spacing: 0) {
                                Text("title")
                                    .padding(5)
                                    .frame(width:bodyView.size.width, alignment: .center)
                                    .background(ConstValue.colorTitle)
                                    .foregroundColor(Color.white)
                                Rectangle().fill(Color.white).frame(width:bodyView.size.width, height:1)
                            }
                            VStack(spacing: 0) {
                                Button(action:{
                                    self.isShowingScanner = true
                                }){
                                    HStack {
                                        Image(systemName: "camera.circle")
                                        Text("scan")
                                    }
                                    .frame(width:bodyView.size.width, height: bodyView.size.height / self.buttonHeightDiv1, alignment: .center)
                                    .background(ConstValue.colorScan)
                                    .foregroundColor(Color.white)
                                }.sheet(isPresented: self.$isShowingScanner) {
                                    CodeScannerView(codeTypes: [.qr,.aztec,.code128,.code39,.code39Mod43,.code93,.dataMatrix,.ean13,.ean8,.itf14,.pdf417,.upce], simulatedData: "https://www.aosystem.co.jp/", completion: self.handleScan)
                                }
                                Rectangle().fill(Color.white).frame(width: bodyView.size.width,height: 1)
                            }
                            VStack(spacing: 0) {
                                Button(action:{
                                    self.isShowingScannerContinuous = true
                                }){
                                    HStack {
                                        Image(systemName: "camera.circle.fill")
                                        Text("continuousScan")
                                    }
                                    .frame(width:bodyView.size.width, height: bodyView.size.height / self.buttonHeightDiv1, alignment: .center)
                                    .background(ConstValue.colorContinuousScan)
                                    .foregroundColor(Color.white)
                                }.sheet(isPresented: self.$isShowingScannerContinuous) {
                                    CodeScannerView(codeTypes: [.qr,.aztec,.code128,.code39,.code39Mod43,.code93,.dataMatrix,.ean13,.ean8,.itf14,.pdf417,.upce], simulatedData: "https://www.aosystem.co.jp/", completion: self.handleScanContinuous)
                                }
                                Rectangle().fill(Color.white).frame(width: bodyView.size.width,height: 1)
                            }
                            /*
                            VStack(spacing: 0) {
                                Button(action:{
                                    self.isAlertClipboardAll = true
                                }){
                                    Text("clipboardAll")
                                        .frame(width:bodyView.size.width, height: bodyView.size.height / 10, alignment: .center)
                                        .background(ConstValue.colorSetting)
                                        .foregroundColor(Color.white)
                                }.alert(isPresented: self.$isAlertClipboardAll, content: self.clipboardAll)
                                Rectangle().fill(Color.white).frame(width: bodyView.size.width,height: 1)
                            }
                            */
                            VStack(spacing: 0) {
                                Button(action:{
                                    self.isShareAll = true
                                }){
                                    HStack {
                                        Image(systemName: "square.and.arrow.up.fill")
                                        Text("shareAll")
                                    }
                                    .frame(width:bodyView.size.width, height: bodyView.size.height / self.buttonHeightDiv2, alignment: .center)
                                    .background(ConstValue.colorShareAll)
                                    .foregroundColor(Color.white)
                                }.sheet(isPresented: self.$isShareAll) {
                                    ActivityView(
                                        activityItems: [self.pub.csv],
                                        applicationActivities: nil
                                    )
                                }
                                Rectangle().fill(Color.white).frame(width: bodyView.size.width,height: 1)
                            }
                            VStack(spacing: 0) {
                                Button(action:{
                                    self.isAlertClearAll = true
                                }){
                                    HStack {
                                        Image(systemName: "clear.fill")
                                        Text("clearAll")
                                    }
                                    .frame(width:bodyView.size.width, height: bodyView.size.height / self.buttonHeightDiv2, alignment: .center)
                                    .background(ConstValue.colorClearAll)
                                    .foregroundColor(Color.white)
                                }.alert(isPresented: self.$isAlertClearAll, content: self.clearAll)
                                Rectangle().fill(Color.white).frame(width: bodyView.size.width,height: 1)
                            }
                            /*
                            VStack(spacing: 0) {
                                Button(action:{
                                    self.isAlertClipboardLatest = true
                                }){
                                    Text("clipboardLatest")
                                        .frame(width:bodyView.size.width, height: bodyView.size.height / self.buttonHeightDiv2, alignment: .center)
                                        .background(ConstValue.colorSetting)
                                        .foregroundColor(Color.white)
                                }.alert(isPresented: self.$isAlertClipboardLatest, content: self.clipboardLatest)
                                Rectangle().fill(Color.white).frame(width: bodyView.size.width,height: 1)
                            }
                            */
                            VStack(spacing: 0) {
                                Button(action:{
                                    self.isShareLatest = true
                                }){
                                    HStack {
                                        Image(systemName: "square.and.arrow.up")
                                        Text("shareLatest")
                                    }
                                    .frame(width:bodyView.size.width, height: bodyView.size.height / self.buttonHeightDiv2, alignment: .center)
                                    .background(ConstValue.colorShareLatest)
                                    .foregroundColor(Color.white)
                                }.sheet(isPresented: self.$isShareLatest) {
                                    ActivityView(
                                        activityItems: [self.latestResult()],
                                        applicationActivities: nil
                                    )
                                }
                                Rectangle().fill(Color.white).frame(width: bodyView.size.width,height: 1)
                            }
                            VStack(spacing: 0) {
                                Button(action:{
                                    self.isAlertBrowserLatest = true
                                }){
                                    HStack {
                                        Image(systemName: "arrow.up.square")
                                        Text("browserLatest")
                                    }
                                    .frame(width:bodyView.size.width, height: bodyView.size.height / self.buttonHeightDiv2, alignment: .center)
                                    .background(ConstValue.colorBrowserLatest)
                                    .foregroundColor(Color.white)
                                }.alert(isPresented: self.$isAlertBrowserLatest, content: self.browserLatest)
                                Rectangle().fill(Color.white).frame(width: bodyView.size.width,height: 1)
                            }
                            VStack(spacing: 0) {
                                Button(action:{
                                    self.isAlertClearLatest = true
                                }){
                                    HStack {
                                        Image(systemName: "clear")
                                        Text("clearLatest")
                                    }
                                    .frame(width:bodyView.size.width, height: bodyView.size.height / self.buttonHeightDiv2, alignment: .center)
                                    .background(ConstValue.colorClearLatest)
                                    .foregroundColor(Color.white)
                                }.alert(isPresented: self.$isAlertClearLatest, content: self.clearLatest)
                                Rectangle().fill(Color.white).frame(width: bodyView.size.width,height: 1)
                            }
                            VStack(spacing: 0) {
                                HStack {
                                    Text(self.pub.csv)
                                        .foregroundColor(Color.black)
                                        .lineLimit(nil)
                                        .padding(self.resultTextPadding)
                                    Spacer()
                                }
                                .frame(width: bodyView.size.width)
                                .background(Color.white)
                            }
                            Spacer(minLength: 150)
                        }
                    }
                    HStack(spacing: 0) {
                        Spacer(minLength: 0)
                        PublicManager.AdView().frame(maxWidth: bodyView.size.width, maxHeight: 50)
                        Spacer(minLength: 0)
                    }.background(Color(red:0.2,green:0.2,blue:0.2))
                }
                .background(ConstValue.colorBg)
                //.navigationBarTitle("")
                .navigationBarHidden(true)
            }
        }
        .navigationViewStyle(StackNavigationViewStyle())
        .onAppear {     //アプリ起動時に実行
            self.dateFormatter.dateFormat = self.dateFormatString
        }
        .onDisappear {     //アプリ終了時
            self.destroyFlag = true
        }
    }
    
    func handleScan(result: Result<String, CodeScannerView.ScanError>) {
        self.isShowingScanner = false
        switch result {
        case .success(let code):
            let dateStr: String = self.dateFormatter.string(from: Date())
            self.pub.csv += "\"" + dateStr + "\",\"" + self.safetyCode(str: code) + "\"\n"
            self.refreshResultText()
        case .failure( _):
            //print("Scanning failed")
            break
        }
    }
    
    func handleScanContinuous(result: Result<String, CodeScannerView.ScanError>) {
        self.isShowingScannerContinuous = false
        switch result {
        case .success(let code):
            let dateStr: String = self.dateFormatter.string(from: Date())
            self.pub.csv += "\"" + dateStr + "\",\"" + self.safetyCode(str: code) + "\"\n"
            self.refreshResultText()
            self.nextScanContinuous()
        case .failure( _):
            //print("Scanning failed")
            break
        }
    }
    
    private func safetyCode(str: String) -> String {
        let str1: String = str.replacingOccurrences(of: "\n", with: "\\n")
        let str2: String = str1.replacingOccurrences(of: "\"", with: "\\\"")
        return str2
    }

    private func nextScanContinuous() {
        withAnimation {
            DispatchQueue.global().async {
                Thread.sleep(forTimeInterval: 1.0)
                DispatchQueue.main.sync {
                    if self.destroyFlag == false {
                        self.isShowingScannerContinuous = true
                    }
                }
            }
        }
    }
    
    //リフレッシュされないのでこの処理を入れてリフレッシュさせる
    private func refreshResultText() {
        let tmpResultTextPadding: CGFloat = self.resultTextPadding
        self.resultTextPadding = 0
        withAnimation {
            DispatchQueue.global().async {
                Thread.sleep(forTimeInterval: 0.1)
                DispatchQueue.main.sync {
                    if self.destroyFlag == false {
                        self.resultTextPadding = tmpResultTextPadding
                    }
                }
            }
        }
    }
    /*
    private func clipboardAll() -> Alert {
        return Alert(title: Text("clipboardAll"),
             message: Text("clipboardAll1"),
             primaryButton: .cancel(Text("cancel")),
             secondaryButton: .destructive(
                Text("copy"),
                action: {
                    UIPasteboard.general.setValue(self.pub.csv, forPasteboardType: kUTTypePlainText as String)
                }
            )
        )
    }
    */
    private func clearAll() -> Alert {
        return Alert(title: Text("clearAll"),
             message: Text("clearAll1"),
             primaryButton: .cancel(Text("cancel")),
             secondaryButton: .destructive(
                Text("clear"),
                action: {
                    self.pub.csv = ""
                    self.refreshResultText()
                }
            )
        )
    }
    /*
    private func clipboardLatest() -> Alert {
        let resultStr: String = self.latestResult()
        return Alert(title: Text("clipboardLatest"),
             message: Text(resultStr),
             primaryButton: .cancel(Text("cancel")),
             secondaryButton: .destructive(
                Text("copy"),
                action: {
                    UIPasteboard.general.setValue(resultStr, forPasteboardType: kUTTypePlainText as String)
                }
            )
        )
    }
    */
    private func browserLatest() -> Alert {
        let resultStr: String = self.latestResult()
        return Alert(title: Text("browserLatest"),
             message: Text(resultStr),
             primaryButton: .cancel(Text("cancel")),
             secondaryButton: .destructive(
                Text("send"),
                action: {
                    if resultStr != "" {
                        if let url = URL(string: resultStr) {
                            UIApplication.shared.open(url)
                        }
                    }
                }
            )
        )
    }

    private func clearLatest() -> Alert {
        let resultStr: String = self.latestResult()
        return Alert(title: Text("clearLatest"),
             message: Text(resultStr),
             primaryButton: .cancel(Text("cancel")),
             secondaryButton: .destructive(
                Text("clear"),
                action: {
                    self.pub.csv = self.exceptlatestCsv()
                    self.refreshResultText()
                }
            )
        )
    }

    private func latestResult() -> String {
        if self.pub.csv == "" {
            return ""
        }
        let ary1: Array<Substring> = (self.pub.csv).split(separator: "\n")
        if ary1.count == 0 {
            return ""
        }
        let ary2: Array<Substring> = ary1[ary1.count - 1].split(separator: ",")
        var str1: Substring = ""
        for i in 1 ..< ary2.count {  //文字列の中にカンマが含まれている場合があるので最後まで繋げる
            str1 += ary2[i]
        }
        if str1.count < 2 { //2文字より少ない場合は不正
            return ""
        }
        let str2: Substring = str1.suffix(str1.count - 1)   //先頭文字を削除"\""
        let str3: Substring = str2.prefix(str2.count - 1)   //後尾文字を削除"\""
        return String(str3)
    }
    
    private func exceptlatestCsv() -> String {
        if self.pub.csv == "" {
            return ""
        }
        let ary1: Array<Substring> = (self.pub.csv).split(separator: "\n")
        if ary1.count == 0 {
            return ""
        }
        var resultCsv: String = ""
        for i in 0 ..< (ary1.count - 1) {
            resultCsv += ary1[i] + "\n"
        }
        return String(resultCsv)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            ContentView().environmentObject(PublicManager())
        }
    }
}

struct ActivityView: UIViewControllerRepresentable {
    let activityItems: [Any]
    let applicationActivities: [UIActivity]?

    func makeUIViewController(context: UIViewControllerRepresentableContext<ActivityView>) -> UIActivityViewController {
        return UIActivityViewController(
            activityItems: activityItems,
            applicationActivities: applicationActivities
        )
    }

    func updateUIViewController(
        _ uiViewController: UIActivityViewController,
        context: UIViewControllerRepresentableContext<ActivityView>
    ) {
        // Nothing to do
    }
}

PublicManager.swift

//
//  PublicManager.swift
//  BarcodeScanner
//
//  Created by akira ohmachi on 2021/04/17.
//

import SwiftUI
import GoogleMobileAds

class PublicManager: ObservableObject {
    
    @AppStorage("csv") var csv: String = ""
 
    //--------------------------------------------------
    //adMob
    
    struct AdView: UIViewRepresentable {
        func makeUIView(context: Context) -> GADBannerView {
            let banner = GADBannerView(adSize: kGADAdSizeBanner)
            #if DEBUG
            banner.adUnitID = "ca-app-pub-3940256099942544/2934735716"
            #else
            banner.adUnitID = "ca-app-pub-0000000000000000/0000000000"
            #endif
            banner.rootViewController = UIApplication.shared.windows.first?.rootViewController
            banner.load(GADRequest())
            return banner
        }

        func updateUIView(_ uiView: GADBannerView, context: Context) {
        }
    }

    //--------------------------------------------------

}