web-dev-qa-db-ja.com

iPadのポートレートモードとランドスケープモードのサイジングクラス

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

97
neelIVP

両方のiPadの向きを同じように扱うことはAppleの意図のように見えますが、多くの人が気づいているように、iPad PortraitとiPad LandscapeのUIレイアウトを変えたいという非常に正当な設計上の理由があります。

残念ながら、現在のOSはこの区別をサポートしていないようです...つまり、Adaptive UIを使用して理想的に無料で取得できるものを達成するために、コードまたは同様の回避策で自動レイアウト制約を操作することに戻っています。

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

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

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

実際、「サイズクラス」は実際には単なる列挙値のペアです。 UIInterface.hから:

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

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

ここで、IBで代替レイアウト(未使用のサイズクラスを使用)を作成するとします。たとえば、iPad Portraitの場合、デバイスにサイズクラス(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);

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

「親」View Controllerでこれを行うサンプル実装を次に示します。

@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'バージョンにカスタムラベルを追加しました。

enter image description hereenter image description here

そして、iPadが両方の向きにあるとき、実行中のように見えます: enter image description hereenter image description here

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

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

[〜#〜] edit [〜#〜](6/4/15):上記のサンプルコードは、基本的にこのテクニックを実証するための概念実証であることに注意してください。独自の特定のアプリケーションに必要に応じて自由に適応してください。

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

174
RonDiamond

RonDiamondによる非常に長い回答の要約として。あなたがする必要があるのは、あなたのルートView Controllerです。

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では、縦幅にコンパクトな幅を使用し、横長に通常の幅を使用します。

41
mohamede1945

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にはi​​Phone 7 Plusと同じサイズの特徴が与えられます。通常、他のiPhoneモデルには、向きに関係なく「通常の幅ではなく」「コンパクトな幅」の特性があります。

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

IPadのSplit Viewは、通常のフルスクリーン操作とは異なるサイズの特性を使用する場合があることに注意してください。

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

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

5
jedwidz

RonDiamondによる長く有益な回答は、原則を理解するための良い出発点ですが、私のために働いたコード(iOS 8以降)はオーバーライドメソッド(UITraitCollection *)traitCollection

そのため、InterfaceBuilderで制約を追加します。たとえば、制約のプロパティInstalledのように、幅-コンパクトのバリエーションを追加します。幅-いずれも横向きに有効、幅-縦向きにコンパクトになります。

現在のView Controllerのサイズに基づいてコードの制約を切り替えるには、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]];
    }
}
4
Jadamec

横向きモードは縦向きモードとどの程度違いますか?まったく異なる場合は、別のView Controllerを作成して、デバイスが横向きのときにロードすることをお勧めします

例えば

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