Storyboard よりも Xib を使いたい理由

これは Aizu Advent Calendar 2018 の 7日目の記事です。
6 日目は id:acomagu さんで、8 日目は id:NoahOrberg さんです。

adventar.org

はじめに

Interface Builder (以下「IB」という。) で UIViewController のレイアウトを組むには、 Storyboard と Xib の2つのファイルを使う方法があります。 その中でも、私が Xib を使う理由について、この記事で紹介します。

Storyboard と Xib

まずは、 Storyboard と Xib について軽く説明します。

Apple のドキュメントによると、Storyboard は、画面間の関係と、画面の中身を表示する視覚的表現だとあります。 developer.apple.com

また、Xib (Nib) は、View や Window 等のアプリ内の視覚的パーツをデザインするためのドキュメントだとあります。 developer.apple.com

IB で視覚的にレイアウトが組めることには、違いはありません。しかし、 UIViewController の生成方法に違いがあります。

UIViewController の生成方法の違い

Storyboard の場合では、UIStoryboard のメソッド instantiateViewController(withIdentifier:) 等で、Storyboard ファイル内のレイアウトを読み込んで UIViewController を生成します。 Xib の場合では、UIViewController のイニシャライザ init(nibName:bundle:) を使い、Nib ファイル内のレイアウトを読み込んで UIViewController を生成します。

class SourceViewController: UIViewController {

    // ボタンを押して、次の画面へ遷移させたい時の処理
    @IBAction func buttonDidTap(_ sender: UIButton) {
        // Storyboard の場合
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        guard let destinationViewController1 = storyboard.instantiateViewController(withIdentifier: "DestinationViewController") as? DestinationViewController else {
            return
        }
        present(destinationViewController1, animated: true) // モーダル遷移

        // Xib の場合
        let destinationViewController2 = DestinationViewController(nibName: "DestinationViewController", bundle: nil)
        present(destinationViewController2, animated: true) // モーダル遷移
    }
}

Storyboard を用いた UIViewController の生成では、 instantiateViewController(withIdentifier:) の内部で、UIViewControllerinit(coder:) を呼び、Storyboard をシリアライズして、そのインスタンスを返しています。これは、イニシャライザが init(coder:) で固定されてしまうため、 以下のような問題が生まれます。

Storyboard の問題

UIViewController のサブクラスが、新しくストアドプロパティを追加し、クラス外からその値を与えたい場合があるとします。Storyboard の場合では、イニシャライザが init(coder:) を呼ぶようになっているため、ストアドプロパティを満たすイニシャライザを定義することは出来ず、 init(coder:) 内でそのプロパティを満たす必要があります。 init(coder:) 内で処理を書かなければいけないため、クラス外から値を与えることが出来ません。

class DestinationViewController: UIViewController {
    let newValue: Int // 新しいストアドプロパティ

    // Storyboard の場合、このイニシャライザが呼ばれる
    required init?(coder aDecoder: NSCoder) {
        newValue = 10 // このイニシャライザ内 (クラス内) でプロパティを満たさなければいけない
        super.init(coder: aDecoder)
    }
}

新しく追加したプロパティに対してのイニシャライザを利用できないとなると、イニシャライザ時にそのプロパティが満たされることが、言語仕様的に保証が出来ません。そのため、そのプロパティを定義するには、 Optional または Implicitly Unwrapped Optional 型にする必要があります。それに伴い、そのプロパティを参照する時には、nil チェックが必要になるケースが現れます。

class SourceViewController: UIViewController {

    @IBAction func buttonDidTap(_ sender: UIButton) {
        // Storyboard の場合
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        guard let destinationViewController = storyboard.instantiateViewController(withIdentifier: "DestinationViewController") as? DestinationViewController else {
            return
        }
        // DestinationViewController 外から値を代入
        destinationViewController.newValue = 10
        present(destinationViewController, animated: true)
    }
}


class DestinationViewController: UIViewController {
    // Optional または Implicitly Unwrapped Optional の必要がある
    var newValue: Int?

