- 投稿日:2020-03-29T20:24:20+09:00
[iOS]Tesseract OCRで認識させた日本語の情報を取得する
※ これは 2018/4/8,16 に個人ブログへ投稿した内容をまとめて転記したものです。
Google が出資しているという文字認識 API の iOS 用ラッパー、 Tesseract OCR iOS を使って文字認識をさせてみた備忘録です。
環境: macOS HighSierra, MacBook Pro 2016, XCode9.3準備
本体のリポジトリはここ。ただし、 CocoaPods から読ませるので、直接 Clone しない。
CocoaPods が入ってない場合は、まず CocoaPods をインストール。
終わったら下記の記事を参考にインストールしていく。
Xcode 7.0 + Swift2 でTesseract-OCR-iOSを使う(追記あり) - 今日も微速転進動かしてみる
テストコードは公式 Wiki の方を使用した。シミュレータで動かす分にはこちらのほうがシンプルで良い。
上の記事でも言っているとおり、 BridgingHeader はいらないので惑わされないように1
Tesseract OCR iOS の中身は Tesseract3.03 らしく、最新2の学習データ (tessdata) は使えない。
Github で公開されている 3.04 か、 Wiki からそれ以前のものを選んで使う。英語版 (eng.tessdata) は 3.04 そのままでも動くが、日本語版 (jpn.tessdata) は 3.04 を使うとエラーが出る。
read_params_file: parameter not found: allow_blob_divisionこれを解決するために、下記に従って学習データ内のパラメータを変更するのだが、このときビルドに使うソースは3.04を使うこと。
4.00 だと加工後の tessdata を読み込んでくれない。それから、
./configure
にパラメータをつけると私の環境では失敗してしまったので、パラメータをなくしたら上手くいった。
これはこの記事を読みながら事前にbrew link icu4c --forceしておいたからかも。
./autogen.sh ./configure make sudo make install make training sudo make training-installなお、一度 4.00 をインストールしてしまったときは、 make したディレクトリで
sudo make uninstall sudo make training-uninstallしたらアンインストールできた…気がする。
日本語を認識させてみる
日本語を認識させて結果の情報を取得してみました。
ソース
Swift 初心者なので、 API リファレンスを見ながら、 Xcode に言われるがまま書いたソースはこちら。
結果
あえてちょっと斜めにしてみましたが、いい感じに認識していますね!
技術的なメモ
認識結果とその表示
let box = block.boundingBox(atImageOf: actualImageRect.size)Tesseract では元画像のサイズを 1×1 としたときの相対位置で対象 (文字) の座標を認識しているようです。
なので、デバイスの画面に収まるように画像を自動レイアウトした場合は、レイアウト後のサイズを取得し、それをatImageOf
に指定して変換してもらう必要があります。
レイアウト後の画像の実サイズを取得する Extension、UIImageView.ContentClippingRect
は下記より。
How to find an aspect fit image’s size inside an image viewExtensionって最高ですね…!
文字のまとまり
let blocks = tesseract.recognizedBlocks(by: G8PageIteratorLevel.symbol)
G8PageIteratorLevel
で認識結果のまとまりの大きさを指定できます。
意味深な定数名が付いていますが、もちろん日本語の形態素解析をして云々なんてしていないので、一通り試してみました。block: ブロック。段落を複数認識したら、そのまとまりということかな?
- 投稿日:2020-03-29T18:46:10+09:00
AlertViewを表示する(ダイアログ的なやつ)
ボタンを押した時に下から出てくるやつを表示するコード
ViewController.swiftimport UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() } private func popUp() { let alertController = UIAlertController(title: "確認", message: "本当に実行しますか", preferredStyle: .actionSheet) let yesAction = UIAlertAction(title: "はい", style: .default, handler: nil) alertController.addAction(yesAction) let noAction = UIAlertAction(title: "いいえ", style: .default, handler: nil) alertController.addAction(noAction) let cancelAction = UIAlertAction(title: "キャンセル", style: .cancel, handler: nil) alertController.addAction(cancelAction) present(alertController, animated: true, completion: nil) } @IBAction func doButtonTap(_ sender: Any) { popUp() } }豆知識
comd + shift + l(エル)
でlibraryを表示できる。
- 投稿日:2020-03-29T18:11:20+09:00
改行コードを含む文字列から改行コードを取り除いて一行の文字列を取得する
- 投稿日:2020-03-29T17:54:43+09:00
UserDefaultsのキーを管理
同じKey名を設定したつもりでもtypoしてしまっていてKeyに期待結果が入ってないなんてことはあるので
enumで管理するにはどうすればいいかまとめておこうと思いましたが当然すでにまとめられていたのでその記事のリンクを貼って終わりにしますhttp://glassonion.hatenablog.com/entry/2016/12/07/140529
これみて自分もとりあえず必要な分だけ作りました
enum UserDefaultsSettings: String { case hogehoge func set(value: Any) { UserDefaults.standard.set(value, forKey: rawValue) } func object() -> Any? { return UserDefaults.standard.object(forKey: rawValue) } // 該当のKeyに対応するvalueがnilじゃないか func hasValue() -> Bool { return self.object() != nil } }あとはUserDefaultsを使って色々なところで値を保存したりしていると使用する場所が散らばって値の変化を追いづらくなるみたいなのでその辺もあとで調べながらまとめていきたい
DataStoreみたいなところで実際のset,object等のUserDefautlsの処理をまとめておけばいい気もするけど
- 投稿日:2020-03-29T17:14:28+09:00
Result型を使ってエラーをスマートにハンドリングする方法(Swift)
はじめに
2020/03/29現在、Swiftではスローするエラーの型を指定できません。
そのため、スローするエラーの型が決まっていたとしても、すべてのエラーをキャッチする必要があります。
MyError
というエラーの型のみを返すmyFunc(isSuccess:)
メソッドを例に考えてみます。
(この例ではMyError.bar
は返ることがないのですが、返る可能性があると考えてください)enum MyError: Error { case foo(_ message: String) case bar(_ message: String) } func myFunc(isSuccess: Bool) throws -> String { if isSuccess { return "Success!!" } else { throw MyError.foo("Foo") } }
myFunc(isSuccess:)
メソッドを呼び出します。
エラーの型をMyError
だと決めていますが、一番下に} catch {
を書いてすべてのエラーをキャッチする必要があります。beforedo { let result = try myFunc(isSuccess: false) // `result` を使う処理 } catch MyError.foo(let message) { // エラーハンドリング } catch MyError.bar(let message) { // エラーハンドリング } catch { // !!!: 絶対に入らないけど必要 }もし一番下の
} catch {
を書いていないと、以下のビルドエラーが発生します。Errors thrown from here are not handled because the enclosing catch is not exhaustive ここからスローされたエラーは、囲んでいるキャッチが完全ではないため処理されませんSwitch文で
default:
をできる限り書かないようにするのと同様、絶対に入らないとわかっているエラーに対してハンドリングしたくありません。
絶対に入らないことを明確にするため、fatalError(_:)
を使っている人も多いと思います。} catch { fatalError("Unexpected error: \(error).") }いずれにせよスマートではないように感じます。
Twitterでスマートな方法を教えていただいたので紹介します。環境
- OS:macOS Mojave 10.14.6
- Swift:5.1.3
- Xcode:11.3.1 (11C504)
結論
先に結論を述べます。
Result.init(catching:)
とResult.mapError(_:)
を使って実現します。afterlet result = Result { try myFunc(isSuccess: false) } .mapError { $0 as! MyError } switch result { case .success(let string): // `string` を使う処理 case .failure(let error): switch error { case .foo(let message): // エラーハンドリング case .bar(let message): // エラーハンドリング } }こうすることで、
MyError
型のみをハンドリングできるようになりました。
エラーを列挙型で定義している場合、Switch文が使えるので、エラーのハンドリング漏れも防げます。解説
おそらくSwiftに慣れていても、上記のような書き方を知らない人は多いと思います。
少なくとも私は知りませんでした。1つずつ解説します。
Result.init(catching:)
まずは1行目です。
let result = Result { try myFunc(isSuccess: false) }こちらは
Result
型のイニシャライザを、トレイリングクロージャを使って呼び出しています。
Result
型にはResult.init(catching:)
という、スローするクロージャを引数に持つイニシャライザがあります。
クロージャの処理が成功した場合は.success
を返し、クロージャの戻り値がAssociated Valueに代入されます。
失敗した場合は.failure
を返し、エラーがAssociated Valueに代入されます。
つまり、今回はmyFunc(isSuccess:)
がString
を返すため、Result<String, Error>
という型が生成されます。Result.mapError(_:)
次に2行目です。
.mapError { $0 as! MyError }こちらは失敗時のエラーの型をマップ(変換)するメソッドです。
Result.init(catching:)
で生成した型はResult<String, Error>
です。
mapError(_:)
でError
型をMyError
型に変換し、Result<String, MyError>
を生成します。ここまでできたら、あとは通常の
Result
型と同じように使えます。switch result { case .success(let string): // `string` を使う処理 case .failure(let error): switch error { case .foo(let message): // エラーハンドリング case .bar(let message): // エラーハンドリング } }おまけ:なぜエラーの型を指定できないか考える
個人的な意見ですが、例えば
throws
の後ろにエラーの型を記述し、スローするエラーの型を指定できると使いやすいと思いました。func myFunc(isSuccess: Bool) throws MyError -> String {なぜこれが実装されないか考えたのですが、おそらく普通に実装しても様々なエラーが発生し得るからだと思いました。
自分ではMyError
型しかスローしていないつもりでも、実装ミスで他のエラーがスローされる可能性は0ではありません。
実際、今回紹介した方法もas! MyError
として強制キャストすることで実現しています。おわりに
これでエラーをスマートにハンドリングできるようになったと思います!
GitHubにサンプルのplaygroundを上げたので、よかったら実行してみてください。
https://github.com/uhooi/SwiftErrorResultSample/blob/master/ErrorResult.playground/Pages/Sample.xcplaygroundpage/Contents.swift他にもいい方法がありましたら、コメントなどで教えていただけると嬉しいです。
参考リンク
- 投稿日:2020-03-29T16:47:06+09:00
UIViewの背景にグラデーションを設定する
前回作ったチュートリアルのFirstVC,SecondVC,ThirdVCの各画面の背景にグラデーションを設定してみます
実装
グラデーションされたCAGradientLayerを返すメソッドを実装
import UIKit extension CAGradientLayer { static func layerForView() -> CAGradientLayer { let gradientLayer = CAGradientLayer() gradientLayer.colors = [UIColor.red.cgColor, UIColor.blue.cgColor] // 設定した各グラデーションカラーのstopポイントを決めている? // これを設定しないとおそらく設定したカラーが画面全体に均一の割合で表示される gradientLayer.locations = [0.0, 0.6] // startPointとendPointを決めることでグラデーションをかける方向を決めている? gradientLayer.startPoint = CGPoint(x: 0, y: 0) gradientLayer.endPoint = CGPoint(x: 0, y: 1) return gradientLayer } }前回作ったチュートリアルの全3画面にグラデーションをかける
import UIKit class TutorialContainerViewController: UIViewController { enum Page:Int { case first case second case third } // MARK: - IBOutlet @IBOutlet weak var nextButton: UIButton! // MARK: - internal var didSelectButtonToHome: (() -> Void)? // MARK: - private var currentPage = Page.first private lazy var pageViewController: UIPageViewController = { // このViewControllerが持つpageViewControllerを取得 let pageVC = children.first as! UIPageViewController return pageVC }() override func viewDidLoad() { super.viewDidLoad() pageViewController.setViewControllers([getFirst()], direction: .forward, animated: true, completion: nil) } @IBAction func tappedButton(_ sender: UIButton) { if currentPage == .first { pageViewController.setViewControllers([getSecond()], direction: .forward, animated: true, completion: nil) currentPage = .second nextButton.isEnabled = false return } if currentPage == .third { didSelectButtonToHome?() } } } extension TutorialContainerViewController { private func getFirst() -> FirstViewController { let vc: FirstViewController = storyboard!.instantiateViewController(identifier: "FirstViewController") let gradientLayer = CAGradientLayer.layerForView() // gradientLayerは生成されただけだとまだフレームサイズが決まってなくてx:0, y:0 ,height:0 ,width: 0 のままなのでinsertSublayerしても表示されないためフレームを決めてあげる必要がある gradientLayer.frame = vc.view.frame // layerのsublayersno0番目にgradientLayerを差し込む vc.view.layer.insertSublayer(gradientLayer, at: 0) ← NEW return vc } private func getSecond() -> SecondViewController { let gradientLayer = CAGradientLayer.layerForView() let vc: SecondViewController = storyboard!.instantiateViewController(identifier: "SecondViewController") gradientLayer.frame = vc.view.frame vc.view.layer.insertSublayer(gradientLayer, at: 0) ← NEW // SecondViewControllerでボタンがタップされた通知が飛んでくる // {}の中身はその通知が飛んできた時に呼ばれる内容だから、getSecond()が呼ばれても{}の中身は呼ばれない vc.selectedButtonToThird = { self.nextButton.isEnabled = true self.pageViewController.setViewControllers([self.getThird()], direction: .forward, animated: true, completion: nil) } return vc } private func getThird() -> ThirdViewController { let vc: ThirdViewController = storyboard!.instantiateViewController(identifier: "ThirdViewController") let gradientLayer = CAGradientLayer.layerForView() gradientLayer.frame = vc.view.frame vc.view.layer.insertSublayer(gradientLayer, at: 0) ← NEW return vc } }gradientLayerのフレームを決めないで表示されなかったり、insertSublayerではなくてaddSublayerしてLabel等のオブジェクトが見えなくなるのはありがちな気がする
*insertSublayerについてはよくわかってなかったけど、下の画像をみてなんとなく理解した.UIViewは必ずCALayerを持っていて、CALayerはその下にさらにsublayerを持つことができる。UIButtonやUILabelといったオブジェクトはaddSubされるものなのでレイヤー的にはSublayerの下にあるみたい。だからaddSublayerすることで背景色が変わったように見えるんだと思う
参考: https://thoughtbot.com/blog/building-ios-interfaces-custom-buttonシュミレーターで確認すると無事グラデーションがかかって表示された
1画面目 2画面目 3画面目 改善
毎回CAGradientLayerインスタンスを生成するコードを書いて、毎回gradientLayerのフレーム決めるコード書いて、毎回addSublayerするコードを書くのは面倒なので
表示する各画面をcontentsVCsみたいなUIViewControllerの配列にあらかじめ入れておいてforEachで回して設定するやり方もできると思いますcontentsVCs.forEach { let gradientLayer = CAGradientLayer.layerForView() gradientLayer.frame = $0.view.frame $0.view.layer.insertSublayer(gradientLayer, at: 0) }ただこのやり方も最初のやり方もそうですが、親であるTutorialContainerViewControllerがこねくりましたり、TutorialContainerViewController内で生成された各VCのプロパティに設定したりとTutorialContainerViewController内でViewのグラデーションの設定を行なっています
でも実際はViewの描画はView自体の責務であり、ViewControllerは知る必要がありません
なので、こういう時はカスタムViewを作ってそこでグラデーションをかける。親であるTutorialContainerViewControllerではただそのViewを使うだけにした方がいいかと思うので修正します
まずは先ほど追加したコードの削除
新しくUIViewのカスタムクラスとしてGradationViewを作成import UIKit class GradationView: UIView { // オートレイアウトでレイアウトが決まってからgradientLayerのフレームの位置・サイズを決めないとgradation.frameの各値が0になるからlayoutSubview内で設定する必要がある override func layoutSubviews() { super.layoutSubviews() let gradientLayer = CAGradientLayer.layerForView() gradientLayer.frame = self.frame self.layer.insertSublayer(gradientLayer, at: 0) } }あとはこのカスタムViewのStoryboard上でFirstVC,SecondVC,ThirdVCのそれぞれのViewに設定すれば完了
*SecondVC, ThirdVCにも同じように設定する追記
storyboardContainerViewControllerで新しいViewを作ってその上にボタンをaddSubviewするように修正しました
参考
https://qiita.com/Gutenberg0x/items/ac737fb3305da3389bf9
https://thoughtbot.com/blog/building-ios-interfaces-custom-button
- 投稿日:2020-03-29T15:14:45+09:00
Swift MkMapViewで地図アプリ作成してみた(21)- 周辺検索した地点をセミモーダルビューに表示する
記事一覧
Swift MkMapViewで地図アプリ作成してみた(記事一覧)
周辺検索した地点をセミモーダルビューに表示する
これを作ります
FloatingPanelをインストールする
swift単体では実装できません。
簡単な方法を探した結果、CocoaPodsで'FloatingPanel'をインストールすることにしました。
ターミナルから下記を実行してインストールする。pod 'FloatingPanel'モーダルビューのデザインパターンを設定する
FloatingPanelLayoutを継承したクラスを作成し、モーダルビューのデザインパターンを設定する。
insetForで、フリックした時のモーダルビューの表示領域を設定する。import Foundation import FloatingPanel class CustomFloatingPanelLayout: FloatingPanelLayout { // セミモーダルビューの初期位置 var initialPosition: FloatingPanelPosition { return .half } var topInteractionBuffer: CGFloat { return 0.0 } var bottomInteractionBuffer: CGFloat { return 0.0 } // セミモーダルビューの各表示パターンの高さを決定するためのInset func insetFor(position: FloatingPanelPosition) -> CGFloat? { var ret: CGFloat! switch position { case .full: ret = nil//56.0 case .half: ret = 262.0 case .tip: ret = 75.0 default: ret = nil } return ret } // セミモーダルビューの背景Viewの透明度 func backdropAlphaFor(position: FloatingPanelPosition) -> CGFloat { return 0.0 } }作成中ですので、記事を後日更新します。
- 投稿日:2020-03-29T15:12:54+09:00
ARKit 3.5のScene Reconstructionサンプルのコードを読む
ARKit 3.5とLiDAR搭載の新型iPad Proが出ましたね。これらを試せるAppleの公式サンプル「Visualizing and Interacting with a Reconstructed Scene」のソースコードを読んでみたメモです。
どんな感じのサンプルかはこちらのツイートの動画がよくわかります。
First look at the iPad Pro LiDAR Scanner pic.twitter.com/kwkl1YBy2n
— Tim Field (@nobbis) March 25, 2020Scene Understandingの設定
ARView.Environment.SceneUnderstanding
構造体のoptions
(型はARView.Environment.SceneUnderstanding.Options
)に.occlusion
と.physics
を追加することでオクルージョンと物理計算を有効化。arView.environment.sceneUnderstanding.options = [] // Turn on occlusion from the scene reconstruction's mesh. arView.environment.sceneUnderstanding.options.insert(.occlusion) // Turn on physics for the scene reconstruction's mesh. arView.environment.sceneUnderstanding.options.insert(.physics)
SceneUnderstanding
オブジェクトはARView
のenvironment
プロパティ(型はARView.Environment
)が持つsceneUnderstanding
プロパティにセットする。https://developer.apple.com/documentation/realitykit/arview/environment/sceneunderstanding/options
デバッグ用にメッシュを可視化
公式サンプルではメッシュが深度に応じて色分けされた見事なビジュアライゼーションが実現されている。
どうやっているのかというと、
ARView
のdebugOptions
プロパティに.showSceneUnderstanding
を追加するだけ。arView.debugOptions.insert(.showSceneUnderstanding)ちなみに
ARSCNView
ではこれができない。Appleの公式サンプルのかっちょいいメッシュのビジュアライゼーションも、ARView.DebugOptionsには追加されたshowSceneUnderstandingというオプションで実現されている。
— Shuichi Tsutsumi (@shu223) March 28, 2020
これに相当するオプションがARSCNViewには追加されていない?ARKit+SceneKit路線は今後メンテされないと思ったほうがよさそう。 pic.twitter.com/IXWvP3QpqVScene Reconstructionの有効化
ARView
の自動設定をオフにし、ARWorldTrackingConfiguration
のsceneReconstruction
プロパティに.meshWithClassification
を指定する。デフォルト以外に.none
と.mesh
(メッシュのClassificationを行わない)がある。arView.automaticallyConfigureSession = false let configuration = ARWorldTrackingConfiguration() configuration.sceneReconstruction = .meshWithClassification configuration.environmentTexturing = .automatic arView.session.run(configuration)タップでClassification結果を可視化
Classificationというのはそのメッシュが壁なのか床なのかテーブルなのか、という分類を示す情報で、ARMeshClassificationというenumで定義されている。
public enum ARMeshClassification : Int { case none = 0 case wall = 1 case floor = 2 case ceiling = 3 case table = 4 case seat = 5 case window = 6 case door = 7 }https://developer.apple.com/documentation/arkit/armeshclassification
公式サンプルでは、画面(
ARView
)タップでraycastによる(.estimatedPlane
との)当たり判定を行い、classificationの判定結果をテキスト表示している。if let result = arView.raycast(from: tapLocation, allowing: .estimatedPlane, alignment: .any).first { let resultAnchor = AnchorEntity(world: result.worldTransform) ... }ここらへんのコードが実はおもしろくて、ここまで簡単にScene Reconstructionやメッシュの可視化ができるのでClassificationの結果も
ARMeshAnchor
のプロパティから取り出して終わりでしょ、と思いきや、そんなプロパティはなく、意外とめんどくさい処理をやっている。https://developer.apple.com/documentation/arkit/armeshanchor
ARMeshAnchorからARMeshClassificationを得る
具体的には
ARMeshAnchor
のgeometry
プロパティに入っているARMeshGeometry
オブジェクトのclassification
プロパティから取り出しているのだが、これがまだARMeshClassification
型ではなくて、ARGeometrySource
型なのである。var vertices: ARGeometrySourcehttps://developer.apple.com/documentation/arkit/armeshgeometry
で、
ARGeometrySource
というのが割とめんどくさいクラスで、そのデータをMTLBuffer
というMetalのバッファに保持している。open class ARGeometrySource : NSObject, NSSecureCoding { /** A Metal buffer containing per-vector data for the source. */ open var buffer: MTLBuffer { get } ... }https://developer.apple.com/documentation/arkit/argeometrysource
サンプルではこのバッファをGPUではなくCPUでほじくり、とりだした生の数値から
ARMeshClassification
を初期化している。extension ARMeshGeometry { ... /// To get the mesh's classification, the sample app parses the classification's raw data and instantiates an /// `ARMeshClassification` object. For efficiency, ARKit stores classifications in a Metal buffer in `ARMeshGeometry`. func classificationOf(faceWithIndex index: Int) -> ARMeshClassification { guard let classification = classification else { return .none } assert(classification.format == MTLVertexFormat.uchar, "Expected one unsigned char (one byte) per classification") let classificationPointer = classification.buffer.contents().advanced(by: classification.offset + (classification.stride * index)) let classificationValue = Int(classificationPointer.assumingMemoryBound(to: CUnsignedChar.self).pointee) return ARMeshClassification(rawValue: classificationValue) ?? .none }タップ位置に近いメッシュのfaceを抽出
前項の処理で説明してないことがあって、
classificationOf(faceWithIndex:)
メソッドではfaceのindexを引数に渡し、そのfaceについてのclassifiation結果を取り出している。メッシュの中にたくさんある中で、タップした位置に近いfaceを取り出してそのclassification結果を可視化しているわけだ。
その「タップした位置に近い(5cm以内)場所にあるfaceを取り出す」実装はこうなっている。
for anchor in meshAnchors { for index in 0..<anchor.geometry.faces.count { // Get the center of the face so that we can compare it to the given location. let geometricCenterOfFace = anchor.geometry.centerOf(faceWithIndex: index) // Convert the face's center to world coordinates. var centerLocalTransform = matrix_identity_float4x4 centerLocalTransform.columns.3 = SIMD4<Float>(geometricCenterOfFace.0, geometricCenterOfFace.1, geometricCenterOfFace.2, 1) let centerWorldPosition = (anchor.transform * centerLocalTransform).position // We're interested in a classification that is sufficiently close to the given location––within 5 cm. let distanceToFace = distance(centerWorldPosition, location) if distanceToFace <= 0.05 { ... } } }この計算をするためには、
ARMeshGeometry
のvertices
プロパティとfaces
プロパティを使用する必要があり、これまたARGeometrySource
型。Metalバッファから数値を読み出すために、ARMeshGeometry
のextensionとして次のようなメソッドが実装されている。extension ARMeshGeometry { func vertex(at index: UInt32) -> (Float, Float, Float) { assert(vertices.format == MTLVertexFormat.float3, "Expected three floats (twelve bytes) per vertex.") let vertexPointer = vertices.buffer.contents().advanced(by: vertices.offset + (vertices.stride * Int(index))) let vertex = vertexPointer.assumingMemoryBound(to: (Float, Float, Float).self).pointee return vertex } ... func vertexIndicesOf(faceWithIndex faceIndex: Int) -> [UInt32] { assert(faces.bytesPerIndex == MemoryLayout<UInt32>.size, "Expected one UInt32 (four bytes) per vertex index") let vertexCountPerFace = faces.indexCountPerPrimitive let vertexIndicesPointer = faces.buffer.contents() var vertexIndices = [UInt32]() vertexIndices.reserveCapacity(vertexCountPerFace) for vertexOffset in 0..<vertexCountPerFace { let vertexIndexPointer = vertexIndicesPointer.advanced(by: (faceIndex * vertexCountPerFace + vertexOffset) * MemoryLayout<UInt32>.size) vertexIndices.append(vertexIndexPointer.assumingMemoryBound(to: UInt32.self).pointee) } return vertexIndices } func verticesOf(faceWithIndex index: Int) -> [(Float, Float, Float)] { let vertexIndices = vertexIndicesOf(faceWithIndex: index) let vertices = vertexIndices.map { vertex(at: $0) } return vertices } func centerOf(faceWithIndex index: Int) -> (Float, Float, Float) { let vertices = verticesOf(faceWithIndex: index) let sum = vertices.reduce((0, 0, 0)) { ($0.0 + $1.0, $0.1 + $1.1, $0.2 + $1.2) } let geometricCenter = (sum.0 / 3, sum.1 / 3, sum.2 / 3) return geometricCenter } }この実装はたぶんめちゃくちゃ多くの人がコピーして使うと思う。サンプルの鑑。
関連
- 投稿日:2020-03-29T14:40:18+09:00
[悲報]Xcode 11.4 でナビゲーションバーのタイトルの色が黒に固定される(*条件あり)
それはXcodeをアップデートした直後のことでした・・・
俺氏「ん?!ナビゲーションタイトルの文字が黒にしかならんやんけ?」
発生条件
- navigation controllerのBar Tintを変更している場合
上記場合に、Title Colorを変更しても、タイトルの色は黒固定になっちゃいます。
Bar Tintをデフォルトにしとけばタイトルカラーを変更できます。
デフォルトカラー以外では変更できないから超不便?
解決法
- 多分Xcode11.4のバグ、アップデートを待ちましょう←
- Xcode11.3.1にダウングレードする(多分これがいい(小並感))
- おーばーふろーで無理やり変更してる人もいた
それではみなさん楽しいバグライフを!?
- 投稿日:2020-03-29T14:40:18+09:00
【悲報】Xcode 11.4 でナビゲーションバーのタイトルの色が黒に固定される(*条件あり)
それはXcodeをアップデートした直後のことでした・・・
俺氏「ん?!ナビゲーションタイトルの文字が黒にしかならんやんけ?」
発生条件
- storyboardでnavigation controllerのBar Tintを変更している場合
上記場合に、Title Colorを変更しても、タイトルの色は黒固定になっちゃいます。
Bar Tintをデフォルトにしとけばタイトルカラーを変更できます。
デフォルトカラー以外では変更できないから超不便?
解決法
- 多分Xcode11.4のバグ、アップデートを待ちましょう←
- Xcode11.3.1にダウングレードする(多分これがいい(小並感))
- おーばーふろーで無理やり変更してる人もいた
それではみなさん楽しいバグライフを!?
- 投稿日:2020-03-29T14:26:31+09:00
Swift MkMapViewで地図アプリ作成してみた(20)- 受信精度の悪いGPSデータを間引く
記事一覧
Swift MkMapViewで地図アプリ作成してみた(記事一覧)
受信精度の悪いGPSデータを間引く
GPSの受信鮮度
あまり該当するケースは存在しないが、GPS鮮度がtimeInterval秒過ぎていた場合、読み捨てる。
if timeInterval <= Int(abs(locations.last!.timestamp.timeIntervalSinceNow)) { return }水平誤差
水平誤差がaccuracyメートルを越えた場合、読み捨てる。
if accuracy < Int(locations.last!.horizontalAccuracy) { return }緯度経度が無効
水平誤差が負の値の場合、緯度経度が無効なので読み捨てる。
if 0 > locations.last!.horizontalAccuracy { return }
- 投稿日:2020-03-29T14:10:40+09:00
【初心者向け】【まとめ】iOSアプリを作ろうとしている方がまず見るべきQiitaの記事をまとめてみました【Swift】
iOSアプリのためのまとめ記事が欲しかった
iOSエンジニアになって早8ヶ月。
最近ようやく慣れてきたものの、初めは実装するのにとっても時間がかかりました。
というのは、実装についてまとまったオールインワンの記事があまりなかったことがもしかしたら原因なのかもと思っています。(自分でもっと勉強しろって話かもしれないですが…)
なので、今回は、自身が開発をしていく上で参考にした記事をまとめてみたいと思います。
これからiOS開発をする方に参考になればと思っています。
参考になるかもしれないソースコード
この記事を作っていくにあたって、以下の記事を参考にして実装したものを載せています。
これを見ながら記事と共に勉強していただければ幸いです。
https://github.com/taichi6930/iOSAppBase/tree/master
早速見るべき記事
ライフサイクル
iOSアプリがどのように動いているか、といった感じです。
作る際にここがわかっていないと「??」となりまくってしまいます(自分がそうでした)。アプリ作成前に一度読んでおくと良いと思います。
iOSアプリのライフサイクル
https://qiita.com/KenNagami/items/766d5f95940c76a8c3cdUIViewControllerのライフサイクル
https://qiita.com/motokiee/items/0ca628b4cc74c8c5599dAppDelegate,UIViewController,UIViewのライフサイクル/iOS/Swift
https://qiita.com/kayo311/items/4710c4ac02a191652a96iosアプリ ViewControllerのライフサイクル
https://qiita.com/usutan/items/71760df10e8523166babページ遷移について
ページ間での移動についてです。
ここも全然分からなかったので、最初に読んでおいた方がいいと思います。
(ページ遷移しないと、ただのワンページアプリになってしまうのでw)同じ/異なるStoryboardでの画面遷移
https://qiita.com/kedarui/items/97b5cc1410d9c61933d5Swiftのページ遷移【Navigation Controller】
https://qiita.com/ryu1_f/items/4a0e452e94c9ba609220TableView
画像のようにcellが縦方向に続いていくViewです。
自身のソースコードではセルの生成とタップ時の処理を記載しました。
(例が少ないので足していく予定です…)参考になった方々の記事です↓
SwiftでTableViewを使ってみよう
https://qiita.com/pe-ta/items/cafa8e20029047993025UITableViewの使い方 【Swift4.2 , Xcode10】
https://qiita.com/abouch/items/3617ce37c4dd86932365UITableViewのデリゲートメソッドまとめ
https://qiita.com/kagemiku/items/22b74010365723c5c4feCollectionView
画像のようにcellが横方向に続いていくViewです。
自身のソースコードでは、セルの生成とタップ時の処理を記載しています。
TableViewと同様に作成すれば良かったので、まずはTableViewを作成してみてください!
WebView
アプリ内でWebページが見れるViewです。
今回はWKWebViewを使用して作成しました。
cookie処理などは入れていないので、今後行っていければいいなと思っています。
参考になった方々の記事です↓
WebKit View(WKWebView) を実装
https://qiita.com/MdRk/items/34912e7ba43568f15905WKWebViewについてのまとめ
https://qiita.com/s_emoto/items/dc3d61626155f5cf83e7
(まだ実装出来ていないので今後していきたい…)生体認証
端末によってはFaceIDやTouchIDでログインすることができるアプリがあります。
端末によってtouchIDだったりFaceIDだったり変化させています。
そこまで難しくなかったので実装してみました。
参考になった方の記事↓
【iOS 11】LocalAuthenticationでFace IDとTouch IDの認証を実装する
https://qiita.com/MilanistaDev/items/b0cd432290d18f336766Alert表示
画像を見れば明らかですがアラート表示についてです。
参考になった方々の記事↓
【Swift】アラートを表示する(Alert/ActionSheet)
https://qiita.com/funacchi/items/b76e62eb82fc8d788da5最後に
この記事は自分のために作ったみたいなところがあります。
今後もメモのような感じで残せたらと思います。何か不足だったり指摘だったり、こんなの載せて欲しいというのがあったら是非気軽に言っていただきたいです!
- 投稿日:2020-03-29T14:03:39+09:00
SwiftのArrayとList(Realm)の比較と変換方法
はじめに
メモアプリに複数のタグを紐付ける機能を実装していた際に、
SwiftのArrayとListという型がそれぞれ別の役割であることに気づかなかったので、
それぞれの役割と変換方法について簡潔にまとめようと思います!環境
- xcode 11.3.1
- swift 5.1.3
ArrayとListの違い
ArrayもListも複数要素を扱うという点では共通ですが
ArrayはSwiftで配列として使われる型です。
Listは「RealmSwift」というRealmを扱うためのライブラリで扱える型です。List型とArray型を使い分ける
今回はViewControllerで
List<Tag>
を扱おうとしたところ、
「Listとなんて型は定義されていないよ!」
と怒られたので、Array<Tag>
に変更しました。そうしたら今度は、Model→Realmに変換できず
nil
になって、
保存されないという事象が発生しました!
nilになってもエラーにはならずただDBに値が入らないので要注意なので、
ロジックはArray<Tag>
にキャスト
データ格納時はList<Tag>
に変換するという実装で対応しました。Array<=>Listの変換方法
Array→List
ArrayからListに変換するときは、Modelで
List<Object>
を指定しておき、
append(objectsIn:)
を使えばList
としてRealmに入れることできました。Modelimport Foundation import RealmSwift class Memo: RObject { @objc dynamic var title : String = "" @objc dynamic var content : String = "" @objc dynamic var createdDate : Date = NSDate() as Date @objc dynamic var updatedDate : Date = NSDate() as Date var tags = List<Tag>() // ここをList型にする } class Tag: Object { @objc dynamic var tagName = "" var memos: LinkingObjects<Memo> { return LinkingObjects(fromType: Memo.self, property: "tags") } }ViewControllerimport UIKit import RealmSwift class MemoTableViewController: UITableViewController { // 一部抜粋 let memoModel = Memo() let realm = try! Realm() try! realm.write { memoModel.title = memo memoModel.content = memo memoModel.tags.removeAll() memoModel.tags.append(objectsIn: tags) // Listに追加 // 一部省略 } }List→Array
ListをArrayに変化するときは
Array()
で変換してあげれば大丈夫です!
また、Array型
に入れるときはappend(contentsOf:)
を用いるようです。ListToArraylet testList = List<Tag>() //List型 var testArray = Array<Any>() // Array型 testArray.append(contentsOf: Array(testList)) // Array()でListを変換まとめ
今回はListとArrayを変換しましたが、
RealmSwiftをimportし、使い回すという方法もありかもしれないです。Javaだと、配列もListも存在するので、Swiftも似た感じかと思い
ListがRealmSwiftの型ということに気づくのに時間かかってしまったので、
javaからSwift入る人は注意してください^^;ArrayでRealmに入れようとするとnilが入り、処理が落ちずに
保存に失敗するのも注意ポイントでした!*実装ベースで気づいたことをまとめたので、もし間違いや指摘がある場合は
確認・修正するのでコメントでお知らせくださいmm
- 投稿日:2020-03-29T13:46:25+09:00
PageViewControllerを使ってチュートリアル画面を実装する
作ろうとしているもののイメージ
https://qiita.com/Motchy_1204/items/5d4c281d7cc1c3c26365
スワイプではなくてボタンをタップして別の画面を表示する
少し違うのはここでは色を切り替えているだけだけど
- 今回は画面を切り替える
- 2画面目では画面内のボタンをタップしてページを切り替える今回の仕組み
各画面ごとのViewControllerを用意してボタンタップすることでそのViewControllerを切り替える
そのためViewControllerにPageViewControllerを持たせる必要があるのでViewControllerにViewControllerを持たせるためのContainerViewを持たせる
次に各画面用のViewControllerを用意して、PageViewControllerによって画面遷移を行うContainerViewとは
ViewControllerの中にViewControllerを配置することができるもの。
xibじゃなくてViewControllerを置きたい!という時に利用する。PageViewControllerとは
スワイプで用意したViewControllerに画面遷移ができる
画面遷移時にアニメーションを付けられる手順1: UIPageViewControlerをViewControllerに持たせる
StoryboardからViewControllerにUIPageViewControlerを持たせます
流れ - ContainerViewとしてUIViewをViewControllerに持たせる - UIPageViewControllerを作る - ContainerViewとUIPageViewControllerを繋ぎEmbedを選択する
UIPageViewControllerを追加 Embedで繋ぐ 完成 コードでこのPageViewControllerを取得していきます
childrenでそのViewControllerが持つViewControllerの配列を取得できるのでchildren.firstで今回ViewControllerがもつUIPageViewControllerを取得することができますAn array of view controllers that are children of the current view controller.
https://developer.apple.com/documentation/uikit/uiviewcontroller/1621452-childrenこのViewControllerは各画面のコンテナになるので名前を変えて、それと今後どこかでこのViewControllerを取得する可能性もあるのでコードで取得できるようにしておきます
名前を変更 コード取得できるように設定 チュートリアルで表示する画面の作成
各画面用のViewControllerをファイルとstoryboard両方に作成
TutorialCotainerViewControllerと同じように、storyboardで作成したViewControllerをコードで取得できるようにStoryboard IDを設定しておきます
FirstVCコード FirstVC Storyboard
SecondVCコード SecondVC Storyboard
ThirdVCコード ThirdVC Storyboard 完成
完成したコードとstoryboardです
SecondVCの時はNextボタンを非活性にして「次へ」ボタンタップでThirdVCへ遷移するようにしています
遷移自体はTutorialContainerVCが担うので、SecondVCでボタンがタップされたらクロージャーでTutorialVCへ通知して遷移させるようにしています| Storyboard | TutorialContainerVC コード | FirstVC コード | SecondVC コード | ThirdVC コード |
|-------|
| |import UIKit class TutorialContainerViewController: UIViewController { enum Page:Int { case first case second case third } // MARK: - IBOutlet @IBOutlet weak var nextButton: UIButton! // MARK: - private var currentPage = Page.first private lazy var pageViewController: UIPageViewController = { // このViewControllerが持つpageViewControllerを取得 let pageVC = children.first as! UIPageViewController return pageVC }() override func viewDidLoad() { super.viewDidLoad() pageViewController.setViewControllers([getFirst()], direction: .forward, animated: true, completion: nil) } @IBAction func tappedButton(_ sender: UIButton) { if currentPage == .first { pageViewController.setViewControllers([getSecond()], direction: .forward, animated: true, completion: nil) currentPage = .second nextButton.isEnabled = false return } } } extension TutorialContainerViewController { private func getFirst() -> FirstViewController { return storyboard!.instantiateViewController(identifier: "FirstViewController") } private func getSecond() -> SecondViewController { let vc: SecondViewController = storyboard!.instantiateViewController(identifier: "SecondViewController") // SecondViewControllerでボタンがタップされた通知が飛んでくる // {}の中身はその通知が飛んできた時に呼ばれる内容だから、getSecond()が呼ばれても{}の中身は呼ばれない vc.selectedButtonToThird = { self.nextButton.isEnabled = true self.pageViewController.setViewControllers([self.getThird()], direction: .forward, animated: true, completion: nil) } return vc } private func getThird() -> ThirdViewController { return storyboard!.instantiateViewController(identifier: "ThirdViewController") } }import UIKit class FirstViewController: UIViewController { }import UIKit class SecondViewController: UIViewController { @IBOutlet weak var nextButtonToThird: UIButton! // クロージャーを設定 var selectedButtonToThird: (() -> ())? override func viewDidLoad() { super.viewDidLoad() } @IBAction func tappedToThird(_ sender: UIButton) { // ボタンがタップされたらクロージャーで通知がいくようにする selectedButtonToThird?() } }import UIKit class ThirdViewController: UIViewController { }おまけ
ページのアニメーションを横にスクロールするようなアニメーションにする
デフォルトではページをめくるようなアニメーションになっています
これを横にスクロールするようなアニメーションにする場合にはstoryboardでPageViewcontrollerを選択して
Transition Style
をScroll
に変更することで実現できますRootViewControllerを切り替える
チュートリアル画面を実装するようなアプリの場合初回起動判定で
初回と判定されればアプリ起動時にチュートリアル画面を表示
初回でないと判定されればトップ画面をアプリ起動時に表示とすることが多いですclass AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { window = UIWindow(frame: UIScreen.main.bounds) window?.rootViewController = makeRootVC() window?.makeKeyAndVisible() return true } // 初回起動のところは自分で判定処理を入れてください func makeRootVC() -> UIViewController { if 初回起動 { let tutorialContainerVC = storyboard?.instantiateViewController(identifier: "TutorialContainerViewController") // TutorialContainerViewControllerでdidSelectButtonToTopが呼ばれたらここに通知がきて {} 内が実行されrootViewControllerが変わることで画面が切り替わる tutorialContainerVC.didSelectButtonToTop = { self.window?.rootViewController = トップ画面のVC } return tutorialContainerVC } // 初回起動ではない場合 return トップ画面のVC } }import UIKit class TutorialContainerViewController: UIViewController { enum Page:Int { case first case second case third } // MARK: - IBOutlet @IBOutlet weak var nextButton: UIButton! // MARK: - internal var didSelectButtonToHome: (() -> Void)? ← NEW // MARK: - private var currentPage = Page.first private lazy var pageViewController: UIPageViewController = { // このViewControllerが持つpageViewControllerを取得 let pageVC = children.first as! UIPageViewController return pageVC }() override func viewDidLoad() { super.viewDidLoad() pageViewController.setViewControllers([getFirst()], direction: .forward, animated: true, completion: nil) } @IBAction func tappedButton(_ sender: UIButton) { if currentPage == .first { pageViewController.setViewControllers([getSecond()], direction: .forward, animated: true, completion: nil) currentPage = .second nextButton.isEnabled = false return } if currentPage == .third { didSelectButtonToHome?() ← NEW } } } extension TutorialContainerViewController { private func getFirst() -> FirstViewController { return storyboard!.instantiateViewController(identifier: "FirstViewController") } private func getSecond() -> SecondViewController { let vc: SecondViewController = storyboard!.instantiateViewController(identifier: "SecondViewController") // SecondViewControllerでボタンがタップされた通知が飛んでくる // {}の中身はその通知が飛んできた時に呼ばれる内容だから、getSecond()が呼ばれても{}の中身は呼ばれない vc.selectedButtonToThird = { self.nextButton.isEnabled = true self.pageViewController.setViewControllers([self.getThird()], direction: .forward, animated: true, completion: nil) } return vc } private func getThird() -> ThirdViewController { return storyboard!.instantiateViewController(identifier: "ThirdViewController") } }ナビゲーションコントローラーを使っている場合
*StoryboardでTutorialContainerViewControllerもトップ画面VCもNavigationControllerと紐づいている、NavigationControllerを持っている場合
class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { window = UIWindow(frame: UIScreen.main.bounds) window?.rootViewController = UINavigationController(rootViewController: makeRootVC) window?.makeKeyAndVisible() return true } // 初回起動のところは自分で判定処理を入れてください func makeRootVC() -> UIViewController { if 初回起動 { return storyboard?.instantiateViewController(identifier: "TutorialContainerViewController") } // 初回起動ではない場合 return トップ画面のVC } }import UIKit class TutorialContainerViewController: UIViewController { enum Page:Int { case first case second case third } // MARK: - IBOutlet @IBOutlet weak var nextButton: UIButton! // MARK: - private var currentPage = Page.first private lazy var pageViewController: UIPageViewController = { // このViewControllerが持つpageViewControllerを取得 let pageVC = children.first as! UIPageViewController return pageVC }() override func viewDidLoad() { super.viewDidLoad() pageViewController.setViewControllers([getFirst()], direction: .forward, animated: true, completion: nil) } @IBAction func tappedButton(_ sender: UIButton) { if currentPage == .first { pageViewController.setViewControllers([getSecond()], direction: .forward, animated: true, completion: nil) currentPage = .second nextButton.isEnabled = false return } if currentPage == .third { // NavigationControllerによって管理されているViewControllersを引数に渡した特定のViewControllerに置き換える(リセットされる) navigationController?.setViewControllers([トップ画面VC], animated: true) } } } extension TutorialContainerViewController { private func getFirst() -> FirstViewController { return storyboard!.instantiateViewController(identifier: "FirstViewController") } private func getSecond() -> SecondViewController { let vc: SecondViewController = storyboard!.instantiateViewController(identifier: "SecondViewController") // SecondViewControllerでボタンがタップされた通知が飛んでくる // {}の中身はその通知が飛んできた時に呼ばれる内容だから、getSecond()が呼ばれても{}の中身は呼ばれない vc.selectedButtonToThird = { self.nextButton.isEnabled = true self.pageViewController.setViewControllers([self.getThird()], direction: .forward, animated: true, completion: nil) } return vc } private func getThird() -> ThirdViewController { return storyboard!.instantiateViewController(identifier: "ThirdViewController") } }間違いがあったらすみません
参考
https://qiita.com/Motchy_1204/items/5d4c281d7cc1c3c26365
https://qiita.com/ninoko1995/items/5498c1e821ba3e94bdd6
https://qiita.com/Kyoya1123/items/588206357cf9c6492047
https://qiita.com/koji-nishida/items/b901f47255ddcfbaa927
- 投稿日:2020-03-29T11:21:04+09:00
Swift MkMapViewで地図アプリ作成してみた(19)- UserDefaultsでデータをバックアップする
記事一覧
Swift MkMapViewで地図アプリ作成してみた(記事一覧)
UserDefaultsでデータをバックアップする
UserDefaultsとは
ユーザーのデフォルトデータベースへのインターフェイスで、アプリを終了しても消えない永続的なデータを保存できる。
簡単なので、ユーザの設定データなどちょっとしたデータを保存するのに良い。データをバックアップするクラスを作成する
データをバックアップするクラスを作成して、UserDefaultsの変数を宣言する。
import Foundation import UIKit class UserDataManager: NSObject { // UserDefaultsを宣言する let userDefaults = UserDefaults.standardクラス初期化時に、UserDefaultsのデータを初期化する。
maxSpeedをUserDefaultsで管理するものとする。// クラスの初期化 override init() { userDefaults.register(defaults: ["maxSpeed": 0.0]) }UserDefaultsからデータを読み込む
UserDefaultsから変数speedにデータを読み込む。
// 保存したデータを読み込む func roadData() { speed = userDefaults.object(forKey: "maxSpeed") as! Double }UserDefaultsにデータを書き込む
UserDefaultsのmaxSpeedにデータを書き込む。
// 保存したデータを読み込む func saveData(_ speed: Double) { userDefaults.set(speed, forKey: "maxSpeed") }
- 投稿日:2020-03-29T10:30:51+09:00
Swift MkMapViewで地図アプリ作成してみた(18)- 位置情報から速度、平均速度、最高速度を求める
記事一覧
Swift MkMapViewで地図アプリ作成してみた(記事一覧)
前提条件
(02)- 現在位置を取得するで取得した位置情報から、速度、平均速度、最高速度を求める。
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations:[CLLocation]) { // この関数で受信した位置情報から、速度、平均速度、最高速度を求める。 let lonStr = (locations.last?.coordinate.longitude.description)! // 経度 let latStr = (locations.last?.coordinate.latitude.description)! // 緯度 }位置情報から速度、平均速度、最高速度を求める
速度を求める
locations.last!.speedで秒速が取得できる。
時速に変換したい場合は、以下の通りとなる。秒速を時速に変換するfunc locationManager(_ manager: CLLocationManager, didUpdateLocations locations:[CLLocation]) { // 秒速を少数第2位の時速に変換 let speed: Double = floor((locations.last!.speed * 3.6)*100)/100}平均速度を求める
速度の合計値を受信回数で除算するとで、平均速度を求めることができる。
平均速度を求めるfunc locationManager(_ manager: CLLocationManager, didUpdateLocations locations:[CLLocation]) { // 平均速度の更新 self.avgSumSpeed += locations.last!.speed self.avgSumCount += 1 let tmpAvgSpeed = floor(((self.avgSumSpeed / Double(self.avgSumCount)) * 3.6)*100)/100最高速度を求める
単純に過去の最高速度を保存する。
最高速度を求めるfunc locationManager(_ manager: CLLocationManager, didUpdateLocations locations:[CLLocation]) { // 秒速を少数第2位の時速に変換 let speed: Double = floor((locations.last!.speed * 3.6)*100)/100} // 最高速度 if speed > dMaxSpeed { dMaxSpeed = speed }
- 投稿日:2020-03-29T09:43:34+09:00
Swift MkMapViewで地図アプリ作成してみた(17)- バックグラウンド・スリープ中でも位置情報を受信する
記事一覧
Swift MkMapViewで地図アプリ作成してみた(記事一覧)
バックグラウンド・スリープ中でも位置情報を受信する
CLLocationManagerのプロパティを設定する
allowsBackgroundLocationUpdatesプロパティをtrueにすれば、
バックグラウンドでGPSを受信することができる。プロパティを設定するlocManager = CLLocationManager() locManager.allowsBackgroundLocationUpdates = trueバックグラウンドにした時のデバイスの画面
正しく受信できれば、左上に位置情報を受信しているアイコンが表示される。
位置情報を受信する関数
バックグラウンド・スリープ中も、下記の関数で位置情報を受信することができる。
位置情報の取得方法は、(02)- 現在位置を取得するを参照してください。func locationManager(_ manager: CLLocationManager, didUpdateLocations locations:[CLLocation]) { let lonStr = (locations.last?.coordinate.longitude.description)! let latStr = (locations.last?.coordinate.latitude.description)! }参考文書