20190305のiOSに関する記事は13件です。

【SwiftChaining】 処理を確定する関数

SwiftChainingの解説記事その5です。

SwiftChainingとは何か?というのは、こちらの最初の記事を参考にしてください。

前回の記事では、監視対象のオブジェクトのchain()を呼んだ後に繋げて書く「処理を構築する関数」の解説をしました。

今回は、「処理を構築した後に確定させて監視を開始する関数」について少し詳しく解説しようと思います。

今まで何度も出てきたものになるのですが、endsyncについてです。

処理を確定する関数

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的には適合しているプロトコルがFetchableSendableかということなのですが、これはまた別の記事で解説しようと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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と同じようなものと思っていただいて良いと思います。

例としてIntStringに変換する場合は、以下のようになります。

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) }

mapdoと同じくクロージャを実行するものではあるので、何でも書きたい処理は書けるのですが、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()

guardmapと同じく関係のない処理を書かずに、単にイベントを通すか通さないかという条件だけを書いたほうが良いと思います。

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同士の場合のみです。型が違う場合は以下に紹介するtuplecombineを使うことになります。

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

ValueHolderKVOAdapterは値を保持しているものなので、イベントを受け取ったら値をセットします。Notifierはそれ自身が値を保持するものではないので、受け取った値をそのまま送信する感じの動作になります。

また、前回の記事にも書きましたが、sendToで送るイベントの型と受け取るオブジェクトの型がオプショナルの有無の違いだけであれば、mapなどで変換しなくてもそのままつなげることができます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】 カスタムキーボードを無効化する

アプリ内でサードパーティー製のカスタムキーボードを制限、無効化する方法を紹介します

脆弱性診断NG

事の発端は、↓でした。

Input Interception: Keyboard Extensions Allowed

これは、fortifyという脆弱性診断の静的解析ツールによって吐かれた脆弱性です。

サードパーティのカスタムキーボードの利用が無効化されていません。カスタムキーボードはユーザ入力を全て取得でき、悪意の有無に関わらずキーロギングを行っている可能性があります。
という脆弱性のようです。

カスタムキーボードの脆弱性とは?

予測変換などに活用するため、ユーザの入力情報を取得しているため、それによって、パスワードやクレジットカード情報などが送信されてしまう脆弱性があるようです。
例えば、有名なアプリsimejiでは下記のサイトのようなリスクが有るようです。
使うのは危険!?Simejiを安全に使う方法

対処法

対処法は、以下のコードをAppDelegate.swiftに書くだけです。
これで、simejiアプリを無効化することができました。

AppDelegate.swift
func application(_ application: UIApplication, shouldAllowExtensionPointIdentifier extensionPointIdentifier: UIApplicationExtensionPointIdentifier) -> Bool {
    if extensionPointIdentifier == .keyboard {
        return false
    }
    return true
}

参考資料

おまけ

「カスタムキーボード 無効化」とぐぐっても出てこなくて苦労しました、、
「カスタムキーボード 制限」とググればよかったんですね、、泣

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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ともにエラーハンドリングなどは全て省略されていますのでプロダクションとして使う場合はもう少し手を加えてあげる必要がありますのでご注意を!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

笑顔判定でAmazonPrimeのドキュメンタルを正確に判定する。

はじめに

こんにちは、drama(@1901drama)です!

Amazon Prime Videoの ドキュメンタル が好きなのですが、
シーズン1から「フジモンの判定甘いんじゃないか」問題が評価を下げる要因として話されています。。。

場回し的な人がいないと展開が少なくなったりしますし、ある程度仕方ないかな とは思うのですが、
そもそも 本当に笑っているのでしょうか?  この問題をARで解決しました!

出来たもの

ドキュメンタルヘルパー


・カメラ画像から顔検出をする
・笑顔かどうかを判定する
・笑ってたらアウトにする(顔をドキュメンタルのロゴに変える/画面のふちに赤っぽいフレームを追加する)


これでどこでもドキュメンタルが出来ます。
※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)

そして、それ以上に驚いたのが、
documental-cpu.jpg
CPU使用率がすごい。
デバイスがiPhone SEというのもありますが、検出精度をlowにしていても、基本100%以上でした。
これを6時間見続ける審査員って相当疲れるんだろうなっと思いました。

難しかった点

1.ARAnchor->画面位置への変換
当初は遠近を把握する為に、ARFaceAnchorを利用して顔判別をしようと思ってました。

class ARFaceAnchor : ARAnchor

しかしフレーム単位では処理が重過ぎたので、アウトマークの画像サイズと場所を高速で更新する方法に変えました。
因みにiPhoneX以上の機種であればARkit2の顔に自動でマスクをつける機能が使えます。
顔を猫に変えるとか、アウトマーク以外の追加であれば、その方が簡単に導入出来ると思います。

参考:Creating Face-Based AR Experiences

2.カメラの向き(横か縦)を、cgimageに引継げない。
UIimageは、撮影時のデバイス向きを保持しているのですが、
動画から取り出したimageの cgimage は、向き情報を持ってなかったです。
顔検出や笑顔判定には向き情報が必要な為、ここを解決出来ず縦でしか使え無いアプリとなってしまいました...。

