UINavigationController
を使用してドリルダウンインターフェイスを表示するiPhoneアプリがあります。最初に1つのビュー、次に別の、最大4レベルの深さまで表示します。最初の3つのビューを縦向きに制限し、最後のビューのみを横向きに回転できるようにします。 4番目のビューから3番目のビューに戻り、4番目のビューが横向きだった場合、すべてを回転させて縦向きに戻します。
IOS 5では、各View ControllerでshouldAutorotateToInterfaceOrientation:
を定義して、許容される向きに対してYESを返しました。ビューコントローラー#4から#3に戻るときにデバイスが横向きに保持されていたとしても、ポートレートに戻るなど、すべてが上記のように機能しました。
IOS 6では、すべてのView Controllerが横向きに回転し、意図しないものを壊します。 iOS 6リリースノートには
より多くの責任がアプリとアプリの委任に移っています。現在、iOSコンテナー(
UINavigationController
など)は、自動回転する必要があるかどうかを判断するために子を参照しません。 [...]システムは、デバイスが回転するたびに、またはView Controllerがフルスクリーンのモーダル表示スタイルで表示されるたびに、サポートされているインターフェイスの向きを最上部のフルスクリーンView Controller(通常はルートView Controller)に要求します。さらに、このView ControllerがshouldAutorotate
メソッドからYESを返した場合にのみ、サポートされている向きが取得されます。 [...]システムは、アプリのsupportedInterfaceOrientationsForWindow:
メソッドによって返される値と、最上部のフルスクリーンコントローラーのsupportedInterfaceOrientations
メソッドによって返される値を交差させることにより、方向がサポートされているかどうかを判断します。
そこでUINavigationController
をサブクラス化し、MainNavigationController
にブールプロパティlandscapeOK
を与え、これを使用してsupportedInterfaceOrientations
で許容可能な向きを返しました。次に、各View ControllerのviewWillAppear:
メソッドに次のような行があります
[(MainNavigationController*)[self navigationController] setLandscapeOK:YES];
MainNavigationController
に望ましい動作を伝えるため。
質問があります:ポートレートモードで4番目のビューに移動し、電話を裏返すと横向きに回転します。次に、戻るボタンを押して、3番目のビューに戻ります。これは、ポートレートでのみ機能するはずです。しかし、それは元に戻りません。どうすればそれができますか?
私は試した
[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationPortrait]
3番目のView ControllerのviewWillAppear
メソッドで、しかし何もしません。これは呼び出すのに間違ったメソッドですか、それとも呼び出すのに間違った場所ですか、またはまったく異なる方法で全体を実装する必要がありますか?
私は同じ問題を抱えていて、自分に合った解決策を見つけました。動作させるには、- (NSUInteger)supportedInterfaceOrientations
をUINavigationControllerに実装するだけではnotで十分です。また、コントローラー#3にこのメソッドを実装する必要があります。これは、コントローラー#4をポップした後、最初にポートレート専用にする方法です。そのため、UINavigationControllerに次のコードがあります。
- (BOOL)shouldAutorotate
{
return YES;
}
- (NSUInteger)supportedInterfaceOrientations
{
if (self.isLandscapeOK) {
// for iPhone, you could also return UIInterfaceOrientationMaskAllButUpsideDown
return UIInterfaceOrientationMaskAll;
}
return UIInterfaceOrientationMaskPortrait;
}
View Controller#3で、次を追加します。
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
View Controller#1、#2、および#4に何かを追加する必要はありません。これは私のために働く、それがあなたを助けることを願っています。
CustomNavigationControllerを追加します
その中でこれらのメソッドをオーバーライドします。
-(BOOL)shouldAutorotate
{
return [[self.viewControllers lastObject] shouldAutorotate];
}
-(NSUInteger)supportedInterfaceOrientations
{
return [[self.viewControllers lastObject] supportedInterfaceOrientations];
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
return [[self.viewControllers lastObject] preferredInterfaceOrientationForPresentation];
}
Plistにすべての方向を追加します
View Controllerで、必要なもののみを追加します。
-(BOOL)shouldAutorotate
{
return YES;
}
-(NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
これらのメソッドは、Navigation Controllerのメソッドをオーバーライドします
SOに関する無数の同様の質問のすべての回答を調べた後、答えはどれも私にとってはうまくいきませんでしたが、彼らはいくつかのアイデアを与えてくれました。問題を解決する方法は次のとおりです。
まず、プロジェクトのターゲットのサポートされているインターフェイスの向きに、回転ビューに必要なすべての向きが含まれていることを確認します。
次に、UINavigationController
のカテゴリを作成します(Appleはサブクラス化しないように指示しているため):
@implementation UINavigationController (iOS6AutorotationFix)
-(BOOL)shouldAutorotate {
return [self.topViewController shouldAutorotate];
}
@end
そのカテゴリと、回転できるようにしたいView Controller(これをRotatingViewController
と呼びます)を、Navigation Controllerを含む最高レベルのView Controllerにインポートします。そのView Controllerで、次のようにshouldAutorotate
を実装します。これは、回転させるView Controllerと同じであってはならないことに注意してください。
-(BOOL)shouldAutorotate {
BOOL shouldRotate = NO;
if ([navigationController.topViewController isMemberOfClass:[RotatingViewController class]] ) {
shouldRotate = [navigationController.topViewController shouldAutorotate];
}
return shouldRotate;
}
最後に、RotatingViewController
で、shouldAutorotate
とsupportedInterfaceOrientations
を次のように実装します。
-(BOOL)shouldAutorotate {
// Preparations to rotate view go here
return YES;
}
-(NSUInteger)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskAllButUpsideDown; // or however you want to rotate
}
これを行う必要があるのは、iOS 6がトップビューコントローラーではなくルートビューコントローラーに回転の制御を与えるためです。個々のビューの回転がスタック内の他のビューとは異なる動作をするようにしたい場合は、ルートビューコントローラーで特定のケースを記述する必要があります。
@Brianの答えにコメントするほどの評判がないので、ここにメモを追加します。
ブライアンは、iOS6がrootViewControllerに回転制御を提供することに言及しました。これは、前述のUINavigationControllerだけでなく、UITabBarControllerでもあります。私の構造は次のようになります。
そこで、最初にカスタムUITabBarControllerにメソッドを追加し、次にカスタムUINavigationControllerに、最後に特定のUIViewControllerにメソッドを追加しました。
UITabBarControllerおよびUINavigationControllerの例:
- (BOOL)shouldAutorotate {
return [self.viewControllers.lastObject shouldAutorotate];
}
- (NSUInteger)supportedInterfaceOrientations {
return [self.viewControllers.lastObject supportedInterfaceOrientations];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
return [self.viewControllers.lastObject shouldAutorotateToInterfaceOrientation:toInterfaceOrientation];
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
return [self.viewControllers.lastObject preferredInterfaceOrientationForPresentation];
}
私は自分の質問に部分的に答えたいです。動作する3番目のviewWillAppear
のUIViewController
メソッドで使用される次のコード行が見つかりました。
[[UIDevice currentDevice]
performSelector:NSSelectorFromString(@"setOrientation:")
withObject:(id)UIInterfaceOrientationPortrait];
しかし、私はこのソリューションが本当に好きではありません。 Appleのドキュメントによれば、デバイスの物理の向きを表す読み取り専用プロパティに割り当てるトリックを使用しています。これは、ユーザーの手で正しい向きにジャンプするようにiPhoneに指示するようなものです。
これは単に機能するので、アプリにこれを残したいです。しかし、それは正しいとは思えないので、きれいな解決策のために質問を開いたままにしておきたいと思います。
これはiOS 6.0のオリエンテーションサポートに使用しています
-(BOOL)shouldAutorotate{
return YES;
}
- (NSUInteger)supportedInterfaceOrientations{
return UIInterfaceOrientationMaskAll;
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{
return UIInterfaceOrientationPortrait;
}
これは非常に見られているスレッドであることです。私が信じているものが一番簡単な答えだと思いました。これはios8以降で動作します
-(BOOL)shouldAutorotate
{
return YES;
}
そして
-(UIInterfaceOrientationMask)supportedInterfaceOrientations{
return UIInterfaceOrientationMaskPortrait;
}
それでおしまい。楽しい!
ああ、ViewControllersはNavigation Controllerに組み込まれているため、サブクラス化や構成は一切必要ありませんでした。
私は同じ種類の問題を解決しました。
View ControllerをプッシュするためにUINavigationController
を使用している場合、以下のメソッドを設定する必要があります。
extension UINavigationController{
override open var shouldAutorotate: Bool {
if topViewController != nil && (topViewController?.isKind(of: LogInViewController.self))!
{
return true
}
return false
}
override open var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if topViewController != nil && (topViewController?.isKind(of: LogInViewController.self))!
{
return .portrait
}
return .landscapeRight
}
override open var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
if topViewController != nil && (topViewController?.isKind(of: LogInViewController.self))!
{
return .portrait
}
return .landscapeRight
}
}
LoginViewController
の代わりに、表示するUIViewController
を使用します。私の場合、LoginViewController
モードでPortrait
モードを表示し、ViewControllers
モードでlandscape
を表示します。
Info.plistファイルに移動して、変更を加えます。
アプリのPLISTで定義されているものとは異なる方向で表示されるすべてのView ControllerクラスでshouldAutorotate()およびsupportedInterfaceOrientations varを実装およびオーバーライドできます。
ただし、非自明なユーザーインターフェイスでは、数十のクラスに追加する問題に直面する可能性があり、それらをすべてサポートするいくつかの共通(MyBaseTableViewController、MyBaseNavigationController、およびMyBaseTabBarController)のサブクラスにしたくない場合があります。
UIViewControllerでこれらのメソッド/変数を直接オーバーライドできないため、通常はUITableViewController、UINavigationController、UITabBarControllerなどの基本クラスであるサブクラスでそれを行うことができます。
したがって、いくつかの拡張機能を実装し、このSwift 4コードスニペットのように、MyPreciousViewControllerを他のすべてとは異なる方向で表示するように設定することもできます。
extension UITableViewController {
override open var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if let last = self.navigationController?.childViewControllers.last,
last != self {
return last.supportedInterfaceOrientations
} else {
return [.portrait]
}
}
}
extension MyPreciousViewController {
override open var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return [.portrait,.landscape]
}
}
extension UINavigationController {
override open var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return [.portrait]
}
}
extension UITabBarController {
override open var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return [.portrait]
}
}
IOS 6アプリのポートレートを強制する場合にのみ、メソッドの下のUIViewControllerサブクラスに追加できます。
- (BOOL)shouldAutorotate {
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
return YES;
} else {
return NO;
}
}
- (NSUInteger)supportedInterfaceOrientations {
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
return UIInterfaceOrientationMaskAll;
} else {
return UIInterfaceOrientationMaskPortrait;
}
}
1つを除き、すべてのVCを縦向きにロックしたかった。これは私のために働いたものです。
ルートView Controllerで、ウィンドウの上部にあるView Controllerの種類を検出し、それに応じてsupportedInterfaceOrientationsメソッドでアプリの向きを設定します。たとえば、WebViewがスタックの一番上にある場合にのみアプリを回転させる必要がありました。 rootVCに追加したものは次のとおりです。
-(NSUInteger)supportedInterfaceOrientations
{
UIViewController *topMostViewController = [[Utils getAppDelegate] appNavigationController].topViewController;
if ([topMostViewController isKindOfClass:[SVWebViewController class]]) {
return UIInterfaceOrientationMaskAllButUpsideDown;
}
return UIInterfaceOrientationMaskPortrait;
}
これは誰にとってもうまくいくとは限りませんが、私にとってはうまくいきます。実装する代わりに...
[(MainNavigationController*)[self navigationController] setLandscapeOK:YES];
すべてのコントローラーのviewWillAppearで、 INavigationControllerDelegate method navigationController:willShowViewController:animated:
をオーバーライドすることにより、このプロセスをUINavigationControllerサブクラス内に集中させることにしました。
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
self.previousVCLandscapeOK = self.isLandscapeOK; // Store the current VC's orientation preference before pushing on the new VC so we can set this again from within the custom "back" method
self.isLandscapeOK = NO; // Set NO as default for all VC's
if ([viewController isKindOfClass:[YourViewController class]]) {
self.isLandscapeOK = YES;
}
}
ナビゲーションスタックからVCをポップすると、このデリゲートメソッドが呼び出されないことがわかりました。 UINavigationControllerサブクラス内から戻る機能を処理しているため、このような特定のVCに適切なナビゲーションバーボタンとアクションを設定できるため、これは問題ではありませんでした...
if ([viewController isKindOfClass:[ShareViewController class]]) {
UIButton* backButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 57, 30)];
[backButton setImage:[UIImage imageNamed:@"back-arrow"] forState:UIControlStateNormal];
[backButton addTarget:self action:@selector(back) forControlEvents:UIControlEventTouchUpInside];
UIBarButtonItem* backButtonItem = [[UIBarButtonItem alloc] initWithCustomView:backButton];
viewController.navigationItem.leftBarButtonItem = backButtonItem;
UIImageView* shareTitle = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"share-title"]];
[shareTitle setContentMode:UIViewContentModeScaleAspectFit];
[shareTitle setFrame:CGRectMake(0, 0, shareTitle.frame.size.width - 10, shareTitle.frame.size.height - 10)];
viewController.navigationItem.titleView = shareTitle;
} else if(...) {
...
}
スタックのVCのポップを処理し、適切な回転設定を設定するback
メソッドは次のようになります...
- (void)back {
self.isLandscapeOK = self.previousVCLandscapeOK;
self.previousVCLandscapeOK = NO;
[self popViewControllerAnimated:YES];
}
ご覧のように、基本的に起こっているのは、最初に2つのプロパティを設定することだけです...
@property (nonatomic) BOOL isLandscapeOK;
@property (nonatomic) BOOL previousVCLandscapeOK;
navigationController:willShowViewController:animated:
で、サポートされている向きが表示されるVC内にあるかどうかを決定します。 VCをポップすると、カスタムの「back」メソッドが呼び出され、isLandscapeOKをpreviousVCLandscapeOK値を介して保存されたものに設定します。
先ほど言ったように、これはすべての人にとってうまくいくわけではありませんが、私にとってはうまく機能し、各View Controllerにコードを追加することを心配する必要はありません。すべてをUINavigationControllerサブクラスで集中管理することができました。
これが私と同じように誰かを助けることを願っています。ありがとう、ジェレミー。
@micmdk replyで@Ram Sの質問に答えるほどの評判がないので、ここにメモを追加します。
ITabbarControllerを使用する場合、@ micmdkのコードでself.viewControllers.lastObjectをself.selectedViewControllerこのように:
- (BOOL)shouldAutorotate {
return [self.selectedViewController shouldAutorotate];
}
- (NSUInteger)supportedInterfaceOrientations {
return [self.selectedViewController supportedInterfaceOrientations];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
return [self.selectedViewController shouldAutorotateToInterfaceOrientation:toInterfaceOrientation];
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
return [self.selectedViewController preferredInterfaceOrientationForPresentation];
}