- 投稿日:2019-02-03T23:52:25+09:00
【iOS 12】フラットな絵文字 EmojiOne を iOS で使ってみる
iOS 標準の絵文字をこんなかんじのフラットな絵文字に置き換えてみます。
iOS 標準の絵文字 EmojiOne の絵文字 この絵文字は、EmojiOne によって無料で提供されているものです。
iOS 12 以降では、EmojiOne をカンタンにアプリに組み込むことができるみたいです。iOS 12 から OpenType-SVG をサポートしたらしい
iOS 12 から OpenType-SVG 形式のカラーフォントをサポートしたみたいです。
UIKit や TextKit に関する公式なアナウンスは見つけることが出来なかったのですが、What’s New in Safari には次の記載がありました。
- OpenType SVG
- Added support for defining letterforms in OpenType fonts using SVG.
WebKit は CoreText に依存しているらしいので、もしかしたら CoreText レベルでの変更によって、TextKit にも影響がでたのかもしれません。
ところで、OpenType-SVG とは、
OpenType-SVG は、OpenType フォントの字形のすべてまたはほんの一部が SVG(Scalable Vector Graphics)アートワークとして表されるフォント形式です。これにより、1 つの字形で複数のカラーおよびグラデーションを表示できます。こうした機能から、OpenType-SVG フォントを「カラーフォント」とも呼んでいます。
OpenType-SVG カラーフォント – Adobe
とのことで、1文字に複数のカラーを表示することできるフォントみたいです。
EmojiOne フォントをアプリに組み込む
アプリにカスタムフォントをバンドルすることで EmojiOne を利用することが出来ます。
まずは EmojiOne フォントをダウンロードします。EmojiOne SVG-based Color Fonts で Open Type Font: emojione-svg.otf のリンクからダウンロードします。
ダウンロードした emojione-svg.otf を Xcode プロジェクトにドラッグ&ドロップします。
次のようにチェックして、リソースに追加します。これで、アプリ内で EmojiOne を利用できるようになります。
インターフェースビルダーから使用する
フォントを次のように指定します。
- Font: Custom
- Family: EmojiOne
コードからは使用できない?
PostScript名 を指定して、
label.font = UIFont(name: "EmojiOneColor", size: 38)あるいは、フォントファミリーを指定して
label.font = UIFont(name: "EmojiOne", size: 38)これで動くはず。
と思ったのですが、なぜかシステムフォントにフォールバックしてしまいました。
なので、今回はコードからのフォントの設定は諦めました。iOS 11 で実行すると?
OpenType-SVG はカラーのベクタデータだけでなく、モノクロのベクタデータも持っています。
iOS 11 の場合はモノクロのフォントにフォールバックするようです。
iOS 12 iOS 11 EmojiOne の使用許諾契約について
クリエイティブ・コモンズ・ライセンスです。
リンクを貼ることで利用できます。EmojiOne's graphics are free to use for any project, commercial or personal, under a free culture Creative Commons License (CC-BY 4.0). Proper attribution (link back) is required for the rights to use the emoji in commercial projects.
こちらの絵文字は EmojiOne によって無料で提供されているものを利用しました。
- 投稿日:2019-02-03T23:39:36+09:00
react-native+TypeScriptなプロジェクトでdotenvを読ませる
https://github.com/zetachang/react-native-dotenv
まずは追加。
yarn add react-native-dotenv
.babelrcに以下追加。".babelrc"... "presets": [..., "module:react-native-dotenv"], ...次に、react-native-dotenvには型定義ファイルがないので自前でdeclareする必要がある。
./src/lib/vendor-typings.d.tsにモジュールを定義。vendor-typings.d.ts// declare module 'react-native-dotenv';起動してみる。これで動くはず。
app.tsximport * as React from 'react' import { Text } from 'react-native' import { YOUR_ENV, } from 'react-native-dotenv' export default = (): JSX.Element => { return <Text>{YOUR_ENV}</Text> } ...
- 投稿日:2019-02-03T23:16:37+09:00
iOSアプリとTwitter連携
はじめに
Social.frameworkまたはUIActivityViewControllerが使えなくなったしまったので、TwitterKit3.4.0で、ツィートするところまで作ってみました。
手順は
- Twitter Developersでアプリ登録(本記事では省略)
- TwitterKitのインストール
- プロジェクトの設定
- 認証部実装
- ツィート実行部実装
で、実装しました。
TwitterKitのインストール
今回はCocoaPodsを使用しました。
Podfileは# Uncomment the next line to define a global platform for your project # platform :ios, '9.0' target 'twitterKitSmp' do # Comment the next line if you're not using Swift and don't want to use dynamic frameworks use_frameworks! # Pods for twitterKitSmp pod 'TwitterKit' <-- 追加 endのような感じです。
プロジェクトのディレクトリで
% pod init <Podfileが作成されるので、pod 'TwitterKit'を追加> % pod installTwitterKitのインストールは以上です。
プロジェクトの設定
info.plistに下記の追加を行います。
<key>CFBundleURLTypes</key> <array> <dict> <!-- 追加ここから --> <key>CFBundleURLSchemes</key> <array> <string>twitterkit-{CONSUMERKEY}</string> </array> <!-- 追加ここまで --> </dict> </array> <!-- 追加ここから --> <key>LSApplicationQueriesSchemes</key> <array> <string>twitter</string> <string>twitterauth</string> </array> <!-- 追加ここまで -->CFBundleURLTypes.CFBundleURLSchemesとLSApplicationQueriesSchemesを追加します。
CFBundleURLSchemesはTwitterAPI側のCallbackURLにも追記します。認証部実装
まず、TwitterKitの初期設定等を行います。
AppDelegate.swiftfunc application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. // 1. TwitterAPIのAPI keyとAPI secret keyを設定 TWTRTwitter.sharedInstance().start(withConsumerKey: "{CONSUMERKEY}", consumerSecret: "{CONSUMERSECRET}") return true } func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { // 2. Twitter認証からのコールバック if TWTRTwitter.sharedInstance().application(app, open: url, options: options) { return true } return false }
- アプリ起動時にTwitterAPIのAPI keyとAPI secret keyを設定します。
- Twitter認証からのコールバックを受け取ります。
次に認証開始部分を実装します。
ViewController.swiftfunc sndTweet() { // 1.ログインされているか? if TWTRTwitter.sharedInstance().sessionStore.hasLoggedInUsers() { // 2.ツィート開始 sndTweetExec() } else { // 3.認証開始 TWTRTwitter.sharedInstance().logIn(with: self, completion: { (session, error) in if let sess = session { print("Signed in as \(sess.userName)") // 4.ツィート開始 self.sndTweetExec() } else { // 5.認証失敗 print("login error: \(error?.localizedDescription)") } }) } }
- Twitterにログインされているか確認します。
- 既にログインされているため、ツィート開始へ進みます。
- Twitter認証を開始します。Twitterのログイン画面に遷移します。ログイン後、アプリの連携を許可するか聞かれるので許可すればアプリへ戻ります。
- 認証が成功したのでツィート開始へ進みます。
- 認証失敗したのでここで終わります。
ツィート実行部実装
ツィートはTwitterKitのTWTRComposerViewControllerを使用しました。
またツィート実行後にアラートを出すように実装します。ViewController.swiftfunc sndTweetExec() { let str:String = "サンプルツィート" // 1.コントローラー初期化 let comp = TWTRComposerViewController.init(initialText: str, image: nil, videoData: nil) // 2.デレゲート comp.delegate = self // 3.コントローラ表示 present(comp, animated: true, completion: nil) }
- TWTRComposerViewControllerを初期化します。今回はテキストのみ。
- デレゲートを設定します。デレゲートはキャンセルとツィート成功、失敗が定義されています。
- コントローラーを表示します。
次にデレゲートの処理を実装します。
ViewController.swiftextension ViewController: TWTRComposerViewControllerDelegate { // キャンセル時 func composerDidCancel(_ controller: TWTRComposerViewController) { print("Cancel") } // ツィート失敗時 func composerDidFail(_ controller: TWTRComposerViewController, withError error: Error) { print("Error") let store = TWTRTwitter.sharedInstance().sessionStore if let userID = store.session()?.userID { store.logOutUserID(userID) } dismiss(animated: false, completion: nil) DispatchQueue.main.async { self.tweetAlert(memo:"Twitterに投稿に失敗しました") } } // ツィート成功時 func composerDidSucceed(_ controller: TWTRComposerViewController, with tweet: TWTRTweet) { print("Ok") dismiss(animated: false, completion: nil) DispatchQueue.main.async { self.tweetAlert(memo: "Twitterに投稿しました。\nご協力ありがとうございます。") } } }今回はツィート実行後にアラートを表示したいため、デレゲートメソッド内でdismiss()をコールして、Viewを閉じています。
DispatchQueue.main.asyncは必要なかったかも知れませんが、念のため。。サンプルアプリ
![]()
![]()
![]()
こんな感じのアプリにしてみました。
ソースコード
にソース一式を置きました。まとめ
以上、ざっとまとめましたが、木になる点として
- CallbackURLが指定できなかった。
指定方法がありそうな気がしましたが、見つけられませんでした。- ツィート画面のカスタマイズしたい。
できそうな感じがしたのですが、今回はできませんでした。
次回、機会があれば再トライしてみます。なお、下記の記事を参考にさせていただきました。ありがとうございました。
- 投稿日:2019-02-03T22:06:48+09:00
ようやくGUI! SwiftでUIKitのLabel部品でHello World
はじめに
ずっとPlaygroundでコンソール(?)プログラミングをしてきた。
Swiftのコード文法てきな部分はわかってきたので
統合開発環境としてのXCodeの使い方を含めて少しずつ挑戦の幅を広げる。手順概略
GUIアプリになると、途端に取り扱うファイルが増えるので
コードを書くだけでは解決しなくなる。
ここではざっくり手順を載せる。
- [Main.storyboard]を開く
- ラベルを配置する
- [アシスタントエディタ]で配置したラベルをソースコードへ取り込む
ラベルの定義@IBOutlet weak var label: UILabel!
- ソースコード上でラベルにテキストを設定する
ラベルにテキストを設定label.text = "Hello World"ソースコード全文
ViewController.swiftimport UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() label.text = "Hello World"//<-手打ちで追加 } @IBOutlet weak var label: UILabel!//<-アシスタントエディタで追加 }
- 投稿日:2019-02-03T22:03:05+09:00
Mac で開発中のサイト(localhost)を iPhone で表示させる
http://localhost:3000
というような今開発しているローカルサイトを手元にある iPhone ですぐに見られるようにします。
基本方針は Bluetooth を使ったインターネット共有で、ハード・ソフト面で他に何も準備しないバージョン。
https の対応はまた今度。環境
- MBP
- Wifi でインターネット接続
- iPhone
- 特別な設定なし
- 有線 LAN なし
- ケーブル類出したくない
方法
1: システム設定 -> 共有 を開きます
2: インターネット共有を設定します
右側の Bluetooth を選んで左側のインターネット共有をオン。
上の下線を引いてある(pcの名前).localをメモ。3: iPhone から Bluetooth で Mac に接続
4: iPhone のブラウザを開いてアドレス欄に
http://(pcの名前).localを入力してアクセス
http://localhost:3000で開発しているならhttp://(pcの名前).local:3000を入力。5: 見れます
備考
- Mac の Wifi が余ってるなら Bluetooth じゃなくてそっちで繋げられます
- というか同じ Wifi 繋げるなら IP 直接打てばいいみたいなところもあります
- 同じネットワークに繋げない特殊環境で役立ててください
- 投稿日:2019-02-03T19:45:56+09:00
テスタブルコードの研究
はじめに
おみくじクラスを作ってみた でおみくじを引く関数を作った。
動作は問題ないが、よりテスタブルなコードへ書き換える。検討
現在のソースコードは以下の通り。
元コードfunc get() -> String { let result = base.randomElement()! //ランダムに配列要素を取り出す let dateStr = toDateStr(date: Date()) //現在時刻を文字列へ変換 history.append(dateStr + " " + result) //履歴配列へ[結果+日時]を追加 return result //おみくじ結果を返す }テストがやりにくい点は以下の点である。
- resultは値がランダム
- dateStrは値が実行時に変化する
- historyはメンバ変数
resultのランダムは必須機能なので仕方ない。
それ以外はget関数から追い出す。get関数から履歴記録部分を別関数へ分離func get() -> String { let result = base.randomElement()! //ランダムに配列要素を取り出す setHistory(result: result) //履歴を記録 return result //おみくじ結果を返す }さらに履歴文言の生成は別関数へ追い出し、必要な情報を渡すようにする。
履歴文言の生成を追い出すfunc setHistory(result: String) { //履歴記録用文字列の生成 let dateStr = toHistoryString(result: result, date: Date()) //履歴配列へ結果を追加 history.append(dateStr + " " + result) }履歴文言の生成は、外部の情報を元に組み立てる関数に閉じる
履歴文言生成関数func toHistoryString(result: String, date: Date) -> String { let dateStr = toDateStr(date: date) //時刻を文字列へ変換 return (dateStr + " " + result) //履歴配列へ[結果+日時]を返却 }結果
今回はテストコードまでは書かなかったが
履歴文言生成関数は、入力が決まれば出力が一意に決まる「テスタブル」なコードができた。
- 投稿日:2019-02-03T16:26:07+09:00
AR顔認識アプリを開発して原宿で街頭インタビューしてYouTubeにアップした話
2019年1月からエンジニア系YouTubeを始めました。今YouTubeにあるエンジニア系の動画は「エンジニアは稼げる」とか「フリーランスエンジニア自由最高」みたいな動画が多く、それはそれいいんですが、技術で攻めて駆逐せねばならぬという僕の謎の正義感が働いているため頑張っています。
基本的にはARやエンジニアの生活について発信するチャンネルですが、今回はいかにもYouTuberっぽい動画且つエンジニアらしくアプリも作ったのでその話を紹介していきたいと思います。
企画
先輩YouTuberでxRエンジニア界隈の友人である @nkjzm と一緒に企画を考えました。
要件は2つ。
- K-BOYがARエンジニアなのでARに沿った内容にしたい
- せっかくコラボするのでインパクトがある内容にしたい
この要件のなかで昨日の13:30-14:00くらいでディスカッションし、14:00-15:00でアプリ開発と撮影準備をし、原宿に飛び出したわけであります。
インパクトを残すという意味で少し下ネタを絡めるというテーマでディスカッションしていました。目的は「IT界隈じゃない人も興味を持ってくれる動画にしよう!」だったので、ギリギリ大丈夫そうな下ネタの経験人数というテーマにしました。
そしてARを絡めるために最終的にたどり着いたのが「ARKitの顔認識を使って顔を読み取って経験人数を予測するアプリ」
この企画プロセスはハッカソンっぽいなあと思います。
アプリ仕様
ARKitのFaceDetectionを使って顔をスキャン感を出す
詳しくはコードをご覧ください。
iPhoneX等で使えるフロントカメラの顔認識を使用しています。
顔のgeomertyをupdateし続けて、あみあみのmaterialを張っているだけで、サンプルコードレベルの実装です。
ゲージをアニメーションさせてスキャン感をだす
UIProgressViewをいじったカスタムクラスを以前作ったことがあったので、同じように再現実装しました。
起動から5秒後に、10秒かけてアニメーションさせてます。
DispatchQueue.main.asyncAfter(deadline: .now() + 5) { self.titleLabel.isHidden = false UIView.animate(withDuration: 5, delay: 0, options: [], animations: { self.hpView.hpBar.setProgress(1, animated: true) }, completion: nil) DispatchQueue.main.asyncAfter(deadline: .now() + 5) { self.titleLabel.text = "経験人数は..." self.countLabel.isHidden = false } }経験人数は適当に配列で用意
もちろん、ネタ企画なので経験人数は配列に入れときます。最速で実装し、原宿に飛び出して使ってもらうのが目的なため、複雑なアルゴリズムを作っている暇はありません。リアルっぽさを出すために少ない人数を多めにしときました。
let array: [String] = [ "0人", "1人", "2人", "3人", "4人", "5人", "6人", "7人", "8人", "11人", "12人", "14人", "18人", "19人", "22人", "28人", "35人", "100人", "160人", ]このarrayの中からrandomで一個選びます
countLabel.text = self.array.randomElement()!ユーザーの反応
詳しくはYouTubeを見てもらいたいと思いますが、ウケてました。本当にアルゴリズムがあるかどうかは重要ではないということがわかりました。
「違うよ!」「え!当たってる!」など反応を楽しむことが目的。
その目的は達成できたのではないかなと思います。嬉しいです。
動画制作
- 撮影はiPhone XS Maxで1時間半
- 動画編集はFinal Cut Proで4時間くらい
- サムネはAdobe Illustlatorで30分くらい
まとめ
アプリを作ってインタビュー、それを動画にするというエンジニアらしいYouTube制作ができたのではないかと思います。
エンジニアといえばブログのイメージが強いですが、より多くの層にリーチするためにYouTubeを撮ってみるのはいかがでしょうか?想像以上にクリエイティブな作業で、編集の大変さがわかると思います。
では!
- 投稿日:2019-02-03T16:23:25+09:00
[iOS] アプリの画面の方向を固定させる方法メモ
- 投稿日:2019-02-03T06:06:57+09:00
[iOS] [Swift] Sequence Protocolのサンプルコード(社内勉強会資料)
はじめに
弊社内の勉強会で、Sequence Protocolの中でも頻繁に使うモノをサンプルコード付きで紹介しました。
ごくシンプルなコードですが、せっかく書いたものを社内だけにとどめることは勿体無いので公開いたします。参加者に「お題」だけを配って(答えを削って)、ハンズオンなどをやってみると楽しいと思います!
環境
・Xcode 10.1
・Swift 4.2サンプルコード
Sequence.playgroundimport UIKit struct Student { var code: String var firstName: String var lastName: String var score: Int var fullName: String { return firstName + " " + lastName } } var students = [Student]() students.append(Student(code: "AB100000", firstName: "Taro", lastName: "Yamada", score: 62)) students.append(Student(code: "AB100001", firstName: "Ichiro", lastName: "Suzuki", score: 81)) students.append(Student(code: "AB100002", firstName: "Hanako", lastName: "Sato", score: 96)) students.append(Student(code: "AB100003", firstName: "Jiro", lastName: "Takahashi", score: 58)) students.append(Student(code: "AB100004", firstName: "Ichiro", lastName: "Tanaka", score: 75)) students.append(Student(code: "AB100005", firstName: "Hanako", lastName: "Yamada", score: 96)) // 70点以上の生徒を抽出する let filtered = students.filter { (student) -> Bool in return student.score >= 70 } for (i, student) in filtered.enumerated() { print("70点以上の生徒\(i+1)人目は\(student.fullName)です。") } /* 70点以上の生徒1人目はIchiro Suzukiです。 70点以上の生徒2人目はHanako Satoです。 70点以上の生徒3人目はIchiro Tanakaです。 70点以上の生徒4人目はHanako Yamadaです。 */ // "コード, フルネーム"という形式の文字列の配列に変換する let stringArray = students.map { (student) -> String in return "\(student.code), \(student.fullName)" } stringArray.forEach { print($0) } /* AB100000, Taro Yamada AB100001, Ichiro Suzuki AB100002, Hanako Sato AB100003, Jiro Takahashi AB100004, Ichiro Tanaka AB100005, Hanako Yamada */ // 全生徒の点数を合計する let total = students.reduce(0) { (total, student) -> Int in return total + student.score } print("全生徒のScoreの合計は\(total)点です") /* 全生徒のScoreの合計は468点です */ // key=コード、value=フルネームというDictionaryに変換する let dic = students.reduce(into: [String: String]()) { (dic, student) in return dic[student.code] = student.fullName } dic.forEach { print($0) } /* (key: "AB100000", value: "Taro Yamada") (key: "AB100001", value: "Ichiro Suzuki") (key: "AB100003", value: "Jiro Takahashi") (key: "AB100004", value: "Ichiro Tanaka") (key: "AB100005", value: "Hanako Yamada") (key: "AB100002", value: "Hanako Sato") */ // Satoが含まれているか? let existsSato = students.contains { (student) -> Bool in return student.lastName == "Sato" } print("Satoさんが含まれているか = \(existsSato)") /* Satoさんが含まれているか = true */ // Yamadaさんの1件目を取得する let first = students.first { (student) -> Bool in return student.lastName == "Yamada" } if let first = first { print(first.fullName) } else { print("見つかりませんでした") } /* Taro Yamada */ // FirstName>LastName順にソートする let sorted = students.sorted { (student1, student2) -> Bool in if student1.firstName == student2.firstName { return student1.lastName < student2.lastName } else { return student1.firstName < student2.firstName } } for (i, student) in sorted.enumerated() { print("FirstName>LastName順の生徒\(i+1)人目は\(student.fullName)です。") } /* FirstName>LastName順の生徒1人目はHanako Satoです。 FirstName>LastName順の生徒2人目はHanako Yamadaです。 FirstName>LastName順の生徒3人目はIchiro Suzukiです。 FirstName>LastName順の生徒4人目はIchiro Tanakaです。 FirstName>LastName順の生徒5人目はJiro Takahashiです。 FirstName>LastName順の生徒6人目はTaro Yamadaです。 */勉強会で使えるリファレンス
サンプルコードで紹介した以外にも、便利なメソッドはたくさんあるかと思います。
SwiftDoc.org は、
・サンプルコードが載っている
・一覧性が優れていて探しやすい
ことから、勉強会の教材として使いやすいと思います。Sequence Protocolの項はこちらになります。
https://swiftdoc.org/v4.2/protocol/sequence/
- 投稿日:2019-02-03T00:06:49+09:00
RxCocoa の UITextField.rx.text を購読するとリターンキーでキーボードが閉じるようになる
はじめに
RxCocoa で UITextField の入力値をイベントストリームとして受け取る場合、
UITextField.rx.textを使います。import UIKit import RxSwift import RxCocoa final class ViewController: UIViewController { @IBOutlet private weak var textField: UITextField! private let bag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() textField.rx.text.subscribe().disposed(by: bag) } }このように
UITextField.rx.textをsubscribe()すると、入力値をイベントストリームとして受け取るだけでなく、なぜか、キーボードのリターンキーのタップでキーボードが閉じるようになりました。RxCocoa だけでなく、ReactiveCocoa でも似たような挙動になります。
ReactiveCocoa では、次のようにして UITextField の入力値をイベントストリームとして受け取る事ができ、やはり、キーボードのリターンキーのタップでキーボードが閉じるようになります。
import UIKit import ReactiveSwift import ReactiveCocoa final class ViewController: UIViewController { @IBOutlet private weak var textField: UITextField! override func viewDidLoad() { super.viewDidLoad() textField.reactive.continuousTextValues.observe { _ in } } }なぜキーボードが閉じるのか?気になったので、調べてみました。
UITextField.rx.textの実装の概要RxCocoa の
rx.textの実装 を追っていくと、次のような実装であることが分かりました。
- UIControl.Events の
.allEditingEventsと.valueChangedをターゲットアクションに追加する- アクションが送信されると、UITextField の
textプロパティを RxSwift のイベントストリームとして発行するまた、ReactiveCocoa の
reactive.continuousTextValuesの実装 では.allEditingEventsをターゲットアクションに追加するようでした。リターンキーの入力でキーボードが閉じるようになる理由
RxCocoa に限らず、
.allEditingEventsをターゲットアクションに追加すると、リターンキーの入力によってキーボードが閉じるようになります。次のようなコードで確認することが出来ます。
import UIKit final class ViewController: UIViewController { @IBOutlet private weak var textField: UITextField! override func viewDidLoad() { super.viewDidLoad() // リターンキーの入力でキーボードが閉じるようになる textField.addTarget(self, action: #selector(self.hoo(sender:)), for: .allEditingEvents) } @objc func hoo(sender: Any) { } }
.allEditingEventsは UITextField の全ての編集イベントを含んでいるので、.editingDidEndOnExitも含んでいます。
.editingDidEndOnExitをターゲットアクションに追加すると、リターンキーの入力でファーストレスポンダをやめるようになります。これが本質的な理由です。次のようなコードに置き換えると
.editingDidEndOnExitイベントが送信されないので、リターンキーをタップしてもキーボードは閉じません。import UIKit final class ViewController: UIViewController { @IBOutlet private weak var textField: UITextField! override func viewDidLoad() { super.viewDidLoad() // リターンキーをタップしてもキーボードは閉じない var events = UIControl.Event.allEditingEvents events.subtract(.editingDidEndOnExit) textField.addTarget(self, action: #selector(self.hoo(sender:)), for: events) } @objc func hoo(sender: Any) { } }
UITextField.rx.textでキーボードが閉じる処理を抑止するにはキーボードが閉じるのは便利ですし、通常はこのままでも問題ないと思いますが、次のように UITextFieldDelegate に適合することで、この挙動を抑止することも出来ます。
textFieldShouldReturn(_:)でfalseを返すtextFieldShouldEndEditing(_:)でfalseを返す
textFieldShouldReturn(_:)でfalseを返した場合は、.editingDidEndOnExitが送信されなくなるので、リターンキーをタップしてもUITextField.rx.textの next イベントが発行されず、キーボードを閉じることを抑止することができます。final class ViewController: UIViewController, UITextFieldDelegate { @IBOutlet private weak var textField: UITextField! private let bag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() textField.delegate = self textField.rx.text .bind(onNext: { print($0.debugDescription) }) .disposed(by: bag) } func textFieldShouldReturn(_ textField: UITextField) -> Bool { // onNext は呼ばれない、キーボードも閉じない return false } }
textFieldShouldEndEditing(_:)は.editingDidEndOnExitの送信後に呼ばれるので、リターンキーのタップでUITextField.rx.textの next イベントが発行され、かつキーボードが閉じるを抑止することができます。final class ViewController: UIViewController, UITextFieldDelegate { @IBOutlet private weak var textField: UITextField! private let bag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() textField.delegate = self textField.rx.text .bind(onNext: { print($0.debugDescription) }) .disposed(by: bag) } func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { // onNext は呼ばれるが、キーボードは閉じない return false } }ということで、意図に合わせて UITextFieldDelegate を実装すれば良さそうです。
RxCocoa ではなく、UIKit の仕様に依存した内容なので、ReactiveCocoa も同様の制御が可能です。まとめ
rx.textを購読するとリターンキーのタップでキーボードが閉じるようになる- この挙動は
.editingDidEndOnExitをターゲットアクションに追加した際の UIKit の標準的な挙動である- この挙動を抑止したい場合は UITextFieldDelegate を実装する















