iPadポートレートモードとランドスケープモードのサイズ設定クラス


97

私は基本的に、xcode 6で導入されたサイジングクラスを使用して、iPadの向き(ポートレートまたはランドスケープ)に応じてサブビューを異なる位置に配置したいと思います。ただし、IB on iPadの個々の横向きまたは縦向きモードをカバーするものはないようです。誰か助けてもらえますか?


デフォルトの画面に必要となる非プログラム的な解決策はないようです
SwiftArchitect '26 / 11/15

回答:


174

iPadの両方の向きを同じように扱うことはAppleの意図のように見えます-しかし、私たちの多くが見つけているように、iPadの縦向きとiPadの横向きのUIレイアウトを変更したいという非常に正当な設計上の理由があります。

残念ながら、現在のOSはこの区別をサポートしていないようです...つまり、アダプティブUIを使用して無料で実現できるはずのことを実現するために、コードまたは同様の回避策で自動レイアウト制約を操作することに戻っています。 。

エレガントなソリューションではありません。

AppleがすでにIBとUIKitに組み込んでいる魔法を利用して、特定の向きに私たちが選択したサイズクラスを使用する方法はありませんか?

問題をより一般的に考えると、「サイズクラス」は、IBに格納されている複数のレイアウトに対処するための単なる方法であり、実行時に必要に応じて呼び出せるようになっていることに気付きました。

実際、「サイズクラス」は実際には列挙値のペアにすぎません。UIInterface.hから:

typedef NS_ENUM(NSInteger, UIUserInterfaceSizeClass) {
    UIUserInterfaceSizeClassUnspecified = 0,
    UIUserInterfaceSizeClassCompact     = 1,
    UIUserInterfaceSizeClassRegular     = 2,
} NS_ENUM_AVAILABLE_IOS(8_0);

したがって、Appleがこれらの異なるバリエーションに名前付けることを決定したものに関係なく、基本的に、それらは、IBに保存されているレイアウトを区別するために、並べ替えの一意の識別子として使用される整数のペアにすぎません。

ここで、IBで(未使用のサイズクラスを使用して)代替レイアウトを作成するとします-たとえば、iPadの縦向きの場合... 実行時に必要に応じてデバイスが選択したサイズクラス(UIレイアウト)を使用する方法があります?

問題に対していくつかの(あまりエレガントではない)アプローチを試した後、デフォルトのサイズクラスをプログラムでオーバーライドする方法があるのではないかと疑っていました。そして(UIViewController.hに)あります:

// Call to modify the trait collection for child view controllers.
- (void)setOverrideTraitCollection:(UITraitCollection *)collection forChildViewController:(UIViewController *)childViewController NS_AVAILABLE_IOS(8_0);
- (UITraitCollection *)overrideTraitCollectionForChildViewController:(UIViewController *)childViewController NS_AVAILABLE_IOS(8_0);

したがって、ビューコントローラー階層を「子」ビューコントローラーとしてパッケージ化し、最上位の親ビューコントローラーに追加できる場合は、条件付きで子をオーバーライドして、デフォルトとは異なるサイズのクラスであると見なすことができます。 OSから。

以下は、「親」ビューコントローラでこれを行うサンプル実装です。

@interface RDTraitCollectionOverrideViewController : UIViewController {
    BOOL _willTransitionToPortrait;
    UITraitCollection *_traitCollection_CompactRegular;
    UITraitCollection *_traitCollection_AnyAny;
}
@end

@implementation RDTraitCollectionOverrideViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self setUpReferenceSizeClasses];
}

- (void)setUpReferenceSizeClasses {
    UITraitCollection *traitCollection_hCompact = [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassCompact];
    UITraitCollection *traitCollection_vRegular = [UITraitCollection traitCollectionWithVerticalSizeClass:UIUserInterfaceSizeClassRegular];
    _traitCollection_CompactRegular = [UITraitCollection traitCollectionWithTraitsFromCollections:@[traitCollection_hCompact, traitCollection_vRegular]];

    UITraitCollection *traitCollection_hAny = [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassUnspecified];
    UITraitCollection *traitCollection_vAny = [UITraitCollection traitCollectionWithVerticalSizeClass:UIUserInterfaceSizeClassUnspecified];
    _traitCollection_AnyAny = [UITraitCollection traitCollectionWithTraitsFromCollections:@[traitCollection_hAny, traitCollection_vAny]];
}

