2019年に使い始めた便利Tools まとめ

Notion

f:id:m2-b0c27:20200112225035p:plain www.notion.so

情報一元化メモツール。

カンバン形式・カレンダー形式・リスト形式・ギャラリー形式など表示方法が様々、機能も盛り沢山なメモツールです。

Google Keep や Inkdrop、EverNoteなど色々試した結果、Notionに辿り着来ました。 使い方に関しては別の記事で書こうと思っています。

SlidePad

slidepad.xyz

Mac上にスライドするブラウザーWindowを追加拡張するツール。

Gmail や Google Calendar, YouTube Music, Trello といったよく使うウェブツールを別Windowで開くことができるツールです。

似たようなツールにStationがありますが、Stationとの違いとしてSlidePadには簡単にWindowを開いたり閉じたりできる機能があります。

"Chromeから常駐しているタブを無くす"、"Chromeのタブの中から特定の常駐タブを探す無駄を無くす"ことが出来ます。

Flotate - ⚠️Deprecated

flotato.com

SlidePadを導入してから使うのをやめたのですが紹介します。

Webアプリケーションをデスクトップアプリとして扱えるようになるツールです。

これによって先程言っていた「"Chromeから常駐しているタブを無くす"、"Chromeのタブの中から特定の常駐タブを探す無駄を無くす"」事ができましたが、"アプリを切り替えるときにCmd+tabで表示されるアプリが増える"というペインが発生しました。

こっちの方が好みという人も多いかもしれません。

Dash

Dash for macOS - API Documentation Browser, Snippet Manager - Kapeli

Dashと聞けばドキュメントツールという印象があるかもしれませんが、自分はスニペットツールとしても使っています。

Githubのissueやpull requestを書くとき、質問するときに思考をまとめたいとき、現在時刻を書きたいときなどでスニペットを使っています。

Chrome Extensions

Vimium

vimium.github.io

Vimのキーバインドを使ってChromeをブラウジング出来るようにするプラグイン。 なるべくマウスやトラックパッドを使いたくない人におすすめ。

Vim同様にキーバインドを自分好みに変えることも出来るので、キーバインドを変えている自分でも大満足のツールです!

キーバインドの変え方は Key Mappings · philc/vimium Wiki を見れば大抵わかります。

紹介として自分のキーバインドも晒しておきます。