    func doSomething() {
        if let newValue = newValue { // nil チェック
            // newValue を用いた処理
        }
    }
}

nil チェックを回避するには、デフォルト値を与え、Optional または Implicitly Unwrapped Optional で無くす必要があります。
また、別の問題として、デフォルト値の有無に関係なく、本来はクラス外へ公開したくない場合でも、そのプロパティをミュータブル (var) にして公開しなければいけません。

class SourceViewController: UIViewController {

    @IBAction func buttonDidTap(_ sender: UIButton) {
        // Storyboard の場合
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        guard let destinationViewController = storyboard.instantiateViewController(withIdentifier: "DestinationViewController") as? DestinationViewController else {
            return
        }
        // DestinationViewController 外から値を代入
        destinationViewController.newValue = 10
        present(destinationViewController, animated: true)
    }
}


class DestinationViewController: UIViewController {
    // デフォルト値を代入
    // クラス外から代入するために、 非公開 (private) に出来ない
    var newValue: Int = 0
}

まとめると、 新しくストアドプロパティを追加した UIViewController のサブクラスを Storyboard から生成し、そのプロパティをクラス外から与えたい場合には、以下の問題があります。

  • そのプロパティに対するイニシャライザを定義できないため、そのプロパティを Optional または Implicitly Unwrapped Optional 型にする必要があり、 nil チェックが必要になるケースが現れる
  • nil チェックを回避することもできるが、適切なデフォルト値を代入する必要がある
  • クラス外から、そのプロパティを代入するために、 必ず ミュータブル (var)、かつ、公開するプロパティでなければならない

Storyboard の問題の解決

Xib の場合、 UIViewControllerinit(nibName:bundle:) を呼ぶことが出来れば、Xib のレイアウトを読み込んだインスタンスを生成出来ます。そのため、UIViewController のサブクラスに、新しくストアドプロパティを追加したとしても、init(nibName:bundle:) を内部で呼ぶようなイニシャライザを定義して呼べば、Xib のレイアウトを読み込み、かつ、そのプロパティを満たすことが出来ます。

class SourceViewController: UIViewController {

    @IBAction func buttonDidTap(_ sender: UIButton) {
        // Xib の場合
        let destinationViewController = DestinationViewController(value: 10, nibName: "DestinationViewController", bundle: nil)
        present(destinationViewController, animated: true)
    }
}

class DestinationViewController: UIViewController {
    // イミュータブルや非公開に出来る
    private let newValue: Int

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    init(value: Int, nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        self.newValue = value
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    }

    func doSomething() {
        // newValue を用いた処理 (nilチェックの必要なし)
    }
}

このイニシャライザは、単に以下のように、プロトコルで表現することも出来ます。

protocol NibInstantiableViewController where Self: UIViewController {
    associatedtype Input
    init(input: Input, nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?)
}

class DestinationViewController: UIViewController, NibInstantiableViewController {
    typealias Input = Int // Input には任意の型を与えられる
    private let newValue: Input

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    required init(input: Input, nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        self.newValue = input // input を用いて、ストアドプロパティを満たす
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    }
}

Storyboard から生成する場合では、クラス外から、新しいストアドプロパティを代入する場合は、そのプロパティを必ずミュータブル (var) 、かつ、公開する必要がありました。 しかし、 Xib の場合では、自前でイニシャライザを利用することが出来るため、必要に応じて、そのプロパティをイミュータブル (let) や 非公開 (private) にすることが出来るようになります。

まとめ

上記で触れていませんでしたが 、Storyboard の場合、Storyboard ID の指定や、生成した UIViewController のダウンキャストなどの手間もあります。Xib の場合、 直接そのクラスのイニシャライザから生成するため、ダウンキャストする必要もなく、Storyboard が抱えるイニシャライズの問題も解消できます。