-(void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    _willTransitionToPortrait = self.view.frame.size.height > self.view.frame.size.width;
}

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]
    _willTransitionToPortrait = size.height > size.width;
}

-(UITraitCollection *)overrideTraitCollectionForChildViewController:(UIViewController *)childViewController {
    UITraitCollection *traitCollectionForOverride = _willTransitionToPortrait ? _traitCollection_CompactRegular : _traitCollection_AnyAny;
    return traitCollectionForOverride;
}
@end

それが機能するかどうかを確認する簡単なデモとして、IBの子コントローラーレイアウトの「Regular / Regular」バージョンと「Compact / Regular」バージョンに特にカスタムラベルを追加しました。

ここに画像の説明を入力してください ここに画像の説明を入力してください

そして、iPadが両方の向きにあるときの実行状態は次のとおりです。 ここに画像の説明を入力してください ここに画像の説明を入力してください

出来上がり!実行時のカスタムサイズクラス構成。

うまくいけば、AppleはこれをOSの次のバージョンで不要にするでしょう。それまでの間、これはプログラムで自動レイアウト制約をいじったり、コードで他の操作を行ったりするよりも、エレガントでスケーラブルなアプローチになる可能性があります。

編集(6/4/15):上記のサンプルコードは、基本的にこの手法を実証するための概念実証であることを覚えておいてください。必要に応じて、独自の特定のアプリケーションに自由に適応してください。

編集(7/24/15):上記の説明が問題をわかりやすくするのに役立つように思われるのは嬉しいことです。私はテストしていませんが、mohamede1945 [下]のコードは、実用的な目的に役立つ最適化のように見えます。ぜひお試しいただき、ご意見をお聞かせください。(完全を期すため、上のサンプルコードはそのままにしておきます。)


1
素晴らしい投稿@RonDiamond!
2015

3
AppleはSafariで幅サイズクラスを再定義するために同様のアプローチを採用しているので、これは多かれ少なかれサポートされているアプローチであると安心できます。追加のivarは実際には必要ありません。UITraitCollection十分に最適化さoverrideTraitCollectionForChildViewControllerれ、めったに呼び出されないため、幅チェックを実行してから作成することは問題になりません。
zwaldowski、2015

1
@zwaldowskiありがとう。ここでのサンプルコードは、単にテクニックを示すためのものであり、必要に応じてさまざまな方法で最適化できます。(原則として、オブジェクトが何度も繰り返し使用される場合(たとえば、デバイスが向きを変えるたびに)、オブジェクトを保持することは悪い考えではないと思いますが、指摘したように、ここでのパフォーマンスの違い最低限の場合があります。)
RonDiamond、2015

1
@Rashmi:説明で触れたように、最終的にどちらを選択するかは問題ではありません。レイアウトを列挙値のペアにマッピングするだけです。したがって、実際に意味のある「サイズクラス」を使用できます。デフォルトとしてどこかに継承される可能性がある他の(正当な)サイズクラスと競合しないことを確認します。
RonDiamond、2015年

1
子ビューコントローラに関しては、私はそれは重要ではないと思います。コンテナに子コントローラとして適切に追加されている限り、プログラムまたはnibからインスタンス化できるはずです。
RonDiamond、2015年

41

RonDiamondによる非常に長い回答に対する要約として。あなたがする必要があるのはあなたのルートビューコントローラーです。

Objective-c

- (UITraitCollection *)overrideTraitCollectionForChildViewController:(UIViewController *)childViewController
{
    if (CGRectGetWidth(self.view.bounds) < CGRectGetHeight(self.view.bounds)) {
        return [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassCompact];
    } else {
        return [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassRegular];
    }
}

迅速:

override func overrideTraitCollectionForChildViewController(childViewController: UIViewController) -> UITraitCollection! {
        if view.bounds.width < view.bounds.height {
            return UITraitCollection(horizontalSizeClass: .Compact)
        } else {
            return UITraitCollection(horizontalSizeClass: .Regular)
        }
    }

次に、storyboradで、縦向きにコンパクトな幅、横向きに通常の幅を使用します。


それが新しい分割画面モードでどのように機能するのか疑問に思っています...確認する1つの方法!
mm2001

UITraitCollectionはiOS 8.0以降のみ
mohamede1945 '16

