RareJob Tech Blog

レアジョブのエンジニア・デザイナーによる技術ブログです

iPadのレッスンルームに端末回転をサポートした話しとユニバーサル対応のお話し

開発本部APP・UXチームの玉置(@tamappe)です。 今回の投稿が3回目になります。お盆は夏季休暇を利用して地元に帰省していました。
帰省中は暑いながらもどんな記事を書こうかなと思いながら時間が無下に過ぎ去ってしまい直近になって焦りながら記事を書いております。

今回は最近リリースしましたレッスンルームのiPad横向き対応について技術的な話しをしたいと思います。
最初は端末回転だけをサポートすればいいのかと思っていましたが色々ハマったポイントがありましたので後述します。

レッスンルームとは

そもそもレアジョブアプリを使ったことがない方向けにレッスンルームについてご紹介したいと思います。 レッスンルームとはレアジョブが提供している講師とのレッスンの場です。

f:id:qed805:20190821173322p:plain
lesson_room_ipad_portrait

(画像はiPadでの縦向きでのレッスンルームの様子です)

それぞれのUIを細かく分類すると下記のようになります。

f:id:qed805:20190821172506p:plain
lesson_room_ipad_portrait_description

動画の部分は本来はフィリピン講師が映る形になりますが開発中はレッスンルームを出入りできますしカメラで自分の顔が見れていますので 顔部分だけ加工してみました。

今回はこのレッスンルームに端末回転を許可する形で横向きのレイアウトを開発することがタスクでした。 注意したいことはこの端末回転はiPadだけですのでiPhoneではそもそも横向きはサポートしていません。

f:id:qed805:20190821172557p:plain
lesson_room_ipad_landscape

(iPadを横向きにして入室した場合の画面)

f:id:qed805:20190821172732p:plain
lesson_room_ipad_landscape_open

(教材を開いている時の画面)

こちらが今回のiPad横画面対応しました横画面のレイアウトになります。

f:id:qed805:20190821172830p:plain
lesson_room_landscape_description

このように分類分けするレイアウトにしました。
レアジョブアプリのデザインは基本的にstoryboardを使ってデザインを開発しています。
そのため、storyboardにautolayoutを貼って端末間のサイズを調整しています。

本来のやり方であればiPad横画面用のstoryboard ファイルを作成して横画面のレイアウトを作るのが最短だと思います。 ですがそのstoryboardを確認すると1つのstoryboardにUINavigationControllerや複数のContainerViewが存在していましたので レッスンルームのControllerだけを切り分けして横画面用のstoryboardを作るのは難しそうだと判断しました。

そこで今回採用したのがSizeClassという技術です。 これを使って1 つのstoryboard内で完結する様に調整したのです。

SizeClassとは

SizeClassとはAppleが提供しているデバイスのサイズを判別するための新しいクラスでiOS 8から導入されました。 iPhone 4 / iPhone 4S までは 端末のサイズが 320 x 480 だったのが
iPhone 5 の登場で初めて320 x 480 以外のサイズで iOS端末が出てきたのがきっかけです。iPhone 5 は 320 x 568 で高さが高くなっています。
さらに翌年には iPhone 6 の登場で高さだけでなく横幅も320 以外のサイズが登場してしまいiOSアプリエンジニアは端末対応に追われていた当時が懐かしく感じます。
当時のAppleはこのようなサイズ違いの端末の対応として AutoLayout という技術を使って解決するように促していましたが、 今度はタブレット端末として登場したiPadでとうとうAutoLayoutでは対応しきれない状況になりました。
iPhoneiPad で別々でstoryboardを管理したりコードでView を切り分けたりはできますが同じViewでそれぞれ2つのクラスを用意するのは非常に大変です。
さらに端末回転もサポートすると縦横比が変わってレイアウトが崩れてしまいますね

そこで登場したのがSize Class というクラスです。

AutoLayoutやSize Class の使い分けは Appleの Human Interface Guidelines にある Adaptivity and Layout という記事が参考になります。

developer.apple.com

非常に簡略化してこれを説明すると

iOS端末のサイズを大きく2つの概念に分けました。

  • Regular (denotes expansive space)
  • Compact (denotes constrained space)

