Storyboard よりも Xib を使いたい理由
これは Aizu Advent Calendar 2018 の 7日目の記事です。
6 日目は id:acomagu さんで、8 日目は id:NoahOrberg さんです。
はじめに
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:)
の内部で、UIViewController
の init(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 の場合、 UIViewController
の init(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
iOS Simulator で動作しないことだけ注意して欲しい。
(正しく動作する方法がわかる方は、プルリク送って下さい 🙏)
まとめ
最初は、 AVPlayerItem
の
tracks
をKVOする実装などしてみたが、デリゲートを設定するだけで良かったらしい。
AVFoundation
を普段触らないから、わからん...
NOCで1位入賞しました
NOCとは
NOC(Native OSS Competition) とは、株式会社サイバーエージェントさん主催の、ネイティブ向けOSSライブラリを作るコンペティションです。今回はUI編ということで、UIに関わるOSSライブラリを作ることがテーマで、10/20(土)-10/21(日)の2日間に開催されました。
私は、普段iOSアプリ開発を行っていることから、iOSサイドで参加しました。
開発の流れ
NOC当日までに、開発するライブラリのアイデアについて、オンライン上でメンターの方にフィードバックして頂き、開発の方針を固めます。そして当日は、メンターのエンジニア社員と開発に集中し、OSSライブラリとして公開を目指します。
当日
初日は、メンターのエンジニア社員の方々からのLTから始まりました。エンジニアとしてのアウトプットの重要性や、いいライブラリとはどのようなものか等について聞くことが出来ました。
その後は、メンターの方と顔合わせをして、ライブラリの最終的なアイデア決めをしました。
それからは、2日間ひたすらメンターと開発に集中しました。
余談ですが、私のメンターは @marty_suzuki さんでした。以前に技術ブログや著書を読んだことがあり、iOSエンジニアとして尊敬する方で、メンターとしてお会いすることが出来て感動しました。
当日には、お弁当やお菓子等の食事の支給もありました。
結果
最終日には、2日間開発したライブラリついて3分間のプレゼンを発表し、ソースコードと一緒に、メンターのエンジニア社員の方々に審査をして頂きました。
私は、iOS向けのカラーピッカーライブラリを開発し、1位に入賞することが出来ました。 副賞として、abemaくんのグッズも頂きました。🙌
1位を獲得することが出来ましたが、2日間で急いで開発したライブラリのため、いくつも問題を抱えています。自身で利用しようとも考えているため、今後OSSライブラリとして育てていきたいと思います!(スターとか貰えるとモチベ向上になります👀) github.com
ライブラリの中の技術の話については、後日、他の記事で書きたいと思います。
まとめ
NOCでは、OSSライブラリの開発から公開の一連の流れを体験することが出来ました。実際に沢山のスターを持つOSSライブラリを開発したエンジニアの方をメンターとして、OSSライブラリの開発について学ぶことが出来ました。また、サイバーエージェントさんのエンジニアとして、職場で役立つ技術についても教えて頂きました。
今回、宿泊先の手配や交通費支給等、金銭的な負担もほとんどなく、開発に集中できる環境を用意して頂きました。 2日間という短い期間でしたが、充実した時間を過ごすことが出来ました。
最後に、NOCという最高な体験を用意して下さったサイバーエージェントさん、本当にありがとうございました!!
スカラシップスポンサー枠でiOSDCに参加してきた
8/30(金)~9/2(日)の4日間、早稲田大学で開催されたiOSDC Japan 2018に参加してきました。今回は、そのレポート記事となります。
今回は、Wantedlyさんが募集していた学生応援プログラムに採択して頂き、スカラシップスポンサー枠で参加させていただきました。チケット代、宿泊費・交通費を支援して頂き、本当にありがとうございました!
iOSDCとは
iOS Developers Conference Japanの略称で、iOS関連技術をコアのテーマとした技術者のためのカンファレンスです。今年は、3日+前夜祭の3.5日間での開催でした。
タイムテーブルは、こちらから閲覧することが出来ます。
この記事では、特に面白いと感じたトークや会場の様子を紹介します。
前夜祭
標準アプリから学ぶ、HIGが教えてくれないiOSデザインのこと
speakerdeck.com
日常的に使うドアを例に、デザインが持つべき役割の話から始まり、標準アプリの歴史から新しいUIが登場する背景などを考察するという内容でした。日常で特に意識することなく使う標準アプリも、意味を持ってデザインされていると知ることが出来ました。 デザイナーの方は偉大と感じると共に、自分もデザインについて学びたいと思う発表でした。
ツールとして利用するUIテスト
speakerdeck.com
各画面サイズやOS, 言語等により膨大になる画面数の確認を、UIテストを用いて解決しようという内容でした。発表者の方が自作したLunch
を使った特定画面の起動、そのスクショの撮影方法などを話されていました。また、Xcode 10から追加されるテストの並列実行を使用した場合の計測をしていました。 このトークを参考に今後UIテストを導入し、画面レイアウトのチェックの自動化をしたいと思いました!
Day1
MicroViewControllerで無限にスケールするiOS開発
www.icloud.com
MicroViewController
という新しい考えを提案し、その概念や実装の仕方、導入によって得られた効果を紹介する内容でした。MicroViewController
は、ファットになりやすいViewController
をView
の部品一つ一つに割り当てることで、責務を分割・隠蔽でき、多人数で開発する時に起こるコンフリクトやオーバーヘッドを軽減するという概念でした。
MicroViewController
を導入したことで、開発速度が2倍、画面の描画が最大で3倍速くなったことを知った時は、衝撃を受けました。個人的にも、ベストトーク賞1位が納得な内容でした。
差分アルゴリズムの原理について
speakerdeck.com
差分計算アルゴリズムの紹介や、そのアルゴリズムを適用したUITableView
の描画の計測などをするという内容でした。自分は、大量のデータをUITableView
で描画したことが無かったため、今後パフォーマンスを考慮するならば、この発表を参考にしようと思いました。
Day2
Depth in Depth
speakerdeck.com
Depth In Depth(深度 詳解)というタイトルで、深度や視差の説明から始まり、深度を使ったアプリのデモや、深度データ取得等の実装方法を紹介していました。自分は、ハードウェアの実装に弱いため、iPhoneのセンサから背景の合成など実装出来ることに感動しました。トークのタイトル通り、深度の深い内容を知ることが出来る面白い内容でした!
Day3
Swiftのジェネリクスはどうやって動いているのかコンパイラのソースから探る
speakerdeck.com
サイズが違う値型と参照型を持つSwiftが、どのようにジェネリクスを実現しているかをコンパイラのソースから読み解くという内容でした。後半は、正直理解が追いつきませんでしたが、今までコンパイラのソースを読んだことがない自分にとっては、新鮮な内容でした。
コンパイルされたSIL
やLLVM-IR
から読み解くことは予想でしかなく、コンパイラの実装から読み解くことが正確であるという発表の流れが、論理的で好きでした。
LLDBを最大限活用してみる。
speakerdeck.com
LLDB
のカスタムコマンドを作り、もっと便利にデバッグをしてみようという内容でした。LLDB
デバッグをほとんどしたことがない自分にとっては、知見の塊でした。Swiftの定数(let
)は、リビルドしないと値を書き換えることが出来ない欠点があると言って、LLDB
で直接メモリの値を操作した時は、思わず笑ってしまいましたw。
会場の様子
LT
ビールやお弁当が用意され、とてもカジュアルな雰囲気で楽しめました。
#iosdc pic.twitter.com/dPxFXxBrOm
— culumn (@Yoshi64190101) September 2, 2018
お弁当が来た! #iosdc pic.twitter.com/p6mlQ9gHhY
— culumn (@Yoshi64190101) September 1, 2018
ランチ
Day1-Day3の3日間は、ランチが提供されていました。
Day2のランチタイムには、WantedlyさんによるReactorKit
のライブコーディングが実施されていました。ReactorKit
は、RxSwift
を使ったFlux
フレームワークで、Wantedly Visitで採用されているらしいです。
ライブコーディング #iosdc #a pic.twitter.com/WHREyhALNN
— culumn (@Yoshi64190101) September 1, 2018
企業ブース&提供
各スポンサー企業さんによる催しや提供がありました。
iOSDCの会場で、キーキャップをプレゼントしています🎁企業ブースで会計freeeアプリを体験してくれた方限定😎 #iOSDC pic.twitter.com/TDKulQJRe7
— freee採用チーム (@freee_saiyo) August 31, 2018
#iOSDC 前夜祭お疲れ様でした!皆様にご協力いただいた結果、半日もせずにこれだけ埋まりました!明日からもよろしくお願いします:D pic.twitter.com/rbuhqXZkl7
— DeNA Tech (@DeNAxTech) August 30, 2018
懇親会
ビールや食事が用意され、すごい賑わいでした。
まとめ
iOSDCの公式サイトにも記述されていますが、まさにエンジニアの祭典でした。レベルの高いiOS関連技術の話を聞くだけの場ではなく、飲食物の提供や催し等のおかげで、本当に祭りのような雰囲気で楽しむことが出来ます。
感想
このiOSDCは、人生初のカンファレンスの参加でした。自分が地方に住んでいることもあり、東京開催のカンファレンスに行くことがありませんでした。今まで、カンファレンスは、後日投稿されるスライドを見て、勉強出来れば良いだろうと考えていました。しかし、カンファレンスでは、現地でしか楽しめないものがあると知ることが出来ました。
また、iOSDCに参加することで、iOS開発者コミュニティの大きさを知ることが出来ました。私の身の回りでは、iOSアプリの開発者が自分しかいませんでした。しかし、iOSDCに参加することで、同じiOSアプリを開発する学生やエンジニアが大勢いることを実感することが出来ました。
今回、スカラップスポンサー枠でiOSDCに行く機会を提供して下さったWantedlyさん、iOSDCの開催を支えて下さったスタッフやスポンサー企業さん、本当にありがとうございました!🙏🙏🙏
三ヶ月間のHaskellセミナーを終えて
Haskellセミナーについて
本年度から学部三年になり、研究室に配属された。私の研究室では、関数型言語を用いて卒業研究を行うことになっており、学部三年生は基礎としてHaskell
の学習をすることになっている。
特に前期は、「プログラミングHaskell」という本を教材にして、指導教員と学生で週一でセミナーを行った。
- 作者: Graham Hutton,山本和彦
- 出版社/メーカー: オーム社
- 発売日: 2009/11/11
- メディア: 単行本(ソフトカバー)
- 購入: 14人 クリック: 503回
- この商品を含むブログ (117件) を見る
セミナーの内容としては
- 課題として、本の各章の最後の練習問題を解き、次章の内容を読んでくる。
- セミナーの時間に、自分が担当した練習問題の解答を皆に解説する。また、読んできた章の内容についてわからないことがあれば、教員に質問する。
- 練習問題の解答と次章の内容に対しての疑問が無くなれば、セミナーの終わりに、次の練習問題を学生に割り振る。
というものだった。
個人的には、どうすれば自分の解答が理解してもらえるかを考え、資料を作成する力をある程度付けることができたと思う。また、自分の解答に対して他の学生や教員から質問が飛んでくるので、違う解法や自分の理解は正しいかを確認することができ、さらに理解を深めることができて良かった。
プログラミングHaskellの内容について
教材として使用したプログラミングHaskellを読んだ感想を書く。本としては全体で13章で構成されているが、セミナーで行った12章までの内容しか書かないことはご了承頂きたい。
1-4章
- 関数型プログラミングと
Haskell
の紹介 - 命名規則、レイアウト規則
- 基本の型やクラス
- 条件分岐, ガード, パターンマッチ
- カリー化とλ式
命名規則や基本的な式は各言語で異なるため、そこは覚えるしかないと思う。
個人的に興味深い内容は、カリー化だった。カリー化された関数を部分適用することで、有益な関数を作ることができることが面白いと感じた。
また、1章でクイックソートが5行で書けると知った時は、衝撃を受けた。
5-7章
リスト内包表記自体はPython
などでも取り入れられているが、自分は初めて知った書き方だった。簡潔にリストを定義することができて便利。
6章では、Haskell
で再帰関数を定義するコツを記載されていて、この章以降で再帰関数を書く時に役に立った。
7章の高階関数では、畳込関数foldr
, foldl
の仕組みを理解することがとても難しかった。今も何となくで理解しているため、詳しく理解したい時は読み返したいと思う。
この本のfoldr
, foldl
の解説はわかりやすいという意見もあるので、自分の理解力が足りないだけかもしれない。
これは本の内容とは関係ないのだが、教員から教えてもらった再帰関数を読む時のコツを紹介したいと思う。再帰関数は、再帰部の中身を掘り下げて読むのではなく、その再帰部が正しく動作すると仮定して、基底部で再帰が止まるかを考えた方が読む効率が良いというものである。自分は、実際にこの思考を用いることで、再帰関数が異なって見えるようになった。
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
のテキストにセットするものである。
UIPickerView
をUITextField
の入力として使用する
まずUIPickerView
をUITextField
の入力のViewとして使用するには、UITextField
のプロパティinputView
にUIPickerView
のインスタンスを代入するだけでよい。
textField.inputView = UIPickerView()
ただ、これだけではUIPickerView
のdataSource
が実装されていないので、選択するデータがない。また、UIPickerView
から選択したときのイベントをdelegate
でハンドリングし、そのデータをUITextField
のテキストにセットする必要がある。
今回はUIPickerView
のdataSource
とdelegate
を用いての実装は省略するが、まあ面倒である。そこで本題のRxSwift
を用いた実装を次に紹介する。
RxSwiftでUIPickerView
とUITextField
をバインドする
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) } }
比較するべきdataSource
とdelegate
を用いた実装が今回はないが、上記のコードを見るだけでも簡潔に書けていることがわかると思う。
まとめ
bind()
メソッドでデータをバインドできるのは便利だと感じた。また、modelSelected()
メソッドがジェネリクスを用いて、データとしてバインドした任意の型を取得できるのも良いと感じた。UIPickerViewDelegate
のpickerView(_:didSelectRow:inComponent:)
は選択したデータの行などしか取得できないので、それに比べると便利だと思う。
今記事のコードを用いたサンプルをgithubにあげておく。
CharacterSetを定義する
CharacterSetとは
言葉通り文字の集合である。Appleのドキュメントにも書いてあるが、文字列の中から特定の文字を検索したいときなどに使う構造体。自分も以前そのような用途で使ったことがある。
CharacterSet - Foundation | Apple Developer Documentation
CharacterSetを定義する
自分は以前CharacterSet
を使用したことがあるが、static var
で既に定義されているdecimalDigits
やalphanumerics
しか使ったことがない。しかし、先日ドキュメントを眺めていたら、コンストラクタ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
に含まれてないので出力される。
アプリを開発する際も、このようにCharacterSet
を定義して、バリデーションなどに使えると思う。