iOSはpresentとdismissについて話しています
7362 ワード
今日クラッシュに遭遇しましたが、presentViewControllerがモダリティビューを弾いたためです.今日はpresentとdismissに関する質問をまとめます.
まずいくつかの問題を並べて,君は答えられるか.
UIViewControllerが3つあると仮定し,それぞれA,B,Cである.以下、「A弾B」とはもしAがすでにBを弾いたならば、この时あなたは1つのCを弾いて、AがCを弾くべきで、それともBがCを弾いて、AがCを弾くことができますか? UIViewControllerの2つのプロパティ、presentingViewControllerとpresentedViewControllerについて.A弾BならA.presentingViewControl=?A.presentedViewController = ?,B.presentingViewController = ?,B.presentedViewController = ? A弾B、B弾Cなら? A弾B、B弾C.A dismissを呼び出すと、どのような結果になりますか?
以下、一つ一つ解答します.
質問2:presentingViewControllerとpresentedViewControllerのプロパティ
まず問題2を見てみましょう.UIViewControllerには、presentedViewControllerとpresentingViewControllerの2つのプロパティがあります.文书の注釈を见てあなたは理解することができるかも知れなくて、どうせスレ主はよく分からないで、分かっても忘れやすくて、覚えられません.
では、自分でDemoを書いて検証してみましょう.私たちはA、B、Cの3つのコントローラを作成して、上にボタンを置いて、Aのボタン、Aのボタン、Bのボタン、Bのボタン、Cを弾きます.終了時にそれぞれのpresentedViewControlとpresentingViewControlのプロパティを印刷します.結果は次のとおりです.
----------------------A弾B後----------------------A B A.presentingView Control(null)A.presentedView Control(null)A.presentingView(presentingViewControl)B.presentingView(null)------------------------------------B弾C後----------------------C A.presentingView Control(null) A.presentedViewController B.presentingViewController B.presentedViewController C.presentingViewController C.presentedViewController (null)
翻訳します
----------------------A弾B後---------------A.presentingViewControl(null)A.presentedViewControl(null)A.presentingViewControl(null)------------------------------------A.presentingViewControl(null) A.presentedViewController B B.presentingViewController A B.presentedViewController C C.presentingViewController B C.presentedViewController (null)
以上の結果から、presentingViewControllerプロパティは親ノードを返し、presentedViewControllerプロパティは子ノードを返し、親ノードまたは子ノードがなければnilを返します.この2つのプロパティは、現在のノードが直接隣接する親子ノードを返します.最下位または最上位のノードを返すわけではありません(この点はドキュメントの注釈と異なります).この結論を例に照らして説明する.
---------------A弾B後---------------A.presentingViewController(null)/Aは最下位で親ノードがないので、Aの親ノードはnil A.presentedViewController B//BをAの上位に返し、BはAの子ノードなので、Aの子ノードはB.presentingViewController A//Bを返す親ノードはA、したがって、Bの親ノードはA B.presentedViewController(null)/Bに子ノードがないので、Bの子ノードはnil---------------------------------------------------------A.presentingViewController(null)/Aが最下位であり、親ノードA.presentedViewController B//Aがない直接子ノードがB.B.presentingViewController A//Bの親ノードがA B.presentedViewController C//Bの子ノードがC C.C.presentingViewController B//Cの直接親ノードがB.presentedViewController(null)/Cは最上位であり、子ノードがない
問題1:presentのレベルの問題、何度も窓を弾いて誰が弾きます
AがすでにBを弾いていたら、この時にCを弾きたいと思っていましたが、正しいやり方は、BがCを弾きます.
A弾Cを試してみると、システムは警告を投げ出し、インタフェースは変化しません.つまり、Cはポップアップされません.警告は以下の通りです.
Warning: Attempt to present on which is already presenting
警告内容を訳すと「Warning:Attempt to present C on A which is already presenting B」
AでCを弾いてみましたが、AはもうBを弾いています.
これでよくわかりますが、presentを使ってモードビューを弾く場合は、最上位のコントローラでしか弾けません.最下位のコントローラで弾くと失敗し、警告を投げ出します.
viewControllerに転送される最上位レイヤサブノードを取得する方法を簡単に書きましたが、参考にしてください.
一つの崩壊問題
記事の冒頭で、クラッシュの問題についてお話ししました.次はクラッシュ時のXcodeのログです.
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Application tried to present modally an active controller .'
調べてみると、presentがpresentedされたビューコントローラがクラッシュすることがわかりました.一般的にはこのような状況は発生しませんが、同じ行presentのコードが複数回実行された可能性がある場合は、バグをチェックし、修復することに注意してください.
問題3:dismissメソッド
dismissの方法はみんなよく知っているでしょう
この方法についての公式ドキュメントの説明を見てみましょう.
The presenting view controller is responsible for dismissing the view controller it presented. If you call this method on the presented view controller itself, UIKit asks the presenting view controller to handle the dismissal. If you present several view controllers in succession, thus building a stack of presented view controllers, calling this method on a view controller lower in the stack dismisses its immediate child view controller and all view controllers above that child on the stack. When this happens, only the top-most view is dismissed in an animated fashion; any intermediate view controllers are simply removed from the stack. The top-most view is dismissed using its modal transition style, which may differ from the styles used by other view controllers lower in the stack.
ドキュメントの指示1.親ノードはdismissを呼び出して弾き出した子ノードを閉じる責任を負います.また、子ノードでdismissメソッドを直接呼び出すこともできます.UImitは親ノードに処理を通知します.2.複数のノードを連続的にポップアップする場合は、最下位の親ノードからdismissを呼び出して、すべてのサブノードを一度に閉じる必要があります.3.複数のサブノードをオフにすると、最上位のサブノードのみがアニメーション効果があり、下位のサブノードは直接削除され、アニメーション効果はありません.私のテストを経て、確かにそうです.
よくあるエラー
次の間違いは起こりやすいでしょう.
Warning: Attempt to present on whose view is not in the window hierarchy!
あなたのコードはそうかもしれません.
あるいはこのような
上記のコードはいずれも失敗し、Bはポップアップされず、上の警告が投げ出されます.警告は明確だviewはまだビューツリー(親ビュー)に追加されておらず、ポップアップビューは許可されていません.すなわち、ビューツリー(親ビュー)にviewControllerのviewが追加されていない場合、このviewControllerでpresentを削除すると失敗し、警告が投げ出されます.
理論的には、UIViewControllerを作成するときにpresentの別のUIViewControllerを作成するべきではありません.サブビュー、サブコントローラを追加することで、同様の効果を実現できます(推奨).
そう書かなければならない場合は、presentの部分を-viewDidAppearメソッドに置くことができます.-viewDidAppearが呼び出されたときself.viewはビューツリーに追加されました.
UIViewのライフサイクルについてviewDidLoadシリーズメソッドの呼び出し順序は,このブログを参考にして非常によく書かれている.UIViewライフサイクルの詳細
この文章があなたに役に立つと思ったら、いいねを押してください.
転載は出典を明記してください.ありがとうございます.
まずいくつかの問題を並べて,君は答えられるか.
UIViewControllerが3つあると仮定し,それぞれA,B,Cである.以下、「A弾B」とは
[A presentViewController:B animated:NO completion:nil];
を意味する以下、一つ一つ解答します.
質問2:presentingViewControllerとpresentedViewControllerのプロパティ
まず問題2を見てみましょう.UIViewControllerには、presentedViewControllerとpresentingViewControllerの2つのプロパティがあります.文书の注釈を见てあなたは理解することができるかも知れなくて、どうせスレ主はよく分からないで、分かっても忘れやすくて、覚えられません.
//UIKit.UIViewController.h
// The view controller that was presented by this view controller or its nearest ancestor.
@property(nullable, nonatomic,readonly) UIViewController *presentedViewController NS_AVAILABLE_IOS(5_0);
// The view controller that presented this view controller (or its farthest ancestor.)
@property(nullable, nonatomic,readonly) UIViewController *presentingViewController NS_AVAILABLE_IOS(5_0);
では、自分でDemoを書いて検証してみましょう.私たちはA、B、Cの3つのコントローラを作成して、上にボタンを置いて、Aのボタン、Aのボタン、Bのボタン、Bのボタン、Cを弾きます.終了時にそれぞれのpresentedViewControlとpresentingViewControlのプロパティを印刷します.結果は次のとおりです.
----------------------A弾B後----------------------A B A.presentingView Control(null)A.presentedView Control(null)A.presentingView(presentingViewControl)B.presentingView(null)------------------------------------B弾C後----------------------C A.presentingView Control(null) A.presentedViewController B.presentingViewController B.presentedViewController C.presentingViewController C.presentedViewController (null)
翻訳します
----------------------A弾B後---------------A.presentingViewControl(null)A.presentedViewControl(null)A.presentingViewControl(null)------------------------------------A.presentingViewControl(null) A.presentedViewController B B.presentingViewController A B.presentedViewController C C.presentingViewController B C.presentedViewController (null)
以上の結果から、presentingViewControllerプロパティは親ノードを返し、presentedViewControllerプロパティは子ノードを返し、親ノードまたは子ノードがなければnilを返します.この2つのプロパティは、現在のノードが直接隣接する親子ノードを返します.最下位または最上位のノードを返すわけではありません(この点はドキュメントの注釈と異なります).この結論を例に照らして説明する.
---------------A弾B後---------------A.presentingViewController(null)/Aは最下位で親ノードがないので、Aの親ノードはnil A.presentedViewController B//BをAの上位に返し、BはAの子ノードなので、Aの子ノードはB.presentingViewController A//Bを返す親ノードはA、したがって、Bの親ノードはA B.presentedViewController(null)/Bに子ノードがないので、Bの子ノードはnil---------------------------------------------------------A.presentingViewController(null)/Aが最下位であり、親ノードA.presentedViewController B//Aがない直接子ノードがB.B.presentingViewController A//Bの親ノードがA B.presentedViewController C//Bの子ノードがC C.C.presentingViewController B//Cの直接親ノードがB.presentedViewController(null)/Cは最上位であり、子ノードがない
問題1:presentのレベルの問題、何度も窓を弾いて誰が弾きます
AがすでにBを弾いていたら、この時にCを弾きたいと思っていましたが、正しいやり方は、BがCを弾きます.
A弾Cを試してみると、システムは警告を投げ出し、インタフェースは変化しません.つまり、Cはポップアップされません.警告は以下の通りです.
Warning: Attempt to present on which is already presenting
警告内容を訳すと「Warning:Attempt to present C on A which is already presenting B」
AでCを弾いてみましたが、AはもうBを弾いています.
これでよくわかりますが、presentを使ってモードビューを弾く場合は、最上位のコントローラでしか弾けません.最下位のコントローラで弾くと失敗し、警告を投げ出します.
viewControllerに転送される最上位レイヤサブノードを取得する方法を簡単に書きましたが、参考にしてください.
// ,
+ (UIViewController *)topestPresentedViewControllerForVC:(UIViewController *)viewController
{
UIViewController *topestVC = viewController;
while (topestVC.presentedViewController) {
topestVC = topestVC.presentedViewController;
}
return topestVC;
}
一つの崩壊問題
記事の冒頭で、クラッシュの問題についてお話ししました.次はクラッシュ時のXcodeのログです.
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Application tried to present modally an active controller .'
調べてみると、presentがpresentedされたビューコントローラがクラッシュすることがわかりました.一般的にはこのような状況は発生しませんが、同じ行presentのコードが複数回実行された可能性がある場合は、バグをチェックし、修復することに注意してください.
問題3:dismissメソッド
dismissの方法はみんなよく知っているでしょう
- (void)dismissViewControllerAnimated: (BOOL)flag completion: (void (^ __nullable)(void))completion
普通、みんなはこのように使って、A弾B、Bの中でdismissを呼び出して弾枠を消します.大丈夫です.じゃあ、A弾B、Aでdismissを呼び出してもいいですか.--大丈夫です.Bは消えます.じゃあ、A弾B、B弾C.A dismissを呼び出すと、どのような結果になりますか?Cが消えたのか、BもCも消えたのか、それとも間違いを報告したのか.--正解はBもCも消えた.この方法についての公式ドキュメントの説明を見てみましょう.
The presenting view controller is responsible for dismissing the view controller it presented. If you call this method on the presented view controller itself, UIKit asks the presenting view controller to handle the dismissal. If you present several view controllers in succession, thus building a stack of presented view controllers, calling this method on a view controller lower in the stack dismisses its immediate child view controller and all view controllers above that child on the stack. When this happens, only the top-most view is dismissed in an animated fashion; any intermediate view controllers are simply removed from the stack. The top-most view is dismissed using its modal transition style, which may differ from the styles used by other view controllers lower in the stack.
ドキュメントの指示1.親ノードはdismissを呼び出して弾き出した子ノードを閉じる責任を負います.また、子ノードでdismissメソッドを直接呼び出すこともできます.UImitは親ノードに処理を通知します.2.複数のノードを連続的にポップアップする場合は、最下位の親ノードからdismissを呼び出して、すべてのサブノードを一度に閉じる必要があります.3.複数のサブノードをオフにすると、最上位のサブノードのみがアニメーション効果があり、下位のサブノードは直接削除され、アニメーション効果はありません.私のテストを経て、確かにそうです.
よくあるエラー
次の間違いは起こりやすいでしょう.
Warning: Attempt to present on whose view is not in the window hierarchy!
あなたのコードはそうかもしれません.
- (void)viewDidLoad {
[super viewDidLoad];
_BViewController = [[UIViewController alloc] init];
[self presentViewController:_BViewController animated:NO completion:nil];
}
あるいはこのような
- (void)viewWillAppear {
[super viewWillAppear];
_BViewController = [[UIViewController alloc] init];
[self presentViewController:_BViewController animated:NO completion:nil];
}
上記のコードはいずれも失敗し、Bはポップアップされず、上の警告が投げ出されます.警告は明確だviewはまだビューツリー(親ビュー)に追加されておらず、ポップアップビューは許可されていません.すなわち、ビューツリー(親ビュー)にviewControllerのviewが追加されていない場合、このviewControllerでpresentを削除すると失敗し、警告が投げ出されます.
理論的には、UIViewControllerを作成するときにpresentの別のUIViewControllerを作成するべきではありません.サブビュー、サブコントローラを追加することで、同様の効果を実現できます(推奨).
- (void)viewDidLoad {
[super viewDidLoad];
_BViewController = [[UIViewController alloc] init];
_BViewController.view.frame = self.view.bounds;
[self.view addSubview:_BViewController.view];
[self addChildViewController:_BViewController]; // ,
}
そう書かなければならない場合は、presentの部分を-viewDidAppearメソッドに置くことができます.-viewDidAppearが呼び出されたときself.viewはビューツリーに追加されました.
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
_BViewController = [[UIViewController alloc] init];
[self presentViewController:_BViewController animated:NO completion:nil];
}
UIViewのライフサイクルについてviewDidLoadシリーズメソッドの呼び出し順序は,このブログを参考にして非常によく書かれている.UIViewライフサイクルの詳細
この文章があなたに役に立つと思ったら、いいねを押してください.
転載は出典を明記してください.ありがとうございます.