20211031のiOSに関する記事は4件です。

Swiftでバーコードリーダー機能を実装

Swiftでバーコードリーダー機能を実装する方法です。 目標 iPhoneのカメラで本の帯の裏に書かれているバーコードを読み込み、13桁の数字のJANコード(isbn13とも言う)を取得する。 実際に実装するコード 以下の6つのステップでご説明します。 モジュールをimport 変数・定数を定義 セッションを開始する関数を定義・呼び出し delegateの設定 画面から離れる直前にセッションを止める 検出エリアに枠線を表示 それでは順番に見ていきましょう。 1. モジュールをimport import AVFoundation まずカメラアプリを導入するために必要なAVFoundationをimportします。 2. 変数・定数を定義 var captureSession : AVCaptureSession? var videoLayer : AVCaptureVideoPreviewLayer? var isbn : String? //検出エリアをカスタマイズする場合のみ使用します let x: CGFloat = 0.05 let y: CGFloat = 0.4 let width: CGFloat = 0.9 let height: CGFloat = 0.15 次に必要な変数・定数を設定します。 AVCaptureSessionとは 画像や動画といった出力データの管理を行うクラス AVCaptureVideoPreviewLayerとは カメラが取得した映像を画面に表示させるクラス の意味があります。 またカメラ画面に四角い枠を表示させて、検出エリアを設定したい場合は下4行の定数も書いておきましょう。(そっちの方がユーザービリティは高そう) 3. セッションを開始する関数を定義・呼び出し func startCapture(){ //画像や動画といった出力データの管理を行うクラス let session = AVCaptureSession() //カメラデバイスの管理を行うクラス guard let device : AVCaptureDevice = AVCaptureDevice.default(for: .video) else { return } //AVCaptureDeviceをAVCaptureSessionに渡すためのクラス guard let input : AVCaptureInput = try? AVCaptureDeviceInput(device: device) else { return } //inputをセッションに追加 session.addInput(input) //outputをセッションに追加 let output = AVCaptureMetadataOutput() session.addOutput(output) //取得したメタデータを置くAVCaptureMetadataOutputの設定(delegateの設定) output.setMetadataObjectsDelegate(self, queue: DispatchQueue.main) //取得したメタデータを置くAVCaptureMetadataOutputの設定(何を検出するか JANコードの場合はean8とean13、他にもqrやcode93などがある) output.metadataObjectTypes = [.ean8, .ean13] //バーコードの検出エリアの設定(設定しない場合、画面全体が検出エリアになる) output.rectOfInterest = CGRect(x: y,y: 1-x-width,width: height,height: width) //セッションを開始 session.startRunning() //画面上にカメラの映像を表示するためにvideoLayerを作る let videoLayer = AVCaptureVideoPreviewLayer(session: session) videoLayer.videoGravity = .resizeAspectFill videoLayer.frame = self.view.bounds //videoLayerを最初に宣言した定数に追加する self.videoLayer = videoLayer self.view.layer.addSublayer(videoLayer) //開放用に保持 self.captureSession = session } //startCapture関数の呼び出し override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) self.startCapture() } startCapture関数はviewDidAppearで呼び出します。 今回はバーコードを読み取るためにmetadataObjectTypesでean8とean13を設定していますが、他にもqrやpdf417といった種類もあります。 詳しくは公式リファレンスをご覧ください。 4. delegateの設定 startCapture関数でAVCaptureMetadataOutputObjectsDelegateのdelegateをselfに設定しているので、必要なmetadataOutput関数を書きます。 func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection){ //バーコードが検出されたら呼び出される for metadataObject in metadataObjects { guard self.videoLayer?.transformedMetadataObject(for: metadataObject) is AVMetadataMachineReadableCodeObject else { continue } guard let object = metadataObject as? AVMetadataMachineReadableCodeObject else { continue } guard let detectionString = object.stringValue else { continue } //冒頭の文字や文字数を検出して、正しいコードの場合のみ処理が行われるようにしています //例えば書籍のJANコード(isbn13)は978から開始される13桁の数字ですので以下のように書いています if detectionString.starts(with: "978") && detectionString.count == 13 { self.isbn = detectionString } } //self.isbnが空じゃない時 if self.isbn != nil { print("取得したコードは\(self.isbn)です!") //取得し終わったらセッションを止めて空にします self.captureSession?.stopRunning() self.captureSession = nil } } 5. 画面から離れる直前にセッションを止める override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) self.captureSession?.stopRunning() self.captureSession = nil } 画面から離れるときにはcaptureSessionを止めた上で空にしておきます。 6. 検出エリアに枠線を表示 今の段階では検出エリアをカスタマイズしても、ユーザーからはそのエリアを認識できません。 そのため検出エリアと同じ場所に枠線を表示させて、エリアを分かりやすくする必要があります。 //枠線の表示 let loadArea = UIView() loadArea.frame = CGRect(x: view.frame.size.width * x, y: view.frame.size.height * y, width: view.frame.size.width * width, height: view.frame.size.height * height) loadArea.layer.borderColor = UIColor.red.cgColor loadArea.layer.borderWidth = 4 loadArea.layer.cornerRadius = 6 loadArea.clipsToBounds = true self.view.addSubview(loadArea) 枠線を表示させるコードはstartCaptureを呼び出した後に書きましょう。 全体コード 最後に今回書いたコードをまとめると以下のようになります。 import UIKit import AVFoundation class CaptureViewController : UIViewController { let detectionArea = UIView() let x: CGFloat = 0.05 let y: CGFloat = 0.4 let width: CGFloat = 0.9 let height: CGFloat = 0.15 var captureSession : AVCaptureSession? var videoLayer : AVCaptureVideoPreviewLayer? var isbn : String? override func viewDidLoad() { super.viewDidLoad() self.title = "バーコード読み取り" self.view.backgroundColor = .white self.navigationItem.rightBarButtonItem = { let btn = UIBarButtonItem(title: "完了", style: .plain, target: self, action: #selector(onPressComplete(_:))) return btn }() } @objc func onPressComplete(_ sender : Any){ self.captureSession?.stopRunning() self.captureSession = nil self.dismiss(animated: true, completion: nil) } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) self.captureSession?.stopRunning() self.captureSession = nil } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) self.startCapture() //枠線の表示 loadArea.frame = CGRect(x: view.frame.size.width * x, y: view.frame.size.height * y, width: view.frame.size.width * width, height: view.frame.size.height * height) loadArea.layer.borderColor = UIColor.red.cgColor loadArea.layer.borderWidth = 4 loadArea.layer.cornerRadius = 6 loadArea.clipsToBounds = true self.view.addSubview(loadArea) } func startCapture(){ //画像や動画といった出力データの管理を行うクラス let session = AVCaptureSession() //カメラデバイスの管理を行うクラス guard let device : AVCaptureDevice = AVCaptureDevice.default(for: .video) else { return } //AVCaptureDeviceをAVCaptureSessionに渡すためのクラス guard let input : AVCaptureInput = try? AVCaptureDeviceInput(device: device) else { return } //inputをセッションに追加 session.addInput(input) //outputをセッションに追加 let output = AVCaptureMetadataOutput() session.addOutput(output) //取得したメタデータを置くAVCaptureMetadataOutputの設定(delegateの設定) output.setMetadataObjectsDelegate(self, queue: DispatchQueue.main) //取得したメタデータを置くAVCaptureMetadataOutputの設定(何を検出するか JANコードの場合はean8とean13、他にもqrやcode93などがある) output.metadataObjectTypes = [.ean8, .ean13] //バーコードの検出エリアの設定(設定しない場合、画面全体が検出エリアになる) output.rectOfInterest = CGRect(x: y,y: 1-x-width,width: height,height: width) //セッションを開始 session.startRunning() //画面上にカメラの映像を表示するためにvideoLayerを作る let videoLayer = AVCaptureVideoPreviewLayer(session: session) videoLayer.videoGravity = .resizeAspectFill videoLayer.frame = self.view.bounds //videoLayerを最初に宣言した定数に追加する self.videoLayer = videoLayer self.view.layer.addSublayer(videoLayer) //開放用に保持 self.captureSession = session } } extension CaptureViewController : AVCaptureMetadataOutputObjectsDelegate { func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection){ //バーコードが検出されたら呼び出される for metadataObject in metadataObjects { guard self.videoLayer?.transformedMetadataObject(for: metadataObject) is AVMetadataMachineReadableCodeObject else { continue } guard let object = metadataObject as? AVMetadataMachineReadableCodeObject else { continue } guard let detectionString = object.stringValue else { continue } //冒頭の文字や文字数を検出して、正しいコードの場合のみ処理が行われるようにしています //例えば書籍のJANコードは978から開始される13桁の数字ですので以下のように書いています if detectionString.starts(with: "978") && detectionString.count == 13 { self.isbn = detectionString } } //self.isbnが空じゃない時 if self.isbn != nil { print("取得したコードは\(self.isbn)です!") //取得し終わったらセッションを止めて空にします self.captureSession?.stopRunning() self.captureSession = nil } } } 参考 https://motty72.hatenablog.com/entry/2019/01/29/233355 https://ichi.pro/swift-de-ba-ko-do-mataha-qr-ko-do-o-yomitoru-hoho-swift-de-no-puroguramingu-18503546520018 https://qiita.com/_asa08_/items/8562fe79ec6528a61b06 https://www.letitride.jp/entry/2019/12/03/125802
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

