言語の話だけじゃない!アニメーションの重要性/try! Swift参加レポート Day2

大島 雅人
102

こんにちは。大島です。2016年3月2日〜3月4日の間で行われていたtry! SwiftというSwiftのカンファレンスに参加してきました。私は、スタディサプリ ENGLISHのサーバーサイド兼iOSを担当しておりアプリはSwiftを使用して書いています。プロダクトのリリース当時はSwift1.2でしたが、今ではSwift3.0まで進化しているSwiftの勢いを感じるカンファレンスでした。

slack_for_ios_upload_1024

今回は、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もアニメーションにこだわって開発しています。英語の会話文を聞いてクイズに答えるトレーニングがあるのですが、その際の会話文が始まる前のところでアニメーションを入れてユーザーが会話文に集中できるようにアニメーションを使用しています。何気ないポイントですが、このアニメーションがないと唐突に会話が始まり違和感があったためこのような方法を取り入れました。

a

Playgroundで行うアニメーションのプロトタイピング

ここからが本セッションのタイトルとなっていたプロトタイピングの話です。プロトタイピングツールは様々なものが出ていますが、XcodeのPlaygroundはとてもプロトタイピングに適しているとのことでした。Playgroundは他の言語でいうとREPLのようなものですが、それをさらに豪華にしたようなものです。

NewtonsCradle_2x

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というのが流行っていておもしろかったです(笑)

middleparrot