20190305のSwiftに関する記事は15件です。

【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で続きを読む

CS193p class note - 3 Swift programming language

Memo:
  1. There is a global function that will create a CountableRange from floating point values.
for i in stride(from: 0.5, through: 15.25, by: 0.3) {   
}
Tuple:
  1. It is nothing more than a grouping of values. You can use it anywhere you can use a type.
  2. You can use tuples to return multiple values from a function or method
Computed properties:
  1. The value of a property can be computed rather than stored.
  2. "set" is optional. You can create a read-only computed property and you don't need to specify the keyword "get".
  3. Lots of times a "property" is derived from other state. That's why we use computed properties.
Access control:
  1. internal: this is the default, it means "usable by any object in my app or framework"
  2. private: this means "only callable from within this object"
  3. private(set): this means "this property is readable outside, but not settable"
  4. fileprivate: accessible by any code in this source file
  5. public: (for framework only)this can be used by objects outside my framework
  6. open: (for framework only)public and objects outside my framework can subclass this.
Extensions:
  1. You can add methods/properties to a class/struct/enum even if you don't have the source.
  2. The properties you add have no storage associated with them (computed only)
  3. You can't re-implement methods or properties that are already there (only add new ones)
Enum:
  1. It can only have discrete states
  2. An enum is a value type, so it is copied when it is passed around.
  3. Each state can (but doesn't have to) have its own "associated data".
  4. When you set the value of an enum you must provide the associated data (if any).
  5. Swift can infer the type on one side of the assignment or the other (but not both).
  6. An enum's state is checked with a switch statement.
  7. If you don't want to do anything in a given case, use "break".
  8. You must handle all possible cases (although you can default uninteresting cases).
  9. Each case in a switch can be multiple lines and does not fall through the next case.
  10. Associated data is accessed through a switch statement using "let syntax"
  11. An enum can have methods and computed properties but no stored properties
  12. "mutating" keyword is required because enum is a value type.
ARC (Automatic Reference Counting):
  1. Reference types (classes) are stored in the heap. The system "counts reference" to each of them and when there are zero references, they get tossed automatically. This process is known as "Automatic Reference Counting" and it is not garbage collection.
  2. You can influence ARC by how you declare a reference-type var with these keywords: strong, weak, unowned
  3. strong: normal reference counting
  4. weak: weak means "if no one else is interested in this, then neither am I, set me to nil in that case". We usually use weak in IBOutlet and delegate.
  5. unowned: "don't reference count this. Crash if I am wrong". This is very rarely used. Usually only to break memory cycles between objects.
  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

Terobosan Terbaru situs judi online rajasenangqq

Terobosan terbaru dari situs judi poker domino online terpercaya rajasenangqq
RAJASENANGQQ DEPO PULSA.jpg
Rajasenangqq mempermudah para member untuk melakukan deposit dengan mudah tanpa harus ribet yaitu dengan system deposit menggunakan pulsa.
Jadi anda tidak perlu harus ke ATM untuk transfer dana deposit.

Untuk anda para pecinta domino QQ, Kini hadir situs domino QQ :

  1. TERBAIK (Dalam hal PELAYANAN)
  2. TERPERCAYA (GAMPANG MENANG)
  3. TERCEPAT (Proses DEPO-WD).

Kami menyediakan 8 permainan dalam 1ID :
- POKER
- BANDAR POKER
- ADU Q
- BANDAR Q
- DOMINO 99
- CAPSA SUSUN
- SAKONG
- BANDAR 66

MIN. DEPO HANYA RP. 10.000

DAPATKAN BONUS {TO - 0.5%, REFERAL - 10%, TRIK}

KLIK LINK :
http://bit.ly/RJ53nang
Whatsapp : http://bit.ly/w4hpRJQQ

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

SwiftのFlowControllerによる画面遷移の基本

FlowControllerの元祖はこちらのIssueで、このIssue提案者が英語で書いた説明記事がこちらです。しかし全て英語でコードが所々しかなく難しかったので、@shizさんの記事をベースにしてつくったサンプルアプリを元に、基本的な『FlowControllerによるpush遷移とpresent遷移のやり方』を解説します。

サンプルアプリ

今回は以下のようなアプリを@shizさんのサンプルコードをベースに作りました。

デモ

Mar-06-2019 11-58-33.gif
他のボタンの動作デモはYoutube版へ

画面遷移の流れ

  1. 記事一覧(ListView)を表示
  2. push(DetailFirstView)
  3. push(DetailSecondView)
  4. push(DetailThirdView)
  5. present(ModalView)
  6. push(ModalDetail)
  7. 完了ボタンで全ての流れを終了

コード

GitHub Repository

FlowControllerとは何なのか

原文や@shizさんの記事ではCoordinatorパターンと比較する形でFlowControllerのメリットを紹介されています。ただペーペープログラマである僕らにとっては「まずCoordinatorパターンって何?」から入らなければならないため理解に苦しむと思います。

FlowControllerとは、簡単に言うと「通常はViewControllerが担っていた画面遷移の処理(責務)を、ViewControllerから切り分けて代わりに担当してくれるController」です。

具体的には「UIViewControllerを継承したViewControllerのラッパー」であり、構造的にはAppFlowController>HogeFlowController>(NavigationController)>ViewControllerという親子関係になっています。

今までViewControllerで行なっていたpushやpresentの画面遷移を親のHogeFlowControllerに(delegateを使って)委任することで、ViewControllerの責務を減らし、コードを少なくスッキリさせています。

ちょっと何いってるかわかんない

実際のところ、色々な設計パターン(アーキテクチャ)や実装の経験を経てより良い設計に辿り着くのが本来の流れだと思う(知識は必要になった時に初めて身につく)ので、「ちょっと何いっているかわかんない」という人は無理にFlowControllerやCoordinatorパターンを使わなくて良いと思います(←説明の怠慢)。

何が良いのか

こういった設計パターンやアーキテクチャの主目的はどれも同じで「責務の切り分け」とそれによる「テストを容易にすること」です。

このFlowControllerの目的・良さも「ViewControllerの画面遷移の責務を切り分けてやることで、コードの可読性や保守性を良くし、テストも容易にできること」と言えると思います。

Coordinatorパターンでもほぼ同じメリットを得られる(はず)ですが、原文や@shizさんの記事にもあるように「Coordinatorパターンにはないメリットを得られるからFlowControllerを使う」わけで、その詳しい違いについては本記事では割愛します。

追記

僕なりにCoordinatorパターンとFlowControllerパターンの違いをまとめたので最後におまけとして付けておきます)。

