41
39

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

SizeClassesを上書きしてiPadでも縦横異なるレイアウトを作る

Posted at

はじめに

受託案件を受けていますと、iPadのレイアウトについて縦横異なるデザインにしたいという要望をよくお聞きします。
私も個人的には賛成です。せっかくiPadの大きさで自由にレイアウトできるなら縦横異なるレイアウトにしてもいいと思っています。縦と横でアプリの使われるUXも変わりますし、それに合わせてレイアウトするのはありと思っています。

さて、iOS8からSizeClassという概念がXcodeに追加されました。
これはiOS6から追加されたレイアウト機構Auto Layoutを補完するもので、SizeClassを使うことでiPhone、iPadを1つのStoryboadでレイアウトできるようになる仕組みです。

以前まではiPhone用、iPad用でStoryboadを分けて作っていたのでそれが1つにまとまることは有意義なことでした。

しかしこのSizeClass、なんとiPadのレイアウトは縦横同じものしか設定できないようになっています。

このままお客様に「縦横レイアウト?iPadじゃ無理っす!」で流してもいいでしょう。
でもiOSエンジニアとして、なんとかしたい。そう思います。

なんとかなりました。それを解説したいと思います。

実行環境

この記事でお話するコードは以下の環境で動作しています。

  • Xcode8.1
  • Swift3.0.1
  • アプリターゲットiOS10

SizeClassのおさらい

SizeClassとは

iPhone,iPadの縦横サイズを抽象的なサイズ「Compact」「Regular」「Any(Compact,Regularどちらでも)」にわけて、それに対してAuto Layoutを指定できる仕組みです。

iPhone, iPad各サイズ対応表

縦(Portrait)の場合は

Device Portrait Width Portrait Height
iPhone4s/5/5s/6/6s/SE/7 Compact Regular
iPhone6Plus/6sPlus/7Plus Compact Regular
iPad Regular Regular

iPhoneは幅がCompactで高さがRegular、iPadは幅も高さもRegularなのが特徴です。

横(Landscape)の場合は

Device Landscape Width Landscape Height
iPhone4s/5/5s/6/6s/SE/7 Compact Compact
iPhone6Plus/6sPlus/7Plus Regular Compact
iPad Regular Regular

iPhone4s-7は幅がCompacteで高さもCompact,iPhonePlus系は幅がRegularで高さがCompact、iPadは幅も高さもRegularで同じなのが特徴です。

SizeClassを上書きする!

このままではiPadは縦も横も同じRegular、Regularなので、レイアウトを分けることができません。結果としてiPadでは縦横のレイアウトを作ることができなくなっています。

これを打破するメソッドがUIViewControllerにあります。
overrideTraitCollection(forChildViewController:)です。これを実行することでSizeClassを任意に上書きすることができます。
このメソッドはContainer View Controllerに対して、子のViewControllerのSizeClassを上書きすることができます。

完成版

まず完成レイアウトをご覧ください。
iPad Proのシュミレーターでビルドをしていますが、縦と横で異なるレイアウトが表示されているのがわかると思います。

zbg6vGObbi.gif

Stroyboardの設定

プロジェクトをDeviceをiPadまたはUniversalで作成し、デフォルトで作成されているViewControllerに対してContainer Viewを設置します。

fig1.png

Container Viewの制約は親Viewに対して上下左右0ポイントの制約を追加します。
fig2.png

次にContainer ViewのEmbed segue されている子ViewControllerにたいして、縦(Width:Compact, Height:Regular)、横(Witdh:Regular, Height:Regular)のレイアウトを作ります。

縦のレイアウト

fig5.png

Width:Compact,Height:Regularでレイアウトをしていきます。
真ん中にLabelを配置します。テキストを「width:C height:R」にします。
Attributes Inspectorを表示して一番下installed横のプラスボタンから「wC hR」のinstalledをオンにします。これでWidth:Compact,Height:Regularのみに表示されるラベルを作成できます。

fig4.png

ラベルの制約は親ビューに対して縦横中心に整列させます。Center Horizontally in ContainerとCenter Vertically in Containerを設定します。

fig3.png

さらにわかりやすいように下に緑のViewを配置します。Viewの制約は下部に高さ200、VFLで表すと下記のように設定しています。

H:|-view-|
V:[view(==200)]-|

横のレイアウト

fig6.png