私にはうまくいきません。2つのコンテナービューがあり、向きに応じて異なる場所に表示する必要があるビューがあります。必要なサイズのクラスをすべて作成しましたが、すべてiPhoneで機能します。私はあなたのコードを使ってiPadの向きも分離してみました。ただし、ビューの奇妙な動作になります。本来のように変化することはありません。何が起こっているのか手掛かりはありますか?
NoSixties 2016年

1
これは私がの- (UITraitCollection *)traitCollection代わりにオーバーライドしたときにうまくいきましたoverrideTraitCollectionForChildViewController。また、制約は特性コレクションと一致する必要があるため、wC(hAny)になります。
H. de Jonge

1
@アポロ私はそうしますが、それは実際の質問に答えません。setOverrideTraitCollection使用方法のAppleのサンプルはこちらを見てみましょうgithub.com/ios8/AdaptivePhotosAnAdaptiveApplication/blob/master/...を
malhal

5

iPadは、縦と横の両方に「通常の」サイズ特性を備えており、縦長と横長の区別がありません。

これらのサイズ特性はUIViewController、メソッドを使用してtraitCollection、カスタムサブクラスコードでオーバーライドできます。次に例を示します。

- (UITraitCollection *)traitCollection {
    // Distinguish portrait and landscape size traits for iPad, similar to iPhone 7 Plus.
    // Be aware that `traitCollection` documentation advises against overriding it.
    UITraitCollection *superTraits = [super traitCollection];
    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
        UITraitCollection *horizontalRegular = [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassRegular];
        UITraitCollection *verticalRegular = [UITraitCollection traitCollectionWithVerticalSizeClass:UIUserInterfaceSizeClassRegular];
        UITraitCollection *regular = [UITraitCollection traitCollectionWithTraitsFromCollections:@[horizontalRegular, verticalRegular]];

        if ([superTraits containsTraitsInCollection:regular]) {
            if (UIInterfaceOrientationIsPortrait([[UIApplication sharedApplication] statusBarOrientation])) {
                // iPad in portrait orientation
                UITraitCollection *horizontalCompact = [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassCompact];
                return [UITraitCollection traitCollectionWithTraitsFromCollections:@[superTraits, horizontalCompact, verticalRegular]];
            } else {
                // iPad in landscape orientation
                UITraitCollection *verticalCompact = [UITraitCollection traitCollectionWithVerticalSizeClass:UIUserInterfaceSizeClassCompact];
                return [UITraitCollection traitCollectionWithTraitsFromCollections:@[superTraits, horizontalRegular, verticalCompact]];
            }
        }
    }
    return superTraits;
}

- (BOOL)prefersStatusBarHidden {
    // Override to negate this documented special case, and avoid erratic hiding of status bar in conjunction with `traitCollection` override:
    // For apps linked against iOS 8 or later, this method returns true if the view controller is in a vertically compact environment.
    return NO;
}

これにより、iPadはiPhone 7 Plusと同じサイズの特性を備えています。他のiPhoneモデルは一般に、向きに関係なく(通常の幅ではなく)「コンパクト幅」特性を備えていることに注意してください。

この方法でiPhone 7 Plusを模倣すると、そのモデルを、コードのカスタマイズを認識しないXcodeのInterface BuilderでiPadの代用として使用できます。

iPadのSplit Viewでは、通常の全画面操作とは異なるサイズの特性が使用される場合があることに注意してください。

この回答は、このブログ投稿で採用されたアプローチに基づいており、いくつかの改善点があります。

アップデート2019-01-02: iPadランドスケープでの断続的な非表示ステータスバー、およびでの(新しい)特性の踏みつぶしを修正するために更新されましたUITraitCollection。また、Appleのドキュメントでは実際にをオーバーライドtraitCollectionしないことを推奨しているため、将来的にはこの手法に問題が発生する可能性があることにも注意してください。


ことをアップルが指定するtraitCollectionプロパティは読み取り専用にする:developer.apple.com/documentation/uikit/uitraitenvironment/...
cleverbit

4

RonDiamondによる長くて役立つ回答は、原則を理解するための良い出発点ですが、私のために機能したコード(iOS 8以降)はオーバーライドメソッドに基づいています (UITraitCollection *)traitCollection

