From 6b69ba19046c7178f8355bc520540f28d3632a38 Mon Sep 17 00:00:00 2001 From: Hristo Hristov Date: Wed, 18 May 2016 15:57:08 +0300 Subject: [PATCH] Fix for https://github.com/NativeScript/NativeScript/issues/2076 (#2135) --- apps/tests/ui/page/page-tests.ios.ts | 54 ++++++++++++++++++++++++++++ ui/frame/frame-common.ts | 2 +- ui/frame/frame.d.ts | 1 + ui/page/page-common.ts | 2 +- ui/page/page.ios.ts | 43 +++++++++++++++------- 5 files changed, 87 insertions(+), 15 deletions(-) diff --git a/apps/tests/ui/page/page-tests.ios.ts b/apps/tests/ui/page/page-tests.ios.ts index 9c296cdf5..8d9fb06b6 100644 --- a/apps/tests/ui/page/page-tests.ios.ts +++ b/apps/tests/ui/page/page-tests.ios.ts @@ -107,4 +107,58 @@ export function test_page_no_anctionBar_measure_no_spanUnderBackground_measure_l contentHeight = lbl._getCurrentLayoutBounds().bottom - lbl._getCurrentLayoutBounds().top; TKUnit.assertTrue(contentHeight < pageHeight, "Page.content be given less space than Page when ActionBar is shown."); +} + +export function test_showing_native_viewcontroller_doesnt_throw_exception() { + let testPage = new Page(); + let loaded = 0; + let unloaded = 0; + let navigatingTo = 0; + let navigatedTo = 0; + let navigatingFrom = 0; + let navigatedFrom = 0; + + testPage.on(Page.loadedEvent, () => loaded++); + testPage.on(Page.unloadedEvent, () => unloaded++); + testPage.on(Page.navigatingToEvent, () => navigatingTo++); + testPage.on(Page.navigatedToEvent, () => navigatedTo++); + testPage.on(Page.navigatingFromEvent, () => navigatingFrom++); + testPage.on(Page.navigatedFromEvent, () => navigatedFrom++); + + helper.navigate(() => { return testPage; }); + + TKUnit.assertEqual(1, navigatingTo, "navigatingTo"); + TKUnit.assertEqual(1, loaded, "navigatingTo"); + TKUnit.assertEqual(1, navigatedTo, "navigatingTo"); + TKUnit.assertEqual(0, navigatingFrom, "navigatingTo"); + TKUnit.assertEqual(0, unloaded, "navigatingTo"); + TKUnit.assertEqual(0, navigatedFrom, "navigatingTo"); + + let page = new Page(); + let navcontroller = frame.topmost().ios.controller; + + let completed = false; + navcontroller.presentViewControllerAnimatedCompletion(page.ios, false, () => completed = true); + TKUnit.waitUntilReady(() => completed); + + TKUnit.assertEqual(testPage.modal, undefined, "testPage.modal should be null"); + + TKUnit.assertEqual(1, navigatingTo, "navigatingTo"); + TKUnit.assertEqual(1, loaded, "navigatingTo"); + TKUnit.assertEqual(1, navigatedTo, "navigatingTo"); + TKUnit.assertEqual(0, navigatingFrom, "navigatingTo"); + TKUnit.assertEqual(0, unloaded, "navigatingTo"); + TKUnit.assertEqual(0, navigatedFrom, "navigatingTo"); + + completed = false; + let controller = page.ios; + controller.dismissViewControllerAnimatedCompletion(false, () => completed = true); + TKUnit.waitUntilReady(() => completed); + + TKUnit.assertEqual(1, navigatingTo, "navigatingTo"); + TKUnit.assertEqual(1, loaded, "navigatingTo"); + TKUnit.assertEqual(1, navigatedTo, "navigatingTo"); + TKUnit.assertEqual(0, navigatingFrom, "navigatingTo"); + TKUnit.assertEqual(0, unloaded, "navigatingTo"); + TKUnit.assertEqual(0, navigatedFrom, "navigatingTo"); } \ No newline at end of file diff --git a/ui/frame/frame-common.ts b/ui/frame/frame-common.ts index dd4ffa5d4..188b33d88 100644 --- a/ui/frame/frame-common.ts +++ b/ui/frame/frame-common.ts @@ -246,7 +246,7 @@ export class Frame extends CustomLayoutView implements definition.Frame { this._updateActionBar(); } - public navigationQueueIsEmpty() { + public navigationQueueIsEmpty(): boolean { return this._navigationQueue.length === 0; } diff --git a/ui/frame/frame.d.ts b/ui/frame/frame.d.ts index 358b6e7ea..fb66b53c1 100644 --- a/ui/frame/frame.d.ts +++ b/ui/frame/frame.d.ts @@ -101,6 +101,7 @@ declare module "ui/frame" { ios: iOSFrame; //@private + navigationQueueIsEmpty(): boolean; navigationBarHeight: number; _processNavigationQueue(page: pages.Page); _updateActionBar(page?: pages.Page); diff --git a/ui/page/page-common.ts b/ui/page/page-common.ts index 2a5c6cf5c..a2684cb63 100644 --- a/ui/page/page-common.ts +++ b/ui/page/page-common.ts @@ -278,7 +278,7 @@ export class Page extends ContentView implements dts.Page { // } - protected _raiseShownModallyEvent() { + public _raiseShownModallyEvent() { let args: dts.ShownModallyData = { eventName: Page.shownModallyEvent, object: this, diff --git a/ui/page/page.ios.ts b/ui/page/page.ios.ts index 597d0df2b..20825c200 100644 --- a/ui/page/page.ios.ts +++ b/ui/page/page.ios.ts @@ -10,16 +10,17 @@ global.moduleMerge(pageCommon, exports); var ENTRY = "_entry"; var DELEGATE = "_delegate"; -function isBackNavigation(frame: any, entry): boolean { +function isBackNavigation(page: Page, entry): boolean { + let frame = page.frame; if (!frame) { return false; } - if (frame._navigationQueue.length === 0) { + if (frame.navigationQueueIsEmpty()) { return true; } else { - var navigationQueue = frame._navigationQueue; + let navigationQueue = (frame)._navigationQueue; for (var i = 0; i < navigationQueue.length; i++) { if (navigationQueue[i].entry === entry) { return navigationQueue[i].isBackNavigation; @@ -117,8 +118,8 @@ class UIViewControllerImpl extends UIViewController { let newEntry = this[ENTRY]; // Don't raise event if currentPage was showing modal page. - if (!page.modal && newEntry && (!frame || frame.currentPage !== page)) { - let isBack = isBackNavigation(frame, newEntry) + if (!page._presentedViewController && newEntry && (!frame || frame.currentPage !== page)) { + let isBack = isBackNavigation(page, newEntry) page.onNavigatingTo(newEntry.entry.context, isBack); } @@ -143,7 +144,7 @@ class UIViewControllerImpl extends UIViewController { page._viewWillDisappear = false; page._enableLoadedEvents = true; - // If page was in backstack or showing modal page it will have parent but it will be in unloaded state so raise loaded here. + // Pages in backstack are unloaded so raise loaded here. if (!page.isLoaded) { page.onLoaded(); } @@ -163,9 +164,9 @@ class UIViewControllerImpl extends UIViewController { let frame = this.navigationController ? (this.navigationController).owner : null; // Skip navigation events if modal page is shown. - if (!page.modal && frame) { + if (!page._presentedViewController && frame) { let newEntry = this[ENTRY]; - let isBack = isBackNavigation(frame, newEntry); + let isBack = isBackNavigation(page, newEntry); // We are on the current page which happens when navigation is canceled so isBack should be false. if (frame.currentPage === page && frame._navigationQueue.length === 0) { isBack = false; @@ -186,6 +187,13 @@ class UIViewControllerImpl extends UIViewController { frame._processNavigationQueue(page); } + + if (!this.presentedViewController) { + // clear presented viewController here only if no presented controller. + // this is needed because in iOS9 the order of events could be - willAppear, willDisappear, didAppear. + // If we clean it when we have viewController then once presented VC is dismissed then + page._presentedViewController = null; + } }; public viewWillDisappear(animated: boolean): void { @@ -195,10 +203,16 @@ class UIViewControllerImpl extends UIViewController { return; } + // Cache presentedViewController if any. We don't want to raise + // navigation events in case of presenting view controller. + if (!page._presentedViewController) { + page._presentedViewController = this.presentedViewController; + } + var frame = page.frame; // Skip navigation events if we are hiding because we are about to show modal page. - if (!page.modal && frame && frame.currentPage === page) { - var isBack = page.frame && (!this.navigationController || !this.navigationController.viewControllers.containsObject(this)); + if (!page._presentedViewController && frame && frame.currentPage === page) { + let isBack = page.frame && (!this.navigationController || !this.navigationController.viewControllers.containsObject(this)); page.onNavigatingFrom(isBack); } @@ -210,7 +224,7 @@ class UIViewControllerImpl extends UIViewController { let page = this._owner.get(); trace.write(page + " viewDidDisappear", trace.categories.Navigation); // Exit if no page or page is hiding because it shows another page modally. - if (!page || page.modal) { + if (!page || page.modal || page._presentedViewController ) { return; } @@ -226,7 +240,9 @@ class UIViewControllerImpl extends UIViewController { // Manually pop backStack when Back button is pressed or navigating back with edge swipe. // Don't pop if we are hiding modally shown page. let frame = page.frame; - if (!modalParent && frame && frame.backStack.length > 0 && (frame)._navigationQueue.length === 0 && frame.currentPage === page) { + // We are not modal page, have frame with backstack and navigation queue is empty and currentPage is closed + // then pop our backstack. + if (!modalParent && frame && frame.backStack.length > 0 && frame.navigationQueueIsEmpty() && frame.currentPage === page) { (frame)._backStack.pop(); } @@ -260,6 +276,7 @@ export class Page extends pageCommon.Page { public _modalParent: Page; public _UIModalPresentationFormSheet: boolean; public _viewWillDisappear: boolean; + public _presentedViewController: UIViewController; // used when our page present native viewController without going through our abstraction. public requestLayout(): void { super.requestLayout(); @@ -345,7 +362,7 @@ export class Page extends pageCommon.Page { protected _hideNativeModalView(parent: Page) { parent.requestLayout(); - parent._ios.dismissModalViewControllerAnimated(utils.ios.MajorVersion >= 8); + parent._ios.dismissModalViewControllerAnimated(utils.ios.MajorVersion >= 7); super._hideNativeModalView(parent); }