# Insert your preferred key mappings here.
map <c-j> scrollFullPageDown
map <c-k> scrollFullPageUp
map [ goBack
map ] goForward
mapkey <c-c> <c-[>
mapkey <c-d> d
mapkey <c-u> u

Quick Tabs

chrome.google.com

Chromeで開いているタブを検索出来るプラグイン。

後で紹介するAlfred - Search Safari and Chrome Tabsの方が使い勝手が良かったので "タブを検索する" という機能は使っていないですが、"以前開いていたタブを開く"という機能を使うために入れています。

f:id:m2-b0c27:20200112230419p:plain

"Older"も"Newer"も出来るのでとても重宝しています。

Copy as Markdown

chrome.google.com

ChromeでリンクをMarkdownのリンクとしてクリップボードにコピーしてくれるプラグイン。 タイトルもそのリンク先に合わせてよしなに用意してくれるので、とても便利です。

自分は Cmd+Shift+L のショートカットを当てて使っています。

ex:

https://masegi.hatenablog.com/entry/useful_tools_2019
↓
[2019年に使い始めた便利Tools まとめ - まっせぎ blog](https://masegi.hatenablog.com/entry/useful_tools_2019)

Alfred Workflow

Bluetooth Connector

www.packal.org

ワイヤレスイヤホンの接続先をスマホからPCに奪い取るときに使っているworkflowです。 (AirPodsならよしなに切り替えてくれるのかな...?)

Github

github.com

Githubでレポジトリなどを検索するときに便利なworkflowです。 自分は主に、 アサインされたissue や レビュワーにアサインされているPR を探すときに使っています。

アサインされたissue : "gh my issues assigned" レビュワーにアサインされているPR : "gh my pulls review requested"

Search Safari and Chrome Tabs

www.packal.org

Chromeの開いているタブの中から特定のタブをワードで探すworkflowです。

自分はChromeのタブを沢山開いてしまう人間なので、目当てのタブを探すのに時間が掛かってしまっていました。 それを解決してくれるworkflowです。

UICollectionViewLayoutについて - 準備編 -

UICollectionViewのDocumentを読んだので、覚え書き程度にまとめました。

登場するメソッドで何をするのかをまとめたもので、具体的な実装方法等(コード)は一切書いていません。具体的な実装方法を知りたい方は、実装編を参照してください。

既知のことが多いと思いますので、流し読みしてください。

UICollectionViewLayoutとは

UICollectionViewのCellなどを、どの位置にどんな状態で表示するかを決定するオブジェクト

UICollectionViewのLayoutを調整するものとして、

- UICollectionViewFlowLayout
- UICollectionViewDelegateFlowLayout

などがある。

その中で最も自由度の高い方法がUICollectionViewLayoutを作成するやり方。

どれを選べば良いかはCookpadさんがブログで書いてくれています。

techlife.cookpad.com

今回はこのUICollectionViewLayoutについて紹介します。

UICollectionViewを構成する視覚要素

UICollectionViewにはレイアウトが必要な3種類の視覚要素がある。

- Cells
- Supplementary views
- Decoration views

UICollectionViewは様々なタイミングでこれらの視覚要素のレイアウト情報を提供するようにLayoutオブジェクトに依頼する。

Cells

セルはレイアウトによって位置を決める主要要素。データによってはセクション毎に分けたりする。

レイアウトオブジェクトの主な仕事はUICollectionViewのViewの中身にセルを配置すること。

Supplementary Views

Header, FooterといったCellとは違うもの。これもUICollectionViewLayoutによって指定しなければならない。(optional)

Decoration Views

装飾するView。Supplementary viewsに似ているが少し違う。

こちらで場所を指定できる。(セル間の区切り線 etc..)

qiita.com

レイアウトを定める

視覚要素(Cells, Supplementary Views, Decoration Views)をどのように配置するかを決めるのが以下のメソッド。

- collectionViewContentSize
- prepare()
- layoutAttributesForElements(in:)

- layoutAttributesForItem(at:)
- layoutAttributesForSupplementaryView(ofKing: at:)
- layoutAttributesForDecorationView(ofKing: at:)

UICollectionView自体のレイアウトが変更されたり、Cellが移動・追加・削除されるたびに新しいレイアウト情報を得るために呼び出される。

collectionViewContentSize

Collection viewの全体のheight, widthを返す。

prepare()

layoutAttributesForElements(in:)は頻繁に呼び出されるため、なるべく計算処理を含まない方が良い。

そこで、レイアウト情報が更新される前に呼ばれるprepare()をOverrideして、レイアウト情報を求める計算処理を記述するのが普通である。

layoutAttributesForElements(in:)

指定された短形内の全てのセルとViewのLayoutAttributesを返す。

Cells, Supplementary views, Decoration viewsの全てのLayoutAttributesを返す必要がある。

下記のメソッドを実装することで、それぞれの要素ごとに責務を分割できる。

これらは自分で呼び出さない限り実行されない

- layoutAttributesForItem(at:)
- layoutAttributesForSupplementaryView(ofKing: at:)
- layoutAttributesForDecorationView(ofKing: at:)

自分はこれらのメソッドを使わず、自分で実装した方が楽な時の方が多い気がします。

Layout Performanceの最適化

Custom layoutを作る時、実際に変更された部分だけを無効・更新するように書くことでパフォーマンスをあげることができる。

invalidateLayout()を呼び出すことで、レイアウト情報のみを更新することができる。(reloadData()はDataSourceとレイアウトを更新する。)

更にUICollectionViewLayoutInvalidationContextのサブクラスを作成し用いることで、より無効・更新する箇所を限定することができる。

以下で最適化するのに必要なメソッドを紹介する。

- shouldInvalidateLayout(forBoundsChange:)
- invalidationContext(forBoundsChange:)
- shouldInvalidateLayout(forPreferredLayoutAttributes:withOriginalAttributes:)
- invalidationContext(forPreferredLayoutAttributes:withOriginalAttributes:)

shouldInvalidateLayout(forBoundsChange:)

CollectionView自体のレイアウトが変更された時に、中のレイアウトを更新するかどうかを決める。

更新する時はtrue、しなくていい時はfalseを返す。

trueを返された場合はinvalidationContext(forBoundsChange:)が呼び出され、この返り値のUICollectionViewLayoutInvalidationContextを引数にinvalidateLayout(with:)を呼び出し、現在のレイアウトを無効にしてからprepare()を呼び出す。

invalidationContext(forBoundsChange:)

ここではUICollectionViewLayoutInvalidationContextを変更に応じて編集し、返す。

このContextをもとにinvalidateLayout(with:)でキャッシュしたレイアウト情報を削除する。

invalidateLayout(with:)

現在のレイアウト情報によるレイアウトを無効にする。

その際に、引数のUICollectionViewLayoutInvalidationContextの情報から更新しなければいけない箇所をを読み取り、事前に計算したレイアウト情報を修正する。(計算後にキャッシュしたレイアウト情報を削除する等々...)

shouldInvalidateLayout(forPreferredLayoutAttributes:withOriginalAttributes:)

Self-sizing(AutoLayout)を行うCellについて、preferredLayoutAttributesFitting(_:)で得られたLayoutAttributesがlayoutAttributesForElements(in:)で指定したものと異なる場合に呼び出される。

先ほど紹介したshouldInvalidateLayout(forBoundsChange:)の引数が変ったもの。

同様に、trueを返すと更新処理に遷り、falseを返すと何もしない。

trueを返すとinvalidationContext(forPreferredLayoutAttributes:withOriginalAttributes:)が呼び出される。

Self-sizing (AutoLayout)の具体的な説明は後述する。

invalidationContext(forPreferredLayoutAttributes:withOriginalAttributes:)

先ほどのinvalidationContext(forBoundsChange:)と同様のことを行う。

(アニメーションを付ける)

アニメーションをつける場合は、これらのメソッドをオーバーライドして適切なレイアウト情報を提供する必要がある。

- initialLayoutAttributesForAppearingItem(at:)
- initialLayoutAttributesForAppearingSupplementaryElement(ofKing: at:)
- initialLayoutAttributesForAppearingDecorationElement(ofKing: at:)
- finalLayoutAttributesForDisappearing~(at:)

さらに、

- finalizeCollectionViewUpdates()

をオーバーライドすることで、全体にアニメーションを追加したり、レイアウト関連の最終タスクを実装したりすることもできる。

これらについては、本記事では割愛する。

Self-Sizing (Auto Layout)

Self-SizingによってCellのSizeが変更になった時、shouldInvalidateLayout(forPreferredLayoutAttributes:withOriginalAttributes:)が呼び出され、レイアウトを更新するか指定できる。

developer.apple.com

引数で与えられるforPreferredLayoutAttributesは、preferredLayoutAttributesFitting(_:)の返り値、つまり、AutoLayoutによって測定されたCGSizeが含まれる。

それをキャッシュし、用いることによってUITableViewのようなSelf-Sizingを行うことができる。

具体的な実装については実装編を参照してください。

おわり。

複雑な画面をRecyclerViewで作るEpoxy - Android

Androidで複雑な画面を作る際に、Airbnbが提供しているEpoxyというライブラリが良い感じだったので紹介します。

この記事はShibuya.apkで発表した、今更ながらEpoxyを元に書いています。 見ていただけるとありがたいです。

今更ながらEpoxy / Lateinit Epoxy - Speaker Deck

また、Epoxyを触った際に作ったレポジトリもGithubで公開しているので、そちらも参考にしてください。

github.com

Epoxyとは?

github.com

  • 複雑な画面をRecyclerViewで作るためのライブラリ
  • Airbnbが作成している
  • 今でも結構活動している
  • 2016/08/25に初リリース
  • androidx対応

ざっくり言うと、各Section(Row)毎のレイアウトを組んで順番に並べれば、それをRecyclerViewの再利用性を活用しつつ簡単に表示してくれる。というものです。

Airbnbが紹介記事を出しているので、是非こちらをお読みください。

medium.com

似たようなものとして、最近ではGroupieも有名です。自分はGroupieを使ったことがないので比較については書きません。

AirbnbはEpoxyの他にも、MvRx、Pairsなどのライブラリを出していて、それらとの相性も良いです。

メリット

  • 簡潔に複雑な画面を作ることができる
  • RecyclerViewを使うことによって、各Viewが表示される直前にロードされるためパフォーマンスが良い
  • Viewの状態を保存しているので、Carouselなどのスクロール位置を画面外にいっても覚えている
  • 差分更新によって滑らかなアニメーションが実現できる

大まかな流れ

・セルのレイアウトを作成

.xml, .ktどちらからでも作成可能ですが、今回はより楽なXMLファイルとdatabindingを用いた方法で作ってみます。.ktで作成したい場合はこちらを参考にしてください。

・セルからEpoxyModelを自動生成

作成したセルをpackage-info.java内で指定することで、EpoxyRecyclerViewで使用するEpoxyModelをXML(.kt)から自動生成します。

・Controllerを作成

RecyclerViewでいう所のAdapterとなる、Controllerを作成します。ここで、各セルの表示順序、データ、OnClick()等を設定します。

・EpoxyRecyclerViewにControllerをセット

最後に、EpoxyRecyclerViewをViewに追加して、Controllerをセットして完成です。

導入

それでは実際に以下のような画面を作ってみたいと思います。

1. 準備

app build.gradle

apply plugin: 'kotlin-kapt'

kapt {
    correctErrorTypes = true
}
dependencies {
  implementation 'com.airbnb.android:epoxy:3.x.y'
  implementation 'com.airbnb.android:epoxy-databinding:3.x.y'
  kapt 'com.airbnb.android:epoxy-processor:3.x.y'
}

x, yには最新のバージョンを入れてください。(投稿時は3.3.0)

package-info.java

EpoxyModelをどのXMLファイルから作るか記述するものです。XMLファイルのみでdatabindingを使用してEpoxyModelを作る際に必要になります。

.kt (or .java)ファイルを置くフォルダの直下に作成してください。

@EpoxyDataBindingLayouts({R.layout.header_view, ... // other layouts })
package com.airbnb.epoxy.sample;

import com.airbnb.epoxy.EpoxyDataBindingLayouts;

これは下記のように接頭辞での指定も可能です。

@EpoxyDataBindingPattern(rClass = R.class, layoutPrefix = "view_holder")
package com.example.package;

2. セルの準備

これは特に変わったことはありません。databindingを使ってレイアウトを組む時と同じです。双方向のdatabindingは出来ないことに注意しましょう。

3. Controllerの作成

Controllerを作成する際はTypedEpoxyController<T>クラスを継承します。このTは中身のデータの型です。二つ渡したいときはTyped2EpoxyController<T, U>というクラスがあります。詳しくはこちらに記載されています。

Controllerでやることは、buildModels(data:)を実装することです。コードをみた方が早いと思うので今回作ったものを載せます。

2. セルの準備で作成したレイアウトをpackage-info.javaに記載後、BuildするとEpopxyModelが自動生成されます。

名前は、

R.layout.~による指定 -> そのまま
ex) R.layout.epoxy_header_view -> EpoxyHeaderViewBindingModel_.java
接頭辞による指定  -> 接頭辞以降
ex) (layoutPrefix = "epoxy")
R.layout.epoxy_header_view -> HeaderViewBindingModel_.java

といったようになります。

これを用いてbuildModels(data:)に記述していくのですが、便利なExtensionがあり、次のように記述出来ます。(今の所は...)

ここでidはViewを一意に判断するための識別子で、その後のciryName, descriptionがXMLファイルで書いた<data />タグの中身です。

このようなViewのブロックを順に並べることで、その順通りに表示されます。

4. EpoxyRecyclerViewにControllerをセット

残りはViewにEpoxyRecyclerViewを追加して、作成したControllerをセットして完成です。セットにはEpoxyRecyclerView.setController(controller:)を使って、controller.setData(data:)でデータを流します。

加えて..

EpoxyVisibilityTracker

スクロールイベントとして、それぞれのRowが表示されているか否か、どれくらい表示されているかを引数としたコールバックを設定できます。こちらに詳しいことが記載されています。

val epoxyVisibilityTracker = EpoxyVisibilityTracker()
epoxyVisibilityTracker.attach(recyclerView)

Carousel

Epoxyの利点として、Carouselが標準で付いていることは大きいです。

導入はとても簡単で、以下のExtensionを追加すれば今までのRowと同様に導入することができます。詳しいことは公式ドキュメントで読んでください。

numViewsToShowOnScreen(num:)とpadding(padding:)でアイテムの間隔等を決めます。

padding(padding:)の引数としてPadding.dp(padding: itemSpacing:)を渡します。それぞれの変更箇所は図の通りです。現状ではitemSizeを指定してレイアウトを決めるということはできないようです。(2019/2/24)

ピッタっと止めるようにするには、RecyclerViewでも使われているSnapHelperFactoryを作成して、Carousel.setDefaultGlobalSnapHelperFactory(factory:)を呼び出すことで変更できます。詳しくはこちらで。

注意点として...

CoordinatorLayoutなどと一緒に使用する際(Nested Scrollが発生する際)は注意が必要です。

github.com

最後に

複雑な画面を作る際に、ScrollView + Fragmentで実装するとパフォーマンスも可読性も落ちてしまう場合があります。

複雑な画面を実装するのに苦労している時は、EpoxyやGroupieなどのRecyclerViewを活用するライブラリを用いて、より簡潔に簡単に作って楽になりましょう!

(次はMvRxについて書こうと思います....)

AVPlayerについて調べた - iOS

https://s3-ap-northeast-1.amazonaws.com/hatena.masegi/AboutAVPlayer/Group.png

AVPlayer周りのドキュメントを読んだので、 AVAsset、AVPlayerItem、AVPlayerについて書いてあること自分に分かりやすいように書き起こしました。

動画をViewに反映する方法については詳しく書いていませんので、ご了承ください。

読んだもの

AVFoundation

  • audiovisual assets
  • control device cameras
  • process audio
  • system audio interactions

Media Assets, Playback, and Editing

  • media assetsにアクセス
  • mediaの再生(再生をカスタマイズ)
  • mediaの編集や結合
  • import and export raw media streams

Asset Modelについて

Media Playback Programming Guide

AVAsset

  • 単一のメディアリソースを表す抽象的で不変の型(abstract, immutable type)
  • ローカルファイルだけでなく、プログレッシブダウンロードされたもの、HLSを使用してストリーミングされたものも表せる
  • formatを気にせずに扱えるようにする
  • locationを気にせず扱えるようにする(URLを用いてイニシャライズ)
  • AVAssetはAVAssetTrackのコンテナオブジェクト

AVAssetTrack扱っている種類

  • video
  • audio
  • subtitles
  • caption
  • timed metadata

Overview

let url: URL = // Local or Remote Asset URL
let asset = AVAsset(url: url)

実際にはAVURLAssetと言う具象サブクラスが生成される。

Wi-Fi下のみでのリソースのダウンロード等のオプションを付けたい時はAVURLAsset.init(url:option:)を使用する。

他にもAVComposition等でイニシャライズすることも可能。

リソースによっては、初期化が完了しても即時に再生できないものもある。再生を要求することはいつでも出来るが、そのためには呼び出し元のスレッドを止める必要があるかもしれない。

そのブロッキングを避けるために、AVAsynchronousKeyValueLoadingを使って完了通知を受けるように登録することができる。

AVAssetを再生するには、AVPlayerItemを生成してAVPlayerを使って再生する。

AVAssetオブジェクトをAVMutableCompositionオブジェクトに挿入することで、1つ以上のAssetからaudiovisual構造を組み立てることができる。

Topics

⚠️必要そうなものだけをピックアップしました。

生成方法

init(url: URL)

ローディングデータ

func cancelLoading()

全てのロードを中断する。

Assetについて

var duration: CMTime

assetの再生時間(CMTimeについて)

AVPlayerItem

Overview

ざっくり言うとAVAssetにロードされたかどうかのStatus情報が付属されたもの。

これを渡してAVPlayerに再生してもらう。

init(url: URL)
init(asset: AVAsset)

上記のイニシャライザに加えて、事前にロードを開始する

init(asset: AVAsset, automaticallyLoadedAssetKeys: [String]?)

がある。

Topics

生成方法

init(url: URL)
init(asset: AVAsset)
init(asset: AVAsset, automaticallyLoadedAssetKeys: [String]?)

その他

var status: AVPlayerItem.Status

.readyToPlay, .unknown, .failedがある。

var loadedTimeRanges: [NSValue]

ロード済みの部分のタイムレンジ

var seekableTimeRanges: [NSValue]

シーク可能な部分のタイムレンジ

func seek(to: CMTime, completionHandler: ((Bool) -> Void)

シーク

func currentTime() -> CMTime

再生されている現在の時間

var presentationSize: CGSize

videoの場合にplayerに提示される表示サイズの比率

AVPlayer

Overview

media assetの再生、タイミング調整を管理するコントローラオブジェクト。

1度に1media assetしか再生できないため、Playerを再利用するならreplaceCurrentItem(with:)メソッドを使う。(AVQueuePlayerというものもあるが、便利ではないので使わない...)

AVPlayerは連続的に状態が変化する動的オブジェクトである。Playerの状態を監視するには二つの方法がある。

一般的な方法

Key-value observing(KVO)を用いて、再生中のアイテムや再生速度などを監視できる。

その際にはobserveValue(forKeyPath:of:change:context:)メソッドを用いる。

時間の変化による監視方法

ある一定時間が経過した時に特定の処理を実行するようにできる。

  • addPeriodicTimeObserver(forInterval:queue:using:)

特定の時間間隔ごとに処理を実行する。

  • addBoundaryTimeObserver(forTimes:queue:using:)

特定の時間が経過した時に処理を実行する。

Viewへの反映方法

AVPlayerは非視覚的オブジェクトであり、スクリーンに動画を反映することはできない。

スクリーンに反映する方法は主に二つある。

AVKitを用いた方法

AVKitにあるAVPlayerViewControllerを用いて表示を行う。(AVPlayerViewはmacOSのみ)

AVPlayerLayerを用いた方法

AVPlayerLayerを用いて表示を行う。

AVPlayerViewControllerのようにコントローラのViewは無いので、自前で設定する必要がある。

Topics

生成方法

init(url: URL)
init(playerItem: AVPlayerItem?)

再生管理

func play()
func pause()

言わずもがな。

var rate: Float

再生速度。(rate = 0⇔pause())

var actionAtItemEnd: AVPlayer.ActionAtItemEnd

再生が終わった時にどうするか。.advance, .pause, .noneがある。

func replaceCurrentItem(with: AVPlayerItem?)

再生するAVPlayerItemを変更する。

var preventsDisplaySleepDuringVideoPlayback: Bool

動画再生中に画面スリープするかどうか。

時間

func seek(to: CMTime)
func seek(to: CMTime, completionHandler: ((Bool) -> Void)

シーク

func currentTime() -> CMTime

再生されている現在の時間

func addPeriodicTimeObserver(forInterval: CMTime, queue: DispatchQueue?, using: (CMTime) -> Void) -> Any
func addBoundaryTimeObserver(forTimes: [NSValue], queue: DispatchQueue?, using: () -> Void) -> Any

監視する系

func removeTimeObserver(Any)

監視を外す

参考

developer.apple.com

nackpan.net

Custom View + RxSwift - iOS

Custom Viewを作るときに、どうやってRxと連携させるかを忘れないように記事にしました。

※ここでは、customView.rx.textDidChanged.~といったように、今までDelegateとして実装していたものをReactiveのExtensionとして記述することをRx連携と呼ばせていただきます。

どうしてRx連携するの?

  • プロダクトがRxをゴリゴリ使っているから
  • コードが綺麗になる

プロダクトがRxSwiftをゴリゴリ使っている

自分が開発しているアプリではアーキテクチャがMVVMで、ViewとViewModelとのつなぎにRxSwift(RxCocoa)を使っています。

そのため、RxSwiftで用意されていない場合は自分で連携させる必要があります。

コードが綺麗になる

コードの量が減るかどうかはプロダクトによりますが、Rxを使うと圧倒的に簡潔に分かり易くかけると思います。

どれくらい綺麗になるか、サンプルコードを書いてみたので比較してみてください。

Rx導入前

 Rx導入後

一番メリットとして感じるのは、今まで分離して関数を用意していたのがclosureとして記述できることだと思います。

結果的にUIViewControllerから煩わしいDelegateなどのコードが減るので、見通しもよくなります。

どうやって実装するの?

自作Viewを作るときに作成したDelegateを使って、DelegateProxy(Proxy: 代理)を作成し、それを使ってReactiveのExtensionとしてDelegateの中身を実装します。

今回は、内部にUITextFieldを持っているFilledTextFieldという自作Viewを使って、Rx連携する手順を紹介します。

コードはGithubのgist上げているので参考にしてください。

CustomView + RxSwift · GitHub

例として自作ViewのDelegateの内の二つの関数をRx連携させてみます。

DelegateProxyの作成

DelegateProxy<P, D>とDelegateProxyType, 自作ViewのDelegateを継承したクラス、DelegateProxyを作成します。

DelegateProxy<P, D>のPは自作View(FilledTextField)、Dは自作ViewのDelegate(FilledTextFieldDelegate)です。

ここでは自作ViewとProxyとがお互いのDelegateをつなげる作業を行います。

Delgateが複数ある場合は変更が必要ですが、そういったケースは稀なので今回は割愛します。

Extensionの実装

ReactiveのExtensionとして実装していきます。

DelegateProxyのmethodInvoked(_:)を使って、Observableに変換します。

paramsはDelegateの引数を指します。

完成!!

終わりに

一番FatになりがちなUIViewControllerのコードを綺麗に書くことができ、コードの可読性も向上できました。

RxSwiftを使いこなすことは難しいですが、上手く使えるようになればコードを綺麗に書けるようになると思います。

使い倒してどんどんコードを綺麗にしていきましょう!

SwiftyXMLParser + Moya + RxSwift 使ってみた

https://s3-ap-northeast-1.amazonaws.com/hatena.masegi/SwiftyXMLParser/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88+2018-11-19+3.44.29.png

iOSアプリを開発中にXMLを返すAPIを使うことになったので、XML形式のものをどうマッピングするかを忘れないように記事にしました。

なんでSwiftyXMLParserを使うの?

iOSでは標準でXMLParserを用意しています。

ではなぜXMLParserを使わないかというと、

  • 使い勝手が悪い
  • Rxと組み合わせて使うとなると色々大変

だからです。

サードパーティのXMLParser Libraryは何個かありますが、今回は

  • Moya (Alamoifre) と一緒に使う
  • Rxをゴリゴリ使っているプロジェクト

という状況だったのと、Star数を鑑みてSwiftyXMLParserを選択しました。

github.com

SwiftyXMLParserの使い方

導入の仕方、それぞれの要素へのアクセスの仕方については、READMEを参考にしてください。

ここでは、READMEに書いていない実際に使うときにはどうするかを書いていきます。

Modelへのマッピング

例としてradikoの番組を取得するAPIを使ってみます。(最近ではXML形式のレスポンスを返すAPI少なくて、探すの大変でした....)

以下のXML形式データをModelへマッピングします。

<station id="QRR">
  <name>文化放送</name>
  <progs>
    <date>20181119</date>
    <prog id="9463445504" master_id="" ft="20181119010000" to="20181119013000" ftl="2500" tol="2530" dur="1800">
      <title>加隈亜衣のアズールレーディオ</title>
      <url>http://www.joqr.co.jp/azur/</url>
      <pfm>加隈亜衣</pfm>
      <img>
        https://radiko.jp/res/program/DEFAULT_IMAGE/QRR/cl_20180328180436_859305.jpg
      </img>
      <metas>
        <meta name="twitter" value="#アズラジ"/>
        <meta name="twitter" value="from:azuradio_joqr"/>
      </metas>
    </prog>
    <prog id="9463445505" master_id="" ft="20181119013000" to="20181119020000" ftl="2530" tol="2600" dur="1800">
      <title>スクエニ第10BD開発局</title>
      <url/>
      <pfm>日高里菜 大久保瑠美</pfm>
      <img>
        https://radiko.jp/res/program/DEFAULT_IMAGE/QRR/20160927220522.png
      </img>
      <metas>
        <meta name="twitter" value="#joqr"/>
        <meta name="twitter" value="from:joqrpr"/>
      </metas>
    </prog>
  </progs>
</station>

XMLParsableプロトコルを作成する

SwiftyXMLParserのオブジェクトであるXML.Accessorからイニシャライズ可能を示す、XMLParsableを作成します。

XMLParsableを実装する

各ModelにXMLParsable#init(accessor: XML.Accessor)を実装します。

ネストしたオブジェクト・配列・attributeへのアクセスなど、引っかかりそうなものは拾い上げたので、

マッピングで苦戦している方は比較して真似してみてください。

Moya + RxSwift

Moyaのセットアップ

Moyaのセットアップについては、こちらを参考にしてください。

Serviceの作成

RxSwiftのSingleを返す関数を用意します。map()を使うことで、綺麗に書くことができます。

完成!

終わりに

XMLを返すapiは絶滅危惧種ですが、使わざるを得ない際は参考にしてみてください。

雑にReactNative触ってみた感想

Native開発しかしたことがない人がReactNativeに触ってみた感想です。

話すこと

AndroidとiOSといったNative開発しかしていない人が、ReactNativeを触ってみた感想、いわゆる First Impression をAndroid, iOSそれぞれと比較しながら雑に話します。

しっかりしたものが読みたい方は、きりみんさんの記事を参考にしてください。

medium.com

ちなみに作ったものはGithubに上げているので、見ていただけるとありがたいです。

github.com

Screen

Deck Swipe Chat

良いと思った点

画面とその他のViewとの違いがなく楽

AndroidだとActivity, Fragment、iOSだとUIViewControllerといった様な、画面全体とその他のViewが区切られておらず、

画面全体もその他のViewもComponentという単位で考えるため学ぶことが少なくて楽。

とりあえず、render()にレイアウトをぶち込めば描画してくれる。

ListViewをReact Nativeで作ってみるとView関連は以下の様になる。

簡単。onCreateViewHolder()でViewをinflateしたりだとか、func tableView(_: ,cellForRowAt:)とかでtableView.dequeue()とかしなくて良い。

XMLに似たレイアウトを記述したものをreturnするだけ。

また、view.setOnClickListenerとかaddTarget(), addGestureRecognizer()とかしないで良い。

クリックイベントを発生させたいものを<TouchableOpacity></>で囲んで関数を渡すだけ。

学習コストが低い、簡単に作れる

上で述べたものとかぶるが、学習コストがかなり低い。

公式のチュートリアルを進めていくだけで、なんとなく実装できる。

facebook.github.io

今回作ったものは、ライブラリのExampleをコピペしたものを組み合わせただけで大した事はしていないが、それでも1日とそこらでこれくらいのものを作れるのはすごいと思った。

JavaScript未経験、React Native未経験でReact Nativeのタスクを振られたときは厳しいと思ったが、

割とすぐに画面ができて楽しかった。

ビルドが早い

Web系の特徴でもあるが、ビルドがとにかく早い。

Android, iOSだとビルド時間が1分以上かかることがざらにあるが、React Nativeだと10秒待つ方が少ないくらい。

ノンストレスで開発ができるのはとても良いことだと思う。

良くないなと思った点

UIパーツの実装方法が多すぎる

Viewのライブラリが豊富で嬉しいと思う反面、ListView使いたいんだけど...と調べると結構な種類が出てくる。

「どれが今使うのに適しているか」と考えて様々なライブラリを巡って、実装の仕方を調べるのは少し億劫になる。

遷移がやや面倒

遷移について考えると、

AndroidだとIntentを使う。iOSだとNavigationControllerのpushやpresentを使う。

となるが、React Nativeだとそうはいかない。

遷移にも様々なライブラリがあり、実装の仕方も人によりけりな所がある。

今回はList画面からChat画面に遷移するときに、Tabを無くしたかったのだが、その遷移を実装するのに手間取った。

結果、新しい画面を生やす(iOSでいうpresent)のではなく、スタックした画面を作って(iOSでいうpush)、遷移の際にTabを非表示とする形になった。

それぞれのOSでの表示崩れ

UIのライブラリを使ったときに、『iOSでは上手く表示されているのにAndroidでは上手く表示されない』ということがあった。

また、Android Oreoでは表示崩れを起こしているのに、Android Pieでは綺麗に表示されたりもした。

これはクロスプラットフォームで開発する際には必ず起こる問題だと思う。

綺麗に映すにはNativeでの開発が結局良いと思われる。

終わりに

軽いものや、今後長く使わない、アプリが大きくなる予定がないものであれば、React Nativeはとても良い選択だと思う。

しかし、デザインがしっかりと決まっていてAndroid, iOSそれぞれちゃんと描画しないといけないとなると、まだまだ厳しいと思われる。

やはり、用途に合わせてNative, React Nativeと切り替えていくのが良いと感じた。