もちろん、 UIViewController 間の関係を組むことが出来る等、 Storyboard にだけが持つ長所もあります。しかし、私は、それよりも Xib が持つ恩恵の方が大きいと考えているため、 Xib を利用するようにしています。

HLS形式の動画から字幕を取得する

HLS形式の動画から字幕情報を取得する機会があり、その実装を試みたがハマった。調べた限りこれに関する記事が無かったため、今回は記事として残す。

字幕情報を取得するには?

AVPlayerItemLegibleOutput を使用する。
AVPlayerItemLegibleOutput は、デリゲートをプロパティとして持っているため、そのデリゲートプロトコルを実装したクラスをセットする。
デリゲートをセットした AVPlayerItemLegibleOutputインスタンスAVPlayerItem のアウトプットとして add することで、デリゲートクラスで再生中に字幕情報を取得することが出来る。

// URLから AVPlayerItem, AVPlayer を生成
let videoURLString = "VIDEO_URL"
guard let videoURL = URL(string: videoURLString) else { fatalError("Failed to create url") }
let videoPlayerItem = AVPlayerItem(url: videoURL)
let videoPlayer = AVPlayer(playerItem: videoPlayerItem)

// AVPlayerItemLegibleOutput を生成する
// デリゲート先をセットし、 `videoPlayerItem` に追加
let legibleOutput = AVPlayerItemLegibleOutput(mediaSubtypesForNativeRepresentation: [])
legibleOutput.setDelegate(self, queue: .main)
videoPlayerItem.add(legibleOutput)

// 動画を再生
videoPlayer.play()

サンプル

今回のサンプルは、 GitHub に上げておく。 github.com

f:id:culumn:20181105192948g:plain

iOS Simulator で動作しないことだけ注意して欲しい。
(正しく動作する方法がわかる方は、プルリク送って下さい 🙏)

まとめ

最初は、 AVPlayerItemtracks をKVOする実装などしてみたが、デリゲートを設定するだけで良かったらしい。
AVFoundation を普段触らないから、わからん...

NOCで1位入賞しました

NOCとは

NOC(Native OSS Competition) とは、株式会社サイバーエージェントさん主催の、ネイティブ向けOSSライブラリを作るコンペティションです。今回はUI編ということで、UIに関わるOSSライブラリを作ることがテーマで、10/20(土)-10/21(日)の2日間に開催されました。
私は、普段iOSアプリ開発を行っていることから、iOSサイドで参加しました。

www.cyberagent.co.jp

開発の流れ

NOC当日までに、開発するライブラリのアイデアについて、オンライン上でメンターの方にフィードバックして頂き、開発の方針を固めます。そして当日は、メンターのエンジニア社員と開発に集中し、OSSライブラリとして公開を目指します。

当日

初日は、メンターのエンジニア社員の方々からのLTから始まりました。エンジニアとしてのアウトプットの重要性や、いいライブラリとはどのようなものか等について聞くことが出来ました。
その後は、メンターの方と顔合わせをして、ライブラリの最終的なアイデア決めをしました。 それからは、2日間ひたすらメンターと開発に集中しました。

余談ですが、私のメンターは @marty_suzuki さんでした。以前に技術ブログや著書を読んだことがあり、iOSエンジニアとして尊敬する方で、メンターとしてお会いすることが出来て感動しました。

当日には、お弁当やお菓子等の食事の支給もありました。 f:id:culumn:20181024000744j:plain

結果

最終日には、2日間開発したライブラリついて3分間のプレゼンを発表し、ソースコードと一緒に、メンターのエンジニア社員の方々に審査をして頂きました。

私は、iOS向けのカラーピッカーライブラリを開発し、1位に入賞することが出来ました。 副賞として、abemaくんのグッズも頂きました。🙌

1位を獲得することが出来ましたが、2日間で急いで開発したライブラリのため、いくつも問題を抱えています。自身で利用しようとも考えているため、今後OSSライブラリとして育てていきたいと思います!(スターとか貰えるとモチベ向上になります👀) github.com

