web-dev-qa-db-ja.com

外部タップでモーダルビューフォームシートコントローラーを閉じる

モーダルビューコントローラーをフォームシートとして提示し、バーボタンアイテムであるキャンセルボタンがクリックされたときにそれを閉じます。そのビューの外側をタップすると、それを閉じる必要があります。参考にしてください。注:私のモーダルビューコントローラーには、ナビゲーションコントローラーが表示されます。

@ cli_hlt、@ Bill Brasky、回答ありがとうございます。フォームシートであるモーダルビューの外部でタップが発生した場合、それを閉じる必要があります。以下にコードを貼り付けています。

-(void)gridView:(AQGridView *)gridView didSelectItemAtIndex:(NSUInteger)index  
{        
    if(adminMode) 
    {
        CHEditEmployeeViewController *editVC = [[CHEditEmployeeViewController alloc] initWithNibName:@"CHEditEmployeeViewController" bundle:nil];
        editVC.delegate = self;
        editVC.pickedEmployee = employee;
        editVC.edit = TRUE;
        editVC.delegate = self;
        UINavigationController *navigationController = [[UINavigationController alloc]initWithRootViewController:editVC];
        navigationController.modalPresentationStyle = UIModalPresentationFormSheet;
        [self presentModalViewController:navigationController animated:YES];

        return;
    }   //the above code is from the view controller which presents the modal     view. Please look at the below code too which is from my modal view controller. Please guide me in a proper way.   -(void)tapGestureRecognizer {

    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 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]) 
        {
            [self dismissModalViewControllerAnimated:YES];
            [self.view.window removeGestureRecognizer:sender];
        }

    }
}
31
jessy

わかったですから、presentModalViewController:メソッドを使用することは不可能だと思います。 「モーダル」ビュー/ウィンドウ/メッセージボックス/などの全体的なアイデア。 pp。isユーザーができませんビュー/ウィンドウ/メッセージボックスなどを処理する以外のことを行うpp。は彼/彼女にしたい。

代わりにやりたいことは、モーダルView Controllerを提示するのではなく、フォームView Controllerを通常の方法でロードして表示することです。マスターコントローラーで、フォームが表示されていることに注意してください。 BOOL変数を使用して、発生する可能性のあるタップを処理します。フォームが表示されている場合は、却下します。

4
cli_hlt

私はこれが古い質問であることを知っていますが、これはIS可能です。「正しい」答えが言っていることにもかかわらず。

これがあなたのやり方です:

モーダル表示する場所、ここでは「tapBehindGesture」からView Controllerにプロパティを追加する必要があります。

その後、viewDidAppear

if(!tapBehindGesture) {
        tapBehindGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapBehindDetected:)];
        [tapBehindGesture setNumberOfTapsRequired:1];
        [tapBehindGesture setCancelsTouchesInView:NO]; //So the user can still interact with controls in the modal view
    }

[self.view.window addGestureRecognizer:tapBehindGesture];

そして、これがtapBehindDetectedの実装です

- (void)tapBehindDetected:(UITapGestureRecognizer *)sender
{

    if (sender.state == UIGestureRecognizerStateEnded)
    {
        //(edited) not working for ios8 above 
        //CGPoint location = [sender locationInView:nil]; //Passing nil gives us coordinates in the window

        CGPoint location = [sender locationInView: self.presentingViewController.view];

        //Convert tap location into the local view's coordinate system. If outside, dismiss the view.
        if (![self.presentedViewController.view pointInside:[self.presentedViewController.view convertPoint:location fromView:self.view.window] withEvent:nil])
        {   
            if(self.presentedViewController) {
                [self dismissViewControllerAnimated:YES completion:nil];
            }
        }
    }
}

割り当てられていないオブジェクトでhandleTapBehindがトリガーされないように、tapBehindGestureview.windowからviewWillDisappearを削除することを忘れないでください。

70
Ares

ジェスチャーレコグナイザーにデリゲートを追加して、iOS 8の問題を解決しました

[taprecognizer setDelegate:self];

これらの応答で

#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;
}

iOS 8 GMで動作する

26

私が知る限り、どの条件でもすぐに機能する答えはないようです。

私の解決策(それを継承するか貼り付ける):

@interface MyViewController () <UIGestureRecognizerDelegate>

@property (strong, nonatomic) UITapGestureRecognizer *tapOutsideRecognizer;

@end

-(void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    if (!self.tapOutsideRecognizer) {
        self.tapOutsideRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapBehind:)];
        self.tapOutsideRecognizer.numberOfTapsRequired = 1;
        self.tapOutsideRecognizer.cancelsTouchesInView = NO;
        self.tapOutsideRecognizer.delegate = self;
        [self.view.window addGestureRecognizer:self.tapOutsideRecognizer];
    }
}

