From c5df2580439c24c57fb364cba91050a00e391c35 Mon Sep 17 00:00:00 2001 From: Alexander Djenkov Date: Wed, 20 Nov 2019 10:00:54 +0200 Subject: [PATCH] feat(modal-view-ios): handle iOS 13 dismiss modal gesture (#8024) * feat(modal-view): introduce cancelable property on ShowModalOptions * fix(modal-view): handle iOS 13 modal dismiss gesture * chore: address PR comments --- .../ui/core/view-base/view-base.d.ts | 5 ++ .../ui/core/view/view.android.ts | 11 +++- nativescript-core/ui/core/view/view.ios.ts | 60 ++++++++++++++++--- 3 files changed, 67 insertions(+), 9 deletions(-) diff --git a/nativescript-core/ui/core/view-base/view-base.d.ts b/nativescript-core/ui/core/view-base/view-base.d.ts index d0618064e..28023a771 100644 --- a/nativescript-core/ui/core/view-base/view-base.d.ts +++ b/nativescript-core/ui/core/view-base/view-base.d.ts @@ -83,10 +83,15 @@ export interface ShowModalOptions { } android?: { /** + * @deprecated Use ShowModalOptions.cancelable instead. * An optional parameter specifying whether the modal view can be dismissed when not in full-screen mode. */ cancelable?: boolean } + /** + * An optional parameter specifying whether the modal view can be dismissed when not in full-screen mode. + */ + cancelable?: boolean } export abstract class ViewBase extends Observable { diff --git a/nativescript-core/ui/core/view/view.android.ts b/nativescript-core/ui/core/view/view.android.ts index 5b42e0047..c6a3132f2 100644 --- a/nativescript-core/ui/core/view/view.android.ts +++ b/nativescript-core/ui/core/view/view.android.ts @@ -640,12 +640,21 @@ export class View extends ViewCommon { args.putInt(DOMID, this._domId); df.setArguments(args); + let cancelable = true; + + if (options.android && (options).android.cancelable !== undefined) { + cancelable = !!(options).android.cancelable; + console.log("ShowModalOptions.android.cancelable is deprecated. Use ShowModalOptions.cancelable instead."); + } + + cancelable = options.cancelable !== undefined ? !!options.cancelable : cancelable; + const dialogOptions: DialogOptions = { owner: this, fullscreen: !!options.fullscreen, animated: !!options.animated, stretched: !!options.stretched, - cancelable: options.android ? !!options.android.cancelable : true, + cancelable: cancelable, shownCallback: () => this._raiseShownModallyEvent(), dismissCallback: () => this.closeModal() }; diff --git a/nativescript-core/ui/core/view/view.ios.ts b/nativescript-core/ui/core/view/view.ios.ts index 83c5e4ce2..ac9cc56d2 100644 --- a/nativescript-core/ui/core/view/view.ios.ts +++ b/nativescript-core/ui/core/view/view.ios.ts @@ -30,6 +30,7 @@ export class View extends ViewCommon { nativeViewProtected: UIView; viewController: UIViewController; private _popoverPresentationDelegate: ios.UIPopoverPresentationControllerDelegateImp; + private _adaptivePresentationDelegate: ios.UIAdaptivePresentationControllerDelegateImp; private _isLaidOut = false; private _hasTransfrom = false; @@ -422,14 +423,19 @@ export class View extends ViewCommon { controller.modalPresentationStyle = presentationStyle; if (presentationStyle === UIModalPresentationStyle.Popover) { - const popoverPresentationController = controller.popoverPresentationController; - this._popoverPresentationDelegate = ios.UIPopoverPresentationControllerDelegateImp.initWithOwnerAndCallback(new WeakRef(this), this._closeModalCallback); - popoverPresentationController.delegate = this._popoverPresentationDelegate; - const view = parent.nativeViewProtected; - // Note: sourceView and sourceRect are needed to specify the anchor location for the popover. - // Note: sourceView should be the button triggering the modal. If it the Page the popover might appear "behind" the page content - popoverPresentationController.sourceView = view; - popoverPresentationController.sourceRect = CGRectMake(0, 0, view.frame.size.width, view.frame.size.height); + this._setupPopoverControllerDelegate(controller, parent); + } + } + + const cancelable = options.cancelable !== undefined ? !!options.cancelable : true; + + if (majorVersion >= 13) { + if (cancelable) { + // Listen for dismiss modal callback. + this._setupAdaptiveControllerDelegate(controller); + } else { + // Prevent users from dismissing the modal. + (controller).modalInPresentation = true; } } @@ -644,6 +650,22 @@ export class View extends ViewCommon { backgroundInternal.hasBorderWidth() || backgroundInternal.hasBorderRadius(); } + + private _setupPopoverControllerDelegate(controller: UIViewController, parent: View) { + const popoverPresentationController = controller.popoverPresentationController; + this._popoverPresentationDelegate = ios.UIPopoverPresentationControllerDelegateImp.initWithOwnerAndCallback(new WeakRef(this), this._closeModalCallback); + popoverPresentationController.delegate = this._popoverPresentationDelegate; + const view = parent.nativeViewProtected; + // Note: sourceView and sourceRect are needed to specify the anchor location for the popover. + // Note: sourceView should be the button triggering the modal. If it the Page the popover might appear "behind" the page content + popoverPresentationController.sourceView = view; + popoverPresentationController.sourceRect = CGRectMake(0, 0, view.frame.size.width, view.frame.size.height); + } + + private _setupAdaptiveControllerDelegate(controller: UIViewController) { + this._adaptivePresentationDelegate = ios.UIAdaptivePresentationControllerDelegateImp.initWithOwnerAndCallback(new WeakRef(this), this._closeModalCallback); + controller.presentationController.delegate = this._adaptivePresentationDelegate; + } } View.prototype._nativeBackgroundState = "unset"; @@ -1019,6 +1041,28 @@ export namespace ios { } } + export class UIAdaptivePresentationControllerDelegateImp extends NSObject implements UIAdaptivePresentationControllerDelegate { + public static ObjCProtocols = [UIAdaptivePresentationControllerDelegate]; + + private owner: WeakRef; + private closedCallback: Function; + + public static initWithOwnerAndCallback(owner: WeakRef, whenClosedCallback: Function): UIAdaptivePresentationControllerDelegateImp { + const instance = super.new(); + instance.owner = owner; + instance.closedCallback = whenClosedCallback; + + return instance; + } + + public presentationControllerDidDismiss(presentationController: UIPresentationController) { + const owner = this.owner.get(); + if (owner && typeof this.closedCallback === "function") { + this.closedCallback(); + } + } + } + export class UIPopoverPresentationControllerDelegateImp extends NSObject implements UIPopoverPresentationControllerDelegate { public static ObjCProtocols = [UIPopoverPresentationControllerDelegate];