iOS14で非推奨となったUITableViewCellのtextLabelプロパティ等の対応(iOS13対応含)

経緯 iOS14でUITableViewCellのtextLabelプロパティ等が非推奨(Deprecated)になりました。代わりに、defaultContentConfiguration等を使用するのですが、こちらはiOS13非対応のため、現状iOS13以前に対応しているアプリだと使えません。 Xcodeが助け舟を出してくれますが、念の為解決方法をメモしておきます。 対応方法 // クラス名は任意。TableViewControllerを使わない場合、親クラスはUITableViewDataSource class TableViewController: UITableViewController { let itemArray = ["A", "B", "C"] override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { itemArray.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { // withIdentifierは任意 let cell = tableView.dequeueReusableCell(withIdentifier: "ToDoItemCell", for: indexPath) if #available(iOS 14.0, *) { // iOS14以降の推奨 var content = cell.defaultContentConfiguration() content.text = itemArray[indexPath.row] cell.contentConfiguration = content } else { // iOS13以前 cell.textLabel?.text = itemArray[indexPath.row] } return cell } } 結果 (これまでと変わりません) 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

iOS14で非推奨となったUITableViewCellのtextLavelプロパティ等の対応(iOS13対応含)

経緯 iOS14でUITableViewCellのtextLabelプロパティ等が非推奨(Deprecated)になりました。代わりに、defaultContentConfiguration等を使用するのですが、こちらはiOS13非対応のため、現状iOS13以前に対応しているアプリだと使えません。 Xcodeが助け舟を出してくれますが、念の為解決方法をメモしておきます。 対応方法 // クラス名は任意。TableViewControllerを使わない場合、親クラスはUITableViewDataSource class TableViewController: UITableViewController { let itemArray = ["A", "B", "C"] override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { itemArray.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { // withIdentifierは任意 let cell = tableView.dequeueReusableCell(withIdentifier: "ToDoItemCell", for: indexPath) if #available(iOS 14.0, *) { // iOS14以降の推奨 var content = cell.defaultContentConfiguration() content.text = itemArray[indexPath.row] cell.contentConfiguration = content } else { // iOS13以前 cell.textLabel?.text = itemArray[indexPath.row] } return cell } } 結果 (これまでと変わりません) 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Flutter Flavor対応(パッケージ未使用)