-(void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    // to avoid nasty crashes
    if (self.tapOutsideRecognizer) {
        [self.view.window removeGestureRecognizer:self.tapOutsideRecognizer];
        self.tapOutsideRecognizer = nil;
    }
}

#pragma mark - Actions 

- (IBAction)close:(id)sender
{
    [self dismissViewControllerAnimated:YES completion:nil];
}

- (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 close:sender];
        }
    }
}

#pragma mark - Gesture Recognizer
// because of iOS8
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
    return YES;
}
16
yershuachu

IOS 7およびiOS 8で動作し、座標の条件付き交換を必要としない私のバージョンは次のとおりです。

- (void)handleTapBehind:(UITapGestureRecognizer *)sender
{
    if (sender.state == UIGestureRecognizerStateEnded)
    {
        CGPoint location = [sender locationInView:self.view];

        if (![self.view pointInside:location withEvent:nil]) {
            [self.view.window removeGestureRecognizer:self.recognizer];
            [self dismissViewControllerAnimated:YES completion:nil];
        }
    }
}
14
jixam

縦向きと横向きの両方で動作するSwift 4バージョン-x、yの交換は不要です。

class TapBehindModalViewController: UIViewController, UIGestureRecognizerDelegate {
private var tapOutsideRecognizer: UITapGestureRecognizer!

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    if (self.tapOutsideRecognizer == nil) {
        self.tapOutsideRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.handleTapBehind))
        self.tapOutsideRecognizer.numberOfTapsRequired = 1
        self.tapOutsideRecognizer.cancelsTouchesInView = false
        self.tapOutsideRecognizer.delegate = self
        self.view.window?.addGestureRecognizer(self.tapOutsideRecognizer)
    }
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)

    if(self.tapOutsideRecognizer != nil) {
        self.view.window?.removeGestureRecognizer(self.tapOutsideRecognizer)
        self.tapOutsideRecognizer = nil
    }
}

func close(sender: AnyObject) {
    self.dismiss(animated: true, completion: nil)
}

// MARK: - Gesture methods to dismiss this with tap outside
@objc func handleTapBehind(sender: UITapGestureRecognizer) {
    if (sender.state == UIGestureRecognizerState.ended) {
        let location: CGPoint = sender.location(in: self.view)

        if (!self.view.point(inside: location, with: nil)) {
            self.view.window?.removeGestureRecognizer(sender)
            self.close(sender: sender)
        }
    }
}

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    return true
}

}

12
Jim Conroy

IOS 8の場合、Martinoの回答ごとに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;
}
10
Erich

Bart van Kuik's answer および NavAutoDismiss およびその他のすばらしいスニペットに基づいています。

class DismissableNavigationController: UINavigationController, UIGestureRecognizerDelegate {
    private var tapOutsideRecognizer: UITapGestureRecognizer!

    override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(animated)

        if tapOutsideRecognizer == nil {
            tapOutsideRecognizer = UITapGestureRecognizer(target: self, action: #selector(DismissableNavigationController.handleTapBehind))
            tapOutsideRecognizer.numberOfTapsRequired = 1
            tapOutsideRecognizer.cancelsTouchesInView = false
            tapOutsideRecognizer.delegate = self
            view.window?.addGestureRecognizer(tapOutsideRecognizer)
        }
    }

    override func viewWillDisappear(animated: Bool) {
        super.viewWillDisappear(animated)

        if tapOutsideRecognizer != nil {
            view.window?.removeGestureRecognizer(tapOutsideRecognizer)
            tapOutsideRecognizer = nil
        }
    }

    func close(sender: AnyObject) {
        dismissViewControllerAnimated(true, completion: nil)
    }

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

            if UIInterfaceOrientationIsLandscape(UIApplication.sharedApplication().statusBarOrientation) {
                location = CGPoint(x: location.y, y: location.x)
            }

            if !view.pointInside(view.convertPoint(location, fromView: view.window), withEvent: nil) {
                view.window?.removeGestureRecognizer(sender)
                close(sender)
            }
        }
    }

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

使用法:

let vc = MyViewController()
let nc = DismissableNavigationController(rootViewController: vc)
nc.modalPresentationStyle = UIModalPresentationStyle.FormSheet
presentViewController(nc, animated: true, completion: nil)
6
neoneye

@ yershuachu's answer 、in Swift 2:

class ModalParentViewController: UIViewController, UIGestureRecognizerDelegate {