f:id:culumn:20181024000213j:plain

ライブラリの中の技術の話については、後日、他の記事で書きたいと思います。

まとめ

NOCでは、OSSライブラリの開発から公開の一連の流れを体験することが出来ました。実際に沢山のスターを持つOSSライブラリを開発したエンジニアの方をメンターとして、OSSライブラリの開発について学ぶことが出来ました。また、サイバーエージェントさんのエンジニアとして、職場で役立つ技術についても教えて頂きました。

今回、宿泊先の手配や交通費支給等、金銭的な負担もほとんどなく、開発に集中できる環境を用意して頂きました。 2日間という短い期間でしたが、充実した時間を過ごすことが出来ました。

最後に、NOCという最高な体験を用意して下さったサイバーエージェントさん、本当にありがとうございました!!

スカラシップスポンサー枠でiOSDCに参加してきた

8/30(金)~9/2(日)の4日間、早稲田大学で開催されたiOSDC Japan 2018に参加してきました。今回は、そのレポート記事となります。

f:id:culumn:20180905111752j:plain

今回は、Wantedlyさんが募集していた学生応援プログラムに採択して頂き、スカラシップスポンサー枠で参加させていただきました。チケット代、宿泊費・交通費を支援して頂き、本当にありがとうございました!

www.wantedly.com

iOSDCとは

iOS Developers Conference Japanの略称で、iOS関連技術をコアのテーマとした技術者のためのカンファレンスです。今年は、3日+前夜祭の3.5日間での開催でした。

iosdc.jp

タイムテーブルは、こちらから閲覧することが出来ます。
この記事では、特に面白いと感じたトークや会場の様子を紹介します。

前夜祭

標準アプリから学ぶ、HIGが教えてくれないiOSデザインのこと

speakerdeck.com
日常的に使うドアを例に、デザインが持つべき役割の話から始まり、標準アプリの歴史から新しいUIが登場する背景などを考察するという内容でした。日常で特に意識することなく使う標準アプリも、意味を持ってデザインされていると知ることが出来ました。 デザイナーの方は偉大と感じると共に、自分もデザインについて学びたいと思う発表でした。

ツールとして利用するUIテスト

speakerdeck.com
各画面サイズやOS, 言語等により膨大になる画面数の確認を、UIテストを用いて解決しようという内容でした。発表者の方が自作したLunchを使った特定画面の起動、そのスクショの撮影方法などを話されていました。また、Xcode 10から追加されるテストの並列実行を使用した場合の計測をしていました。 このトークを参考に今後UIテストを導入し、画面レイアウトのチェックの自動化をしたいと思いました!

Day1

MicroViewControllerで無限にスケールするiOS開発

www.icloud.com
MicroViewControllerという新しい考えを提案し、その概念や実装の仕方、導入によって得られた効果を紹介する内容でした。MicroViewControllerは、ファットになりやすいViewControllerViewの部品一つ一つに割り当てることで、責務を分割・隠蔽でき、多人数で開発する時に起こるコンフリクトやオーバーヘッドを軽減するという概念でした。
MicroViewControllerを導入したことで、開発速度が2倍、画面の描画が最大で3倍速くなったことを知った時は、衝撃を受けました。個人的にも、ベストトーク賞1位が納得な内容でした。

差分アルゴリズムの原理について

speakerdeck.com
差分計算アルゴリズムの紹介や、そのアルゴリズムを適用したUITableViewの描画の計測などをするという内容でした。自分は、大量のデータをUITableViewで描画したことが無かったため、今後パフォーマンスを考慮するならば、この発表を参考にしようと思いました。

Day2

Depth in Depth

speakerdeck.com
Depth In Depth(深度 詳解)というタイトルで、深度や視差の説明から始まり、深度を使ったアプリのデモや、深度データ取得等の実装方法を紹介していました。自分は、ハードウェアの実装に弱いため、iPhoneのセンサから背景の合成など実装出来ることに感動しました。トークのタイトル通り、深度の深い内容を知ることが出来る面白い内容でした!