このRegular と Compactをそれぞれ幅と高さに当てはめると

  • Regular width, Regular height
  • Compact width, Compact height
  • Regular width, Compact height
  • Compact width, Regular height

のように4通りに切り分けられます。この4通りは上記の参考記事にも記載されています。
この概念をiPhoneiPadにそれぞれ適用させると iPadなどのタブレットはPortrait(縦向き)やLandscape(横向き)に関わらず Regular width, Regular height に、iPhone端末は一部例外がありますがPortrait(縦向き) のときは Compact width, Regular height, Landscape(横向き) のときは Regular width, Compact height になります。

このように分類分けするときの技術が Size Class になります。

Size Class の使い方について

これをレッスンルームのstoryboardに当てはめて iPad の横画面の場合にのみ レイアウトを大幅に変える対応を行います。

記事の冒頭でお見せしましたレッスンルームのstoryboardは次の画像のようになります。

f:id:qed805:20190814135735p:plain
lesson_room_storyboard

基準がiPhone 8 Plus となっていてこれをベースにタブレット端末で横画面のときにのみレイアウトを切り替える仕様になります。

storyboardのベースの切り替え方は次のようにします。

f:id:qed805:20190814135829p:plain
lesson_room_storyboard_2

iPadを選択します。そうすると次のようにstoryboardのviewのサイズがiPad Pro のサイズに切り替わります。

f:id:qed805:20190814135928p:plain
lesson_room_storyboard_3

このようになります。

また端末の縦向き・横向きの切り替えはOrientation を切り替えることでPortrait やLandscape に切り替えられます。
これがSizeClass を適用させる上での前提知識になります。

次にstoryboardを使っている場合はAutoLayoutでレイアウト間のマージンをそれぞれ設定していると思います。 SizeClass の場合はこれが肝となります。 AutoLaytoutのNSLayoutConstraint をそれぞれのViewにRegular Size(以下R)のときの値 , Compact Size(以下C)の時の値をそれぞれ設定することができるのです。

私の場合、当てはめ方としては

  1. Rは iPad の幅と高さのすべて
  2. Cは iPhoneの幅と高さ
  3. iPhone 6 以上の高さが 縦向き のときだけR
  4. Plus 端末の幅が横向きのときだけR ( iPhone XS MaxiPhone XR の幅は横向きのときにR)

のルールで覚えています。これでも例外として() の部分が漏れてしまいます。

今回の仕様はiPadの場合でかつ横向きだったときにレイアウトを変更すればよいので例外ルールは考える必要はありません。
1のルールに従ってiPadは基本RなのでR指定でConstraint を指定すればよいと考えます。
つまりはstoryboard 上で 通常時の constraint と R 用の constraint をそれぞれ設定します。

R用のconstraint の設定の方法を紹介します。

今回は参考として 画像の灰色のView(チャット部分のView)のbottomに Regular の場合は20 px マージンを取るように変更してみます。

f:id:qed805:20190814160752p:plain
lesson_room_storyboard_4

灰色のViewが選択された状態で新しく bottom のconstraint を指定します。 20pxとします。

f:id:qed805:20190814160928p:plain
lesson_room_landscape_5

20pxのbottom のconstraint を作るとエラーが発生しますがエラーを無視します。

f:id:qed805:20190814161044p:plain
lesson_room_constraint

f:id:qed805:20190814161140p:plain
bottom_constraint

作成した20px のbottom のconstraint をダブルクリックして編集画面を表示させます。

f:id:qed805:20190814161231p:plain
bottom_constraint_2

このように表示されたあとは一番下に「+ installed」の項目があります。
この左側の「+」をタップすると

f:id:qed805:20190814161355p:plain
bottom_constraint_3

CompactとRegularを選べますのでwidth / height ともに Regular を選択して「Add Variation」をクリックして
新しい「wR hR installed」が作成されます。

これが width Regular かつ height Regular だった場合の contraint です。
ただ2つともinstalled のチェックボックスが入っているときの挙動だと不安定になりますので
「wR hR installed」の部分だけチェックボックスが入るように1つ目のチェックボックスを外します。

f:id:qed805:20190814161834p:plain
bottom_constraint_4

