ユーザーがモーダル ビューの外側をタップしたときに、FormSheetPresentation モーダル ビュー コントローラーを閉じたい...これを行うアプリがたくさんあるのを見てきました (たとえば、iPad の eBay) が、下のビューがタッチから無効になっているため、その方法がわかりませんモーダル ビューがこのように表示される場合 (おそらくポップオーバーとして表示されますか?)...誰か提案はありますか?
14 に答える
私は1年遅れていますが、これは非常に簡単です。
モーダル ビュー コントローラーで、ジェスチャ レコグナイザーをビューのウィンドウにアタッチします。
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapBehind:)];
[recognizer setNumberOfTapsRequired:1];
recognizer.cancelsTouchesInView = NO; //So the user can still interact with controls in the modal view
[self.view.window addGestureRecognizer:recognizer];
[recognizer release];
処理コード:
- (void)handleTapBehind:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded)
{
CGPoint location = [sender locationInView:nil]; //Passing nil gives us coordinates in the window
//Then we convert the tap's location into the local view's coordinate system, and test to see if it's in or outside. If outside, dismiss the view.
if (![self.view pointInside:[self.view convertPoint:location fromView:self.view.window] withEvent:nil])
{
// Remove the recognizer first so it's view.window is valid.
[self.view.window removeGestureRecognizer:sender];
[self dismissModalViewControllerAnimated:YES];
}
}
}
それはそれについてです。これは便利で、多くの場合直感的な動作です。
iOS 8 のUIGestureRecognizer
場合、横向きの場合は、 を実装し、タップされた位置の (x,y) 座標を交換する必要があります。これが iOS 8 のバグによるものかどうかはわかりません。
- (void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// add gesture recognizer to window
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapBehind:)];
[recognizer setNumberOfTapsRequired:1];
recognizer.cancelsTouchesInView = NO; //So the user can still interact with controls in the modal view
[self.view.window addGestureRecognizer:recognizer];
recognizer.delegate = self;
}
- (void)handleTapBehind:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded) {
// passing nil gives us coordinates in the window
CGPoint location = [sender locationInView:nil];
// swap (x,y) on iOS 8 in landscape
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0")) {
if (UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)) {
location = CGPointMake(location.y, location.x);
}
}
// convert the tap's location into the local view's coordinate system, and test to see if it's in or outside. If outside, dismiss the view.
if (![self.view pointInside:[self.view convertPoint:location fromView:self.view.window] withEvent:nil]) {
// remove the recognizer first so it's view.window is valid
[self.view.window removeGestureRecognizer:sender];
[self dismissViewControllerAnimated:YES completion:nil];
}
}
}
#pragma mark - UIGestureRecognizer Delegate
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
return YES;
}
他のアプリは、ビューの外側をクリックしてビューを閉じることができる場合、モーダル ビューを使用していません。 UIModalPresentationFormSheets
このままでは解雇できません。(実際、SDK3.2 の UIModal もできません)。UIPopoverController
領域外をクリックして閉じることができるのは のみです。(Apple の iPad HIG に反して) アプリ開発者が背景画面を影で覆い、 (または他の UIModal ビュー) の UIPopoverController
ように表示する可能性は非常に高いです。UIModalPresentationFormSheets
[...] UIModalPresentationCurrentContext スタイルにより、View Controller はその親のプレゼンテーション スタイルを採用できます。各モーダル ビューでは、淡色表示された領域に基になるコンテンツが表示されますが、そのコンテンツでのタップは許可されません。したがって、ポップオーバーとは異なり、モーダル ビューには、ユーザーがモーダル ビューを閉じることができるコントロールが必要です。
詳細については、開発者サイトの iPadProgrammingGuide を参照してください (ページ 46 -- 「モーダル ビューのプレゼンテーション スタイルの構成」)。
上記のコードはうまく機能しますが、if ステートメントを次のように変更します。
if (!([self.view pointInside:[self.view convertPoint:location fromView:self.view.window] withEvent:nil] || [self.navigationController.view pointInside:[self.navigationController.view convertPoint:location fromView:self.navigationController.view.window] withEvent:nil]))
{
// Remove the recognizer first so it's view.window is valid.
[self.view.window removeGestureRecognizer:sender];
[self dismissModalViewControllerAnimated:YES];
}
これにより、引き続きナビゲーション バーを操作できるようになります。それ以外の場合は、タップするとモーダル ビューが閉じます。
iOS 8 用に回答が更新されました
どうやら、iOS 8 にはUIDimmingView
タップ ジェスチャ認識機能があり、初期実装に干渉するため、無視し、失敗する必要はありません。
今はスピードの時代なので、ほとんどの人はおそらく上記のコードをコピーするだけです..しかし、残念ながら、コードに関してはOCDに苦しんでいます。
これは、Danilo Campos の回答とカテゴリを使用するモジュラー ソリューションです。また、前述のように、モーダルを他の方法で閉じる場合に発生する可能性がある重要なバグも解決します。
注: iPhone と iPad の両方でビュー コントローラーを使用しており、iPad のみを登録/登録解除する必要があるため、if ステートメントが存在します。
更新:素晴らしいFCOverlayコードで適切に動作せず、提示されたビューでジェスチャを認識できなかったため、要点が更新されました。これらの問題は修正されています。カテゴリの使用は次のように簡単です。
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if (self.presentingViewController) {
[self registerForDismissOnTapOutside];
}
}
- (void)viewWillDisappear:(BOOL)animated
{
if (self.presentingViewController) {
[self unregisterForDismissOnTapOutside];
}
[super viewWillDisappear:animated];
}
このコードをコピーして ModalViewController に貼り付けます。
- (void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
//Code for dissmissing this viewController by clicking outside it
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapBehind:)];
[recognizer setNumberOfTapsRequired:1];
recognizer.cancelsTouchesInView = NO; //So the user can still interact with controls in the modal view
[self.view.window addGestureRecognizer:recognizer];
}
- (void)handleTapBehind:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded)
{
CGPoint location = [sender locationInView:nil]; //Passing nil gives us coordinates in the window
//Then we convert the tap's location into the local view's coordinate system, and test to see if it's in or outside. If outside, dismiss the view.
if (![self.view pointInside:[self.view convertPoint:location fromView:self.view.window] withEvent:nil])
{
// Remove the recognizer first so it's view.window is valid.
[self.view.window removeGestureRecognizer:sender];
[self dismissModalViewControllerAnimated:YES];
}
}
}
Very important:
モーダル ポップアップ ウィンドウを閉じる他の方法がある場合は、タップ ジェスチャ レコグナイザーを削除することを忘れないでください。
タップ認識エンジンがまだイベントを発生させていたので、これを忘れていて、後でクレイジーなクラッシュが発生しました。
AppleのiOSHIGによると、1.モーダルビューには、それ自体に何も入力せずに却下する機能はありません。2.ユーザー入力が必要な状況でモーダルビューを使用します。
これは、ios7、8、およびナビゲーションバーで機能します。
ナビゲーション バーが必要ない場合は、パイプの後の if ステートメントで location2 と 2 番目の条件を削除するだけです。
@MiQUELこれもあなたのために働くはずです
- (void)handleTapBehind:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded)
{
CGPoint location1 = [sender locationInView:self.view];
CGPoint location2 = [sender locationInView:self.navigationController.view];
if (!([self.view pointInside:location1 withEvent:nil] || [self.navigationController.view pointInside:location2 withEvent:nil])) {
[self.view.window removeGestureRecognizer:self.recognizer];
[self dismissViewControllerAnimated:YES completion:nil];
}
}
}
編集:これおよび上記の他のソリューションが機能するには、ジェスチャ認識デリゲートになる必要がある場合もあります。次のようにします。
@interface CommentTableViewController () <UIGestureRecognizerDelegate>
レコグナイザーのデリゲートとして自分自身を設定します。
self.recognizer.delegate = self;
このデリゲート メソッドを実装します。
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}
代わりに UIPresentationController を使用します。
- (void)presentationTransitionWillBegin
{
[super presentationTransitionWillBegin];
UITapGestureRecognizer *dismissGesture=[[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(dismissGestureTapped:)];
[self.containerView addGestureRecognizer:dismissGesture];
[[[self presentedViewController] transitionCoordinator] animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
} completion:nil];
}
- (void) dismissGestureTapped:(UITapGestureRecognizer *)sender{
if (sender.state==UIGestureRecognizerStateEnded&&!CGRectContainsPoint([self frameOfPresentedViewInContainerView], [sender locationInView:sender.view])) {
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}
}
LookInside の例から変更
遅いことは承知していますが、CleanModal の使用を検討してください (iOS 7 および 8 でテスト済み)。
次のようにMZFormSheetControllerを使用できます。
MZFormSheetController *formSheet = [[MZFormSheetController alloc] initWithSize:customSize viewController:presentedViewController];
formSheet.shouldDismissOnBackgroundViewTap = YES;
[presentingViewController mz_presentFormSheetController:formSheet animated:YES completionHandler:nil];