言語の話だけじゃない!アニメーションの重要性/try! Swift参加レポート Day2
大島 雅人
こんにちは。大島です。2016年3月2日〜3月4日の間で行われていたtry! SwiftというSwiftのカンファレンスに参加してきました。私は、スタディサプリ ENGLISHのサーバーサイド兼iOSを担当しておりアプリはSwiftを使用して書いています。プロダクトのリリース当時はSwift1.2でしたが、今ではSwift3.0まで進化しているSwiftの勢いを感じるカンファレンスでした。
今回は、try! Swift参加レポート Day1に引き続き、2日目の様子とセッションの中から個人的に特に印象に残ったプロトタイピングの魔法とProtocol-Oriented Programming in Networkingのセッションをご紹介したいと思います。
プロトタイピングの魔法
FacebookのiOSエンジニアであり、かの有名なアニメーションのライブラリfacebook/popのメンテナンスなどをされているAdam Bellさんの発表です。プロトタイピングということでデザイン系のツールを使ったプロトタイピングかと思っていましたが、アニメーションに関するプロトタイピングの話でとても興味深かったです。
スキューモーフィズムとインタラクションは違う
セッションは初代iPadのTV CMの紹介から始まりました。iBooksで本棚が現れるアニメーションやメールを選択する際の様子など、アニメーションやインタラクションが常に変わっていて魔法のようだったと語っていました。まさに、十分に発展した技術というのは魔法と見分けがつかないということそのものだと言っていました。
iOS7になってフラットデザインが採用され現在もその流れは続いています。確かに今iOS6以前のデザインを見ると少し古臭い感じがしてしまいますが、iOS7になったとき簡素化されたアニメーションで魔法がなくなってしまったとAdamさんは言っていました。
iOS6まで採用されていた現実世界の物に似せたデザインのことをスキューモーフィズムといいます。iBooksの本棚のような見た目、メモ帳の紙のような質感に似せていた頃のデザインを覚えているかたはいるでしょうか。そんな中、Adamさんがスライドで示していたこの言葉が特に印象的でした。
Skeuomorphism ≠ Interaction
そのスキューモーフィズムとアニメーションやインタラクションは違う、アニメーションは前後のコンテキストを埋めてくれるとても重要なものだと言っていました。一方でアニメーションを有用なものにするには、無駄なアニメーションはいらない、ただ単に注目させるだけではない、速さが重要ともおっしゃっていました。
アニメーションから逃げずに挑戦する
では、なぜアニメーションを実装することをやらなくなってしまうのか。それは、以下の3つだとAdamさんは語っていました。
- 意欲がない
- 技術力がない
- 惰性
最近はSwiftの言語的な機能に注目が集まっていてとてもいいことなのですが、iOSアプリエンジニアとしてはアニメーションやインタラクションにこだわることが腕の見せ所だと思います。iOSエンジニアとしてやっていく以上、難しいViewやアニメーションの要求がきたときに出来ないと言わないように腕を磨いていきたいと思いました。
スタディサプリ ENGLISHでの例
余談になりますが、スタディサプリ ENGLISHもアニメーションにこだわって開発しています。英語の会話文を聞いてクイズに答えるトレーニングがあるのですが、その際の会話文が始まる前のところでアニメーションを入れてユーザーが会話文に集中できるようにアニメーションを使用しています。何気ないポイントですが、このアニメーションがないと唐突に会話が始まり違和感があったためこのような方法を取り入れました。
Playgroundで行うアニメーションのプロトタイピング
ここからが本セッションのタイトルとなっていたプロトタイピングの話です。プロトタイピングツールは様々なものが出ていますが、XcodeのPlaygroundはとてもプロトタイピングに適しているとのことでした。Playgroundは他の言語でいうとREPLのようなものですが、それをさらに豪華にしたようなものです。
Xcode7.3 beta3からPlayground上でもタッチ操作が可能になったとのことで、アニメーションのプロトタイピングにも使いやすくなりました。さらに、より直感的にPlayground上でのジェスチャーを実装できるb3ll/SwiftyGestureRecognitionというライブラリも紹介していました。以下のようなコードでとても直感的にUIGestureRecognizer
を扱うことができます。
let gestureRecognizer = UIPanGestureRecognizer(view: aView)
.didBegin { (gestureRecognizer) in
print("began")
}.didChange { (gestureRecognizer) in
print("changed")
}.didEnd { (gestureRecognizer) in
print("ended")
}
本セッションでは、ポケモンのモンスターボールを選んでモンスターが出てくるサンプルアプリを使ってライブコーディングでアニメーションのデモを行っていました。Xcode7.3以上でないと試すことができませんが、実際のサンプルコードもアップされています。
facebook/popのアニメーションを使い、何もアニメーションがないところからモンスターボールを投げるとくるくると回りパカッと割れるところまでのアニメーションをつけるデモを行っていました。Playground上でタッチ操作もできるようになったので、トライアンドエラーを繰り返しながら素早くアニメーションを実装することができるので、今後プロトタイピングする際には積極的に使っていこうと思いました。
Protocol-Oriented Programming in Networking
APIKitなど有名なライブラリを開発しているメルカリのiOSエンジニア、石川さんのセッションでした。RxSwiftについてキャッチアップできていなかったので個人的に楽しみなセッションでとても興味深く聞くことができました。
NSURLSessionをprotocolを使ってラップする
iOSのクライアントからAPI呼び出しを行う際に、呼び出し側のコードをよりシンプルに安全に書くための方法を説明してくれています。SwiftではProtocolにtypealiasで型を指定することができます。これによって、あるリクエストのレスポンスの型が必ず一意に決まるということが表現できます。
下記のようにリクエストのprotocolを実装する際に、Responseの型を指定することができます。
struct SearchRepositoriesRequest: GitHubRequestType {
let query: String
typealias Response = PaginationResponse<Repository>
var method: HTTPMethod { return .GET }
var path: String { return "/search/repositories" }
var parameters: AnyObject { return ["q": query]
}
スタディサプリ ENGLISHでも、同じような方法をとっています。NSURLSession
直接ではなくAlamofire
を利用していますが、リクエストをprotocol
で定義し、typealias
でレスポンスの型が決まるようにしています。
プロジェクトが始まった当初はAPIのエンドポイントを定義したEnumに各リクエストを書いていましたが、下記のようにリクエストごとにprotocol
に準拠したstructを
作るようにしたことでレポジトリの実装がとてもシンプルになりました。
struct CoursesRequest: Requestable {
private(set) var url: String
typealias Response = [Course]
init(url: String){
self.url = url
}
var method: APIClient.Method {
return .GET
}
var authenticationType: APIClient.AuthType {
return .JWT
}
var parameters: [String : AnyObject]? {
return nil
}
var encoding: APIClient.ParameterEncoding {
return .URL
}
func responseFrom(json json: JSON?) throws -> Response {
guard let json = json where !json.isEmpty else { throw InfraError.JsonParseError }
return json["courses"].arrayValue.map { (subJson: JSON) -> Course in
Course(json: subJson)
}
}
}
RxSwiftを利用したライブコーディング
今までのAPI呼び出しの話をもとに、RxSwiftを使った実例をライブコーディングで説明してくれました。とてもわかりやすいデモで、RxSwiftの使い方などを調べられていなかったので雰囲気をおおまかに掴むことができました。Streamに対してfilterやmapなどを使って処理を実装していくものなんだろうなということは分かっていたんですが、UIScrollViewのイベントなどをどのようにStreamにしていくのかがコードで見て理解できました。
override func viewDidLoad() {
super.viewDidLoad()
rx_sentMessage("viewWillAppear:")
.map { _ in () }
.bindTo(viewModel.refreshTrigger)
.addDisposableTo(disposeBag)
tableView.rx_reachedBottom
.bindTo(viewModel.loadNextPageTrigger)
.addDisposableTo(disposeBag)
viewModel.loading.asDriver()
.drive(indicatorView.rx_animating)
.addDisposableTo(disposeBag)
viewModel.elements.asDriver()
.drive(tableView.rx_itemsWithCellIdentifier("Cell")) { _, repository, cell in
cell.textLabel?.text = repository.fullName
cell.detailTextLabel?.text = "🌟\(repository.stargazersCount)"
}
.addDisposableTo(disposeBag)
}
上記のサンプルのtableView.rx_reachedBottomという部分を見て、RxSwiftがUIScrollViewのイベントをストリームにしてくれているということを知ることができました。この部分を開発者が実装するものだと思い込んでいて利用するのは大変なのではと思っていましたが、こういった部分をライブラリ側でやっていてくれてるのであれば使いやすいのではという印象を持ちました。
tableView.rx_reachedBottom
はライブラリで提供されているものではなく、石川さんが作成したエクステンションでした。該当部分のコードはこちらにアップされています。どのように作成すればよいかイメージをつかむことができました。
extension UIScrollView {
var rx_reachedBottom: Observable<Void> {
return rx_contentOffset
.flatMap { [weak self] contentOffset -> Observable<Void> in
guard let scrollView = self else {
return Observable.empty()
}
let visibleHeight = scrollView.frame.height - scrollView.contentInset.top - scrollView.contentInset.bottom
let y = contentOffset.y + scrollView.contentInset.top
let threshold = max(0.0, scrollView.contentSize.height - visibleHeight)
return y > threshold ? Observable.just() : Observable.empty()
}
}
}
まとめ
他にもBoundariesの概念を初めて知ったり、 objc.ioの作者の方のUITableViewの話が聞けたりと大満足の1日でした。
最近はもっぱらScalaを書いていますが、3日間カンファレンスに参加しiOS開発はやっぱり楽しいと再認識しました。また、海外のiOSエンジニアのセッションを聞いたり、会話をする中で英語の勉強も必要だなと感じられるとてもいいカンファレンスでした。
try!Swiftの参加者のSlackで、なぜかParty Parrotというのが流行っていておもしろかったです(笑)