Day3

Swiftのジェネリクスはどうやって動いているのかコンパイラのソースから探る

speakerdeck.com
サイズが違う値型と参照型を持つSwiftが、どのようにジェネリクスを実現しているかをコンパイラのソースから読み解くという内容でした。後半は、正直理解が追いつきませんでしたが、今までコンパイラのソースを読んだことがない自分にとっては、新鮮な内容でした。
コンパイルされたSILLLVM-IRから読み解くことは予想でしかなく、コンパイラの実装から読み解くことが正確であるという発表の流れが、論理的で好きでした。

LLDBを最大限活用してみる。

speakerdeck.com LLDBのカスタムコマンドを作り、もっと便利にデバッグをしてみようという内容でした。LLDBデバッグをほとんどしたことがない自分にとっては、知見の塊でした。Swiftの定数(let)は、リビルドしないと値を書き換えることが出来ない欠点があると言って、LLDBで直接メモリの値を操作した時は、思わず笑ってしまいましたw。

会場の様子

LT

ビールやお弁当が用意され、とてもカジュアルな雰囲気で楽しめました。

ランチ

Day1-Day3の3日間は、ランチが提供されていました。 f:id:culumn:20180906151825j:plain Day2のランチタイムには、WantedlyさんによるReactorKitのライブコーディングが実施されていました。ReactorKitは、RxSwiftを使ったFluxフレームワークで、Wantedly Visitで採用されているらしいです。

企業ブース&提供

各スポンサー企業さんによる催しや提供がありました。

f:id:culumn:20180906154731j:plainf:id:culumn:20180906154734j:plain

懇親会

ビールや食事が用意され、すごい賑わいでした。 f:id:culumn:20180906151139j:plain f:id:culumn:20180906151255j:plain

まとめ

iOSDCの公式サイトにも記述されていますが、まさにエンジニアの祭典でした。レベルの高いiOS関連技術の話を聞くだけの場ではなく、飲食物の提供や催し等のおかげで、本当に祭りのような雰囲気で楽しむことが出来ます。

感想

このiOSDCは、人生初のカンファレンスの参加でした。自分が地方に住んでいることもあり、東京開催のカンファレンスに行くことがありませんでした。今まで、カンファレンスは、後日投稿されるスライドを見て、勉強出来れば良いだろうと考えていました。しかし、カンファレンスでは、現地でしか楽しめないものがあると知ることが出来ました。
また、iOSDCに参加することで、iOS開発者コミュニティの大きさを知ることが出来ました。私の身の回りでは、iOSアプリの開発者が自分しかいませんでした。しかし、iOSDCに参加することで、同じiOSアプリを開発する学生やエンジニアが大勢いることを実感することが出来ました。

今回、スカラップスポンサー枠でiOSDCに行く機会を提供して下さったWantedlyさん、iOSDCの開催を支えて下さったスタッフやスポンサー企業さん、本当にありがとうございました!🙏🙏🙏

三ヶ月間のHaskellセミナーを終えて

Haskellセミナーについて

本年度から学部三年になり、研究室に配属された。私の研究室では、関数型言語を用いて卒業研究を行うことになっており、学部三年生は基礎としてHaskellの学習をすることになっている。
特に前期は、「プログラミングHaskell」という本を教材にして、指導教員と学生で週一でセミナーを行った。

プログラミングHaskell

プログラミングHaskell

セミナーの内容としては

  • 課題として、本の各章の最後の練習問題を解き、次章の内容を読んでくる。
  • セミナーの時間に、自分が担当した練習問題の解答を皆に解説する。また、読んできた章の内容についてわからないことがあれば、教員に質問する。
  • 練習問題の解答と次章の内容に対しての疑問が無くなれば、セミナーの終わりに、次の練習問題を学生に割り振る。

