web-dev-qa-db-ja.com

iphone SDKがipad上のモーダルViewControllersの外側をクリックして閉じる

ユーザーがモーダルビューの外側をタップしたときにFormSheetPresentationモーダルビューコントローラーを閉じたい...私はこれを実行している多くのアプリ(iPadのebayなど)を見てきましたが、下のビューがタッチから無効になっているため、どうすればいいのかわかりませんモーダルビューがこのように表示されるとき(おそらくポップオーバーとして表示されますか?)...誰か提案がありますか?

53
Daniel

私は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];
        }
     }
}

それだけです。 HIGはのろわれた、これは便利でしばしば直感的な行動です。

127
Danilo Campos

他のアプリは、その外側をクリックしてビューを閉じることができる場合、モーダルビューを使用していません。 UIModalPresentationFormSheetsをこの方法で閉じることはできません。 (実際には、SDK3.2のどのUIModalでもかまいません)。エリアの外側をクリックして閉じることができるのは、UIPopoverControllerだけです。 (AppleのiPad HIGに反して)アプリ開発者が背景画面を陰影表示してUIPopoverControllerを表示し、UIModalPresentationFormSheets(または他のUIModal View)のように表示することは非常に可能です。

[...] UIModalPresentationCurrentContextスタイルにより、ビューコントローラは親のプレゼンテーションスタイルを採用できます。各モーダルビューでは、淡色表示の領域に基になるコンテンツが表示されますが、そのコンテンツをタップすることはできません。したがって、ポップオーバーとは異なり、モーダルビューには、ユーザーがモーダルビューを閉じることができるコントロールが含まれている必要があります。

詳細については、開発者サイトのiPadProgrammingGuideを参照してください(ページ46-"モーダルビューのプレゼンテーションスタイルの構成")

11
Jann

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;
}
11
Erich

上記のコードはうまく機能しますが、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];
    }

これにより、ナビゲーションバーを引き続き操作できるようになります。それ以外の場合は、タップしてモーダルビューを閉じます。

10
Keith

iOS 8用に更新された回答

どうやら、iOS 8では、UIDimmingViewにタップジェスチャー認識機能があり、初期実装を妨害しているため、無視して失敗する必要はありません。


これはスピードの時代なので、ほとんどはおそらく上記のコードをコピーしているだけです。しかし、残念ながら、コードに関して言えば、OCDに悩まされています。

ここでは、カテゴリを使用したDanilo Camposの回答を使用するモジュラーソリューション 。また、他の方法でモーダルを却下する場合に発生する可能性のある重要なバグを解決します 前述のとおり

注:ifステートメントはiPhoneとiPadの両方でView Controllerを使用しており、iPadのみが登録/登録解除する必要があるためです。

UPDATE:Gistが更新されました。すごい 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];
}
9
Mazyod

このコードをコピーして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];
        }
    }
}
8
Samidjo

Very important:モーダルポップアップウィンドウを閉じる方法が他にある場合は、タップジェスチャー認識機能を忘れずに削除してください。

タップレコグナイザがまだイベントを発生させていたため、これを忘れて、後でクレイジークラッシュしました。

3
foddex

AppleのiOS HIGによると、1.モーダルビューには、それ自体に何も入力しないと閉じることができません。 2.ユーザー入力が必要な状況でモーダルビューを使用します。

2
Raymond Wang

これは、iOS 7、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;
}
1
Alexandre G

代わりに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の例から変更

1
yhlin

それはかなり実行可能です。

ここを見てください

https://stackoverflow.com/a/26016458/4074557

これは、iPadを自動的に閉じるNavigationController(モーダル)です(外側をタップすると)。

その中のあなたのビューコントローラを使用してください。

それが役に立てば幸い。

0
Cjs

遅いのはわかっていますが、CleanModal(iOS 7および8でテスト済み)の使用を検討してください。

https://github.com/orafaelreis/CleanModal

0
orafaelreis

Swift 2/Xcode Version 7.2(7C68))では、次のコードがうまくいきました。

注意:このコードは、提示されたFormSheetまたはページシートのViewController.Swiftファイルに配置する必要があります。ここでは、「PageSheetViewController.Swift」

class PageSheetViewController: UIViewController, UIGestureRecognizerDelegate {

    override func viewDidAppear(animated: Bool) {
        let recognizer = UITapGestureRecognizer(target: self, action:Selector("handleTapBehind:"))
        recognizer.delegate = self
        recognizer.numberOfTapsRequired = 1
        recognizer.cancelsTouchesInView = false
        self.view.window?.addGestureRecognizer(recognizer)
    }

    func gestureRecognizer(sender: UIGestureRecognizer,
        shouldRecognizeSimultaneouslyWithGestureRecognizer:UIGestureRecognizer) -> Bool {
            return true
    }

    func handleTapBehind(sender:UIGestureRecognizer) {
        if(sender.state == UIGestureRecognizerState.Ended){
            var location:CGPoint = sender.locationInView(nil)

            // detect iOS Version 8.0 or greater
            let Device = UIDevice.currentDevice()
            let iosVersion = Double(Device.systemVersion) ?? 0
            let iOS8 = iosVersion >= 8

            if (iOS8) {
                // in landscape view you will have to swap the location coordinates
                if(UIInterfaceOrientationIsLandscape(UIApplication.sharedApplication().statusBarOrientation)){
                    location = CGPointMake(location.y, location.x);
                }
            }

            if(!self.view.pointInside(self.view.convertPoint(location, fromView: self.view.window), withEvent: nil)){
                self.view.window?.removeGestureRecognizer(sender)
                self.dismissViewControllerAnimated(true, completion: nil)
            }
        }
    }
}
0
Roland Krinner

次のように MZFormSheetController を使用できます。

MZFormSheetController *formSheet = [[MZFormSheetController alloc] initWithSize:customSize viewController:presentedViewController];
formSheet.shouldDismissOnBackgroundViewTap = YES;
[presentingViewController mz_presentFormSheetController:formSheet animated:YES completionHandler:nil];
0
Maxim Pavlov