ユーザーがモーダルビューの外側をタップしたときにFormSheetPresentationモーダルビューコントローラーを閉じたい...私はこれを実行している多くのアプリ(iPadのebayなど)を見てきましたが、下のビューがタッチから無効になっているため、どうすればいいのかわかりませんモーダルビューがこのように表示されるとき(おそらくポップオーバーとして表示されますか?)...誰か提案がありますか?
私は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はのろわれた、これは便利でしばしば直感的な行動です。
他のアプリは、その外側をクリックしてビューを閉じることができる場合、モーダルビューを使用していません。 UIModalPresentationFormSheets
をこの方法で閉じることはできません。 (実際には、SDK3.2のどのUIModalでもかまいません)。エリアの外側をクリックして閉じることができるのは、UIPopoverController
だけです。 (AppleのiPad HIGに反して)アプリ開発者が背景画面を陰影表示してUIPopoverController
を表示し、UIModalPresentationFormSheets
(または他のUIModal View)のように表示することは非常に可能です。
[...] UIModalPresentationCurrentContextスタイルにより、ビューコントローラは親のプレゼンテーションスタイルを採用できます。各モーダルビューでは、淡色表示の領域に基になるコンテンツが表示されますが、そのコンテンツをタップすることはできません。したがって、ポップオーバーとは異なり、モーダルビューには、ユーザーがモーダルビューを閉じることができるコントロールが含まれている必要があります。
詳細については、開発者サイトのiPadProgrammingGuideを参照してください(ページ46-"モーダルビューのプレゼンテーションスタイルの構成")
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;
}
上記のコードはうまく機能しますが、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の回答を使用するモジュラーソリューション 。また、他の方法でモーダルを却下する場合に発生する可能性のある重要なバグを解決します 前述のとおり 。
注: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];
}
このコードをコピーして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のiOS HIGによると、1.モーダルビューには、それ自体に何も入力しないと閉じることができません。 2.ユーザー入力が必要な状況でモーダルビューを使用します。
これは、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;
}
代わりに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の例から変更
それはかなり実行可能です。
ここを見てください
https://stackoverflow.com/a/26016458/4074557
これは、iPadを自動的に閉じるNavigationController(モーダル)です(外側をタップすると)。
その中のあなたのビューコントローラを使用してください。
それが役に立てば幸い。
遅いのはわかっていますが、CleanModal(iOS 7および8でテスト済み)の使用を検討してください。
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)
}
}
}
}
次のように MZFormSheetController を使用できます。
MZFormSheetController *formSheet = [[MZFormSheetController alloc] initWithSize:customSize viewController:presentedViewController];
formSheet.shouldDismissOnBackgroundViewTap = YES;
[presentingViewController mz_presentFormSheetController:formSheet animated:YES completionHandler:nil];