Width:Regular, Height:Regularでレイアウトしていきます。
Labelを配置して、縦の場合と同じようにAttributes Inspectorを表示して一番下installed横のプラスボタンから「wR hR」のinstalledをオンにします。
ラベルのテキストは「Width:R Height:R」にします。

制約をWidth:R Height:Rに対して追加します。親ビューに対してLeading Margin、Center Vertically in Containerを追加します。

viewを配置して背景をオレンジに変更して、右側に表示するように幅200で制約を追加します。VFLで表すと下記のように設定しています。

H:|[view(==200]-|
V:|-[view]-|

以上でレイアウトは終了です。

コード

続いてコードです。ViewControllerクラスに対して下記を実装すればiPadで縦横レイアウトが分かれる様になります。

import UIKit

class ViewController: UIViewController {

    //for hack sizeClass
    var isPortrait = false
    var traitCollectionCompactRegular  =  UITraitCollection()
    var traitCollectionAnyAny = UITraitCollection()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.setUpReferenceSizeClasses()
    }
}
extension ViewController {

    //Mark: - sizeClass
    func setUpReferenceSizeClasses() {
        let traitCollectionHcompact = UITraitCollection.init(horizontalSizeClass: UIUserInterfaceSizeClass.compact)
        let traitCollectionVRegular = UITraitCollection.init(verticalSizeClass: UIUserInterfaceSizeClass.regular)
        self.traitCollectionCompactRegular = UITraitCollection.init(traitsFrom: [traitCollectionHcompact, traitCollectionVRegular])
        
        let traitCollectionHAny = UITraitCollection.init(horizontalSizeClass: UIUserInterfaceSizeClass.unspecified)
        let traitCollectionVAny = UITraitCollection.init(verticalSizeClass: UIUserInterfaceSizeClass.unspecified)
        self.traitCollectionAnyAny = UITraitCollection.init(traitsFrom: [traitCollectionHAny, traitCollectionVAny])
        
        self.isPortrait = self.view.frame.height > self.view.frame.width
    }
    
    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)
        self.isPortrait = size.height > size.width
    }
    
    override func overrideTraitCollection(forChildViewController childViewController: UIViewController) -> UITraitCollection? {
        let traitCollectionForOverride = self.isPortrait ? self.traitCollectionCompactRegular : self.traitCollectionAnyAny
        
        return traitCollectionForOverride
        
    }
    
}


解説

何をしているかといいますと、
setUpReferenceSizeClasses()メソッドでプロパティを初期化しています。traitCollectionCompactRegularはUITraitCollectionのインスタンを保持するプロパティですが、水平方向にCompact,垂直方向にRegularのSizeClass情報を保持させます。
traitCollectionAnyAnyは水平方向、垂直方向ともにAnyのSizeClass情報を保持させます。
isPortraitは端末が縦か横かを判別させるプロパティです。

viewWillTransition(to:with:)メソッドはコンテナビューのサイズが変更されたときに実行されるUIViewControllerのメソッドです。


override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)
    self.isPortrait = size.height > size.width
}
    

プロパティisPortraitをデバイスが縦なのか横なのかを判別します。

overrideTraitCollection(forChildViewController:)は子のViewControllerのSizeClassを上書きするメソッドです。レイアウトに変更がかかると呼ばれるようです。(呼ばれるタイミングについてすこし不確かなので知っている方がいればご連絡いただければ幸いです)

override func overrideTraitCollection(forChildViewController childViewController: UIViewController) -> UITraitCollection? {
    let traitCollectionForOverride = self.isPortrait ? self.traitCollectionCompactRegular : self.traitCollectionAnyAny
    
    return traitCollectionForOverride
    
}

isPortraitプロパティによってtraitCollectionCompactRegulartraitCollectionAnyAnyの値を返すようにしています。これをすることによってデバイスが縦の場合はSizeClassをWidth:Compact,Height:Regularに上書きすることができます。

お断り

今回のやり方では、iPadの縦をCompact,RegularにしたことでiPhoneの縦レイアウトと共通になっています。
つまりiPhoneとiPadのレイアウトを同じにしなければいけなくなります。
これを分けたいという場合には今回の記事内容は適しませんのでご了承ください。

おわりに

iPadのレイアウトを縦と横を分けるTipsをご紹介しました。
けっこう使う機会は多い気がします。
参考になりましたら幸いです。

参考サイト

41
39
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
41
39

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?