というものだった。
個人的には、どうすれば自分の解答が理解してもらえるかを考え、資料を作成する力をある程度付けることができたと思う。また、自分の解答に対して他の学生や教員から質問が飛んでくるので、違う解法や自分の理解は正しいかを確認することができ、さらに理解を深めることができて良かった。

プログラミングHaskellの内容について

教材として使用したプログラミングHaskellを読んだ感想を書く。本としては全体で13章で構成されているが、セミナーで行った12章までの内容しか書かないことはご了承頂きたい。

1-4章

命名規則や基本的な式は各言語で異なるため、そこは覚えるしかないと思う。
個人的に興味深い内容は、カリー化だった。カリー化された関数を部分適用することで、有益な関数を作ることができることが面白いと感じた。
また、1章でクイックソートが5行で書けると知った時は、衝撃を受けた。

5-7章

リスト内包表記自体はPythonなどでも取り入れられているが、自分は初めて知った書き方だった。簡潔にリストを定義することができて便利。
6章では、Haskell再帰関数を定義するコツを記載されていて、この章以降で再帰関数を書く時に役に立った。
7章の高階関数では、畳込関数foldr, foldlの仕組みを理解することがとても難しかった。今も何となくで理解しているため、詳しく理解したい時は読み返したいと思う。 この本のfoldr, foldlの解説はわかりやすいという意見もあるので、自分の理解力が足りないだけかもしれない。

taiju.hatenablog.com

これは本の内容とは関係ないのだが、教員から教えてもらった再帰関数を読む時のコツを紹介したいと思う。再帰関数は、再帰部の中身を掘り下げて読むのではなく、その再帰部が正しく動作すると仮定して、基底部で再帰が止まるかを考えた方が読む効率が良いというものである。自分は、実際にこの思考を用いることで、再帰関数が異なって見えるようになった。

8-9章

  • パーサー
  • 対話プログラム

8章では、実際にパーサーに必要な関数を実装していく。最初、パーサの連結や選択の実装が理解できず、パーサーがどうして正しく動作するかが理解できていなかった。しかし、一旦理解ができるようになると、パーサーを組み上げることができて楽しかった。
9章では、アクションとHaskellでの入出力の実装が紹介されている。 8章が理解できれば、ここも理解することは難しくないと思う。

10-12章

  • 型とクラスの定義
  • 切符番号遊び
  • 遅延評価

10章では、新しい型やクラスの定義方法が紹介されている。Haskell再帰型を定義することができることを用いて、木構造や数式を処理する仮想マシンなどを実装している。
11章では、切符番号遊びを解くプログラムを実装する。演算のための型を新しく定義し、総当たりで解を求める。最初は効率は無視して実装するが、代数的な性質などを利用して、プログラムの効率を上げていく様子を見ることができて面白かった。
12章では、Haskellの評価戦略について解説している。以前からHaskellは遅延評価であると聞いたことはあったが、遅延評価自体を詳しく知らなかった。この章では、評価戦略について詳しく解説されており、遅延評価とは何か知ることができて面白かった。

まとめ

この本では、Haskellの機能やパーサーなどを作成するための思考, 関数が丁寧に解説されていて良かった。また、本の内容に従って実装すれば、自分でシーザー暗号解読器, 数式パーサー, 数式パーサーを用いたCUI電卓プログラム, CUIライフゲーム, 切符番号遊びなどが実装できるのも良い点だと感じた。解説だけがずっと記載されていると、 本を読むこと自体に飽きたりするが、実際にコードを書くことで、何かプログラムを動作させることができるのは楽しかった。

今後

後期にもまたセミナーを開いて下さる予定なので、そちらを頑張っていきたい。内容としては、プログラミングHaskellには記載されていなかったモナドのより深い概念やFRPについてやるらしいので楽しみである。大学の夏季休業で後期まで大分時間が空いてしまうので、セミナーの内容を時々復習して、Haskellについて忘れないようにしたい。

RxSwiftでUIPickerViewとUITextFieldのテキストをバインドする