一般的には画面の向きも 別変数で取得して一緒に利用しているのでしょうか?
このあたりご存知の方いれば教えて欲しいです。

まとめ

ドキュメンタルの結論
「笑ってるけど、皆 まあまあ 笑ってる!!」 でした。なので楽しく見ましょう。

ここまで読んで頂きありがとうございました!

動画:ドキュメンタル シーズン5
引用中の動画は他のものに置き換えるかも知れません。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CIDetectorでAmazonPrimeのドキュメンタルを正確に判定する。

はじめに

こんにちは、drama(@1901drama)です!

Amazon Prime Videoの ドキュメンタル が好きなのですが、
シーズン1から「フジモンの判定甘いんじゃないか」問題が評価を下げる要因として話されています。。。

場回し的な人がいないと展開が少なくなったりしますし、ある程度仕方ないかな とは思うのですが、
そもそも 本当に笑っているのでしょうか?  この問題をARで解決しました。

出来たもの

ドキュメンタルヘルパー


・カメラ画像から顔検出をする
・笑顔かどうかを判定する
・笑ってたらアウトにする(顔をドキュメンタルのロゴに変える/画面のふちに赤っぽいフレームを追加する)


これでどこでもドキュメンタルが出来ます。
やってみてわかったのですが、他の人も結構笑顔判定されます。
※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)

そして、それ以上に驚いたのが、
documental-cpu.jpg
CPU使用率がすごい。
デバイスがiPhone SEというのもありますが、検出精度をlowにしていても、基本100%以上でした。
これを6時間見続ける審査員って相当疲れるんだろうなっと思いました。

難しかった点

1.ARAnchor->画面位置への変換
当初は遠近を把握する為に、ARFaceAnchorを利用して顔判別をしようと思ってました。

class ARFaceAnchor : ARAnchor

しかしフレーム単位では処理が重過ぎたので、アウトマークの画像サイズと場所を高速で更新する方法に変えました。
因みにiPhoneX以上の機種であればARKit2の顔に自動でマスクをつける機能が使えます。
顔を猫に変えるとか、アウトマーク以外の追加であれば、その方が簡単に導入出来ると思います。

参考:Creating Face-Based AR Experiences

2.カメラの向き(横か縦)を、cgimageに引継げない。
UIimageは、撮影時のデバイス向きを保持しているのですが、
動画から取り出したimageの cgimage は、向き情報を持ってなかったです。
顔検出や笑顔判定には向き情報が必要な為、ここを解決出来ず縦でしか使え無いアプリとなってしまいました...。

一般的には画面の向きも 別変数で取得して一緒に利用しているのでしょうか?
このあたりご存知の方いれば教えて欲しいです。

まとめ

ドキュメンタルの結論
「笑ってるけど、皆 まあまあ 笑ってる」 でした。なので楽しく見ましょう。

ここまで読んで頂きありがとうございました!

動画:ドキュメンタル シーズン5
引用中の動画は他のものに置き換えるかも知れません

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】スワイプ、フリックで分岐するSafari風スクロール【C#】

【Unity】スワイプ、フリックで分岐するSafari風スクロール【C#】

はじめに

スワイプした分スクロールできて、かつフリックすると速度をもってスクロールできる、iOS標準ブラウザのSafari風のUXのスクロール方法を実装したい!

ついでに2本指での拡大と縮小も実装しました。(なぜ?)
応用時はクランプなど用いてスクロール範囲を制御してください。

荒い実装ではありますが、そこそこ良い動きになったので備忘録かつ共有。

開発環境

OS: MacOS Mojave
開発環境: Unity 2018.2.14f1 personal
開発言語: C#

ソースコードとその説明

SwipeManager.cs
using 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/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Firebase DebugViewの設定方法

画面例

Firebaseコンソール→アナリティクス→DbugView
シュミレーターを実行するとリアルタイムにイベントに対して更新されるようになる

スクリーンショット 2019-03-05 16.03.14.png

ドキュメント

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 のデバッグ コンソールにイベントが表示され、イベントが送信されているかどうかを即座に確認することができます。

設定例

スクリーンショット 2019-03-05 17.00.40.png

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

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[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点リーダー等の省略文字は出ない。

備考

(特になし)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Tips] UITextViewの行数などを取得する

スクリーンショット 2019-03-05 16.34.16.png

概要

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点リーダー等の省略文字は出ない。

備考

(特になし)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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()で上記の設定を行うことでうまく動作するようになる。

参考: UICollectionViewFlowLayout estimatedItemSize does not work properly with iOS12 though it works fine with iOS 11.*

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

バグに気づきやすくなる??演算子を改造したカスタムオペレータ 【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")

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

バグに気づきやすくなる`??`演算子を改造したカスタムオペレータ 【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許容を許容している場合は??を,コンパイラに翻弄されてしまっている場合は!?を使う,といったような使い分けが出来ると良いと思った.

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む