回答:
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 [下]のコードは、実用的な目的に役立つ最適化のように見えます。ぜひお試しいただき、ご意見をお聞かせください。(完全を期すため、上のサンプルコードはそのままにしておきます。)
UITraitCollection
十分に最適化さoverrideTraitCollectionForChildViewController
れ、めったに呼び出されないため、幅チェックを実行してから作成することは問題になりません。
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で、縦向きにコンパクトな幅、横向きに通常の幅を使用します。
- (UITraitCollection *)traitCollection
代わりにオーバーライドしたときにうまくいきましたoverrideTraitCollectionForChildViewController
。また、制約は特性コレクションと一致する必要があるため、wC(hAny)になります。
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/...
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]];
}
}
横向きモードと縦向きモードの違いは何ですか?まったく異なる場合は、別のビューコントローラーを作成し、デバイスが横向きのときにロードすることをお勧めします
例えば
if (UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation))
//load landscape view controller here
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)
}
@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
}}