    private var tapOutsideRecognizer: UITapGestureRecognizer!

    override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(animated)

        if(self.tapOutsideRecognizer == nil) {
            self.tapOutsideRecognizer = UITapGestureRecognizer(target: self, action: "handleTapBehind:")
            self.tapOutsideRecognizer.numberOfTapsRequired = 1
            self.tapOutsideRecognizer.cancelsTouchesInView = false
            self.tapOutsideRecognizer.delegate = self
            self.view.window?.addGestureRecognizer(self.tapOutsideRecognizer)
        }
    }

    override func viewWillDisappear(animated: Bool) {
        super.viewWillDisappear(animated)

        if(self.tapOutsideRecognizer != nil) {
            self.view.window?.removeGestureRecognizer(self.tapOutsideRecognizer)
            self.tapOutsideRecognizer = nil
        }
    }

    func close(sender: AnyObject) {
        self.dismissViewControllerAnimated(true, completion: nil)
    }

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

            if (!self.view.pointInside(self.view.convertPoint(location, fromView: self.view.window), withEvent: nil)) {
                self.view.window?.removeGestureRecognizer(sender)
                self.close(sender)
            }
        }
    }

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

}
6
Bart van Kuik

スイフト3

class ModalParentViewController: UIViewController, UIGestureRecognizerDelegate {

private var tapOutsideRecognizer: UITapGestureRecognizer!

override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)

    if(self.tapOutsideRecognizer == nil) {
        self.tapOutsideRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.handleTapBehind))
        self.tapOutsideRecognizer.numberOfTapsRequired = 1
        self.tapOutsideRecognizer.cancelsTouchesInView = false
        self.tapOutsideRecognizer.delegate = self
        appDelegate.window?.addGestureRecognizer(self.tapOutsideRecognizer)
    }
}

override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(animated)

      if(self.tapOutsideRecognizer != nil) {
        appDelegate.window?.removeGestureRecognizer(self.tapOutsideRecognizer)
        self.tapOutsideRecognizer = nil
    }
}

func close(sender: AnyObject) {
    self.dismiss(animated: true, completion: nil)
}

// MARK: - Gesture methods to dismiss this with tap outside
func handleTapBehind(sender: UITapGestureRecognizer) {
    if (sender.state == UIGestureRecognizerState.ended) {
        let location: CGPoint = sender.location(in: nil)

        if (!self.view.point(inside: self.view.convert(location, from: self.view.window), with: nil)) {
            self.view.window?.removeGestureRecognizer(sender)
            self.close(sender: sender)
        }
    }
}

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    return true
}

}

4

私は、iOS 7.1でもiOS 8.3でも問題なくこの形式で使用しています。

- (void)viewDidAppear:(BOOL)animated
{
     [super viewDidAppear:animated];

     [self.view.window addGestureRecognizer:self.tapBehindGesture];
}

- (void)viewWillDisappear:(BOOL)animated
{
     [super viewWillDisappear:animated];

     [self.view.window removeGestureRecognizer:self.tapBehindGesture];
}

- (UITapGestureRecognizer*)tapBehindGesture
{    
    if (_tapBehindGesture == nil)
    {
        _tapBehindGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapBehindRecognized:)];
        _tapBehindGesture.numberOfTapsRequired = 1;
        _tapBehindGesture.cancelsTouchesInView = NO;
        _tapBehindGesture.delegate = self;
    }

    return _tapBehindGesture;
}

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    return YES;
}
2
olejnjak

IPad用に自動的に閉じるナビゲーションコントローラーを作成しました

https://github.com/curciosobrinho/NavAutoDismiss

上記と同じコードで、iOS 8で動作します。

したがって、すべてのクレジットは上記の人々に割り当てられます。

より一般的で使いやすいようにしただけです。

両方のファイルをプロジェクトにコピーするだけです

ヘッダーファイル(.h)をインポートする

そして、表示したいViewControllerをrootViewControllerとして使用します。

どうやって使うのですか:

//Import the .h file
#import "NavDismissViewController.h"

//Instanciate your view controller (the view you want to present)

YourViewController * yourVC = [YourViewController new];

//Instanciate the NavDismissViewController with your view controller as the rootViewController

NavDismissViewController *nav = [[NavDismissViewController alloc] initWithRootViewController:yourVC];

//if you want to change the navigationBar translucent behaviour

[nav.navigationBar setTranslucent:NO];

//Choose the Modal style

nav.modalPresentationStyle=UIModalPresentationFormSheet;

//present your controller

[self presentViewController:nav animated:YES completion:nil];

//Done
0
Cjs