今回のサンプルの処理流れ

今回作成したサンプルの構造としては以下の画像のようになっています。
2019-03-05 16:59 Office Lens.jpeg

AppFlowControllerがすべてのFlowControllerの親です(アプリ起動後にAppDelegateapplication(_:didFinishLaunchingWithOptionsAppFlowController)AppFlowControllerstart()を呼んでフローをスタートしています)。

初期画面

最初にListViewControllerを表示したいので、まずListFlowControllerを生成します(この過程でembeddedNavigationController = UINavigationController()も生成している)。

次にListFlowControllerの中でListViewControllerを生成し、embeddedNavigationController.viewControllers = [listViewController]とすることで初期画面を表示します。

push遷移

そしてNextボタンが押された時にdelegateであるListFlowControllerに画面遷移を依頼し、そこで今度はDetailFirstViewControllerを生成し、embeddedNavigationController.viewControllers = [firstViewController]とすることで画面遷移をするわけです。

present遷移

present遷移の場合は新たなナビゲーションを開始(新しいNavigationControllerを生成)しなければならないため、ThirdViewControllerからdelegateであるListFlowControllerに依頼し、さらにListFlowControllerdelegateであるAppFlowControllerに新たなフロー(ModalFlowController)を開始するように依頼します。

ここでpresent遷移をするのはModalFlowControllerです。ModalFlowControllerもUIViewControllerを継承しているのでpresent遷移が可能で、その子が上下左右ぴたぴたに張り付いているだけなので、見た目としてはModalViewControllerがpresent遷移したのと同じになります。

ModalFlowController.swift
override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        children.first?.view.frame = view.bounds
    }

flowのremove

最後のFifthViewControllerで「完了」ボタンを押した時は、同様にdelegateを使ってNewModalFlowControllerを経由して一番親であるAppFlowControllerに「フローを終了して!」と依頼する形になります。

フロー開始時にAppFlowControlleradd(childController: listFlowController)のようにaddしているだけなら、同じくAppFlowControllerself.children.forEach { remove(childController: $0) }を実行してやれば、今のフローを全て削除してホーム画面などに戻ることができます(サンプルではListFlowを再度startして記事一覧画面を表示しています)。

しかし今回はpresent遷移(下からViewが上がってくる遷移)なのでAppFlowControlleradd(childController: newModalFlowController)とせずviewController.present(newModalFlowController, animated: true)としています(ここでのviewControllerはpresent遷移する直前のThirdViewControllerのインスタンスを、delegateで依頼する時に引数としてAppFlowControllerまで渡してきたものです)。

なので画面を消すときもdismiss(上から下に下がる消え方)をしなければなりません。そこでModalFlowControllerAppFlowControllerに「フローを終了して」と依頼した直後にself.dismiss(animated: true)で自分自身をdismissしています。

