- 投稿日:2019-05-07T23:12:34+09:00
RealmSwiftのidについて
RealmSwiftでのidの扱いは少し注意しないといけません
idのオートインクリメント機能がないんですidの使い方を何パターンか表記したいと思います。
①UUIDStringを使う
idは基本的にはIntで扱うと思いますが、オートインクリメントしないのであれば、一意な値であれば問題ないと思います。
ここではNSUUID().UUIDStringを使い、ユニークなidを作成します。test.swiftclass Test: Object { @objc dynamic var id : String = NSUUID().uuidString @objc dynamic var createAt = Date() // IDをプライマリキーにする override static func primaryKey() -> String? { return "id" } }モデルの定義でidをString型に設定します。
これでユニークな値になるので、idが重複することは無く、idで検索や更新ができます。
ただ、1点注意したいのはidでソートはできなくなります。
そこで、createAtを定義し、作成日時でソートするようにしています。
もしくは別のidを作成し、モデルが作成される度に+1していく値を持っておくのもいいかもしれません。①オートインクリメント機能の実装
オートインクリメント機能は実装で補うことができます。
どうしてもidをInt型で使いたい場合はこちらの方法を取ることになるとおもいます。test.swiftclass Test: Object { @objc dynamic var id : Int = 0 // IDをプライマリキーにする override static func primaryKey() -> String? { return "id" } func save() { let realm = try! Realm() try! realm.write { if self.id == 0 { self.id = self.newId()} realm.add(self, update: true) } } // 現在存在する最後のidがあれば、+1、なければ1を設定する static func newId(realm: Realm) -> Int { if let task = realm.objects(Task.self).sorted(byKeyPath: "id").last { return task.id + 1 } else { return 1 } } }上記のように登録する際にidを+1にして登録します。
更新か登録かは呼び出し先で制御してくださいただ、この方法を使うには条件があります。
削除しないことが前提です。
例えば、idが1のデータを削除した場合、次に作られるデータはidが1のデータになります。
つまり、欠番がありません。結果的にidでソートしても想定外の並び順になることがありえます。
2つの方法を記載しましたが、削除しないというのはあまりないと思うので、UUIDStringを使う方が現実的かと思います。
参照
- 投稿日:2019-05-07T22:23:52+09:00
Swift ,SpriteKit ,SKPhysicsBody について (physicsBodyのプロパティ、メソッド篇)
1. Defining How Forces Affect a Physics Body : 力が物理体に与える影響の定義
var affectedByGravity: Boolこの物理的ボディが物理的世界の重力の影響を受けるかどうかを示すブール値。
物理学の世界の重力プロパティは、シーン内のボリュームベースのボディに適用される重力を定義します。
デフォルト値はtrueです。 このプロパティはエッジベースのボディでは無視され、ボディは常に重力の影響を受けません。
ffectedByGravityがfalseに設定されている物理体は、linearGravityField(withVector :)およびradialGravityField()によって作成された重力場の影響を受けます。
var allowsRotation: Bool物理学本体がそれに適用される角力とインパルスの影響を受けるかどうかを示すブール値。
デフォルト値はtrueです。 このプロパティは、システム内の力の影響を受けないエッジベースのボディでは無視されます。
var isDynamic: Boolよく使うダイナミック
物理学本体が物理学シミュレーションによって移動されるかどうかを示すブール値フォルト値はtrueです。 値がfalseの場合、物理学本体はそれに適用されるすべての力とインパルスを無視します。 このプロパティは、エッジベースのボディでは無視されます。 それらは自動的に静的です。
2.Defining a Physics Body’s Physical Properties : 物理体の物理的性質を定義する
物理的特性を一度設定するか動的に変更して、物理的本体を移動し、他のオブジェクトと衝突させます。
var mass: CGFloat体の質量(キログラム)。
相対的なオブジェクトの質量がゲーム全体で一貫している限り、実際の単位は任意です。 体の質量は、その運動量と、物体に力がどのように加わるかに影響します。
質量と密度の特性は相互に関連しています。 どちらかのプロパティの値を変更すると、もう一方のプロパティの値は自動的に再計算されて一貫性が保たれます。 デフォルト値は、物理学本体のサイズと本体のデフォルト密度に基づいています。
var density: CGFloatオブジェクトの密度(キログラム/平方メートル)。
相対的なオブジェクトの質量がゲーム全体で一貫している限り、実際の単位は任意です。
質量と密度の特性は相互に関連しています。 どちらかのプロパティの値を変更すると、もう一方のプロパティの値は自動的に再計算されて一貫性が保たれます。
デフォルト値は1.0です。
var area: CGFloat体で覆われた領域。
このプロパティは、体重のプロパティを計算するためにdensityプロパティと組み合わせて使用されます。
面積に返される値はメートル単位で測定されます。SpriteKitで使用されているように、ポイントに変換する必要がある場合は、値に150²を掛けます。 次のリストは、10ポイント四方の箱の面積を計算する方法を示しています。
bodySize = CGSize(幅:10、高さ:10)
physicsBody = SKPhysicsBody(RectangularOf:bodySize)とします。
let areaInPoints = physicsBody.area * pow(150、2)// areaInPoints = 100
var friction: CGFloat摩擦
物理体の表面の粗さ。このプロパティは、この物理体と接触している物理体に摩擦力を加えるために使用されます。 プロパティは0.0から1.0の間の値でなければなりません。 デフォルト値は0.2です
var restitution: CGFloat払い戻し
物理学の体の弾力。このプロパティは、物理的なボディが他のオブジェクトから反射するときにどれだけのエネルギーを失うかを決定するために使用されます。 プロパティは0.0から1.0の間の値でなければなりません。 デフォルト値は0.2です。
var linearDamping: CGFloat線形減衰
体の線速度を遅くするプロパティです。このプロパティは、体にかかる流体または空気の摩擦力をシミュレートするために使用されます。 プロパティは0.0から1.0の間の値でなければなりません。 デフォルト値は0.1です。 値が0.0の場合、オブジェクトに線形減衰は適用されません。
var angularDamping: CGFloat角度減衰
体の回転速度を遅くする特性。このプロパティは、体にかかる流体または空気の摩擦力をシミュレートするために使用されます。 プロパティは0.0から1.0の間の値でなければなりません。 デフォルト値は0.1です。 値が0.0の場合、オブジェクトに角減衰は適用されません。
3.Working with Collisions and Contacts:衝突や連絡先を扱う
衝突検出用にノードを設定する方法を学びます。
SpriteKitは、接触したり同じ空間を占有しようと試みる物理体間の2種類の相互作用をサポートします。
連絡先は、2つの体が互いに接触していることを知る必要があるときに使用されます。ほとんどの場合、衝突が発生したときにゲームプレイを変更する必要があるときに連絡先を使用します。
衝突は、2つのオブジェクトが相互に侵入するのを防ぐために使用されます。あるボディが別のボディにぶつかると、SpriteKitは自動的に衝突の結果を計算し、衝突の中のボディにインパルスを適用します。
var categoryBitMask: UInt32カテゴリビットマスク
この物理体がどのカテゴリに属するかを定義するマスク。シーン内のすべての物理学本体は、それぞれがビットマスク内のビットに対応する、最大32の異なるカテゴリに割り当てることができます。 あなたはあなたのゲームで使用されるマスク値を定義します。 collisionBitMaskおよびcontactTestBitMaskプロパティと組み合わせて、どの物理体が互いに対話するか、およびゲームがいつこれらの対話を通知されるかを定義します。
デフォルト値は0xFFFFFFFF(全ビット設定)です。
var collisionBitMask: UInt32衝突ビットマスク
どのカテゴリの物理体がこの物理体と衝突する可能性があるかを定義するマスク。2つの物理体が互いに接触すると、衝突が発生する可能性があります。 このボディの衝突マスクは、論理積演算を実行して他のボディのカテゴリマスクと比較されます。 結果がゼロ以外の値の場合、このボディは衝突の影響を受けます。 それぞれの団体は、他の団体の影響を受けたいかどうかを独自に選択します。 たとえば、これを使用すると、衝突の計算によってボディの速度に無視できるほどの変化が生じるのを防ぐことができます。
デフォルト値は0xFFFFFFFF(全ビット設定)です。
var usesPreciseCollisionDetection: Bool精密な衝突検出を使用
物理学の世界で反復的な衝突検出アルゴリズムを使用するかどうかを決定するブール値。SpriteKitが衝突検出を実行すると、まずシーン内のすべての物理学本体の位置を特定します。それからそれは衝突か接触が起こったかどうか確認する。この計算方法は高速ですが、衝突を見逃してしまうことがあります。小さなボディは非常に速く動くので、2つが互いに接触するアニメーションのフレームがなくても、別の物理ボディを完全に通過します。
衝突しなければならない物理体がある場合は、より正確な衝突モデルを使用して相互作用を確認するようにSpriteKitに指示することができます。このモデルはもっと高価なので、控えめに使うべきです。どちらかのボディが正確な衝突を使用している場合、すべてのコンタクトが確実に検出されるように、複数のコンタクトの反復が評価されます。
デフォルト値はfalseです。衝突する2つの物体が正確な衝突検出を実行せず、1つのフレーム内で一方が他方を完全に通過する場合、衝突は検出されません。どちらのボディでもこのプロパティがtrueに設定されている場合、シミュレーションはこれらの衝突を検出するためにより正確で高価な計算を実行します。このプロパティは、小さくて動きの速い物体ではtrueに設定する必要があります
var contactTestBitMask: UInt32ビットマスクテストへのお問い合わせ
どのカテゴリの物理体がこの物理体との交差通知を引き起こすかを定義するマスク。2つのボディが同じスペースを共有している場合は、論理AND演算を実行して、各ボディのカテゴリマスクが他のボディのコンタクトマスクに対してテストされます。 どちらかの比較の結果がゼロ以外の値になった場合は、SKPhysicsContactオブジェクトが作成されて物理学の世界のデリゲートに渡されます。 最高のパフォーマンスを得るために、あなたが興味を持っているインタラクションのためにコンタクトマスクのビットだけを設定してください。
デフォルト値は0x00000000です(全ビットクリア)。
func allContactedBodies() -> [SKPhysicsBody]全ての接触体()
この物理体が接触している物理体。戻り値
このボディが接触しているSKPhysicsBodyオブジェクトの配列。
4.Applying Forces and Impulses to a Physics Body:物理の体に力と衝撃を加える
物理体を動かす
速度、重力、衝撃など、さまざまな物理特性を使って体を動かします。
func applyForce(CGVector)力を適用(_ :)
物理体の重心に力を加えます。
各次元に加えられた力の大きさを表すベクトル。 力はニュートンで測定されます。この方法は、角加速度を与えずにボディを加速します。 加速度は単一のシミュレーションステップ(1フレーム)に適用されます。
func applyTorque(CGFloat)トルクを適用(_ :)
オブジェクトにトルクをかけます。
トルクの量(ニュートンメートル)。この方法では、直線的な加速度を発生させることなく、体に角加速度が発生します。 力は単一のシミュレーションステップ(1フレーム)に適用されます。
func applyForce(CGVector, at: CGPoint)力を適用(_:at :)
物理体の特定の点に力を加えます。
力が物理ボディに適用された場所を定義するシーン座標内のポイント。力は体の特定の点に加えられるため、直線加速度と角加速度の両方を与える可能性があります。 力は単一のシミュレーションステップ(1フレーム)に適用されます。
func applyImpulse(CGVector)インパルスを適用する(_ :)
物理体の重心にインパルスを適用します。
各次元でどれだけの運動量が与えられたかを表すベクトル。 インパルスはニュートン秒で測定されます。この方法は、体の角速度を変えずに体の線速度に影響を与えます。
func applyAngularImpulse(CGFloat)角のあるインパルスを適用する()
物体に角運動量を与えるインパルスを適用します。この方法は、体の線速度を変えずに体の角速度に影響を与えます。
func applyImpulse(CGVector, at: CGPoint)インパルスを適用(_:at :)
物理学の特定の点にインパルスを適用します。
インパルスが物理ボディに適用された場所を定義するシーン座標内のポイント。このインパルスはオブジェクト上の特定のポイントに適用されるため、体の速度と角速度の両方を変える可能性があります。
5.Inspecting a Physics Body’s Position and Velocity:物理学の体の位置と速度を調べる
var velocity: CGVector速度
物理体の速度ベクトル。メートル/秒で測定されます。
var angularVelocity: CGFloat角速度
物理体の角速度角速度は、1秒あたりのラジアンで測定される(0.0,0.0,1.0)の軸ベクトルを中心とした擬似ベクトルです。
var isResting: Bool休んでいる
オブジェクトが物理シミュレーション内で静止しているかどうかを示すブール値プロパティ。このプロパティは、ボディが静止していると判断したときに、物理シミュレーションによって自動的にtrueに設定されます。 これは、体がシステム内の別の体に静止していることを意味します。 静止物体は、インパルスがオブジェクトに適用されるか、または別のオブジェクトが衝突するまで、物理シミュレーションに参加しません。 これにより、物理シミュレーションのパフォーマンスが向上します。 世界のすべての物体が静止している場合、シミュレーション全体が静止しているので、物理学の世界で実行される計算の数が減ります。
6.Reading a Physics Body’s Node:物理学のノードを読む
var node: SKNode?SKNodeオブジェクトのphysicsBodyプロパティに割り当てることで、ボディをノードに関連付けます。 本体がノードに関連付けられていない場合、値はnilです。
7.Determining Which Joints Are Connected to a Physics Body:どのジョイントが物理的ボディに接続されているかを判断する
var joints: [SKPhysicsJoint]関節
関節はこの物理的な体に接続されています。
このプロパティは、このフィジックスボディに接続されているシーンのフィジックスワールドに追加されたすべてのジョイントを定義するSKPhysicsJointオブジェクトの配列を保持します。8.Interacting with Physics Fields:物理学分野との対話
var fieldBitMask: UInt32フィールドビットマスク
どの物理学分野のカテゴリがこの物理学本体に力を及ぼすことができるかを定義するマスク。物理ボディがSKFieldNodeオブジェクトの領域内にある場合、そのフィールドノードのcategoryBitMaskプロパティは、論理AND演算を実行することによって、この物理ボディのfieldBitMaskプロパティと比較されます。 結果がゼロ以外の値の場合、フィールドノードの効果は物理学本体に適用されます。
デフォルト値は0xFFFFFFFF(全ビット設定)です。
var charge: CGFloat電荷
物理体の電荷電荷は、電磁界によって物理体への電磁力の影響を計算するために使用されます。 SKFieldNodeを参照してください
9.Pinning a Physics Body to a Node’s Parent:物理ボディをノードの親に固定する
物理ボディの固定と回転
ノードを固定して、親ノードの特定の点を中心に自由に回転できるようにします。
var pinned: Boolピン留め
物理ボディのノードがその親ノードに固定されているかどうかを示すブール値。デフォルト値はfalseです。 trueの場合、ノードの位置はその親に対して相対的に固定されています。 ノードの位置は、行動や物理力によって変更することはできません。 ノードは衝突やその他の力に反応して自由にその位置を中心に回転できます。 親ノードに物理体がある場合、2つの物理体はピンジョイントで接続されているかのように扱われます。
- 投稿日:2019-05-07T21:01:07+09:00
UICollectionViewの引っ張り上げ更新はFooterでやると楽よ
UICollectionViewの引っ張り上げ更新の実装では、よく
contentInsetとcontentOffsetを弄くり回して算出してるものが多いのですが、どうにもぱっと見で実装内容が理解できないので(頑張って説明変数使っていても)別の方法を考えていました。
FacebookのiOSアプリ見ているとUICollectionViewのFooterを使っていそうだったので、真似してみたら上手くいったのでご紹介致します。R.swiftを使っていたので、適時コードは読み替えて実装お願い致します。
ActivityFooterView
今回自分はStoryBoardを使ったのでUICollectionViewにFooter追加して以下のクラスと紐付けしましたが、使わない派の人はviewDidLoadあたりでregisterお願いします。
footer.swiftimport UIKit class ActivityFooterView: UICollectionReusableView { @IBOutlet weak var activityIndicator: UIActivityIndicatorView! }UICollectionViewController(or Delegate)
UICollectionViewDelegateのheader/footerが表示される直前に呼ばれる
collectionView(_:willDisplaySupplementaryView:forElementKind:at:)delegateメソッドでアニメーション再生の関数を叩いていきます。そこで非同期の関数を呼び出し、クロージャでstopAnimating()やreloadData()を呼び出すことで引っ張り上げ更新は実現されます。
データ取得関数などの結果がdelegateで帰ってくる場合は、cachedFooterViewのようなfooterViewのキャッシュプロパティを使う必要があります。pullup-update.swift/* UICollectionViewでの引っ張り上げ更新に対して、Footerを利用するアプローチを選択した。 理由としては、contentInsetやoffsetをぐちゃぐちゃ弄ると意図しない挙動を作り込みやすいし、分かりにくい処理になるので避けたかったため このアイデアの参考にしたのはFacebookアプリで、おそらく同じような実装をしている。試してみてもらうと分かるが全く同じ挙動をする。 */ override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { let footerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: R.reuseIdentifier.footer, for: indexPath)! return footerView } override func collectionView(_ collectionView: UICollectionView, willDisplaySupplementaryView view: UICollectionReusableView, forElementKind elementKind: String, at indexPath: IndexPath) { let footerView = collectionView.dequeueReusableSupplementaryView(ofKind: elementKind, withReuseIdentifier: R.reuseIdentifier.footer, for: indexPath)! footerView.activityIndicator.startAnimating() doSomething() { // closure footerView.activityIndicator.stopAnimating() collectionView.reloadData() } }感想
ものすごくスッキリ実装出来たので、かえって怖い...。
問題なければ今後もこの実装を使っていくつもりですが、問題や改善点あればコメント助かりますー。![]()
- 投稿日:2019-05-07T19:48:10+09:00
【iOS】オープンソースのOCRライブラリ「SwiftyTesseract」を試してみる
背景
【iOS】オープンソースのOCRライブラリ調査の続きです。
前回の調査の過程で、Tesseract OCR iOSとSwiftOCRの2つがGitHubのスター数から見ても人気だということが分かりました。しかしながら、日本語認識に関しては課題が多く、実用向きではない印象を受けました。そこで個人的に注目しているのがSwiftyTesseractです。
というわけで、SwiftyTesseractを実際に試してみることにしました。
SwiftyTesseractについて
SwiftyTesseractの最大の特徴は、LSTMニューラルネットベースのOCRエンジンが追加された最新安定バージョンのTesseract 4をラップしている点です。ディープラーニングは詳しくないので深掘りしませんが、これで何が嬉しいかというと、Tesseract OCR iOSやSwiftOCRと比べて、ある程度スピードと正確性を両立させた日本語OCRが手軽に実現できるということです。
前提
本記事で使用した環境は次のとおりです。
- macOS 10.14.4
- Xcode 10.2.1
- CocoaPods 1.4.0
- Swift 4.2
Xcodeプロジェクトを作成する
まずはXcodeプロジェクトを新規作成し、Build SettingsでSwift Language Versionを変更します。
SwiftyTesseractのSwiftバージョンに合わせて、Swift 4.2を選択します。
SwiftyTesseractをXcodeプロジェクトへ導入する
CocoaPods、または、CarthageでSwiftyTesseractを導入します。
SwiftyTesseractの解説には、CocoaPodsとCarthage双方の導入方法が紹介されていますので、好きな方法で導入してください。ちなみに今回は、CocoaPodsを使ってSwiftyTesseractを導入してみます。
まずはPodfileを開いて次の一行を追加します。Podfilepod 'SwiftyTesseract', '~> 2.0'次にPodfileを保存して、下記のコマンドでインストールします。
$ pod install以上でSwiftyTesseractの導入は完了です。
以降は「プロジェクト名.xcworkspace」というファイルを開いて開発を進めていきます。学習データをXcodeプロジェクトへ追加する
SwiftyTesseractでは、Tesseract 1用にあらかじめ用意されている学習データをそのまま利用することができます。
Tesseract用の学習データには、次の3つのタイプが用意されています。
- tessdata_best (LSTM、正確性重視)
- tessdata_fast (LSTM、速度重視)
- tessdata (パターン認識とLSTMの組み合わせ?、互換性重視?)
SwiftyTesseractの解説によると、たいていのケースで最良の選択肢はtessdata_fastになるだろうと書いてあるので、今回はtessdata_fastの日本語学習データを利用します。
- パソコン上にtessdataという名称でフォルダを作成しておきます。
![]()
- tessdata_fastのリポジトリにアクセスし、jpn.traineddataをクリックします。
![]()
- Downloadボタンをクリックします。
![]()
- あらかじめ作成しておいたtessdataフォルダへjpn.traineddataをダウンロードします。
![]()
- tessdataフォルダをXcodeプロジェクトへドラッグします。
![]()
- オプションは次のように選択します。
![]()
- 水色のアイコンで表示されたtessdataフォルダの配下にjpn.traineddataがあればOKです。
![]()
以上で日本語学習データを利用する準備が整いました。
画像をXcodeプロジェクトへ追加する
目的の画像ファイルを用意し、Xcodeプロジェクトへ取り込んでおきます。
今回は次の画像(sample.png)で試してみます。ロゴや下線があったり、「」<>:・〜※などの記号があったり、よく見ると紙が波打って文字のラインが若干ずれていたりといったトラップが仕込まれていますね。
果たして結果や如何に!?OCR機能を実装する
ここまで来たら、あとはOCR機能を実装するだけです。
ViewController.swiftを次のとおり書き換えます。ViewController.swiftimport UIKit import SwiftyTesseract class ViewController: UIViewController { let swiftyTesseract = SwiftyTesseract(language: RecognitionLanguage.japanese) override func viewDidLoad() { super.viewDidLoad() let start = Date() let fileName = "sample.png" guard let image = UIImage(named: fileName) else { return } swiftyTesseract.performOCR(on: image) { recognizedString in guard let text = recognizedString else { return } print("\(text)") print("\(-start.timeIntervalSinceNow)") } } }ポイント解説
SwiftyTesseractのインポート
何はともあれ、SwiftyTesseractをインポートします。
これによってSwiftyTesseractが提供するメソッド等が利用できるようになります。import SwiftyTesseractSwiftyTesseractのインスタンスを生成
SwiftyTesseractのインスタンスを生成すると同時に、OCRで認識する言語の指定をここで行います。
下記は日本語を認識する場合の記述ですが、tessdataフォルダに含まれる学習データの言語を指定してあげる必要があります。 2let swiftyTesseract = SwiftyTesseract(language: RecognitionLanguage.japanese)OCRの実行
OCRの実行は、単純にsample.pngから生成したUIImageをperformOCR(on:completionHandler:)メソッドへ渡すだけです。
完了ハンドラでは認識された文字列を受け取って処理します。let fileName = "sample.png" guard let image = UIImage(named: fileName) else { return } swiftyTesseract.performOCR(on: image) { recognizedString in guard let text = recognizedString else { return } print("\(text)") // ... 省略 }認識結果を確認する
気になる認識結果です。
1600x900の画像を使ってシミュレータで実行した場合は、1.6秒程度を要しました。
1600x900、シミュレータ実行時の認識結果(tessdata_fast)「ねんきん定期便」「ねんきんネット」に関する 下 ら お問い合わせは、 下記専用番号へ マク 昭 2 sm 0570-058-555 ※O5Oから始まる電話でおかけになる場合は(東京)03-6700-1144 <受付時間> 月 曜日 午前8:30征後7:00 火金曜日 。 午前8:30っ生後5:15 ※祝日、 1 2 月 2 9日1月3日はご利用いただけません。 ※月曜日が祝日の場合は、翌日以降の開所日初日に午後 7 : 00まで 相談をお受けします。 お問い合わせの際は、「ねんきん定期便」 (ヘー 1 ページ) の照会番号、 基礎年金番号または個人番号をお知らせください。 ご利用にあたっての留意事項 ら 0エルは、 一般の固定電話からおかけになる場合は、 全国どこからでも市内通話料金でご利用できます。 7当レ、 ・「O57O」の最初の「O」 を省略したり、 市外局番を付けて間違い電話になるケースが完生しています.。 おかけ間違いのないよう、 ご注意ください。 ・ 月曜日などの休日明けやお手元に通知書が届いた直後 (5日間程度) は、電話がつながりにくくなりのます。 週の後半月の後半はつながりやすくなっています。 ・オンライ ン端末の移働時間によっては、 ご照会の回答が翌日以降になる場合があります。800x450の画像を使ってシミュレータで実行した場合は、1.3秒程度を要しました。
800x450、シミュレータ実行時の認識結果(tessdata_fast)「ねんきん定期便」「ねんきんネット」に関する お問い合わせは、下記専用番号へ トン 回 記 0570-058-555 ※O5Oから始まる電話でおかけになる場合は(東京) 03-6700-1144 g叶) <受付時間> 月 果 日 。 生前Bi30一年径7:00 ※祝日、1 2月29日1月3日はご利用いただけません。 ※月曜日が祝日の場合は、避日以降の開所日補日午後7 : 0 0まで 相談をお受けします。 お問い合わせの隊は、「ねんきん定期便」 (ムー 1 ページ) の照会番号、 基礎年金番号または個人番号をお知らせください。 ご利用にあたっての留意事項 本ルは のたから33 国どこびらでも中衝料金でご利用できます。 ただし、 ・_「O570」の最初の 「O」を省略したり、 市外局番を付けて間違い電話になるケースが発生しています。 おかけ商違いのないよう、 ご注意ください。 ・ 月明日などの休日明けやお手元に運知書が届いた直後 (5日間程度) は、重話がつながりにくくなります。 週の後半月の後半はつながりやすくなっています。 ・オインライン端末の知午時聞によっては、 ご暑会の回答が翌日以降になる場合があります。まとめ
認識精度はお世辞にも実用レベルとは言い難いながらも、ハードル高めな画像を使ったわりには頑張ってくれてる気がします。(個人の感想です)
無料のOCRライブラリで、画像や学習データに手を加えずとも、ここまで認識できるのは凄いことではないでしょうか。
認識範囲を限定したり、前処理で画像を適切に加工したり、学習データをチューニングしたりなど、認識精度をさらに向上させる方法を試す余地も大いにありそうです。 3
今後もSwiftyTesseractの動向を追っていきたいと思います。
おまけ
参考までに、tessdata_bestとtessdataの学習データを使った認識結果も掲載しておきます。
tessdata_bestでの認識結果
1600x900の画像を使ってシミュレータで実行した場合は、認識に5.3秒程度を要しました。
1600x900、シミュレータ実行時の認識結果(tessdata_best)「 ね ん きん 定期 便 」「 ね ん きん ネッ ト 」 に 関す る 三 | お 問い 合わ せ は 、 下 記 専 用 番号 へ や -。 記 2 0570-058-555 ※O050 か ら 始 まる 電話 で お か け に な る 場合 は (東京 ) 09-6 700-1144 mm < 受付 時 間 > 月 曜 日 年 前 9 30 午後 4:00 ※ 祝 日 、1 2 月 2.9 日 1 月 3 日 は ご 利用 いた だ け ま せん 。 ※ 月 曜日 が 祝日 の 場合 は 、 翌 日 以降 の 開所 日 初日 に 午後 7 : 00 ま で 相談 を お 受け し ます 。 お 問い 合わ せ の 際 は 、「 ね ん きん 定期 便 」 (Aー 1 ペー ジ ) の 照会 番号 、 基礎 年 金 番号 また は 個人 番号 を お 知ら せく だ さい 。 ご 利用 に あたっ て の 留意 事項 * ヤル は 、 一 般 の 固定 電話 か ら お か け に な る 場合 は 、 全国 どこ か ら で も 市 内 通話 料金 で ご 利用 で きま す 。 に だ し 、 ・「O570」 の 最初 の 「O」 を 省略 し た り 、 市 外 局 災 を 付け て 問 違 い 電 話 に な る ケ ー ス が 発生 し て いま す 。 お か け 間 違い の な いよ う 、 ご 注意 くだ さい 。 * 月 曜日 な どの 休日 明け や お 手元 に 通知 書 が 届い た 直後 (5 日 間 程度 ) は 、 電 話 が つなが り に くく な の ます 。 週 の 後半 や 月 の 後半 は つなが りや すぐ な っ て いま す 。 * オン ライ ン 端 末 の 稼働 時 間 に よ っ て は 、 ご 照会 の 回 答 が 翌日 以降 に な る 場合 が あり ます 。tessdataでの認識結果
1600x900の画像を使ってシミュレータで実行した場合は、認識に3.0秒程度を要しました。
1600x900、シミュレータ実行時の認識結果(tessdata)` ね ん き ん 定 期 便 」` ね ん き ん ネ ッ ト 」 に 関 す る ミ ロ お 問 い 合 わ せ は 、 下 記 専 用 番 号 ヘ ン シ 許 啓 0⑤⑦0-0⑤⑧-⑤⑤⑤ ※O⑤O か ら 始 ま る 電 話 で お か け に な る 場 合 は ( 東 京 ) 0③-⑥⑦00- ⑪④④ c_-awms < 受 付 時 間 > 月 _ 曜 _ 日 平 前 ⑨ : ③ 0 へ 平 後 ④ : 0 0 ※ 祝 日 、① ② 月 ② ⑨ 日 ー① 月 ③ 日 は ご 利 用 い た だ け ま せ ん 。 * ※ 月 曜 日 が 祝 日 の 場 合 は 、 翌 日 以 降 の 開 所 日 初 日 に 午 後 ⑦ : 0 0 ま で 相 談 を お 受 け し ま す 。 お 問 い 合 わ せ の 際 は 、 ` ね ん き ん 定 期 便 」 (Aー ① ペ ー ジ ) の 照 会 番 号 、 基 礎 年 金 番 号 ま た は 個 人 番 号 を お 知 ら せ く だ さ い 。 ご 利 用 に あ た っ て の 留 意 事 項 * 戸昇乞イ ヤ ル は 、 一 般 の 固 定 電 話 か ら お か け に な る 場 合 は 、 全 国 ど こ か ら で も 市 内 通 話 料 金 で ご 利 用 で き ま す 。 に だ し 、 ・ `0⑤⑦0」 の 最 初 の `O」 を 省 略 し た り 、 外局を付けて間這い電話になるケ ー ス が 発 生 し て い ま す 。 お か け 閾 違 い の な い よ う 、 ご 注 意 く だ さ い 。 ・月鱧貝萱との休日明けやお手元仁通知害か届いた直後 (⑤ 日 間 程 度 ) は 、 電 話 が つ な が り に く く な り ま す 。 週の欝キゆ貝の後半はつなかりやすくなっています。 ・ オ ン ラ イ ン 端 末 の 稼 働 時 間 に よ っ て は 、 ご 照 会 の 回 答 が 翌 日 以 降 に な る 場 合 が あ り ま す 。
TesseractはGoogleによって開発が進められているOCRエンジンです。 ↩
今回は日本語のみ指定していますが、複数の言語を指定することもできます。 ↩
SwiftyTesseractの解説を見ても、予測可能な結果を返すために画像加工などの最適化をほどこすのは、ライブラリ側ではなくアプリ側であるとも受け取れる記述が確認できます。 ↩
- 投稿日:2019-05-07T18:11:42+09:00
Cocoapodsのライブラリ開発における注意点
ライブラリのソースコードを変更してもデバッグ時に反映されない問題
開発環境
- Xcode Version 10.2 (10E125)
- Cocoapods Version 1.6.1
Cocoapodsのライブラリを開発している際、Demoプロジェクトを開発しながらもライブラリのソースをいじりたくなるタイミングがあると思います。
Pods/Development Pods/LibraryNameディレクトリの中にソースがあるので、そのまま変更して問題ないのですが、ソースコードを変更しても実行した際に反映されないと言う問題が起きていました。
その問題を解消した方法を記載します。
Cleanすると反映されるようになりますが、根本的な解決策ではありませんよね。解決方法
Menu bar > File > Workspace settings ...を選択。Build SystemをNew Build System(default)からLegacy Build Systemに変更する。これらの手順を踏むことで、ライブラリのソースコードが反映されるようになります。
めでたしめでたし!
教えていただいた @shoheiyokoyama さんありがとうございました?♂️
- 投稿日:2019-05-07T17:46:36+09:00
【iOS】オープンソースのOCRライブラリ調査
はじめに
iOSで利用可能なオープンソースのOCRライブラリを調査してみました。
前提条件
下記条件で調査しました。
- 無料であること
- オフラインで利用できること
- サーバサイドやクラウドサービスを介さず利用できること
- 手書き文字は考慮しないこと
- 日本語の認識が可能であること
- 日本語学習データの作成 or チューニングが可能なこと
調査結果
以下の3つのライブラリをピックアップしてみました。
Tesseract OCR iOS SwiftOCR SwiftyTesseract OCRエンジン パターン認識 ニューラルネットワーク LSTMニューラルネットワーク 日本語学習データの提供 有 無 有 ライセンス MIT license Apache License,
Version 2.0MIT License 依存ライブラリ Tesseract 3
Leptonica
libpng
libjpeg
libtiffSwift-AI
GPUImage
Union-FindTesseract 4
Leptonica
libpng
libjpeg
libtiffTesseract OCR iOS
OCRエンジンにGoogleが開発を進めているTesseractを採用しているだけあって、情報量が多く人気もあるライブラリです。ただし、Tesseract本体、及び、対応する学習データのバージョンが若干古いため、日本語の認識精度に関しては疑問符が付きます。現在進行系でTesseract最新バージョンの実装を進めている(?)模様。
SwiftOCR
Tesseractよりパフォーマンスに優れる点をアピールしているライブラリです。ただし、短い英数字コードの認識が得意らしく、誌や文を認識したい場合はTesseractが良いと説明しています。日本語を認識するためには学習データを作成する必要があり、トレーニングツールが提供されているとは言え、十分な精度を望むのであれば導入に相応の時間を要しそうです。
SwiftyTesseract
Tesseract OCR iOSに同じく、TesseractをOCRエンジンとして採用しているライブラリです。Tesseract OCR iOSとの違いは最新安定バージョンのTesseractを採用している点で、パフォーマンスと認識精度が大きく向上しています。ただし、Tesseract本体のAPIを完全にはサポートしておらず、利用できない機能があるので注意が必要です。
まとめ
ここ数年で見聞きする機会が多くなった「ディープラーニング」によって、OCRライブラリの文字認識精度が飛躍的に向上している印象を受けました。
調査したライブラリの中では、SwiftyTesseractが最も手軽かつ実用向きではないかと考えます。
他にも有用なライブラリをご存知の方がいらっしゃいましたら、ぜひご教示ください。おまけ
クラウド可かつ課金可まで条件を緩めると、Firebase向けML Kitのテキスト認識(OCR)一択な気がします。
- 投稿日:2019-05-07T11:52:45+09:00
便利一言メモ Swift
Dictionary union(連合)
import Foundation extension Dictionary { func union(other: Dictionary?) -> Dictionary { guard let otherDic = other else { return self } var tmp = self otherDic.forEach { tmp[$0.0] = $0.1 } return tmp } }String isAllHalfWidthCharacter(半角・全角の判定)
import Foundation extension String { var isAllHalfWidthCharacter: Bool { // 半角も全角も1文字でカウント let nsStringlen = self.count let utf8 = (self as NSString).utf8String // Cのstrlenは全角を2で判定する let cStringlen = Int(bitPattern: strlen(utf8)) if nsStringlen == cStringlen { return true } return false }その他にも 参考 https://qiita.com/yusuga/items/f182fa7a60c3f8c774ea
String deleteLastSpace(末尾の空白を除去する)
func deleteLastSpace() -> String { if self == "" { return "" } var text = self var lastCharcter = text.substring(from: text.index(before: text.endIndex)) while (lastCharcter == " " || lastCharcter == " "){ text = text.substring(to: text.index(before: text.endIndex)) if text == "" { break } lastCharcter = text.substring(from: text.index(before: text.endIndex)) } return text } }UIColor 共通化へ
import UIKit import Foundation extension UIColor { class func loginViewBgColor() -> UIColor { return UIColor.rgb(r: 247, g: 181, b: 0, alpha: 1.0) } class func loginButtonColor() -> UIColor { return UIColor.rgb(r: 247, g: 181, b: 0, alpha: 1.0) } class func loginBackWhiteViewShadowColor() -> UIColor { return UIColor.rgb(r: 26, g: 20, b: 6, alpha: 0.08) } class func loginTextFieldColor() -> UIColor { return UIColor.rgb(r: 248, g: 246, b: 243, alpha: 1.0) } class func loginTextFieldBorderColor() -> UIColor { return UIColor.rgb(r: 234, g: 231, b: 226, alpha: 1.0) } class func commentViewBgColor() -> UIColor { return UIColor.rgb(r: 241, g: 238, b: 234, alpha: 1.0) } class func submitButtonColor() -> UIColor { return UIColor.rgb(r: 247, g: 181, b: 0, alpha: 1.0) } class func validationViewBorderColor() -> UIColor { return UIColor.rgb(r: 238, g: 20, b: 47, alpha: 1.0) } class func normalButtonGradationTopColor() -> UIColor { return UIColor.rgb(r: 248, g: 185, b: 0, alpha: 1.0) } class func normalButtonGradationBottomColor() -> UIColor { return UIColor.rgb(r: 238, g: 170, b: 0, alpha: 1.0) } class func reLoadButtonGradationTopColor() -> UIColor { return UIColor.rgb(r: 126, g: 125, b: 124, alpha: 1.0) } class func reLoadButtonGradationBottomColor() -> UIColor { return UIColor.rgb(r: 117, g: 116, b: 114, alpha: 1.0) } class func customButtonTextShadowColor() -> UIColor { return UIColor.rgb(r: 26, g: 26, b: 26, alpha: 0.08) } class func customButtonShadowColor() -> UIColor { return UIColor.rgb(r: 26, g: 26, b: 26, alpha: 0.16) } class func registerTableBorderColor() -> UIColor { return UIColor.rgb(r: 229, g: 225, b: 219, alpha: 1.0) } class func areaLightGrayFontColor() -> UIColor { return UIColor.rgb(r: 208, g: 205, b: 200, alpha: 1.0) } class func areaBlackFontColor() -> UIColor { return UIColor.rgb(r: 26, g: 20, b: 6, alpha: 1.0) } class func backButtonColor() -> UIColor { return UIColor.rgb(r: 161, g: 159, b: 156, alpha: 1.0) } class func blackFontColor() -> UIColor { return UIColor.rgb(r: 50, g: 48, b: 42, alpha: 1.0) } }UIColor 16進数指定のカラー
class func rgbColor(rgbValue: UInt) -> UIColor { return UIColor( red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0, green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0, blue: CGFloat( rgbValue & 0x0000FF) / 255.0, alpha: CGFloat(1.0) ) }UIViewController getTopViewController(トップで表示しているビューコントローラー取得)
import Foundation import UIKit extension UIViewController { class func getTopViewController() -> UIViewController? { var topController = UIApplication.shared.keyWindow?.rootViewController if var tc = topController { while tc.presentedViewController != nil { if let present = tc.presentedViewController { tc = present } } topController = tc } return topController } }UIApplication topViewController(トップで表示しているビューコントローラー取得)
import Foundation import UIKit extension UIApplication { class func topViewController(controller: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? { if let navigationController = controller as? UINavigationController { return topViewController(controller: navigationController.visibleViewController) } if let tabController = controller as? UITabBarController { if let selected = tabController.selectedViewController { return topViewController(controller: selected) } } if let presented = controller?.presentedViewController { return topViewController(controller: presented) } return controller } }UIImage resize(リサイズ)
import UIKit extension UIImage { func resize(size: CGSize) -> UIImage { let widthRatio = size.width / self.size.width let heightRatio = size.height / self.size.height let ratio = (widthRatio < heightRatio) ? widthRatio : heightRatio let resizedSize = CGSize(width: (self.size.width * ratio), height: (self.size.height * ratio)) UIGraphicsBeginImageContext(resizedSize) draw(in: CGRect(x: 0, y: 0, width: resizedSize.width, height: resizedSize.height)) let resizedImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return resizedImage! } }UIView top,bottom,right,left(リサイズ)
import UIKit extension UIView { var top : CGFloat{ get{ return self.frame.origin.y } set{ var frame = self.frame frame.origin.y = newValue self.frame = frame } } var bottom : CGFloat{ get{ return frame.origin.y + frame.size.height } set{ var frame = self.frame frame.origin.y = newValue - self.frame.size.height self.frame = frame } } var right : CGFloat{ get{ return self.frame.origin.x + self.frame.size.width } set{ var frame = self.frame frame.origin.x = newValue - self.frame.size.width self.frame = frame } } var left : CGFloat{ get{ return self.frame.origin.x } set{ var frame = self.frame frame.origin.x = newValue self.frame = frame } } }BaseTextField カスタムテキスト
import UIKit class BaseTextField: UITextField, UITextFieldDelegate { var parentScrollView:UIScrollView? = nil var parentScrollViewDelegate:UIScrollViewDelegate? = nil var doEditExec:(()->Void) = {_ in } func setup(parentScrollView:UIScrollView, parentScrollViewDelegate:UIScrollViewDelegate, didEditExec:@escaping ()->Void = {_ in }) { self.delegate = self self.parentScrollView = parentScrollView self.parentScrollViewDelegate = parentScrollViewDelegate self.doEditExec = didEditExec } func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { Util.enableScrollview(viewDelegate:self.parentScrollViewDelegate!, scrollview: self.parentScrollView!) self.parentScrollView?.contentOffset = CGPoint(x: 0, y: 0) return true } func textFieldShouldReturn(_ textField: UITextField) -> Bool{ textField.resignFirstResponder() self.parentScrollView?.contentSize = CGSize(width: AppConfig.screenWidth, height: 0) doEditExec() return true } } @IBDesignable class CustomTextField: BaseTextField { }今更ながらIBDesignableとIBInspectableについて
https://qiita.com/stastaahaha/items/e45e7559255fb7666fd8PickerTextField カスタムピッカーテキストフィールド
import UIKit class PickerTextField: UITextField, UIPickerViewDelegate, UIPickerViewDataSource { var dataList = [String]() var isInit = false private let leftViewWidth: CGFloat = 15 override func layoutSubviews() { super.layoutSubviews() commonInit() } func setup(dataList: [String]) { self.dataList = dataList let picker = UIPickerView() picker.delegate = self picker.dataSource = self picker.showsSelectionIndicator = true let toolbar = UIToolbar(frame: CGRect(x:0, y:0, width:0, height:40)) let spacer = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.flexibleSpace, target: target, action: nil) let doneItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(PickerTextField.done)) toolbar.setItems([spacer, doneItem], animated: true) self.text = dataList[0] self.inputView = picker self.inputAccessoryView = toolbar } public func numberOfComponents(in pickerView: UIPickerView) -> Int { return 1 } func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int { return 1 } func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { return dataList.count } func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { return dataList[row] } func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { self.text = dataList[row] } func cancel() { self.text = "" self.endEditing(true) } func done() { self.endEditing(true) } func commonInit() { if isInit { return } let leftView = UIView(frame: CGRect(x: 0, y: 0, width: leftViewWidth, height: self.frame.size.height)) self.leftView = leftView self.leftViewMode = UITextFieldViewMode.always isInit = true } }Protocols Response / Parameters
import Foundation protocol Response { var data: AnyObject { get } init(data: AnyObject?) throws }import Foundation protocol Parameters { init(values: [String:AnyObject]) func toDictionary() -> [String:AnyObject] } protocol Parameter { init(value: AnyObject) func toAny() -> AnyObject }Responses Responses
import Foundation class BaseResponse: Response { // レスポンスデータ保持用 var data: AnyObject /** コンストラクタ - parameter data: レスポンスデータ */ required init(data: AnyObject?) throws { self.data = data! } }Requests Requests
import Foundation class BaseRequest: Parameters { required init(values: [String : AnyObject]) { } func toDictionary() -> [String:AnyObject] { return [:] } }
- 投稿日:2019-05-07T10:55:57+09:00
【学習記録17】2019/5/7(火)
学習時間
4.0H
使用教材
・改訂新版Swift実践入門 ── 直感的な文法と安全性を兼ね備えた言語 WEB+DB PRESS plus
学習分野
第3章制御構文
第4章関数とクロージャコメント
学習開始からの期間:17日目
今日までの合計時間:50.5H
- 投稿日:2019-05-07T10:10:07+09:00
SwiftGenとR.swift 導入するにあたって
はじめに
SwiftGenとR.swiftはiOS開発上でリソース管理を楽にできるライブラリ。というより、ツールと言った方がしっくりくるかと思います。
コードに存在するハードコーディングをなくし、運用保守しやすくなります。
そして今回弊社の新規プロジェクトでどちらか採用しようと考え、いろいろ比較したのをまとめます。対応しているリソース比較
以下のように対象プロジェクトの欲しい部分は両方ありました。
プロジェクトによってはCoreData扱えるSwiftGenは良いですね。(CoreData扱ったことないので、詳しくわかりませんがw)
SwiftGen R.swift image ◯ ◯ font ◯ ◯ color ◯ ◯ localize string ◯ ◯ storyboard ◯ ◯ segue ◯ ◯ nib ◯ ◯ reusable cell ◯ ◯ core data ◯ × plist ◯ △(※1) file △(※2) ◯ ※1. File読み込みまでは簡潔にできる。
※2. jsonとyamlは設定できる。導入比較
Xcodeの設定上でBuild Phaseに各々のshellを追加し、走らせる点は同様ですね。
両者の導入方法を見てみましょう。SwiftGen
https://github.com/SwiftGen/SwiftGen/blob/master/README.md#installation
以下の
swiftgen.ymlファイルでカスタマイズできますので、汎用性が高いですね。swiftgen.ymlstrings: inputs: Resources/Base.lproj filter: .+\.strings$ outputs: - templateName: structured-swift4 output: Generated/strings.swift xcassets: inputs: - Resources/Images.xcassets - Resources/MoreImages.xcassets outputs: - templateName: swift4 output: Generated/assets-images.swift加えてbrewでも用意されているため、xcodeでいちいちビルドして、リソースファイル生成という手間がありません。
$ brew update $ brew install swiftgenR.swift
https://github.com/mac-cain13/R.swift/blob/master/README.md#cocoapods-recommended
特に設定ファイルもなく、ライブラリをプロジェクトに導入し、Build Phasesで設定し、ビルドすれば、リソースファイルが生成されます。
一律
Rprefixで呼び出せるのは一意性があって、良いですね。まとめ
SwiftGen
- カスタマイズできる
- コマンド実行でき、ビルドの手間が省ける
- CoreDataが扱える
R.swift
- 導入が簡単
- カスタマイズできないため、プロジェクト間の揺れはない
- 対応リソース間の呼び出し方に揺れがない
結局今回のプロジェクトでは知見がないSwiftGenの方をお試し採用しました。
今回は導入するにあたっての比較となりましたが、いかがでしょうか?ぜひ皆さんの今後の開発のてだすけになれば、幸いです。
- 投稿日:2019-05-07T00:46:56+09:00
Swift:最前面にあるアプリケーションが何かを監視する
背景
macOS向け常駐アプリを作っていて,最前面(frontmost)にあるアプリが何であるかを監視し続けたくなった.これまでタイマーを使って定期的に最前面のNSWindowのオーナーを調べるという方法を取っていたが,最前面のアプリが切り替わったタイミングのみで監視対象の更新を行いたかった.
実装
Appdelegate.swift@NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate { func applicationDidFinishLaunching(_ aNotification: Notification) { NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(check(_:)), name: NSWorkspace.didActivateApplicationNotification, object: nil) } @objc func check(_ notification: NSNotification) { guard let name = NSWorkspace.shared.frontmostApplication?.localizedName else { return } Swift.print(name) } }
NSWorkspace.didActivateApplicationNotificationの通知タイミングで最前面のアプリケーションを調べれば良い.