したがって、制約のプロパティInstalledなど、Width-Compactのバリエーションを使用して、InterfaceBuilderに制約を追加します。したがって、幅-横向きの場合はすべて、幅-縦向きの場合はコンパクトになります。

現在のビューコントローラーのサイズに基づいてコードの制約を切り替えるには、UIViewControllerクラスに次のコードを追加します。

- (UITraitCollection *)traitCollection
{
    UITraitCollection *verticalRegular = [UITraitCollection traitCollectionWithVerticalSizeClass:UIUserInterfaceSizeClassRegular];

    if (self.view.bounds.size.width < self.view.bounds.size.height) {
        // wCompact, hRegular
        return [UITraitCollection traitCollectionWithTraitsFromCollections:
                @[[UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassCompact],
                  verticalRegular]];
    } else {
        // wRegular, hRegular
        return [UITraitCollection traitCollectionWithTraitsFromCollections:
                @[[UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassRegular],
                  verticalRegular]];
    }
}

0

横向きモードと縦向きモードの違いは何ですか?まったく異なる場合は、別のビューコントローラーを作成し、デバイスが横向きのときにロードすることをお勧めします

例えば

    if (UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation)) 
    //load landscape view controller here

はい、これは1つのオプションです。しかし、私はそれが最も最適なものだとは思いません。私の要点は、ios8のiphoneのポートレートモードとランドスケープモードに異なるサイズ設定クラス特性を使用するオプションがある場合、iPadでも同じではないということです。
neelIVP 2014年

Xcode 6以前は、向きに応じて異なるストーリーボードを使用できました。ほとんどのView Controllerが同じ場合、これはあまり効率的ではありません。ただし、さまざまなレイアウトに非常に便利です。Xcode 6では、これを行う方法はありません。たぶん、向きに応じて異なるビューコントローラを作成することが唯一の解決策です。
Bagusflyer 2014年

2
毎回異なるviewControllerをロードすることは、特に画面で何かが発生している場合は非常に非効率的です。同じビューを使用して、自動レイアウト制約またはコード内の要素の位置を操作する方がはるかに優れています。または、アップルがこの問題に対処するまで、上記のハックを使用します。
Pahnev、2015

0

Swift 5バージョン。正常に動作します。

override func overrideTraitCollection(forChild childViewController: UIViewController) -> UITraitCollection? {
    if UIScreen.main.bounds.width > UIScreen.main.bounds.height {
        let collections = [UITraitCollection(horizontalSizeClass: .regular),
                           UITraitCollection(verticalSizeClass: .compact)]
        return UITraitCollection(traitsFrom: collections)
    }
    return super.overrideTraitCollection(forChild: childViewController)
}

-3

@RonDiamondソリューションのSwift 3.0コード

class Test : UIViewController {


var _willTransitionToPortrait: Bool?
var _traitCollection_CompactRegular: UITraitCollection?
var _traitCollection_AnyAny: UITraitCollection?

func viewDidLoad() {
    super.viewDidLoad()
    self.upReferenceSizeClasses = null
}

func setUpReferenceSizeClasses() {
    var traitCollection_hCompact: UITraitCollection = UITraitCollection(horizontalSizeClass: UIUserInterfaceSizeClassCompact)
    var traitCollection_vRegular: UITraitCollection = UITraitCollection(verticalSizeClass: UIUserInterfaceSizeClassRegular)
    _traitCollection_CompactRegular = UITraitCollection(traitsFromCollections: [traitCollection_hCompact,traitCollection_vRegular])
    var traitCollection_hAny: UITraitCollection = UITraitCollection(horizontalSizeClass: UIUserInterfaceSizeClassUnspecified)
    var traitCollection_vAny: UITraitCollection = UITraitCollection(verticalSizeClass: UIUserInterfaceSizeClassUnspecified)
    _traitCollection_AnyAny = UITraitCollection(traitsFromCollections: [traitCollection_hAny,traitCollection_vAny])
}

func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
    _willTransitionToPortrait = self.view.frame.size.height > self.view.frame.size.width
}

func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
    _willTransitionToPortrait = size.height > size.width
}

func overrideTraitCollectionForChildViewController(childViewController: UIViewController) -> UITraitCollection {
    var traitCollectionForOverride: UITraitCollection = _willTransitionToPortrait ? _traitCollection_CompactRegular : _traitCollection_AnyAny
    return traitCollectionForOverride
}}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.