From 05c2460fc4989dae4d7fa1ee52f6d54e0c3113f5 Mon Sep 17 00:00:00 2001 From: Teodor Dermendjiev Date: Wed, 27 Jun 2018 16:48:11 +0300 Subject: [PATCH] feat: Pass NS app to the native app instead of presenting it over the root VC (#5967) * feat: Pass NS app native controller to the native app instead of presenting it over the rootViewController When NativeScript embedded app is created from the native one we check for whether the topmost UIViewController has NativeScriptEmbedder protocol (implemented in the iOS Runtime) method 'presentNativeScriptApp:'. If yes, we call it with the NS app viewcontroller as a parameter so the embedder has control over the NS app (where and how to present it etc.) For backwards compatibility we present the NS app on top of the topmost UIViewController as a fallback. * style: Fix lint errors * feat: Check for protocol instead of selector in embedding I * Check for rootController instead of topViewController to prevent crash if !rootController * feat: Introduce NativeScriptEmbedder singleton NativeScriptEmbedder is responsive for communication between the NS and the native iOS app. His delegate will implement methods which we can call from javascript such as "presentNativeScriptApp:". --- .gitignore | 1 + .../application/application.ios.ts | 24 ++++++++++++++--- tns-core-modules/platforms/README.md | 1 + .../platforms/ios/src/NativeScriptEmbedder.h | 26 +++++++++++++++++++ .../platforms/ios/src/NativeScriptEmbedder.m | 19 ++++++++++++++ .../platforms/ios/src/module.modulemap | 4 +++ tns-core-modules/tns-core-modules.d.ts | 11 ++++++++ tns-core-modules/utils/utils.d.ts | 7 +++++ tns-core-modules/utils/utils.ios.ts | 19 ++++++++++++++ 9 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 tns-core-modules/platforms/README.md create mode 100644 tns-core-modules/platforms/ios/src/NativeScriptEmbedder.h create mode 100644 tns-core-modules/platforms/ios/src/NativeScriptEmbedder.m create mode 100644 tns-core-modules/platforms/ios/src/module.modulemap diff --git a/.gitignore b/.gitignore index 991763a9a..6cd38c8e6 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ libs node_modules package platforms +!tns-core-modules/platforms reports tags diff --git a/tns-core-modules/application/application.ios.ts b/tns-core-modules/application/application.ios.ts index a84397b4d..87a852358 100644 --- a/tns-core-modules/application/application.ios.ts +++ b/tns-core-modules/application/application.ios.ts @@ -131,16 +131,26 @@ class IOSApplication implements IOSApplicationDefinition { // TODO: Expose Window module so that it can we styled from XML & CSS this._window.backgroundColor = utils.ios.getter(UIColor, UIColor.whiteColor); + this.notifyAppStarted(notification); + + } + + public notifyAppStarted(notification?: NSNotification) { const args: LaunchEventData = { eventName: launchEvent, object: this, - ios: notification.userInfo && notification.userInfo.objectForKey("UIApplicationLaunchOptionsLocalNotificationKey") || null + ios: notification && notification.userInfo && notification.userInfo.objectForKey("UIApplicationLaunchOptionsLocalNotificationKey") || null }; notify(args); notify({ eventName: "loadAppCss", object: this, cssFile: getCssFileName() }); - this.setWindowContent(args.root); + // this._window will be undefined when NS app is embedded in a native one + if (this._window) { + this.setWindowContent(args.root); + } else { + this._window = UIApplication.sharedApplication.delegate.window; + } } @profile @@ -235,6 +245,7 @@ class IOSApplication implements IOSApplicationDefinition { this._window.makeKeyAndVisible(); } } + } const iosApp = new IOSApplication(); @@ -296,7 +307,14 @@ export function start(entry?: string | NavigationEntry) { if (rootController) { const controller = getViewController(rootView); rootView._setupAsRootView({}); - rootController.presentViewControllerAnimatedCompletion(controller, true, null); + let embedderDelegate = NativeScriptEmbedder.sharedInstance().delegate; + if (embedderDelegate) { + embedderDelegate.performSelectorWithObject("presentNativeScriptApp:", controller); + } else { + let visibleVC = utils.ios.getVisibleViewController(rootController); + visibleVC.presentViewControllerAnimatedCompletion(controller, true, null); + } + iosApp.notifyAppStarted(); } } } diff --git a/tns-core-modules/platforms/README.md b/tns-core-modules/platforms/README.md new file mode 100644 index 000000000..daac1d36d --- /dev/null +++ b/tns-core-modules/platforms/README.md @@ -0,0 +1 @@ +## Platform specific native code \ No newline at end of file diff --git a/tns-core-modules/platforms/ios/src/NativeScriptEmbedder.h b/tns-core-modules/platforms/ios/src/NativeScriptEmbedder.h new file mode 100644 index 000000000..1a35943b2 --- /dev/null +++ b/tns-core-modules/platforms/ios/src/NativeScriptEmbedder.h @@ -0,0 +1,26 @@ +// +// NativeScriptEmbedder.h +// NativeScript +// +// Created by Teodor Dermendzhiev on 6/19/18. +// +#include + +// When embedding NativeScript application embedder needs to conform to this protocol +// in order to have control over the NativeScript UIViewController +// otherwise NativeScript application is presented over the topmost UIViewController. +@protocol NativeScriptEmbedderDelegate +- (id)presentNativeScriptApp:(UIViewController*)vc; +@end + +@interface NativeScriptEmbedder : NSObject + +@property(nonatomic, retain, readonly) id delegate; + ++ (NativeScriptEmbedder *)sharedInstance; + +- (void)setDelegate:(id )aDelegate; + + +@end + diff --git a/tns-core-modules/platforms/ios/src/NativeScriptEmbedder.m b/tns-core-modules/platforms/ios/src/NativeScriptEmbedder.m new file mode 100644 index 000000000..b19fe206f --- /dev/null +++ b/tns-core-modules/platforms/ios/src/NativeScriptEmbedder.m @@ -0,0 +1,19 @@ +#import "NativeScriptEmbedder.h" + +@implementation NativeScriptEmbedder + ++ (NativeScriptEmbedder *)sharedInstance { + static NativeScriptEmbedder *sharedInstance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [[NativeScriptEmbedder alloc] init]; + }); + + return sharedInstance; +} + +- (void)setDelegate:(id )aDelegate { + _delegate = aDelegate; +} + +@end diff --git a/tns-core-modules/platforms/ios/src/module.modulemap b/tns-core-modules/platforms/ios/src/module.modulemap new file mode 100644 index 000000000..a360bf684 --- /dev/null +++ b/tns-core-modules/platforms/ios/src/module.modulemap @@ -0,0 +1,4 @@ +module NativeScriptEmbedder { + header "NativeScriptEmbedder.h" + export * +} \ No newline at end of file diff --git a/tns-core-modules/tns-core-modules.d.ts b/tns-core-modules/tns-core-modules.d.ts index 3e5b0b7ef..2160ec3fa 100644 --- a/tns-core-modules/tns-core-modules.d.ts +++ b/tns-core-modules/tns-core-modules.d.ts @@ -160,3 +160,14 @@ interface Array { //Dialogs declare function alert(message?: any): void; declare function confirm(message?: string): boolean; + +// Embedding +declare interface NativeScriptEmbedderDelegate /* NSObject */ { + presentNativeScriptApp(any/* UIViewController*/): any; + performSelectorWithObject(string, any): any; +} + +declare class NativeScriptEmbedder { + public static sharedInstance(): NativeScriptEmbedder; + public delegate: NativeScriptEmbedderDelegate; +} diff --git a/tns-core-modules/utils/utils.d.ts b/tns-core-modules/utils/utils.d.ts index 3cb7a8675..5f095b4d4 100644 --- a/tns-core-modules/utils/utils.d.ts +++ b/tns-core-modules/utils/utils.d.ts @@ -247,6 +247,13 @@ export module ios { * iOS - this folder is read-only and contains the app and all its resources. */ export function getCurrentAppPath(): string; + + /** + * Gets the currently visible(topmost) UIViewController. + * @param rootViewController The root UIViewController instance to start searching from (normally window.rootViewController). + * Returns the visible UIViewController. + */ + export function getVisibleViewController(rootViewController: any/* UIViewController*/ ): any/* UIViewController*/; } /** diff --git a/tns-core-modules/utils/utils.ios.ts b/tns-core-modules/utils/utils.ios.ts index 19d96af2a..77da535b7 100644 --- a/tns-core-modules/utils/utils.ios.ts +++ b/tns-core-modules/utils/utils.ios.ts @@ -116,6 +116,25 @@ export module ios { return NSString.stringWithString(NSString.pathWithComponents(paths)).stringByStandardizingPath; } + + export function getVisibleViewController(rootViewController: UIViewController): UIViewController { + if (rootViewController.presentedViewController) { + return getVisibleViewController(rootViewController.presentedViewController); + } + + if (rootViewController.isKindOfClass(UINavigationController.class())) { + return getVisibleViewController((rootViewController).visibleViewController); + } + + if (rootViewController.isKindOfClass(UITabBarController.class())) { + let selectedTab = (rootViewController).selectedViewController; + return getVisibleViewController(rootViewController); + } + + return rootViewController; + + } + } export function GC() {