- 投稿日:2019-03-05T23:01:54+09:00
【SwiftChaining】 処理を確定する関数
SwiftChainingの解説記事その5です。
SwiftChainingとは何か?というのは、こちらの最初の記事を参考にしてください。
前回の記事では、監視対象のオブジェクトの
chain()
を呼んだ後に繋げて書く「処理を構築する関数」の解説をしました。今回は、「処理を構築した後に確定させて監視を開始する関数」について少し詳しく解説しようと思います。
今まで何度も出てきたものになるのですが、
end
とsync
についてです。処理を確定する関数
end
end
は、chain()
の後に繋げて書いて構築した処理を確定し、監視を開始します。let notifier = Notifier<Int>() let chain1 = notifier.chain() // ここではまだ監視は始まらず処理が追加できる let chain2 = chain1.do { _ in } // endを呼んで処理が確定し監視が始まる let observer = chain2.end() // endの後に処理を追加しようとするとfatalErrorになる // chain2.do { _ in } observer.invalidate() // invalidateされたので監視は止まる
end
は返り値にObserver
のインスタンスを返し、それ以降はもう処理をつなげることはできません。処理を追加しようとしてもfatalError
になります。なお、
end
を呼んだ時点では何もイベントは起きず、その後監視対象のオブジェクトからイベントが発生したらchain()
以降で書いた処理が実行されます。バインディングという意味では、監視を開始したタイミングで値を知りたくなるのですが、その場合は次で紹介するsync
を呼ぶことになります。sync
sync
は、chain()
の後に繋げて書いて構築した処理を確定し、1度イベントを送信させた上で監視を開始します。let sender = ValueHolder(1) let receiver = ValueHolder(0) let observer = sender.chain() .sendTo(receiver) .sync() // syncが呼ばれたことによってreceiverは1になっている print(receiver.value) sender.value = 2 // 変更を監視しreceiverは2になっている print(receiver.value)監視対象のオブジェクトが常に値を返すことのできる場合に使うことができ、
sync
を呼ぶと内部的にend
を呼びつつ、監視対象のオブジェクトから一度イベントを送らせる事が出来ます。
end
だと変更があったら送られるだけですが、sync
で監視し始めにも送ることによって繋げた値を同期させられるという感じです。なお、監視対象のオブジェクトからはいくつも
chain()
を呼んで複数監視ができ、普通に対象の値が変更された時のイベントはその全部に対して送られますが、sync
を呼んだ際のイベントはそのひとつだけに送られます。ちなみに、ここで出てきた「常に値を返すことのできる」オブジェクトというのは何かと言うと、今までの記事で紹介したものの中では…
- ValueHolder
- KVOAdapter
というクラスのオブジェクトになります。
逆に「値を返す事ができない」クラスは…
- Notifier
- NotificationAdapter
- UIControlAdapter
などです。後者のような値を持っていなくて返すことができないオブジェクトからのみ処理が繋がっている場合は
end
でしか終わらせることができません。
merge
などで値を返せるオブジェクトと返せないオブジェクトの処理を混ぜた場合にはsync
を呼ぶことはできますが、無い袖は振れないので値を返せるオブジェクトからのみイベントが送信されます。なお、「値を返せる」「値を返せない」の違いは何かと言うと、
SwiftChaining
的には適合しているプロトコルがFetchable
かSendable
かということなのですが、これはまた別の記事で解説しようと思います。
- 投稿日:2019-03-05T22:21:28+09:00
【SwiftChaining】処理を構築する関数
SwiftChainingの解説記事その4です。
SwiftChainingとは何か?というのは、こちらの最初の記事を参考にしてください。
今までの記事で、
SwiftChaining
で用意しているオブジェクトのchain()
という関数を呼ぶと、そのあとにオブジェクトの値が変更された時などに実行される処理を書ける、ということを紹介しました。今回はその
chain()
を呼んだ後に書く部分について少し詳しく解説しようと思います。Chainとは
監視対象となるオブジェクトの
chain()
関数を呼ぶとChain
という型のインスタンスを返します。Chain
は監視対象のオブジェクトがイベントを送信する時に実行される処理を構築するクラスです。(監視対象となるオブジェクトというのは、
SwiftChaining
的にはSendable
というプロトコルに適合したオブジェクトということになるのですが、プロトコルに関しては別の記事で解説する予定です。)
Chain
の持つ関数を呼ぶとまたその返り値にChain
を返すという感じで、処理をメソッドチェーンでどんどん追加して繋げて書くことができるようになっています。
Chain
の関数はいろいろあるのですが、大きく分けると「処理を構築する」関数と「処理を確定する」関数があります。例えば以下のようなものです。処理を構築する関数
- do
- map
- guard
- merge
- tuple
- combine
- sendTo
処理を確定する関数
- end
- sync
メソッドチェーンで処理をいくつもつなげて書いていけるのは「処理を構築する関数」の方です。今回はこの「処理を構築する関数」について解説します。
処理を構築する関数
do
do
は、イベントを受け取った時に登録したクロージャの処理を単に実行するものです。クロージャの引数には、受け取ったイベントの値が入ってきます。イベントの値はそのまま次の処理に渡されます。let notifier = Notifier<Int>() let observer = notifier .chain() .do({ (value: Int) in // 何か処理をする }) .end() // "1"が送信されるので、doのクロージャの引数valueに1が渡され実行される notifier.notify(value: 1)真面目に書くとこのような感じになるのですが、クロージャの記述は普通に省略して書く事が出来るので、通常は以下のコードくらい省略する方が書きやすいでしょう。
.do { value in // 何か処理をする }ちなみに、これまでのサンプルコードに出てきた
sendTo
でオブジェクトに値を反映させるというのも、内部的にはdo
を使って値をセットしているだけです。何かしらイベントが来た時に、最終的にバインドするような処理を書くのは、基本的にdo
を使うということになります。また、開発中に「この時点ではどんな値が来ているのか」と調べたい時も、
do
を挟み込んで確認するという使い方もできると思います。map
map
は、受け取ったイベントの値を変化させて次の処理に渡すことができます。Arrayのmap
と同じようなものと思っていただいて良いと思います。例として
Int
をString
に変換する場合は、以下のようになります。let notifier = Notifier<Int>() let observer = notifier .chain() .map({ (intValue: Int) -> String in return String(intValue) }) .do({ (strValue: String) in // 変換されたStringの値を受け取る }) .end() notifier.notify(value: 1)こちらもクロージャの記述を省略する方が書きやすいでしょう。最大限まで省略すると以下のようになります。
.map { String($0) }
map
もdo
と同じくクロージャを実行するものではあるので、何でも書きたい処理は書けるのですが、map
では単に値を変換をする処理を書くだけに留めておくほうが良いと思います。guard
guard
は、受け取ったイベントをそこで止めることができます。登録したクロージャでtrue
を返すとそのまま値を次の処理へ渡しますが、false
を返すと処理を打ち切ります。例として、
Int
が奇数の場合だけイベントを通すコードは以下のようになります。let notifier = Notifier<Int>() let observer = notifier .chain() .guard({ (value: Int) -> Bool in return value % 2 != 0 }) .do({ value in // valueが奇数の時だけ実行される }) .end()
guard
もmap
と同じく関係のない処理を書かずに、単にイベントを通すか通さないかという条件だけを書いたほうが良いと思います。merge
merge
は、2つの同じ型のChain
の処理の流れを同じ型のまま1つにまとめます。let notifier1 = Notifier<Int>() let notifier2 = Notifier<Int>() let chain1 = notifier1.chain() let chain2 = notifier2.chain() let observer = chain1 .merge(chain2) .do { value in // 両方の値を受け取る } .end() // どちらから送ってもdoが実行される notifier1.notify(value: 1) notifier2.notify(value: 2)
merge
が使えるのは同じ型のChain
同士の場合のみです。型が違う場合は以下に紹介するtuple
やcombine
を使うことになります。tuple
tuple
は、2つのChain
の処理の流れをひとつのタプルにまとめます。こちらは
merge
と違い別の型でもまとめることができます。その代わり2つのOptionalの要素を持ったタプルに変換されます。let chain1 = notifier1.chain() let chain2 = notifier2.chain() let observer = chain1 .tuple(chain2) .do { (value1: Int?, value2: String?) in // value1かvalue2のどちらかだけ値が入って来る }.end() // doが実行されvalue1に値が渡される notifier1.notify(value: 1) // doが実行されvalue2に値が渡される notifier2.notify(value: "2")
tuple
から出力されるタプルは、2つの要素両方に値が入ることはありません、どちらか一方に値があればもう一方は必ずnil
になります。2つの処理の流れが混ざらずに並走しているだけとイメージしてもらうと良いかもしれません。ただ、2つの要素がバラバラに送られてきてもそのままでは扱いにくいと思うので、実際には次に紹介する
combine
を使うことが多くなるかもしれません。combine
combine
は、2つのChain
の流れをひとつのタプルにまとめます。tuple
と違うのは、受け取った値を内部で保存しており、必ず2つの値が揃った状態でタプルのイベントが送信されることです。ただ、タプルの中身はオプショナルではなく、どちらか一方がイベントを一度も受け取っていなければ
combine
からイベントが送信されることはありません。let holder1 = ValueHolder(1) let holder2 = ValueHolder("2") let chain1 = holder1.chain() let chain2 = holder2.chain() let observer = chain1 .combine(chain2) .do { (value1: Int, value2: String) in // 両方の値が揃って実行される } .sync()上記のコード例のように、
combine
でまとめる送信元がValueHolder
のように値を常に送信できる種類のものだと、sync()
を呼んだ時点で2つともから1度値が送信されるので、以後どちらかだけが変わってもcombine
の後に値が送信されます。これが
Notifier
のように送信元が値を持たない種類のものだと、敢えて明示的に値を送信しておかないとcombine
での値が揃わないので、注意が必要です。let notifier1 = Notifier<Int>() let notifier2 = Notifier<String>() let observer = notifier1.chain() .combine(notifier2.chain()) .do { (value1: Int, value2: String) in // 両方の値が揃ってから実行される } .end() // この時点ではdoは実行されない notifier1.notify(value: 0) // この時点でも片方しか値がないのでdoは実行されない notifier1.notify(value: 1) // 2つとも値が揃ったのでdoが実行される notifier2.notify(value: "2")sendTo
sendTo
は、引数に渡したオブジェクトにイベントの値を送る関数です。let notifier = Notifier<Int>() let holder = ValueHolder<Int>(0) // notifierから値を送信したらholderが値を受け取るようにする let observer = notifier.chain().sendTo(holder).end() // notifierから値が送信されholderに1がセットされる notifier.notify(value: 1)具体的にどのようなオブジェクトが受け取れるのかというと、
SwiftChaining
的にはReceivable
というプロトコルに適合したクラスということになります。プロトコルの具体的な説明はまた別のPRでしようと思いますが、今まで出てきたクラスでは以下のものが適合しています。
- ValueHolder
- KVOAdapter
- Notifier
ValueHolder
やKVOAdapter
は値を保持しているものなので、イベントを受け取ったら値をセットします。Notifier
はそれ自身が値を保持するものではないので、受け取った値をそのまま送信する感じの動作になります。また、前回の記事にも書きましたが、
sendTo
で送るイベントの型と受け取るオブジェクトの型がオプショナルの有無の違いだけであれば、map
などで変換しなくてもそのままつなげることができます。
- 投稿日:2019-03-05T22:20:38+09:00
【Swift】 カスタムキーボードを無効化する
アプリ内でサードパーティー製のカスタムキーボードを制限、無効化する方法を紹介します
脆弱性診断NG
事の発端は、↓でした。
Input Interception: Keyboard Extensions Allowedこれは、fortifyという脆弱性診断の静的解析ツールによって吐かれた脆弱性です。
サードパーティのカスタムキーボードの利用が無効化されていません。カスタムキーボードはユーザ入力を全て取得でき、悪意の有無に関わらずキーロギングを行っている可能性があります。
という脆弱性のようです。カスタムキーボードの脆弱性とは?
予測変換などに活用するため、ユーザの入力情報を取得しているため、それによって、パスワードやクレジットカード情報などが送信されてしまう脆弱性があるようです。
例えば、有名なアプリsimejiでは下記のサイトのようなリスクが有るようです。
使うのは危険!?Simejiを安全に使う方法対処法
対処法は、以下のコードをAppDelegate.swiftに書くだけです。
これで、simejiアプリを無効化することができました。AppDelegate.swiftfunc application(_ application: UIApplication, shouldAllowExtensionPointIdentifier extensionPointIdentifier: UIApplicationExtensionPointIdentifier) -> Bool { if extensionPointIdentifier == .keyboard { return false } return true }参考資料
- http://blogs.quovantis.com/best-practices-to-avoid-security-vulnerabilities-in-your-ios-app/
- 自分のアプリでカスタムキーボードを使われないように制限する
おまけ
「カスタムキーボード 無効化」とぐぐっても出てこなくて苦労しました、、
「カスタムキーボード 制限」とググればよかったんですね、、泣
- 投稿日:2019-03-05T20:11:10+09:00
SwiftからPHPへファイルをPOSTする
iOS側のコード
//アップロードしたいファイルのパスを取得 let path = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("test").appendingPathExtension("csv") var req = URLRequest(url: URL(string: "http://{ホストのアドレス}/upload.php")!) req.httpMethod = "POST" URLSession.shared.uploadTask(with: req, fromFile: path) { (data, res, err) in print(data, res, err) }.resume()PHP側のコード
<?php date_default_timezone_set('Asia/Tokyo'); //タイムゾーンを設定する $date = date("YmdHis"); //時刻の取得 $file = file_get_contents("php://input"); //RequestのBodyを取得 $fileName = "./${date}.csv"; //保存する名前を取得 file_put_contents($fileName,$file); //保存 ?>Swift、PHPともにエラーハンドリングなどは全て省略されていますのでプロダクションとして使う場合はもう少し手を加えてあげる必要がありますのでご注意を!!
- 投稿日:2019-03-05T19:34:36+09:00
笑顔判定でAmazonPrimeのドキュメンタルを正確に判定する。
はじめに
こんにちは、drama(@1901drama)です!
Amazon Prime Videoの ドキュメンタル が好きなのですが、
シーズン1から「フジモンの判定甘いんじゃないか」問題が評価を下げる要因として話されています。。。場回し的な人がいないと展開が少なくなったりしますし、ある程度仕方ないかな とは思うのですが、
そもそも 本当に笑っているのでしょうか? この問題をARで解決しました!出来たもの
ドキュメンタルヘルパー
Documental HELP その1 pic.twitter.com/C5NppqChsm
— drama(ARエンジニア) (@1901drama) 2019年3月5日
・カメラ画像から顔検出をする
・笑顔かどうかを判定する
・笑ってたらアウトにする(顔をドキュメンタルのロゴに変える/画面のふちに赤っぽいフレームを追加する)
Documental HELP その2 pic.twitter.com/q3UKT4qxbB
— drama(ARエンジニア) (@1901drama) 2019年3月5日
これでどこでもドキュメンタルが出来ます。
※1/30 フレーム単位なのでジャッジが厳しいです。
尚、やってみてわかったのですが、他の人も結構笑顔判定されます。仕様
以下の流れでドキュメンタルをしています。
1.カメラで取得した動画リアルタイムで、バッファに入れます。
2.バッファに入った動画を、フレーム(1/30)単位で画像に変換します。
3.画像ごとに顔検出を行います。
4.顔検出された場合、顔の特徴点から笑顔か否かを判定します。
5.笑顔と判定された場合、顔の位置と大きさを、描写用のコントローラに受け渡します。
6.定められた位置と顔の大きさのアウトマークを画面に追加(表示)します。
7.01秒後に追加したアウトマークを削除します。
8.上記1~7をループ▼因みに、顔検出&笑顔判定はとても簡単でした。
//顔検出 let detector = CIDetector(ofType: CIDetectorTypeFace, context: nil, options: [CIDetectorAccuracy: CIDetectorAccuracyHigh]) // 取得するパラメーター(笑顔判定)を指定する let options = [CIDetectorSmile : true] // 画像からパラメーター(笑顔判定)を抽出する ※ciimage = フレーム単位で取得した画像 let features = detector.featuresInImage(ciImage, options: options)そして、それ以上に驚いたのが、
CPU使用率がすごい。
デバイスがiPhone SEというのもありますが、検出精度をlowにしていても、基本100%以上でした。
これを6時間見続ける審査員って相当疲れるんだろうなっと思いました。難しかった点
1.ARAnchor->画面位置への変換
当初は遠近を把握する為に、ARFaceAnchorを利用して顔判別をしようと思ってました。class ARFaceAnchor : ARAnchorしかしフレーム単位では処理が重過ぎたので、アウトマークの画像サイズと場所を高速で更新する方法に変えました。
因みにiPhoneX以上の機種であればARkit2の顔に自動でマスクをつける機能が使えます。
顔を猫に変えるとか、アウトマーク以外の追加であれば、その方が簡単に導入出来ると思います。2.カメラの向き(横か縦)を、cgimageに引継げない。
UIimageは、撮影時のデバイス向きを保持しているのですが、
動画から取り出したimageの cgimage は、向き情報を持ってなかったです。
顔検出や笑顔判定には向き情報が必要な為、ここを解決出来ず縦でしか使え無いアプリとなってしまいました...。一般的には画面の向きも 別変数で取得して一緒に利用しているのでしょうか?
このあたりご存知の方いれば教えて欲しいです。まとめ
ドキュメンタルの結論
「笑ってるけど、皆 まあまあ 笑ってる!!」 でした。なので楽しく見ましょう。ここまで読んで頂きありがとうございました!
動画:ドキュメンタル シーズン5
引用中の動画は他のものに置き換えるかも知れません。
- 投稿日:2019-03-05T19:34:36+09:00
CIDetectorでAmazonPrimeのドキュメンタルを正確に判定する。
はじめに
こんにちは、drama(@1901drama)です!
Amazon Prime Videoの ドキュメンタル が好きなのですが、
シーズン1から「フジモンの判定甘いんじゃないか」問題が評価を下げる要因として話されています。。。場回し的な人がいないと展開が少なくなったりしますし、ある程度仕方ないかな とは思うのですが、
そもそも 本当に笑っているのでしょうか? この問題をARで解決しました。出来たもの
ドキュメンタルヘルパー
Documental HELP その1 pic.twitter.com/C5NppqChsm
— drama(ARエンジニア) (@1901drama) 2019年3月5日
・カメラ画像から顔検出をする
・笑顔かどうかを判定する
・笑ってたらアウトにする(顔をドキュメンタルのロゴに変える/画面のふちに赤っぽいフレームを追加する)
Documental HELP その2 pic.twitter.com/q3UKT4qxbB
— drama(ARエンジニア) (@1901drama) 2019年3月5日
これでどこでもドキュメンタルが出来ます。
やってみてわかったのですが、他の人も結構笑顔判定されます。
※1/30 フレーム単位なのでジャッジは厳しめです。仕様
以下の流れでドキュメンタルをしています。
1.カメラで取得した動画リアルタイムで、バッファに入れます。
2.バッファに入った動画を、フレーム(1/30)単位で画像に変換します。
3.画像ごとに顔検出を行います。
4.顔検出された場合、顔の特徴点から笑顔か否かを判定します。
5.笑顔と判定された場合、顔の位置と大きさを、描写用のコントローラに受け渡します。
6.定められた位置と顔の大きさのアウトマークを画面に追加(表示)します。
7.0.1秒後に追加したアウトマークを削除します。
8.上記1~7をループ▼因みに、顔検出&笑顔判定はとても簡単でした。
//顔検出 let detector = CIDetector(ofType: CIDetectorTypeFace, context: nil, options: [CIDetectorAccuracy: CIDetectorAccuracyHigh]) // 取得するパラメーター(笑顔判定)を指定する let options = [CIDetectorSmile : true] // 画像からパラメーター(笑顔判定)を抽出する ※ciimage = フレーム単位で取得した画像 let features = detector.featuresInImage(ciImage, options: options)そして、それ以上に驚いたのが、
CPU使用率がすごい。
デバイスがiPhone SEというのもありますが、検出精度をlowにしていても、基本100%以上でした。
これを6時間見続ける審査員って相当疲れるんだろうなっと思いました。難しかった点
1.ARAnchor->画面位置への変換
当初は遠近を把握する為に、ARFaceAnchorを利用して顔判別をしようと思ってました。class ARFaceAnchor : ARAnchorしかしフレーム単位では処理が重過ぎたので、アウトマークの画像サイズと場所を高速で更新する方法に変えました。
因みにiPhoneX以上の機種であればARKit2の顔に自動でマスクをつける機能が使えます。
顔を猫に変えるとか、アウトマーク以外の追加であれば、その方が簡単に導入出来ると思います。2.カメラの向き(横か縦)を、cgimageに引継げない。
UIimageは、撮影時のデバイス向きを保持しているのですが、
動画から取り出したimageの cgimage は、向き情報を持ってなかったです。
顔検出や笑顔判定には向き情報が必要な為、ここを解決出来ず縦でしか使え無いアプリとなってしまいました...。一般的には画面の向きも 別変数で取得して一緒に利用しているのでしょうか?
このあたりご存知の方いれば教えて欲しいです。まとめ
ドキュメンタルの結論
「笑ってるけど、皆 まあまあ 笑ってる」 でした。なので楽しく見ましょう。ここまで読んで頂きありがとうございました!
動画:ドキュメンタル シーズン5
引用中の動画は他のものに置き換えるかも知れません
- 投稿日:2019-03-05T18:59:46+09:00
【Unity】スワイプ、フリックで分岐するSafari風スクロール【C#】
【Unity】スワイプ、フリックで分岐するSafari風スクロール【C#】
はじめに
スワイプした分スクロールできて、かつフリックすると速度をもってスクロールできる、iOS標準ブラウザのSafari風のUXのスクロール方法を実装したい!
ついでに2本指での拡大と縮小も実装しました。(なぜ?)
応用時はクランプなど用いてスクロール範囲を制御してください。荒い実装ではありますが、そこそこ良い動きになったので備忘録かつ共有。
開発環境
OS: MacOS Mojave
開発環境: Unity 2018.2.14f1 personal
開発言語: C#ソースコードとその説明
SwipeManager.csusing System.Collections; using System.Collections.Generic; using UnityEngine; public class SwipeManager : MonoBehaviour { //フリック時のスピード private float x_speed = 0; private float y_speed = 0; //カメラ拡大縮小時のスピード(実装環境によって要調整) private float cameraSpeed = 0.4f; //初期位置 private Vector2 startPos; //最初のタップからの経過時間 private float duration = 0; //オブジェクトの移動比率を操作する変数(実装環境によって要調整) private float moveRatio = 0.5f; //スワイプするオブジェクト(この場合スクロールビューのコンテンツ)の外部参照 public GameObject obj; //拡大縮小用カメラの外部参照 public Camera cam; void Update() { //経過時間の計算 duration += Time.deltaTime; //一本指での操作(スワイプ、フリック) if(Input.touchCount == 1){ //タップ情報の取得 Touch touch = Input.GetTouch(0); //タップ状態の分岐 switch(touch.phase){ //タップ開始 case TouchPhase.Began: //タッチ開始座標、時間取得 this.startPos = Input.mousePosition; duration = 0; break; //タップ中、指が動いている case TouchPhase.Moved: //オブジェクト移動(スワイプ) Vector3 nowPosi = obj.transform.localPosition; nowPosi.x = nowPosi.x - touch.deltaPosition.x*moveRatio; nowPosi.y = nowPosi.y - touch.deltaPosition.y*moveRatio; obj.transform.localPosition = nowPosi; break; //タップ終了 case TouchPhase.Ended: //タップ終了位置取得 Vector2 endPos = Input.mousePosition; //触れていた秒数でフリックとスワイプ分岐 if(duration <= 0.5){ float x_flickLength = endPos.x - this.startPos.x; float y_flickLength = endPos.y - this.startPos.y; // フリックの長さを速度に変換する this.x_speed = x_flickLength / 500.0f; this.y_speed = y_flickLength / 500.0f; }else{ //フリック判定じゃない場合はcameraのTranslate速度を0にする this.x_speed = 0; this.y_speed = 0; } break; } } //オブジェクト移動(フリック) obj.transform.Translate(this.x_speed, this.y_speed, 0); //毎フレーム減速させる this.x_speed *= 0.8f; this.y_speed *= 0.8f; //2本指での操作(ピンチイン、アウト) if (Input.touchCount == 2){ // 両方のタップ情報を取得 Touch touchZero = Input.GetTouch(0); Touch touchOne = Input.GetTouch(1); // 前フレームでのタップ位置 Vector2 touchZeroPrePos = touchZero.position - touchZero.deltaPosition; Vector2 touchOnePrePos = touchOne.position - touchOne.deltaPosition; // 各フレームのタッチ間の距離 float preTouchDeltaMag = (touchZeroPrePos - touchOnePrePos).magnitude; float touchDeltaMag = (touchZero.position - touchOne.position).magnitude; //各フレーム間の距離の差 float deltaMagnitudeDif = preTouchDeltaMag - touchDeltaMag; //タッチ間の距離の変化からカメラの平行投影サイズを変更 cam.orthographicSize += deltaMagnitudeDif * cameraSpeed; // 平行投影サイズは0以上になるようにする(クランプの範囲は要調整) cam.orthographicSize = Mathf.Clamp(cam.orthographicSize, 0f , 2.0f); } } }終わりに
TouchScriptなどの便利なアセットを使用するとおそらく早かったのですが、スクリプトで実装してもそこまで時間がかからないのと、自分でスクリプトを書いた方が拡張性に富むと思ったのでこのような実装をしました。どなたかの助けになれば幸いです。
参考文献
「フリックとスワイプの違い」
https://enjoy.sso.biglobe.ne.jp/archives/swipe_flick/
「Unity C#で時間の取得」
http://webbeginner.hatenablog.com/entry/2015/09/04/053623
「ピンチによる拡大」
https://unity3d.com/jp/learn/tutorials/topics/mobile-touch/pinch-zoom
「【Unity2D】スワイプでオブジェクトを上下左右自由自在に動かす方法」
https://miyagame.net/swipe/
- 投稿日:2019-03-05T17:10:28+09:00
Firebase DebugViewの設定方法
画面例
Firebaseコンソール→アナリティクス→DbugView
シュミレーターを実行するとリアルタイムにイベントに対して更新されるようになるドキュメント
https://firebase.google.com/docs/analytics/ios/events?hl=ja
Xcode デバッグ コンソールでイベントを表示する
詳細ログを有効にして SDK によるイベントのログ記録を監視し、イベントが正しく記録されているかどうかを確認することができます。対象となるのは、自動で記録されるイベントと手動で記録されるイベントの両方です。 詳細ログを有効にするには、次のようにします。 Xcode で [Product]、[Scheme]、[Edit scheme] の順に選択します。 左側のメニューから [Run] を選択します。 [Arguments] タブを選択します。 [Arguments Passed On Launch] セクションで -FIRAnalyticsDebugEnabled を追加します。 以後、アプリを実行すると Xcode のデバッグ コンソールにイベントが表示され、イベントが送信されているかどうかを即座に確認することができます。設定例
Xcodeコンソール上で出てくるログ例
2019-03-05 16:25:15.946784+0900 {アプリの名前}[{数字の列}:{数字の列}] {バージョン番号} - [Firebase/Analytics][I-{数字の列}] Debug mode is on 2019-03-05 16:25:15.950686+0900 {アプリの名前}[{数字の列}:{数字の列}] {バージョン番号} - [Firebase/Analytics][I-{数字の列}]Analytics v.50200000 started 2019-03-05 16:25:15.953470+0900 {アプリの名前}[{数字の列}:{数字の列}] {バージョン番号} - [Firebase/Analytics][I-{数字の列}] To disable debug logging set the following application argument: -noFIRAnalyticsDebugEnabled (see http://goo.gl/RfcP7r) 2019-03-05 16:25:15.986549+0900 {アプリの名前}[{数字の列}:{数字の列}] {バージョン番号} - [Firebase/Analytics][I-{数字の列}] Debug logging enabled躓いたこと
scheme毎にGoogleService-Info.plistわけているからか
なぜかdebugの方のスキーマに、[Arguments Passed On Launch] セクションで -FIRAnalyticsDebugEnabled を追加しても
下記のようなエラーのログがでていた。(現在調査中)とりあえずリリースビルドの方に[Arguments Passed On Launch] セクションで -FIRAnalyticsDebugEnabledを追加して、本番環境のFirebaseでみることにした
2019-03-05 15:50:42.202211+0900 {アプリの名前}[{数字の列}:{数字の列}] {バージョン番号} - [Firebase/Analytics][I-{数字の列}] Analytics requires Google App ID from GoogleService-Info.plist. Your data may be lost. Google App ID has been changed. Original, new ID: {数字}:{数字の列}, {数字}:{数字の列}:ios:{数字の列} 2019-03-05 15:50:42.254265+0900 {アプリの名前}[{数字の列}:{数字の列}] {バージョン番号} - [Firebase/Analytics][I-{数字の列}] Analytics v.50200000 started 2019-03-05 15:50:42.254957+0900 {アプリの名前}[{数字の列}:{数字の列}] {バージョン番号} - [Firebase/Analytics][I-{数字の列}] To enable debug logging set the following application argument: -FIRAnalyticsDebugEnabled (see http://goo.gl/RfcP7r)参考
https://firebase.google.com/docs/analytics/ios/events?hl=ja
https://qiita.com/kazu14m1/items/3da746522ec41a8d5d2c
- 投稿日:2019-03-05T16:11:20+09:00
[Tips] UITextviewの行数とかを取得する
概要
UITextviewの表示状況取得のためのTips。ちなみに設定系は、[Tips] UITextviewをUILabelっぽく使うで。
0,取得タイミングについて
同じtextでも表示領域がちがえば行数も変わってくるわけで、例えば表示前に情報を取得しようと思ってもうまくいかないことが多い。AutoLayoutがベースになっているなら、UIViewControllerライフサイクルの
viewWillLayoutSubViews
タイミングで取得するのをオススメ。1,行数を取得する
UITextViewの行数を取得するextension UITextView { var numberOfLines: Int { // prepare var computingLineIndex = 0 var computingGlyphIndex = 0 // compute while computingGlyphIndex < layoutManager.numberOfGlyphs { var lineRange = NSRange() layoutManager.lineFragmentRect(forGlyphAt: computingGlyphIndex, effectiveRange: &lineRange) computingGlyphIndex = NSMaxRange(lineRange) computingLineIndex += 1 } // return if textContainer.maximumNumberOfLines > 0 { return min(textContainer.maximumNumberOfLines, computingLineIndex) } else { return computingLineIndex } } }Apple公式ドキュメントに方法(Objective-C!!!)がでていたのでこれを参考に実装。ただし、素直に実装してみると、「最大行数設定しているのに改行の入り方によってはそれよりも大きい数が返ってきてしまう」という不具合があったため、上記のように返り値に少し工夫をした。
参考:Apple: Text Layout Programming Guide: Counting Lines of Text
2,表示省略されているかを取得する
UITextViewの表示省略されているかextension UITextView { var isTruncated: Bool { // prepare let lineMax = textContainer.maximumNumberOfLines > 0 ? textContainer.maximumNumberOfLines : Int.max var computingLineIndex = 0 var computingGlyphIndex = 0 // compute & return while computingGlyphIndex < layoutManager.numberOfGlyphs { // analyze line let overLineMax = computingLineIndex > lineMax let existsTruncatedGlyph: Bool = { let truncatedGlyphRange = layoutManager.truncatedGlyphRange(inLineFragmentForGlyphAt: computingGlyphIndex) return truncatedGlyphRange.location != NSNotFound }() // guard guard !overLineMax, !existsTruncatedGlyph else { return true } // prepare for next var lineRange = NSRange() self.layoutManager.lineFragmentRect(forGlyphAt: computingGlyphIndex, effectiveRange: &lineRange) computingGlyphIndex = NSMaxRange(lineRange) computingLineIndex += 1 } return false } }1の応用。指定行の省略文字位置を取得するファンクション
truncatedGlyphRange(inLineFragmentForGlyphAt:)
がlayoutManagerにあるためこれを用いる。ちなみに、設定系の話だが、
.textContainer.lineBreakMode = .byTruncatingTail
とかの設定をしないと3点リーダー等の省略文字は出ない。備考
(特になし)
- 投稿日:2019-03-05T16:11:20+09:00
[Tips] UITextViewの行数などを取得する
概要
UITextviewの表示状況取得のためのTips。ちなみに設定系は、[Tips] UITextviewをUILabelっぽく使うで。
0,取得タイミングについて
同じtextでも表示領域がちがえば行数も変わってくるわけで、例えば表示前に情報を取得しようと思ってもうまくいかないことが多い。AutoLayoutがベースになっているなら、UIViewControllerライフサイクルの
viewWillLayoutSubViews
タイミングで取得するのをオススメ。1,行数を取得する
UITextViewの行数を取得するextension UITextView { var numberOfLines: Int { // prepare var computingLineIndex = 0 var computingGlyphIndex = 0 // compute while computingGlyphIndex < layoutManager.numberOfGlyphs { var lineRange = NSRange() layoutManager.lineFragmentRect(forGlyphAt: computingGlyphIndex, effectiveRange: &lineRange) computingGlyphIndex = NSMaxRange(lineRange) computingLineIndex += 1 } // return if textContainer.maximumNumberOfLines > 0 { return min(textContainer.maximumNumberOfLines, computingLineIndex) } else { return computingLineIndex } } }Apple公式ドキュメントに方法(Objective-C!!!)がでていたのでこれを参考に実装。ただし、素直に実装してみると、「最大行数設定しているのに改行の入り方によってはそれよりも大きい数が返ってきてしまう」という不具合があったため、上記のように返り値に少し工夫をした。
参考:Apple: Text Layout Programming Guide: Counting Lines of Text
2,表示省略されているかを取得する
UITextViewの表示省略されているかextension UITextView { var isTruncated: Bool { // prepare let lineMax = textContainer.maximumNumberOfLines > 0 ? textContainer.maximumNumberOfLines : Int.max var computingLineIndex = 0 var computingGlyphIndex = 0 // compute & return while computingGlyphIndex < layoutManager.numberOfGlyphs { // analyze line let overLineMax = computingLineIndex > lineMax let existsTruncatedGlyph: Bool = { let truncatedGlyphRange = layoutManager.truncatedGlyphRange(inLineFragmentForGlyphAt: computingGlyphIndex) return truncatedGlyphRange.location != NSNotFound }() // guard guard !overLineMax, !existsTruncatedGlyph else { return true } // prepare for next var lineRange = NSRange() self.layoutManager.lineFragmentRect(forGlyphAt: computingGlyphIndex, effectiveRange: &lineRange) computingGlyphIndex = NSMaxRange(lineRange) computingLineIndex += 1 } return false } }1の応用。指定行の省略文字位置を取得するファンクション
truncatedGlyphRange(inLineFragmentForGlyphAt:)
がlayoutManagerにあるためこれを用いる。ちなみに、設定系の話だが、
.textContainer.lineBreakMode = .byTruncatingTail
とかの設定をしないと3点リーダー等の省略文字は出ない。備考
(特になし)
- 投稿日:2019-03-05T13:51:30+09:00
UICollectionViewFlowLayoutのestimatedItemSizeを指定するとiOS12で表示がおかしい
問題
UICollectionViewFlowLayoutのestimatedItemSizeに以下を設定する。
if let flowLayout = self.collectionView.collectionViewLayout as? UICollectionViewFlowLayout { flowLayout.estimatedItemSize = CGSize(width: 1, height: 1) }もしくは
if let flowLayout = self.collectionView.collectionViewLayout as? UICollectionViewFlowLayout { flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize }本来有効になるUICollectionViewCellのSelf-Sizing CellsがiOS12未満だと問題ないが、iOS12ではうまく動かない。
対応
class CollectionViewCell: UICollectionViewCell { @IBOutlet weak internal var label: UILabel! override func awakeFromNib() { super.awakeFromNib() self.contentView.translatesAutoresizingMaskIntoConstraints = false let leftConstraint = self.contentView.leftAnchor.constraint(equalTo: self.leftAnchor) let rightConstraint = self.contentView.rightAnchor.constraint(equalTo: self.rightAnchor) let topConstraint = self.contentView.topAnchor.constraint(equalTo: self.topAnchor) let bottomConstraint = self.contentView.bottomAnchor.constraint(equalTo: self.bottomAnchor) NSLayoutConstraint.activate([leftConstraint, rightConstraint, topConstraint, bottomConstraint]) } }UICollectionViewCellのawakeFromNib()で上記の設定を行うことでうまく動作するようになる。
- 投稿日:2019-03-05T04:53:16+09:00
バグに気づきやすくなる??演算子を改造したカスタムオペレータ 【Swift】
はじめに
確かに,
??
をむやみに使うとデバッグ段階で気づくべきバグに気付けなくなる可能性があるので,下記の記事で述べられていることはとても有意義に感じた.
一方で,下記の記事で紹介されている方法を毎回行うのも面倒なので,カスタムオペレータを作ってみた.安易な気持ちで
as? Int ?? 0
みたいなコードを書いてはいけません(戒め
https://qiita.com/lovee/items/a82087f8c3663f207c7f作ったカスタムオペレータ
!?
二項演算子で表現した.
エラー分は関西人風味でお届け.infix operator !? func !?<T>(lhs: T?, rhs: T) -> T { return lhs ?? { assertionFailure("って,アンラップ出来んやないか~~~~い?") return rhs }() } let text: String? = nil // デバッグ環境ならエラー, プロダクション環境なら"hello world"が返却されます print(text !? "hello world")
- 投稿日:2019-03-05T04:53:16+09:00
バグに気づきやすくなる`??`演算子を改造したカスタムオペレータ 【Swift】
はじめに
確かに,
??
をむやみに使うとデバッグ段階で気づくべきバグに気付けなくなる可能性があるので,下記の記事で述べられていることはとても有意義に感じた.
しかし,毎回記述するのも大変そうなので,簡単に扱えるカスタムオペレータを作ってみた.安易な気持ちで
as? Int ?? 0
みたいなコードを書いてはいけません(戒め
https://qiita.com/lovee/items/a82087f8c3663f207c7f作ったカスタムオペレータ
!?
二項演算子で表現した.
エラー文は関西人風味でお届け.これでバグを出ても決して苛立つ事は無くなるので一石二鳥である.ぜひ採用してみてほしい.infix operator !? func !?<T>(lhs: T?, rhs: T) -> T { return lhs ?? { assertionFailure("って,アンラップ出来んやないか~~~~い?") return rhs }() } let text: String? = nil // デバッグ環境ならエラー, プロダクション環境なら"hello world"が返却されます print(text !? "hello world")最後に
プログラマが真にnil許容を許容している場合は
??
を,コンパイラに翻弄されてしまっている場合は!?
を使う,といったような使い分けが出来ると良いと思った.