これで Regular width と Regular height の場合のみこのconstraint が使われるようになります。
ですがこれだけだともともとのbottomの 0px マージンのconstraint がすべての場合に適用されますのでエラーは消えません。
そこでもともとのbottomの 0px マージンのconstraint の方を修正します。

bottomの 0px マージンのconstraint をダブルクリックしてconstraint の編集画面を開きます。

f:id:qed805:20190814162149p:plain
bottom_constraint_5

このcontraint に 「wR hR installed」のvariation を追加します。追加した「wR hR installed」のチェックボックスを外します。

f:id:qed805:20190814162312p:plain
bottom_constraint_6

この設定をすればさきほどのエラーが解消されたことが分かります。
これでiPad でRegular size だったときの constraint の追加が完成します。

ただSizeClass においてはiPadはPortrait とLandscape の両方で R ですので端末が縦向きであったときと横向きだったときのハンドリングができません。
そのためここで黒魔術を使って iPadで横向きのときには 高さを C に変換する必要がありました。

特定条件で 端末のSizeを Compact や Regular に変換する

前回のSizeClass の登場とともに端末回転で重要なUITraitCollection というクラスが登場しました。
端末の向きに関するクラスUIInterfaceOrientationが非推奨になり、UITraitCollectionを使って実装しなければなりません。
結論からいうとUITraitCollectionのoverrideTraitCollection(forChildViewController:)を使うことで端末の Regular size と Compact size を入れ替えることができます。
こちらのメソッドはUIViewControllerに存在します。
レッスンルームの画面はUINavigationControllerが持ちますのでNavigationController を継承したカスタムクラスを作成して これをUINavigationController に使います。

class SampleNavigationController: UINavigationController { // UIViewController であればこちらを継承させる

    /// SizeClassを意図的に上書きすることができるメソッド
    override func overrideTraitCollection(forChild childViewController: UIViewController) -> UITraitCollection? {
        let any = UITraitCollection(verticalSizeClass: .unspecified)
        let compact = UITraitCollection(verticalSizeClass: .compact)
        
        // landscape (ここでiPad 端末かつ横向きの場合の変換処理を返す)
        if [iPad端末のフラグ] && self.view.frame.width > self.view.frame.height {
            return UITraitCollection(traitsFrom: [any, compact])
        }
        
        // portrait
        return UITraitCollection(traitsFrom: [any, any])
    }
}

このようにコードを書いたクラスをプロジェクトファイルに追加してstoryboard のUINavigationController に継承させます。
これによって iPad で横画面になったときに検知して 高さが Regular から Compact に切り替わります。

f:id:qed805:20190814170037p:plain
iPad_convert

最後になりますが、今回紹介したSize Class で上のレクチャーは「wR hR installed」でしたがプロダクトコードを確認したら 「hC installed」とちゃんとheight がcompact のときのconstraint を設定していました。
「wR hR installed」ではなくて 「hC installed」になります。

これでiPad でかつ 横向き用のConstraint が機能して横向き用のレイアウトが表示されるようになります。
この技術を使って1つのstoryboard でiPhoneiPad それぞれ別々のconstraint を設定して見栄えの異なるデザインを設計できました。

このようにうまくSize Class を利用すると特定のstoryboard にだけレイアウトの違うデザインを実現させることができます。
ぜひSize class を使ってみてください。

最後に勉強会イベントの話しを

余談としてイベントの告知をします。 来週28日(水)にメドピア社と共同で「IT x 社会貢献」をテーマに勉強会を開催します。

medpeer.connpass.com

レアジョブとしては「学習体験の向上」を軸に、アプリ開発、データ活用をメインコンテンツとしてお話します。
私はアプリ開発者として「レアジョブアプリでのアクセス負荷で生じたAPI遅延問題をアプリエンジニア視点で対策を考えてみる」をテーマにして登壇します。
近年のモバイルアプリの開発においてもはやサーバーサイドとの連携は切っても切れない役割を果たしています。 今回のイベントではそのAPIを考慮したWebRTCとの連携開発で起こり得たツラミを知見としてまとめて発表する予定です。

懇親会もあるので楽しめるイベントになると思います。 ご興味のある方はぜひこのイベントに参加していただければいいなと思います。