IPadアプリを作成しています。アプリの画面の1つは、UISplitViewControllerの使用に完全に適しています。ただし、アプリのトップレベルはメインメニューであり、UISplitViewControllerを使用したくありません。 Appleは次のように述べているため、これには問題があります。
UISplitViewController
はアプリのトップレベルのビューコントローラーである必要があります。つまり、そのビューはUIWindow
のサブビューとして追加する必要があります。
使用する場合、UISplitViewController
はアプリの存続期間中存在する必要があります。つまり、UIWindowからビューを削除して別のビューを配置したり、その逆を行ったりしないでください。
読んで実験した結果、Appleの要件を満たすのは実行可能な選択肢にすぎないようで、私たち自身はモーダルダイアログを使用することです。そのため、アプリのルートレベルにUISplitViewControllerがあり(つまり、そのビューがUIWindowのサブビューとして追加されています)、メインメニューを表示するために、フルスクリーンのモーダルダイアログとしてUISplitViewControllerにプッシュします。次に、メインメニュービューコントローラーのモーダルダイアログを閉じることで、分割ビューを実際に表示できます。
この戦略はうまく機能しているようです。しかし、それは疑問を投げかけます:
1)モーダルなしで、前述のすべての要件も満たす、これを構造化するためのより良い方法はありますか?モーダルダイアログとしてプッシュされるため、メインUIが表示されるのは少し奇妙に思えます。 (モーダルは、焦点を絞ったユーザータスク用であると想定されています。)
2)私のアプローチのためにアプリストアが拒否されるリスクがありますか?このモーダル戦略は、Appleのヒューマンインターフェイスガイドラインに従って、おそらく「誤用」モーダルダイアログです。しかし、彼らは私に他にどのような選択肢を与えましたか?とにかく、彼らは私がこれをしていることを知っていますか?
Touche!同じ問題に遭遇し、モーダルを使用して同じ方法で解決しました。私の場合、分割ビューの前に表示されるのはログインビューであり、メインメニューでもありました。私はあなたが考えたのと同じ戦略を使いました。私(および私が話した他のいくつかの知識のあるiOSの人々)は、より良い方法を見つけることができませんでした。私にとってはうまくいきます。とにかく、ユーザーはモーダルに気付くことはありません。それらを提示します。そして、はい、AppStoreには内部で同じことをしているアプリがかなりあることもわかります。 :)別の注意点として、いつかどうにかしてもっと良い方法を見つけたら教えてください:)
UISplitViewControllerの前にいくつかのUIViewControllerを表示するというこの概念(ログインフォームなど)は、必要になるまで、非常に複雑であることが判明したと真剣に信じていませんでした。そのようなビュー階層を作成します。
私の例はiOS8とXCode6.0(Swift)に基づいているので、この問題が以前に同じように存在したかどうか、またはiOS8で導入されたいくつかの新しいバグが原因であるかどうかはわかりません、しかし私が見つけた同様の質問のすべてから、私はこの問題に対する完全な「それほどハッキーではない」解決策を見ませんでした。
私が解決策にたどり着く前に私が試したいくつかのことをあなたに案内します(この投稿の終わりに)。各例は、CoreDataを有効にせずにMaster-Detailテンプレートから新しいプロジェクトを作成することに基づいています。
didFinishLaunchingWithOptions
からLoginViewControllerのprepareForSegue
に移動しますこれはほとんど機能しました。アプリがLoginViewControllerで起動し、ボタンをタップしてUISplitViewControllerにセグエした後、奇妙なバグが発生しているため、ほぼ言います:向きの変更時にマスタービューコントローラーを表示および非表示にすることはアニメーション化されなくなりました。
この問題にしばらく苦労し、実際の解決策がなかった後、私はそれが何らかの形でそれに関連していると思いました奇妙なルール UISplitViewControllerはrootViewControllerでなければなりません(この場合はそうではありません、LoginViewControllerはそうです)これはそれほど完璧ではない解決策です。
最後に、UISplitViewControllerを設定するための定型コードの後に、このコードをAppDelegateのdidFinishLaunchingWithOptions
に追加します。
window?.makeKeyAndVisible()
splitViewController.performSegueWithIdentifier("segueToLogin", sender: self)
return true
または、代わりに次のコードを試してください。
window?.makeKeyAndVisible()
let loginViewController = splitViewController.storyboard?.instantiateViewControllerWithIdentifier("LoginVC") as LoginViewController
splitViewController.presentViewController(loginViewController, animated: false, completion: nil)
return true
これらの例は両方とも、同じいくつかの悪いことを生み出します。
Unbalanced calls to begin/end appearance transitions for <UISplitViewController: 0x7fc8e872fc00>
正しく機能することがわかった唯一の方法は、ウィンドウのrootViewControllerをその場で変更する場合です。
didFinishLaunchingWithOptions
でアニメーションなしで実行しますが、UIから呼び出されるとアニメーション化されます。AppDelegateのサンプルコードは次のとおりです。
var loggedIn = false
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
setupRootViewController(false)
return true
}
func setupRootViewController(animated: Bool) {
if let window = self.window {
var newRootViewController: UIViewController? = nil
var transition: UIViewAnimationOptions
// create and setup appropriate rootViewController
if !loggedIn {
let loginViewController = window.rootViewController?.storyboard?.instantiateViewControllerWithIdentifier("LoginVC") as LoginViewController
newRootViewController = loginViewController
transition = .TransitionFlipFromLeft
} else {
let splitViewController = window.rootViewController?.storyboard?.instantiateViewControllerWithIdentifier("SplitVC") as UISplitViewController
let navigationController = splitViewController.viewControllers[splitViewController.viewControllers.count-1] as UINavigationController
navigationController.topViewController.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem()
splitViewController.delegate = self
let masterNavigationController = splitViewController.viewControllers[0] as UINavigationController
let controller = masterNavigationController.topViewController as MasterViewController
newRootViewController = splitViewController
transition = .TransitionFlipFromRight
}
// update app's rootViewController
if let rootVC = newRootViewController {
if animated {
UIView.transitionWithView(window, duration: 0.5, options: transition, animations: { () -> Void in
window.rootViewController = rootVC
}, completion: nil)
} else {
window.rootViewController = rootVC
}
}
}
}
そしてこれはLoginViewControllerからのサンプルコードです:
@IBAction func login(sender: UIButton) {
let delegate = UIApplication.sharedApplication().delegate as AppDelegate
delegate.loggedIn = true
delegate.setupRootViewController(true)
}
iOS 8でこれが正しく機能するためのより良い/よりクリーンな方法があるかどうかも聞きたいです。
そして、誰があなたが1つのウィンドウしか持つことができないと言いましたか? :)
私の答え この同様の質問について が役立つかどうかを確認してください。
このアプローチは私にとって非常にうまく機能しています。複数の表示や状態の復元について心配する必要がない限り、このリンクされたコードは必要なことを行うのに十分なはずです。ロジックを逆向きにしたり、既存のコードを書き直したりする必要はなく、それでも利用できます。アプリケーション内のより深いレベルでのUISplitViewの-(AFAIK)を破ることなくAppleガイドライン。
同じ問題に直面している将来のiOS開発者のために:ここに別の答えと説明があります。あなたはそれをルートビューコントローラーにする必要があります。そうでない場合は、モーダルをオーバーレイします。
プロジェクトでこの問題に遭遇し、解決策を共有したいと思いました。私たちの場合(iPadの場合)、両方のビューコントローラーが表示された状態でUISplitViewController
から始めたいと思いました(preferredDisplayMode = .allVisible
を使用)。詳細(右)階層のある時点で(こちら側にもナビゲーションコントローラーがありました)、分割ビューコントローラー全体に新しいビューコントローラーをプッシュしたいと思いました(モーダルトランジションは使用しません)。
IPhoneでは、この動作は無料で提供されます。常に1つのViewControllerしか表示されないためです。しかし、iPadでは、何か別のことを理解する必要がありました。最終的に、分割ビューコントローラーを子ビューコントローラーとして追加するルートコンテナービューコントローラーを使用することになりました。このルートビューコントローラーは、ナビゲーションコントローラーに組み込まれています。分割ビューコントローラの詳細ビューコントローラが分割ビューコントローラ全体に新しいコントローラをプッシュする場合、ルートビューコントローラはこの新しいビューコントローラをナビゲーションコントローラとともにプッシュします。
別のオプション:詳細ビューコントローラーに、モーダルビューコントローラーを表示します。
let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
if (!appDelegate.loggedIn) {
// display the login form
let storyboard = UIStoryboard(name: "Storyboard", bundle: nil)
let login = storyboard.instantiateViewControllerWithIdentifier("LoginViewController") as UIViewController
self.presentViewController(login, animated: false, completion: { () -> Void in
// user logged in and is valid now
self.updateDisplay()
})
} else {
updateDisplay()
}
ログインフラグを設定せずにログインコントローラを閉じないでください。 iPhoneではマスタービューコントローラーが最初に来るので、非常によく似たコードがマスタービューコントローラーにある必要があることに注意してください。
-presentViewController:animated:completion:
を介して、UISplitViewControllerを提示するための私のアプローチに貢献したいと思います(ただし、それが機能しないことは誰もが知っています)。以下に応答するUISplitViewControllerサブクラスを作成しました。
-presentAsRootViewController
-returnToPreviousViewController
このクラスは、他の成功したアプローチと同様に、UISplitViewControllerをウィンドウのrootViewControllerとして設定しますが、-presentViewController:animated:completion:
で(デフォルトで)取得するものと同様のアニメーションで設定します。
PresentableSplitViewController.h
#import <UIKit/UIKit.h>
@interface PresentableSplitViewController : UISplitViewController
- (void) presentAsRootViewController;
@end
PresentableSplitViewController.m
#import "PresentableSplitViewController.h"
@interface PresentableSplitViewController ()
@property (nonatomic, strong) UIViewController *previousViewController;
@end
@implementation PresentableSplitViewController
- (void) presentAsRootViewController {
UIWindow *window=[[[UIApplication sharedApplication] delegate] window];
_previousViewController=window.rootViewController;
UIView *windowSnapShot = [window snapshotViewAfterScreenUpdates:YES];
window.rootViewController = self;
[window insertSubview:windowSnapShot atIndex:0];
CGRect dstFrame=self.view.frame;
CGSize offset=CGSizeApplyAffineTransform(CGSizeMake(0, 1), window.rootViewController.view.transform);
offset.width*=self.view.frame.size.width;
offset.height*=self.view.frame.size.height;
self.view.frame=CGRectOffset(self.view.frame, offset.width, offset.height);
[UIView animateWithDuration:0.5
delay:0.0
usingSpringWithDamping:1.0
initialSpringVelocity:0.0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
self.view.frame=dstFrame;
} completion:^(BOOL finished) {
[windowSnapShot removeFromSuperview];
}];
}
- (void) returnToPreviousViewController {
if(_previousViewController) {
UIWindow *window=[[[UIApplication sharedApplication] delegate] window];
UIView *windowSnapShot = [window snapshotViewAfterScreenUpdates:YES];
window.rootViewController = _previousViewController;
[window addSubview:windowSnapShot];
CGSize offset=CGSizeApplyAffineTransform(CGSizeMake(0, 1), window.rootViewController.view.transform);
offset.width*=windowSnapShot.frame.size.width;
offset.height*=windowSnapShot.frame.size.height;
CGRect dstFrame=CGRectOffset(windowSnapShot.frame, offset.width, offset.height);
[UIView animateWithDuration:0.5
delay:0.0
usingSpringWithDamping:1.0
initialSpringVelocity:0.0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
windowSnapShot.frame=dstFrame;
} completion:^(BOOL finished) {
[windowSnapShot removeFromSuperview];
_previousViewController=nil;
}];
}
}
@end
UISplitViewを初期ビューとして実行しましたが、モーダルでフルスクリーンUIViewに移動し、UISplitViewに戻りました。 SplitViewに戻る必要がある場合は、カスタムセグエを使用する必要があります。
このリンクを読む(日本語から翻訳)
@tadijaの答えに加えて、私は同様の状況にあります:
私のアプリは電話専用で、タブレットUIを追加しています。 Swift同じアプリで-そして最終的にはすべてのアプリを移行して同じストーリーボードを使用することにしました(IPadバージョンが安定していると感じたら、電話での使用は簡単です。 XCode6からの新しいクラス)。
私のシーンではまだセグエが定義されていませんが、それでも機能します。
アプリデリゲートのコードはObjectiveCにあり、少し異なりますが、同じアイデアを使用しています。前の例とは異なり、シーンのデフォルトのViewControllerを使用していることに注意してください。これは、ランタイムがUINavigationController
ではなく通常のUISplitViewController
を生成するIOS7/IPhoneでも機能すると思います。 rootVCを変更する代わりに、iPhoneのログインビューコントローラーをプッシュする新しいコードを追加することもできます。
- (void) setupRootViewController:(BOOL) animated {
UIViewController *newController = nil;
UIStoryboard *board = [UIStoryboard storyboardWithName:@"Storyboard" bundle:nil];
UIViewAnimationOptions transition = UIViewAnimationOptionTransitionCrossDissolve;
if (!loggedIn) {
newController = [board instantiateViewControllerWithIdentifier:@"LoginViewController"];
} else {
newController = [board instantiateInitialViewController];
}
if (animated) {
[UIView transitionWithView: self.window duration:0.5 options:transition animations:^{
self.window.rootViewController = newController;
NSLog(@"setup root view controller animated");
} completion:^(BOOL finished) {
NSLog(@"setup root view controller finished");
}];
} else {
self.window.rootViewController = newController;
}
}