Coordinatorパターンとの違い(おまけ)

@shizさんの記事の各見出しを僕なりに整理してみました。@shizさんの記事や原文を読んで良くわからなかったときに、僕なりの補足を見ることで皆さんの理解の助けになればと思います(ListFlowControllerのフローの前にもうひとつ、LoginFlowControllerのフローが開始されているので、AppFlowController>LoginFlowController>ViewControllerという構造になっている前提で以下は読んでいただければと)。

3.FlowControllerは依存関係を管理

LoginDependencyContainerなどに依存すべきプロパティ?を追加してやることで簡単に依存性を注入できる?←Coordinatorパターンは?


4. 子FlowControllerの管理が簡単

FlowControllerはUIViewControllerを継承しているので、
addとremoveのメソッド(Extension)だけで子FlowControllerできる。←Coordinatorパターンは?


5.UIWinowを保持する必要がない

Coordinatorパターンの場合はinitで都度UIWindowを触っている。

FlowControllerならUIViewControllerを継承しているので、UIWindowをいじるコードを各必要がない。

6.個々のフローを疎結合にできる


疎結合とはシンプルな結合という意味です(対義語は密結合)。Coordinatorパターンでは親のAppCoordinatorでNavigationControllerを生成して、子であるLoginCoordinatorをinitする時に引数で渡さなければならないから結合度が高い(密結合である)。


FlowControllerなら子であるLoginFlowController自身でNavigationControllerを生成できる。

7. UIResponderを継承している


Coordinatorパターンと違って、FlowControllerはUIViewControllerを継承しているのでtouchesBeganを始めとした様々なイベントのデリゲートメソッドを使用することができる
。

8. FlowControllerから画面の表示などを管理できる


本来は子VCにさせる表示関係の仕事も、親であるFlowControllerも一応できるよってことではないかと思います(でもそれをしてしまうと役割切り分けた意味なくね?臨時策?
)。

9. NavigationControllerの戻るボタン


CoordinatorパターンはNavigationの「戻る」ボタンを手動で設定しなければならない。

FlowControllerは(UIViewControllerを継承しているので?)自動でやってくれる。

10. コールバックで画面遷移を依頼する

よく分からない。Coordinatorパターンがどうやって画面遷移をしているか知らない(おい)。

FlowControllerは「ViewControllerが、delegate(親であるLoginFlowController)に依頼して画面遷移する」のですが、コールバックは使ってないんじゃないのかな…(画面遷移完了したらViewControllerでバック後の処理を実行するとかにはなっていないと思うのだが)。

まとめ

まだ記事を書いてる途中で理解がうやむやだったりするので、リプいただければ随時補足・修正していきます!

  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

NSObject-Rxの実装を読み解く(二番煎じ)

NSObject-Rxの実装を紐解きます。

RxSwiftを利用していれば、おそらく利用したことのあるライブラリ、RxSwiftCommunity/NSObject-Rx

一応解説しておくと、NSObjectのサブクラスであれば、DisposeBagを宣言する必要なく利用することができるという便利な代物です。

RxSwiftで開発をしているととても良く使うライブラリの一つではないでしょうか。

今回、ひょんなことからこのライブラリがどのようにしてdisposeBagを実装しているのかを確認してみたくなって、びっくりしたので、まとめておきます。

Swiftの機能のみを利用した実装ではない。

今回話しているRxSwiftCommunity/NSObject-RXは、Swiftの機能のみを利用した実装にはなっていません。

NSObject-Rxの実体ファイルは下記ファイルなのですが、結構無茶苦茶やってますね。

https://github.com/RxSwiftCommunity/NSObject-Rx/blob/master/NSObject%2BRx.swift

大まかな流れは、

  1. Reactive Protocolのデフォルト拡張をBase: AnyClassに指定(すべてのクラスでデフォルト拡張される)
  2. Objective-Cのメンバ変数追加ができるAssociated Objectというびっくり黒魔術を利用してDisposeBagを宣言
    (SwiftでObjective-Cの黒魔術ってどうなった?: https://qiita.com/fmtonakai/items/e9036dec4af2609b5715)

  3. 初回アクセス時には生成、セット時にはsetter内部でセットされたdisposeBagをAssociated Object経由でプロパティかのように保持

  4. disposeBagの生成、アクセス処理はobjc_sync_enterobjc_sync_exitで強制同期。

結構すごい実装になってました。

ちなみになんですが、Associated ObjectはピュアSwiftだと動作しないようなので、Ubuntuとかで開発する場合は、うまくいかない感じだと思われます。

読み解いてみて

Objective-C由来の黒魔術がこんなところで生かされていたのが結構驚きでした。

  • このエントリーをはてなブックマークに追加
  • 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で続きを読む