内容 Flutter開発時にFlavorが導入されたプロジェクトに参画したり、他のメンバーがFlavorを導入した後にFlutterを触ったりすることはあったが、自分でFlavorを導入したことがなかったので、導入までの流れをまとめる。 やり方は様々あるようですが、今回は、main_dev.dartとmain_prod.dartの2ファイルを作成する方法で行います。 開発環境 PC:macOS Big Sur エディター:Visual Studio Code, Android Studio, Xcode Dart:2.13.1 Flutter:2.2.1 Visual Studio Code:1.16.2 Android Studio:4.1.2 Xcode:13.1 その他  Flavorパッケージは未使用 今回は開発環境と本番環境を作成する。 それぞれの名前は以下のように統一している。(Android, iOS共に同じ名前を使用する。) 開発環境:dev 本番環境:prod 準備 以下のファイルを作成する。 ・main_dev.dart ・main_prod.dart ・app_config.dart ・main_common.dart ・home_page.dart main_dev.dart import 'package:flutter/material.dart'; import 'app_config.dart'; import 'main_common.dart'; void main() { var configuredApp = AppConfig( appDisplayName: "Dev App", child: MyApp(), ); runApp(configuredApp); } main_prod.dart import 'package:flutter/material.dart'; import 'app_config.dart'; import 'main_common.dart'; void main() { var configuredApp = AppConfig( appDisplayName: "Prod App", child: MyApp(), ); runApp(configuredApp); } app_config.dart import 'package:flutter/material.dart'; class AppConfig extends InheritedWidget { AppConfig({ required this.appDisplayName, required Widget child, }) : super(child: child); final String appDisplayName; static AppConfig? of(BuildContext context) { return context.dependOnInheritedWidgetOfExactType<AppConfig>(); } @override bool updateShouldNotify(InheritedWidget oldWidget) => false; } main_common.dart import 'package:flutter/material.dart'; import 'app_config.dart'; import 'home_page.dart'; class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { var config = AppConfig.of(context); return _buildApp(config!.appDisplayName); } Widget _buildApp(String appName) { return MaterialApp( title: appName, theme: ThemeData( primaryColor: Color(0xFF43a047), accentColor: Color(0xFFffcc00), primaryColorBrightness: Brightness.dark, ), home: HomePage(), ); } } home_page.dart import 'package:flutter/material.dart'; import 'app_config.dart'; class HomePage extends StatefulWidget { const HomePage({Key? key}) : super(key: key); @override State<HomePage> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { final config = AppConfig.of(context); return Scaffold( appBar: AppBar( title: Text(config!.appDisplayName), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.headline4, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: const Icon(Icons.add), ), ); } } Androidの設定 android/app/src/build.gradleファイルを開き、android{}内にdev、prodという名前でflavorの設定を追加する。 build.gradle android { compileSdkVersion 30 sourceSets { main.java.srcDirs += 'src/main/kotlin' } defaultConfig { applicationId "com.sample.flutter_flavor_sample_app" minSdkVersion 16 targetSdkVersion 30 versionCode flutterVersionCode.toInteger() versionName flutterVersionName } // ここから flavorDimensions "flutter-flavor" productFlavors { dev { dimension "flutter-flavor" applicationId "com.example.dev" resValue "string", "app_name", "Dev App" } prod { dimension "flutter-flavor" applicationId "com.example.prod" resValue "string", "app_name", "Prod App" } } // ここまで追加 buildTypes { release { signingConfig signingConfigs.debug } } } 次にandroid/app/src/main/AndroidManifest.xmlファイルのlabelを変更する。 AndroidManifest.xml android:label="hogehoge_app_name" <-ここのアプリ名を変更する(プロジェクト作成時の名前が記載されている)           ↓ android:label="@string/app_name" @string/app_nameにはresValueに記入した値が代入される。 dev環境で実行すると、アプリ名は「Dev App」に、prod環境実行すると、アプリ名は「Prod App」になる。 Android Studioからエントリーポイントを設定する。 以下の画像のような流れで設定をする。 「Edit Configurations...」をクリックする。 表示されたダイアログの左上にある+ボタンをクリックして、Flutterを選択する。 NameとDart entrypoint、Build flavorを入力する。 devとprodの2つを以下のように設定する。 ・dev Name: dev Dart entrypoint: hogehoge/lib/main_dev.dart Build flavor: dev ・prod Name: prod Dart entrypoint: hogehoge/lib/main_prod.dart Build flavor: prod ※Dart entrypointはmain.dartからコピーして、main.dartの部分を変更する。 iOSの設定 Runner.xcworkspaceを開いて、メニューバーのProduct/Scheme/New Scheme...をクリックする。 以下のようなダイアログが表示されるので、devとprodを追加する。 メニューバーのProduct/Scheme/Manage Scheme...をクリックすると、追加したSchemeが確認できる。 (今回はdevとprodを追加している。その他のSchemeもあるが、関係ない。) PROJECTのRunnerのInfoタブを表示する。 +ボタンをクリックして、ConfigurationにDegug-dev、Degug-prod、Release-prodを追加する。 追加の際には下記のようなダイアログが表示されるので、対応するものを選択する。 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む