最近RxSwiftの勉強をしていて思いついたバインドをやってみる。
UIPickerViewからデータを選択し、そのデータをUITextFieldのテキストにセットするものである。 f:id:culumn:20180606232037p:plain

UIPickerViewUITextFieldの入力として使用する

まずUIPickerViewUITextFieldの入力のViewとして使用するには、UITextFieldのプロパティinputViewUIPickerViewインスタンスを代入するだけでよい。

textField.inputView = UIPickerView()

ただ、これだけではUIPickerViewdataSourceが実装されていないので、選択するデータがない。また、UIPickerViewから選択したときのイベントをdelegateでハンドリングし、そのデータをUITextFieldのテキストにセットする必要がある。
今回はUIPickerViewdataSourcedelegateを用いての実装は省略するが、まあ面倒である。そこで本題のRxSwiftを用いた実装を次に紹介する。

RxSwiftでUIPickerViewUITextFieldをバインドする

class ViewController: UIViewController {
    @IBOutlet weak var strTextField: UITextField!
    let disposeBag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()

        let strPickerView = UIPickerView()
        strTextField.inputView = strPickerView

        // strsをstrPickerViewのデータ(タイトル)としてバインド
        let strs = ["abc", "def", "ghi"]
        Observable.just(strs)
            .bind(to: strPickerView.rx.itemTitles) { _, str in
                return str
            }
            .disposed(by: disposeBag)
        // strPickerViewから選択したデータ(タイトル)をstrTextFieldのテキストにバインド
        strPickerView.rx.modelSelected(String.self)
            .map { strs in
                return strs.first
            }
            .bind(to: strTextField.rx.text)
            .disposed(by: disposeBag)
    }
}

比較するべきdataSourcedelegateを用いた実装が今回はないが、上記のコードを見るだけでも簡潔に書けていることがわかると思う。

まとめ

bind()メソッドでデータをバインドできるのは便利だと感じた。また、modelSelected()メソッドがジェネリクスを用いて、データとしてバインドした任意の型を取得できるのも良いと感じた。UIPickerViewDelegatepickerView(_:didSelectRow:inComponent:)は選択したデータの行などしか取得できないので、それに比べると便利だと思う。

今記事のコードを用いたサンプルをgithubにあげておく。

github.com

CharacterSetを定義する

CharacterSetとは

言葉通り文字の集合である。Appleのドキュメントにも書いてあるが、文字列の中から特定の文字を検索したいときなどに使う構造体。自分も以前そのような用途で使ったことがある。
CharacterSet - Foundation | Apple Developer Documentation

CharacterSetを定義する

自分は以前CharacterSetを使用したことがあるが、static varで既に定義されているdecimalDigitsalphanumericsしか使ったことがない。しかし、先日ドキュメントを眺めていたら、コンストラクタinit(charactersIn:)を呼べば、自分でも定義できることを知った。

// 文字a, b, cを含んだ文字の集合
let abcSet = CharacterSet(charactersIn: "abc")

CharacterSetを使用する例

自分でCharacterSetを定義できれば、そのCharacterSetを使い文字の検索ができる。例えばこんな感じ。

extension CharacterSet {
    // 16進数で使用する文字の集合を定義
    static var hexDigits = CharacterSet(charactersIn: "0123456789ABCDEFabcdef")
}

func checkHexDigits(from string: String) {
    // 文字列に16進数表す文字以外が含まれていないかチェック
    if string.rangeOfCharacter(from: CharacterSet.hexDigits.inverted) != nil {
        print("target string(\(string)) contains non hex digits")
    }
}

var colorCode = "ffFFff"
checkHexDigits(from: colorCode)

colorCode = "ffFFfg"
checkHexDigits(from: colorCode)

実際にPlaygroundで実行すると以下となる。 "ffFFfg"の'g'はhexDigitsに含まれてないので出力される。 f:id:culumn:20180531230628p:plain

アプリを開発する際も、このようにCharacterSetを定義して、バリデーションなどに使えると思う。