UITableViewCellの中身をRxで監視するときのtips
シナリオ
- テキスト入力をもつTableViewCellがある
- 画面には更新ボタンがあり、押した時に、キーボードを閉じたい
結論ソース
※諸々省略してます
// UITableViewCellにcellForRowAtIndexまでにunbindさせるための情報を定義する extension Reactive where Base: UITableViewCell { var prepairForReuse: Observable<Void> { return methodInvoked(#selector(base.prepareForReuse)) .map { _ -> Void in return Void() } } var obsolete: Observable<Void> { return Observable.merge(prepairForReuse, deallocated) } } extension ViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: T.reuseIdentifier, for: indexPath) as TextCell weak var weakCell = cell updateButton.rx.tap .takeUntil(cell.rx.obsolete) .subscribe(onNext: { [weak self] _ in weakCell?.closeKeyboard() }) .disposed(by: disposeBag) } }
sentMessageでprepairForReuseを監視
prepairForResuseとdeallocのどちらかのイベントを受け取るまで監視しておけば良いので、それらをまとめてobsoleteというイベントを生やしました。
これをしないと、tableViewをスクロールするたびにsubscribeが増えるので予期せぬ動作を引き起こす可能性がある。
2017年買ったもの
とりあえず、2017年買ったものの総評を書いていこうと思う。 あくまで個人的な感想です。
買ったもの
- SITPACK
- 婚約指輪/結婚指輪
- ゲーミングチェア
- 自作PC
- イケアのデスクセット
- Nintendo Switch
- ヘッドホンハンガー
- 1000BASE-T対応のスイッチングハブ
- 1000BASE-T対応のUSB-LANコネクタ
- iPad Pro 10.9 inch Cellularモデル 256GB
- Apple Pencil
- LinksMateのSim 5GBプラン
- iPhone X
- Apple Watch Series 3 Cellularモデル
- MacBook Pro 15inch BTO(CPU3.1GB, SSD1TB, US Keyboard)
- USB-C to USB-A
- HDMI=DVIケーブル
- JetBrains All in Pack
リストにすると、必要な物ではなく欲しいものを買いすぎているなぁ。。。という印象 来年は買う前の判断を厳しくしていこう。
SITPACK
一口総評: 「★2 活躍できていない」
2017年1月頃入手
一言で言うと、「超コンパクトな折りたたみ椅子」
Makuakeのクラウドファンディングで1年位前にファンディング したのがやっと届いた。
ファンディング時には遅くとも1年前には届いてる予定だったけど、生産体制ができてないのにファンディング枠を増やして受注してたとのことで、届かない状態が1年くらい続いてた。
本当にくるか心配だったけど1年越しで入手。
毎月のように来る謝罪メールを確認するのが日課になっていた。
1個5000円くらいしたのかな。4つセットで購入し2つは家庭用、2つは友人に
予定どおり来ていれば海外旅行に持っていくはずだったのだけど、機会を逃したため全く使えていない
1年の始まりとして、非常に香ばしいスタート
結婚指輪/婚約指輪
一口総評: 「★5 嫁が喜んでくれた。よかった」
1月末に注文し、3月中旬ごろに完成
プロボーズを1月に結構し、承諾してもらったため一緒に嫁と買いに行った。
表参道のとあるお店で嫁が気に入ったものがあったので即決。
手元に届くまで約2ヶ月.
もともとアクセサリー系はだめな人間で、つけ始めたときの違和感が異常だったけど、すぐに慣れた。
自作PC
一口総評: 「★5 PCゲームでも、開発用でも活躍」
2017年2月頃に購入
8年ぶりに自作PCをやりたくなったため、独身最後の贅沢や!とそこそこのスペックを揃えた。
スペック (リンクはAmazonに飛びます。)
- CPU: Intel i7 7700K
- CPUクーラー: CORSAIR H115i CW-9060027-WW
- メモリ: GSK F4-2400C15D-32GVR
- マザボ: ASUS STRIX Z270F GAMING
- グラボ: ASUS ROG STRIX-GTX1080-A8G-GAMING
- キャプボ: Intensity Pro 4K
- 電源: CORSAIR HX850i
- SDD: ADATA XPG SX8000 ASX8000NP-512GM-C (M.2接続SSD)
- HDD: Western Digital WD30EFRX
- ケース: CORSAIR Crystal 570X RGB CC-9011098-WW
- OS: Microsoft Windows 10 Pro
- Display 1: iiyama ProLite GB2488HSU-2
- Display 2: iiyama ProLite E2483HS-B1
- キーボード: RealForce 87UB
- マウス: Corsair Katar
CPU, メモリ, マザボ, SSD, グラボはド定番みたいなところをチョイス 2,3年はパーツを買い換えなくても大丈夫だと思う。
基本的にはPUBGやスプラトゥーンの録画・ライブ配信に使っているのですが、
Androidアプリのビルドサーバーとして使ったりUbuntu載せたりして楽しんでます。
PCケースが2月頃は品切れ中で、黒透という3000円くらいのケースを一時的に使っていた。
一時的とはいえ完全にケチったせいで、グラボが収まらなくて、しばらくPCの外にはみ出た状態で運用していたw
汎用性を持つものの購入はやはりいいですね。
本業ではゲームの生放送サービスのネイティブアプリのエンジニアをやっているので、自分の生活にシンクロした買い物だったと思う。
CPUクーラー CORSAIR H115i CW-9060027-WW
ちょっとラジエータがでかい。大きさ的には 50cm四方くらいのケースを付けてやっと搭載できるレベル
Intensity Pro 4K
1080p60fpsをキャプチャできるもので、一番XSplitやOBSと相性が良かった。
実は他のキャプボで試したところ映らず、1度も使わずして売った時は凹みました。
電源 CORSAIR HX850i
組んでるPCもミドルクラスなのでそれに合わせた感じ。将来、GPU2つかもしれないということでこの容量。そしてPlatinum。
キーボード RealForce 87UB
一口総評: 「★3 HHKBは駆逐されるのだろうか」
しぶい!HHKBに近い打鍵感 。安定感を生み出す1.4kg。
上がRealforce, 下がHHKB Professional2
矢印キーがないHHKBが一回り小さい印象。 打鍵中に安定するな〜と思っていたら、底面のすべり止めもまた大きめだった。
また、US配列だと半角/全角の切り替えが若干めんどくさかった。
自分は右下のaltをあまり使わないので、そこに半角/全角の切り替えを割り当てました。
IKEAのデスク
一口総評: 「★3 機能性よりもシンプルさを選んでよかった」
2017年2月頃に購入
PCとモニターを設置するために購入。
リビングに置くので、見た目を嫁にも相談し、白のデスクにした。
掲載している画像のテーブルトップよりも薄く丈夫なものがあったのでそちらに変更して購入。
LERBERGという名前の脚にテーブルトップを載せる形式。可愛い感じ
ワゴンとか棚は無いのがシンプルで良い。
ゲーミングチェア
一口総評: 「★3 リクライニングは良い」
デスクときたら椅子!
本当は人間工学系のイスを買いたいけど、高いということで、ゲーミングチェアを選択。
メルカリで偶然良品(AKRacing)に出会ったため、元値の5, 6割の値段で新品同様の品を手に入れることができた。
リクライニングで完全フラットになるので、小休憩しやすい。
余談ですが、AKRacingのHPの"AKRacing フォトギャラリー"はギャグだと思うので覗いてみてほしい。
きれいな女性がオフィスにゲーミングチェアで座ってたり、暖炉の前でくつろいでたりしている異様な空気感w
モニターアーム
(配線はこの後きれいにしました)
一口総評: 「★5 デスクがかなり広くなった」
2017年12月頃購入
Amazonのサイバーマンデーセールでガス圧式で移動が容易なできるモニターアームが安くなっていた。1万が6000円くらいだったかな
頻繁に位置を変えないなら、2枚設置可能で4000円台で最低要件を満たすモニターアームを発見し、購入
設置してみるとモニターの脚がデスクを占める割合が大きかったんだな〜と痛感した。
配線もアームに括りつけることができるので本当にスッキリした。実用性も気持ちよさも兼ね備えていて本当に買ってよかった。
ヘッドホンハンガー
一口総評: 「★3 定位置のための投資」
物の固定位置を決めることが収納の鍵(うまるちゃんの兄しらべ)らしいので、Amazonで800円くらいのものを購入。 確かに固定の位置に戻す癖がついた。
Nintendo Swiftchと諸々
一口総評: 「★3 安定」
購入したソフトは
Splatoon2にハマっていて、Splathon(企業対抗戦)にも参加した。
次の第7回は年が明けてすぐ!楽しみです。
1GBbps対応のネットワーク物理
スイッチングハブ、LANケーブル、Nintendo Switch用LANコネクタをすべて1GBbps対応にした。
不便は感じてなかったけど、ケーブルは余り物とか付属品でなんとかしていたので、ちゃんと1GBbps対応のものに刷新。
Apple製品たち
どんだけ肉の写真とってるんや・・・!!
- iPad Pro 10.9 inch Cellularモデル 256GB
- LinksMateのSim 5GBプラン
- Apple Pencil
- iPhone X
- Apple Watch Series 3 Cellularモデル
- MacBook Pro 15inch BTO(CPU3.1GB, SSD1TB, US Keyboard)
これだけでかなりの出費をしてる気がする…
言い訳をするならばAndroidエンジニアからiOSエンジニアへの転向がきっかけ。
iPad Pro 10.9 inch Cellularモデル 256GB
2017年8月頃に購入
一言総評: 「★3 確かにタブレットの方がカジュアルに調べ物や読書ができる」
デバッグ用の端末+プライベートや旅行でPCを持ち歩かないために購入
Nexus 9も持ってるが、やっぱりiPadProの方がサクサクだったので買い替えてよかった。
CellularモデルということでLinksMateのsimを指している。
AbemaやOPENRECを垂れ流しにしてるが、通信料がほぼディスカウントされるのでかなりいい。Wi-Fiの帯域を食わないのも○
未使用バケットが繰越せて、10GBくらいたまった月もあった。
Apple Pencil
一言総評: 「★3 さすがApple純正のシンクロ率。。。だけど高いような。」
iPadの画面を触りたくなくて購入。やはり純正品同士のシンクロ率はソニー製品くらい感じる。
ちょっとしたメモや物事の整理をiPad + Pencilですることが多くなった。
iPhone X
一言総評: 「★3 安定のiPhone. 特に驚きはない。」
ずっとXperiaとNexusを使っていたがiOSエンジニアに転向したことと、プロダクトのiPhoneX対応をしたこともあって購入。
ホームボタンが無いことにはすぐ慣れた。
7plusから使えるようになったポートレート撮影はかなり重宝していると思う。嫁が夕食の写真を毎度撮っているのだけど、Xを貸してくれと言われる。しかもSNSには投稿しない(笑)
Xで撮った写真1
Xで撮った写真2
後ろの海老天までぼやけちゃってるけど、ポートレートモード楽しい。
Apple Watch Series 3 Cellularモデル
一言総評: 「★4 あると超便利、なくても困らない」
通知を選りすぐりしたり、頻繁にアクセスする情報(天気やスケジュール)が絞られているので
情報を確認するテンポの良さを感じた。
「時間という気になる情報を一瞬で把握できる」という時計のコンセプト に当てはまるものは意外と存在するなあという感覚。
また、通知から情報の詳細や他の情報に目移りしないようにできているのがいいなと思った。Android Wearのコンセプトもこんな感じだった気がする。
「時間を確認するコンテンツに時間を浪費する」わけにはいかない。
僕は好きです。
また、会社の入館証をApple Watchに登録していて、
入館証のチェッカーがちょうど左胸の位置にあたるので「それでも俺らは抵抗するで、拳で✊」出社を決めています。
MacBook Pro 15inch 2017 late BTO(CPU3.1GB, SSD1TB, US Keyboard)
一言総評: 「★3 ただ、ただ、高い」
12月24日に上海から届く。会社で使っているPCと同じスペックなので特にワクワクもない。
現在使っているのは5年前の物(2012年の初代Retina MBPにBTOでメモリを16GBにした)なのでそろそろ潮時。
Apple製品にしては特にトラブルもなく、5年もったことに驚き。(本来はこうあって欲しいw)
さすがにAppCodeを立ち上げると重くてコーディングするのが辛かったため、買い替え。
本当は絶賛キャンペーン中である 「ヨドバシのApple製品でもポイント10%キャンペーン 」に乗りたかったけど、US配列がないので断念。
会社にも家にもHHKBを使っているでノートの配列関係ないけど。
JetBrains All Products Pack
一言総評: 「★4 AppCodeの完成度が高い。ただし、Xcodeの新機能がワンテンポ遅れる」
Xcode9からAlcatrazが使えなくてVimバインディングが気軽にできなさそう だったので、AppCodeを検討しました。
IntelliJのみを契約していたが、AppCode(swift/objective-c)、たまにWebStormを使うので、All Products Pack を選択。
IntelliJは4年くらい使っているので、
使い慣れたIDEのショートカットがAppCodeでもそのまま使えるのがGood。
まとめ
My Best 3
- モニターアーム
- 自作PC
- iPhoneX
FastlaneがSwiftで書けるようになった〜
これはSwiftアドベントカレンダーの17日目の記事です。
Swiftの方はプラットフォームに依存しないエントリーを書くべきかと思いましたが、
FastlaneのSwift対応がタイムリーだったのでこっちにしました。
元はSwiftでTCPソケット通信を書こうと思ってたので、年末にでも。それでは本題へ
今回は下記のプロジェクトを元に紹介していきます。
https://github.com/matsuokah/fastlane-swift-samplegithub.com
Fastfileの設定ファイルやその周辺がSwiftで書けるようになりました
fastlaneがもともとRubyなのは周知の事実ですが、rubyの実装をSwiftから叩く実装が2.69.0から入りました。
Swift対応の実装方針としてはブリッジ、フック、そしてlane定義に分かれています。
ブリッジ: Rubyのコマンドをコールするラッパーを自動生成
フック: ブリッジファイルをコールしたり、エントリーポイントとなるコード
lane定義: コマンド(ブリッジ部分)を叩いたり、ビルドの設定や環境変数を定義している箇所
そしてこれらが、xcodeプロジェクトで管理できるようになっています。
今回の一番のポイントは「慣れたエディタで慣れた言語をつかえる」。これだけで効率化のイメージが湧きますよね
早速使ってみる
fastlane init swift
でfastlane関連のSwiftのファイルが生成されます。
To edit your new Fastfile.swif type: open ./fastlane/swift/FastlaneSwiftRunner/FastlaneSwiftRunner.xcodeproj
というメッセージが出ています。
生成されたプロジェクトの開いて、グループの構成をみると下記のようになっていて、基本的にはFastfile.swift
だけを編集すればOK
* Autogenerated API => いわゆるRubyのブリッジ部分 * Fastfile Components * Networking => 文字通り * RunnerCode => フック部分 * Appfile.swift, Fastfile.swift => 設定、ビルドフローの定義ファイル * 各コマンド(Gymfile.swifなど)
このプロジェクトをビルドするとMacでexecutableなバイナリが出来上がります。
生成されたバイナリを介してfastlaneの部分が実行されています。また、fastlaneコマンドをつかって起動した場合、バイナリ自体のビルドもよしなに走るようになっています。
Fastfile
を編集してビルドする
Fastfile
クラスはLaneFile
クラスを継承しています。LaneFile
には各実装が織り込まれているのでビルドフローを詳しく知りたい人は読んでみるといいと思います。
つけるメソッド名はdebugLane
のように接尾辞を付ける必要があります。理由はlane
を接尾辞にもつメソッドをフィルタしてlaneを見つけ出してフックしているためです
class Fastfile: LaneFile { var fastlaneVersion: String { return "2.69.3" } func debugLane() { buildApp() crashlytics(apiToken: "TOKEN", buildSecret: "SECRET") } }
最低限の実装はこれだけで済むはずです。
ここからさらに ConfigurationやExport Methodなど、enumを定義してそれらを扱うクラスを用意すれば省コード化が可能になります。
enum Configuration: String { case debug case release var exportMethod: ExportMethod { switch self { case .release: return .appStore default: return .development } } }
上記はConfigurationの列挙ですが、プロジェクトによってはstagingや準本番のような環境もあるかと思います。
それらの環境変数を洗い出したあとは変数のマネージャクラスを用意すればOK
すべて記載すると長いので、Protocolだけ記載しておきます
protocol BuildContextProtocol { // Xcode var workspace: String { get } var scheme: String { get } var configuration: String { get } // Build var buildDir: String { get } var ipaName: String { get } var ipaPath: String { get } var dsymName: String { get } var dsymPath: String { get } var exportMethod: String { get } }
最終的には下記のようなコードに収まります。
func debugLane() { desc("Submit a new Beta Build to Crashlytics") let buildContext = BuildContext() buildContext.build() crashlytics(apiToken: "TOKEN" , buildSecret: "SECRET" , ipaPath: buildContext.ipaPath , groups: Fabric.testerGroup, notifications: true ) }
各環境変数・コンフィグの切り替えを省コード化して書いてみたサンプルをおいときます。
https://github.com/matsuokah/fastlane-swift-sample
今回省コード化のために抜き出した設定ファイルです
ハマったこと
- FastlaneRunnerをgit管理下に置こうとして失敗する
- gitignoreに追加しました。また、バイナリなので12MBほどの大きさです。毎度FastlaneのSwift部も勝手にビルドが行われて、バイナリが再度生成されるので追跡しなくていいと思います。
- ルビーでは配列で扱っている部分もすべてStringになっている
- Crashlyticsの
groups
とかがそうなのですが、rubyだと["tester1", "tester2"]
のように配列を指定できますが、SwiftではインターフェースにはString採用されているので使い方に工夫が必要になりそうです。
- Crashlyticsの
- 現状、メソッドにパラメータを渡していない
- 試してはいないのですが、フック部では
_ = fastfileInstance.perform(NSSelectorFromString(laneMethod))
と書かれていることから、引数が使えないのでは?と妄想しています。
- 試してはいないのですが、フック部では
まとめ
ということで、まだまだexperimantalで機能的にまだ満たされていない部分もありますがそこはPRポイントですね!
若干のワークアラウンドが発生しますが、FastfileがSwiftで定義でできるようになったことでグルーコードが非常に書きやすくなったかと思います!
個人的にはRubyが書けるならRubyでいいなと思います。あくまでRuby主導なリポジトリですので、Rubyで直接実行できるにこしたことはないです。
CIを前提としたプロジェクトのテンプレートができてた話
これはiOSアドベントカレンダーの10日目の記事です。
私にとって今年は、iOSエンジニアに転向した年でした。 それまではAndroid。 そして、携わっているプロジェクトのSwift化(未完)だったりiPhoneX対応だったりと劇的な半年でした。
ほぼゼロから始めたので、iOS SDKのサンプルアプリをいくつか作って勉強してたのですが、
そのうち、 CIを前提としたプロジェクトのテンプレートのような物が出来上がっていたのでそれを紹介したいと思います。
TL;DR
AnsibleでCIできるまでセットアップするプロジェクトテンプレートができてましたという話です。
大まかな使い方
- initialタグをチェックアウト
- プロジェクトをつくって、リポジトリと同じパスに配置。( initial-projectタグの状態を作る)
- プロビジョニング(Ansible)を実行(実行すると、apply-provisionタグの状態になる)
※ 実際に使う場合はトークンなどの認証情報が設定されている必要があります。
CI
- fastlane
Deploy
- Crashlytics Beta
Package Manager
- CocoaPods
Provisioning
- Ansible
- Bundler
テンプレートプロジェクト
下記のテンプレートプロジェクトをもとに詳細を書いていきます
https://github.com/matsuokah/ProjectTemplate-iOSgithub.com
リポジトリをクローンしてからfastlaneを叩くまで2,3コマンドでできるようにする
Ansibleで環境構築を自動化することで、fastlaneのコマンドを叩くまでの時間を短縮することができました。 なるべく前提を減らし、ビルドに必要なアプリケーションがなくても動くように作りました。
流れを書くと
make setup_tools
コマンドでシェルスクリプトをフック- シェルスクリプトでXcodeのインストール済みかどうかを判定。なければ、Safariを開いてXcodeのインストールを促す
- Ansibleがなければインストール、あればアップデート
- AnsibleのPlaybookを実行
Ansibleでやったこと
brew
で必要なアプリのインストール- 各テンプレートのデプロイ
bundler
のインストール
テンプレート
主に作ったテンプレートは下記の通り
.gitignore
Podfile
fastlane
関連Gemfile
.gitignore
.gitignore
は特にプロジェクト作る度にまずこれを作りますが、うっかり忘れたり、排除対象の列挙が漏れると思わぬファイルを追跡してしまいます。
gitignore.ioで作るのもめんどくさい
今回はある程度使うものを予め盛り込んだテンプレートで対応しました。
脱線しますが、CLIではcurl http://gitignore.io/api/swift >> .gitignore
という書き方も可能です。
Podfile
Crashlytics Betaを使いたいので予め盛り込んだ状態にしています。
XXXXX.xcodeproj
にヒットしたターゲット名で生成しています。これであとはpod install
するだけ。
よく使うライブラリは他にも盛り込んでもいいかも。RxSwiftとか。
fastlane
関連
テンプレート化したのは下記の3つ。Fastfile
以外は自前の定義ファイルです。
Fastfile
- ビルドシーケンス
AppContext
Env
Gemfile
- Bundlerのインストール
Fastlane
のEnv
の管理について
プライベートリポジトリを使っていたので、気にせず直書きしていました。(セキュリティの意識は甘々ですが別の漏れても被害はほぼないので。) また、サンプルを作るときには、Bitbucketを使っていました。Privateリポジトリの作成が無料かつ無制限につかえます。 余談ではありますがCIにはBitriseを使いました。Bitriseは10分以内ビルドなら月に200回まで無料で使えます。 iOSかつ、プライベートなリポジトリのCIが無料でできるのはBitriseだけ(俺調べ)
テンプレートの使い方まとめ(再掲)
- タグinitialをチェックアウト
- プロジェクトをつくる(実行すると、タグ: initial-projectの状態になる)
- プロビジョニング(Ansible)を実行(実行すると、タグapply-provisionの状態になる)
まとめ
ということで、Xcodeプロジェクトを作ってからfastlaneを走らせるまでの作業を自動化してみました。
殆どのプロジェクトでは初回のみなのであまり機会がなさそうなソースですが、
手順が自動化でき、自分にとってのプロジェクトの作成マニュアルができました。
今回は盛り込んでいませんが、xcodeprojを使えばシェルの埋め込みも可能だと思います。
まだまだプロジェクトのテンプレートは育ちそうです。
それでは次は17日のSwiftのAdvent Calendarで〜。
Macでスクリーンショットの保存先の変更と古いスクショの自動削除
デスクトップがスクショの嵐・・・!
こんなデスクトップになった経験はないでしょうか。 スクリーンショットの保存先はデスクトップなので、スクショを撮ってるうちにいつの間にかデスクトップがスクショで埋め尽くされることがあります。
精神衛生上よくない!!!
デスクトップは常にきれいでありたいものです。本当作業中で一時的なファイルを置くだけにしたい。
割れ窓理論があるように、デスクトップが心の余裕の無さや秩序を保とうとしているかを表してるんじゃないかという観念に狩られ るんですよね。もはやこれは健やかな精神なのかしるためのバロメータでもあるといえようッッ!!
ということで、整理される環境を整えたいと思います。
結論
2つの工夫で解決できます
1. ターミナルで保存先の変更
$ mkdir -p ~/Pictures/ScreenShots && defaults write com.apple.screencapture location ~/Pictures/ScreenShots
2. Automator + Calendarで自動的に古いスクリーンショットをゴミ箱に移動する
ターミナルで保存先の変更はDefaultsで解決!
約2年前の記事でも言及してますがdefaults-write.comでいろんな設定値をコマンドラインから設定することが可能です。
defaults
は、設定アプリで設定不可能な項目にアプローチできるので痒かった設定を自分好みに設定することでよりMacを使いやすくすることができます。
任意のディレクトリを作成し、そこに保存する設定を書き込みましょう。
私の場合は、写真フォルダの下にスクリーンショット用のフォルダを作成して、そこを保存先として扱うようにしました。
ターミナルで下記のコマンド2つを実行するのみです
$ mkdir -p ~/Pictures/ScreenShots $ defaults write com.apple.screencapture location ~/Pictures/ScreenShots
~/Pictures/ScreenShots
をFinderのサイドバーに登録しておくと便利です。
自動削除にはAutomator!!
次は自動削除です。デスクトップに保存しなくなった分、スクショが棚卸しされなくなりそうです。
いつの間にか凄まじい数のスクショになっているかもしれません。
スクショは往々にしてインスタントなデータなので作成日から1ヶ月過ぎたら消されてもほぼ問題無いと思います。
ということで、1ヶ月以上前のスクショを自動的に削除する仕組みを作っていきます。
Automatorを使います。スクリプトを書かない選択をしてみました。
こいつです。癖さえわかれば作業・業務を自動化できるので楽しくなります。
エンジニアなら自動化のスクリプトを組むのは日常茶飯事ですがそれがGUIベースでできるイメージです。
アプリケーションベースのアクションが定義されていてその結果を次の操作に繋いでいくイメージでしょうか。
アプリケーションベースのアクションとは
例えば、Finderだったら
- フォルダを取得する
- フォルダの名前を変更する
のような単位です。
立ち上げると
こんなウィンドウが立ち上がるので左のカラムに並んでいるアクションを、右のエリアにドラッグしてワークフローを作っていきます。
今回やりたいことは「30日以上前に作成したスクリーンショットの削除」
Automatorで扱えるフローに分解すると
これをAutomatorのフローに当てはめると
Get Specified Finder Items
Get Folder Contents
Filter Finder Items
Move Finder Items to Trash
ということでできました。
これで実行するとスクショフォルダ内にある作成日が31日以上前のファイルが一括でゴミ箱に移動されます。
Calendarアプリで定期実行に組み込む
実はCalendarアプリでアプリケーションの定期実行をすることができます。
手順としては
- ワークフロー実行用のカレンダーを作成
- 上記で作成したカレンダーにスクリーンショット削除の予定をカレンダーに追加
- 全日 or 0時などで 毎日繰り返し予定に
- カスタムアラートでその時間になったらファイルを開くを選択
こんな感じです
ワークフロー実行用のカレンダーを作成
なぜ、ワークフロー実行用のカレンダーを追加したかというと、毎日定期実行の予定がカレンダーを埋め尽くすことになるからです。 カレンダーは分けておいて、ワークフロー実行用のカレンダーを普段は非表示にしておくことをおすすめします。
まとめ
- defaultsでスクショの保存先を変えてDesktopにスクショがたまらないようにしよう!
- Automatorとカレンダーでスクショの棚卸しはPCにやらせよう!
Lottieで再生するアニメーションを作って読み込ませるまで
↑の記事は、Lottieを使ってアニメーションの再生をするところを実装しました。
実はハンバーガーアイコンのアニメーションには大きな余白が含まれていて
このまま使うとアイコン自体が非常に小さい表示になってしまっていました。
ということで、次はAfterEffectsプロジェクトを編集して、JSONを書き出すところ紹介したいと思います。
1. AfterEffectsのプロジェクトでアニメーションを編集する
まずはハンバーガーアイコンのプロジェクトをよみこみます。
おわかりかと思いますが、コンポジションのサイズ(いわゆる全体の大きさと思ってください)が800x600
になっていて、アイコンに対しての余白が非常に大きい状態です。
そこでComposition Settingsを開き、サイズをアイコンが収まるサイズの正方形にします。
設定すると、以下のようになります
2. 書き出す
次に書き出しです。
Lottieでは、このオープンソースライブラリでjson形式にExportすることを想定して作られています。
AdobeCCを使っていれば、↓のページでインストールすればAfterEffectsで使用可能になります。
https://exchange.adobe.com/addons/products/12557exchange.adobe.com
それでは、AfterEffectsでプラグインを起動します。
Window > Extensions > Bodymovin
で、起動できます
起動するとコンポジションを選択する画面がでてきます。
Render
で書き出してみます。
特に設定をしていないとこのように、書き出しが許可されてないよというエラーとともに解決方法が提示されます。
After Effects CC > Preference > General > Allow Scripts to Write Files and Access Network
にチェックを入れます
もう一度書き出すと成功します。
あとは、吐き出されたJSONファイルをiOS/Androidに組み込んで使うだけです。
これで、自作のアニメーションを組み込めるぞ 🎉🎉🎉🎉🎉🎉🎉
Lottieことはじめ
Lottieとは
Airbnb謹製のアニメーションツールでAfterEffectsでexportしたアニメーションをiOS、Androidで再生できるというすぐれものです。
上記のGIFはlottie-iosより転載
僕自身はこういうアニメーションはあまり好きではないのですが、
味気なさがなくなりますね!
ということで、シンプルなハンバーガーアイコンのスイッチを例に使い始めるまでの流れをまとめてみました。
1. Carthageで組み込み
github "airbnb/lottie-ios" "master"
cartfileに上記を記述してcarthage update --platform iOS
して、プロジェクトに組み込むだけですね。
2. スイッチを組み込む
LOTAnimatedSwitch
というクラスがあるのでそれを使います。
LOTAnimatedSwitch.h
をみるとスイッチを作成するクラスメソッドが用意されてるのでこちらを使います。
/// Convenience method to initialize a control from the Main Bundle by name + (instancetype _Nonnull)switchNamed:(NSString * _Nonnull)toggleName; /// Convenience method to initialize a control from the specified bundle by name + (instancetype _Nonnull)switchNamed:(NSString * _Nonnull)toggleName inBundle:(NSBundle * _Nonnull)bundle;
toggleNameにはアニメーションを記述したjsonファイルを指定する必要があります。
LottieではLottieFilesといって、
アニメーションをクリエイティブ・コモンズライセンスで公開しているストアがありますのでそこからダウンロードしてきます。
今回はハンバーガーアイコンをダウンロードしてきます。
ダウンロードされたzipを解答するとAfterEffectsのプロジェクトファイルと、Export済みのjsonがはいっているのでjsonをプロジェクトに組み込みます
let animatedSwitch = LOTAnimatedSwitch.init(named: "Hamburger")
これで読み込めるようになりました。
3. スイッチのアニメーションの範囲を決める
このままではアニメーションはうまく動きません。なぜならスイッチのON/OFFに対してのアニメーションの対応付をしていないからです。
スイッチはoff -> on
のアニメーションとon -> off
のアニメーションがあります
この間ですね。
LOTAnimatedSwitch.h
をみるとアニメーションの範囲を割合で設定するメソッドが用意されています
- (void)setProgressRangeForOnState:(CGFloat)fromProgress toProgress:(CGFloat)toProgress NS_SWIFT_NAME(setProgressRangeForOnState(fromProgress:toProgress:)); - (void)setProgressRangeForOffState:(CGFloat)fromProgress toProgress:(CGFloat)toProgress NS_SWIFT_NAME(setProgressRangeForOffState(fromProgress:toProgress:));
先ほどダウンロードしたHamburger.json
ではoff->on->off
ということでonに戻るまでのアニメーションが記述されています。
アニメーションの進捗割合に対応付けると
off -> on
: 0 -> 0.5
on -> off
: 0.5 -> 1.0
と表すことができます。
したがって、setProgressの記述は以下のようになります。
animatedSwitch.setProgressRangeForOnState(fromProgress: 0, toProgress: 0.5) animatedSwitch.setProgressRangeForOffState(fromProgress: 0.5, toProgress: 1.0)
この進捗割合はアニメーションの元ファイルに依存します。
off -> on
: 0 -> 1.0
on -> off
: 1.0 -> 0
で表せる場合もあるでしょう。
ということで、アニメーションの対応付が完了し、スイッチの作成ができました。
※レイアウトのコードは本筋から外れるので記載していません
4. 動かしてみる
タップしてるのですがわかりづらいですね(汗)
ということで、アニメーションの組み込みができました。
5. InterfaceBuilderで組み込めるようにしてみる
InterfaceBuilderで必要な要素だけを設定したらいい感じに動いてほしいです。
毎度、アニメーションの対応付のコードを書くのは面倒です。
ということで、InterfaceBuilderで設定できるようにします。
LottieSwitchView.swift
@IBDesignable @IBInspectable
を使って、Interface Builderでアニメーションを定義できるようにします
import UIKit import Lottie @IBDesignable class LottieSwitchView: UIView { @IBInspectable var filename: String = "" @IBInspectable var fromProgressToOn: CGFloat { set(newValue) { _fromProgressToOn = LottieSwitchView.shrinkInZeroToOne(value: newValue) } get { return _fromProgressToOn } } @IBInspectable var toProgressToOn: CGFloat { set(newValue) { _toProgressToOn = LottieSwitchView.shrinkInZeroToOne(value: newValue) } get { return _toProgressToOn } } @IBInspectable var fromProgressToOff: CGFloat { set(newValue) { _fromProgressToOff = LottieSwitchView.shrinkInZeroToOne(value: newValue) } get { return _fromProgressToOff } } @IBInspectable var toProgressToOff: CGFloat { set(newValue) { _toProgressToOff = LottieSwitchView.shrinkInZeroToOne(value: newValue) } get { return _toProgressToOff } } //// actual value private var _fromProgressToOn: CGFloat = 0 private var _toProgressToOn: CGFloat = 0.5 private var _fromProgressToOff: CGFloat = 0.5 private var _toProgressToOff: CGFloat = 1.0 override func awakeFromNib() { super.awakeFromNib() let animatedSwitch = LOTAnimatedSwitch.init(named: filename) animatedSwitch.setProgressRangeForOffState(fromProgress: fromProgressToOff, toProgress: toProgressToOff) animatedSwitch.setProgressRangeForOnState(fromProgress: fromProgressToOn, toProgress: toProgressToOn) self.addSubview(animatedSwitch) animatedSwitch.fitToParent() } } private extension LottieSwitchView { static func shrinkInZeroToOne(value: CGFloat) -> CGFloat { return min(1.0, max(value, 0)) } }
これで、InterfaceBuilderでファイル名、アニメーションの範囲を指定できるようになりました
InterfaceBuilder上ではUIViewで枠だけを作っていて
awakeFromNibで内部的にLOTAnimatedSwitch
を作ってaddSubviewしています。
fitToParent
は親Viewと同じframeになるようにConstraintsを設定しているだけです。
以上、Lottie事始めでした。
リポジトリ
https://github.com/matsuokah/LottieSamplegithub.com
次は、「AfterEffectsからアニメーションのJSONをExportする」記事を書こうと思います
↓書きました
アクションシートのクロージャをObservable化して処理を一本化する
アクションシートってよくつかわれるんですかね? 私が携わっているプロジェクトではそこそこ使われています。
コレです
アクションシートの基本的な使い方
let actionSheet = UIAlertController(title:"Title", message: "Message", preferredStyle: .actionSheet) let action1 = UIAlertAction(title: "Action 1", style: .default) { (action: UIAlertAction!) in print("Selected Action!") } // キャンセル let cancel = UIAlertAction(title: "Cancel", style: .cancel) { (action: UIAlertAction!) in print("Selected Cancel!") } actionSheet.addAction(action1) actionSheet.addAction(cancel) present(alert, animated: true, completion: nil)
- UIAlertControllerをつくる
- アクションを追加する
- 表示する
これが基本的な使い方です。
また、UIAlertActionには選択されたときのクロージャが用意されていて、都度、処理を定義する必要があります。
パッとみて感じるのが、繰り返しの記述ということです。 こういうのをループにしたくなるのがプログラマの性ですよね。
ここで私は2つ思い浮かびました
- UIAlertActionの生成を共通化できないか?
- 「どれが選択されたか」をストリームとして扱えないか?
ということで、Rxをつかってコレを実現したいと思います。
アクションシートの特徴
簡単に述べると下記の3点
- アクションシート自体はタイトルとメッセージを持つ
- アクション毎にタイトルと選択された時の処理を持つ
- キャンセルは例外
選択をObservable化するにあたっての方針
- アクションの生成元となる情報の配列を引数に取るメソッドを定義し、
UIAlertAction
の生成をまとめる。 - どのアクションが選択されたかをenumで表現する
- アクションシートの起動はUIViewControllerのextensionにまかせてします
実装
早速実装です。 なお、Githubにもソースを上げてます
ActionSheetAction.swift
まずは、アクションシートの各行を表現する構造体を定義します
// MARK: - Action internal struct ActionSheetAction<Type: Equatable> { internal let title: String internal let actionType: Type internal let style: UIAlertActionStyle }
ジェネリクス(Type
)を用いることによって外からアクションのタイプ(enum)をインジェクトできるようにします。
この型は最終的にObservableに用います。
UIViewController+RxActionSheet.swift
次に、UIViewControllerにUIAlertControllerを表示するextensionを定義します
import UIKit import RxSwift internal extension UIViewController { internal func showActionSheet<Type>(title: String?, message: String? = nil, cancelMessage: String = "Cancel", actions: [ActionSheetAction<Type>]) -> Observable<Type> { let actionSheet = UIAlertController(title: title, message: message, preferredStyle: .actionSheet) return actionSheet.addAction(actions: actions, cancelMessage: cancelMessage, cancelAction: nil) .do(onSubscribed: { [weak self] in self?.present(actionSheet, animated: true, completion: nil) }) } }
サブスクライブされた時にアクションシートを表示しています
UIAlertController+RxActionSheet.swift
最後に、UIAlertControllerにアクション追加と同時にObservable化して返すextensionを実装します
import UIKit import RxSwift internal extension UIAlertController { internal func addAction<Type>(actions: [ActionSheetAction<Type>], cancelMessage: String, cancelAction: ((UIAlertAction) -> Void)? = nil) -> Observable<Type> { return Observable.create { [weak self] observer in actions.map { action in return UIAlertAction(title: action.title, style: action.style) { _ in observer.onNext(action.actionType) observer.onCompleted() } }.forEach { action in self?.addAction(action) } self?.addAction(UIAlertAction(title: cancelMessage, style: .cancel) { cancelAction?($0) observer.onCompleted() }) return Disposables.create { self?.dismiss(animated: true, completion: nil) } } } }
こんな感じでObservableを作成し、各アクションのクロージャでactionTypeとともにonNextを発行してあげることで
アクションシートの選択をストリーム化することができました。
使い方
AnimalSelectAction.swift
まずアクションの一覧をenumで定義します
enum AnimalSelectAction: String { case dog, cat, rabbit, panda static var AnimalSelectActions: [AnimalSelectAction] { return [.dog, .cat, .rabbit, .panda] } }
ViewController.swift
import UIKit import RxSwift final class ViewController: UIViewController { private let disposeBag = DisposeBag() @IBOutlet weak var showActionSheetButton: UIButton! override func viewDidLoad() { super.viewDidLoad() showActionSheetButton.rx .tap .asDriver() .drive(onNext: { [weak self] _ in self?.showActionSheet() }).disposed(by: disposeBag) } } private extension ViewController { func showActionSheet() { let actions = AnimalSelectAction.AnimalSelectActions .map { return ActionSheetAction(title: $0.rawValue, actionType: $0, style: .default) } // アクションシートを表示し、返ってくるObservableをSubscribeしておく。 showActionSheet(title: "Which Do you like?", actions: actions) .subscribe { (event: Event<AnimalSelectAction>) in NSLog(event.debugDescription) }.disposed(by: disposeBag) } }
動作
まとめ
ということで、ViewController側ではアクションシートの選択肢の列挙とそれに対して選択された時の記述をするだけで良くなりました。
UIAlertControllerもほぼ意識せずにできてるのはメリデメあるかもですが記述が簡潔になったかと思います。
extension最高\(^o^)/