feat(ios): multi-window support (#10786)

This commit is contained in:
Nathan Walker
2025-11-03 17:01:43 -08:00
committed by GitHub
parent c8c741072f
commit 96733c2ecc
13 changed files with 1623 additions and 71 deletions

View File

@@ -80,6 +80,19 @@ function applySdkVersionClass(rootView: View): void {
const globalEvents = getNativeScriptGlobals().events;
// Scene lifecycle event names
export const SceneEvents = {
sceneWillConnect: 'sceneWillConnect',
sceneDidActivate: 'sceneDidActivate',
sceneWillResignActive: 'sceneWillResignActive',
sceneWillEnterForeground: 'sceneWillEnterForeground',
sceneDidEnterBackground: 'sceneDidEnterBackground',
sceneDidDisconnect: 'sceneDidDisconnect',
sceneContentSetup: 'sceneContentSetup',
};
export type SceneEventName = (typeof SceneEvents)[keyof typeof SceneEvents];
// helper interface to correctly type Application event handlers
interface ApplicationEvents {
off(eventNames: string, callback?: any, thisArg?: any): void;

View File

@@ -216,3 +216,28 @@ export interface AndroidActivityBackPressedEventData extends AndroidActivityEven
export interface LoadAppCSSEventData extends ApplicationEventData {
cssFile: string;
}
/**
* iOS Event data containing information for scene lifecycle events (iOS 13+).
*/
export interface SceneEventData extends ApplicationEventData {
/**
* The UIWindowScene instance associated with this event.
*/
scene?: UIWindowScene;
/**
* The UIWindow associated with this scene (if applicable).
*/
window?: UIWindow;
/**
* Scene connection options (for sceneWillConnect event).
*/
connectionOptions?: UISceneConnectionOptions;
/**
* Additional user info from the notification.
*/
userInfo?: NSDictionary<any, any>;
}

View File

@@ -192,6 +192,72 @@ export class iOSApplication extends ApplicationCommon {
* @param onReceiveCallback A callback function that will be called each time the observer receives a notification.
*/
removeNotificationObserver(observer: any, notificationName: string);
/**
* Checks if the application supports scenes.
*/
supportsScenes(): boolean;
/**
* Checks if the application supports multiple scenes.
*/
supportsMultipleScenes(): boolean;
/**
* Checks if the application is using the scene lifecycle.
*/
isUsingSceneLifecycle(): boolean;
/**
* Opens a new window with the specified data.
* @param data The data to pass to the new window.
*/
openWindow(data: Record<any, any>): void;
/**
* Closes a secondary window/scene.
* If no target is provided, attempts to close a non-primary active scene.
* @param target Optional target to resolve the scene to close. Can be a View, UIWindow, UIWindowScene, or a string scene identifier.
*/
closeWindow(target?: View | UIWindow | UIWindowScene | string): void;
/**
* Gets all windows for the application.
*/
getAllWindows(): UIWindow[];
/**
* Gets all scenes for the application.
*/
getAllScenes(): UIScene[];
/**
* Gets all window scenes for the application.
*/
getWindowScenes(): UIWindowScene[];
/**
* Gets the primary window for the application.
*/
getPrimaryWindow(): UIWindow;
/**
* Gets the primary scene for the application.
*/
getPrimaryScene(): UIWindowScene;
/**
* Sets the root view for a specific window.
* @param window The window to set the root view for.
* @param view The view to set as the root view.
*/
setWindowRootView(window: UIWindow, view: View): void;
/**
* The scene delegate for the application.
* Get the current one or set a custom one.
*/
sceneDelegate: UIWindowSceneDelegate;
}
export const VALID_FONT_SCALES: number[];

View File

@@ -5,9 +5,9 @@ import { IOSHelper } from '../ui/core/view/view-helper';
import type { NavigationEntry } from '../ui/frame/frame-interfaces';
import { getWindow } from '../utils/native-helper';
import { SDK_VERSION } from '../utils/constants';
import { ios as iosUtils } from '../utils/native-helper';
import { ApplicationCommon, initializeSdkVersionClass } from './application-common';
import { ApplicationEventData } from './application-interfaces';
import { ios as iosUtils, dataSerialize } from '../utils/native-helper';
import { ApplicationCommon, initializeSdkVersionClass, SceneEvents } from './application-common';
import { ApplicationEventData, SceneEventData } from './application-interfaces';
import { Observable } from '../data/observable';
import { Trace } from '../trace';
import {
@@ -81,6 +81,31 @@ class CADisplayLinkTarget extends NSObject {
};
}
/**
* Detect if the app supports scenes.
* When an app configures UIApplicationSceneManifest in Info.plist
* it will use scene lifecycle management.
*/
let sceneManifest: NSDictionary<any, any>;
function supportsScenes(): boolean {
if (SDK_VERSION < 13) {
return false;
}
if (typeof sceneManifest === 'undefined') {
// Check if scene manifest exists in Info.plist
sceneManifest = NSBundle.mainBundle.objectForInfoDictionaryKey('UIApplicationSceneManifest');
}
return !!sceneManifest;
}
function supportsMultipleScenes(): boolean {
if (SDK_VERSION < 13) {
return false;
}
return UIApplication.sharedApplication?.supportsMultipleScenes;
}
@NativeClass
class Responder extends UIResponder implements UIApplicationDelegate {
get window(): UIWindow {
@@ -94,10 +119,129 @@ class Responder extends UIResponder implements UIApplicationDelegate {
static ObjCProtocols = [UIApplicationDelegate];
}
if (supportsScenes()) {
/**
* This method is called when a new scene session is being created.
* Important: When this method is implemented, the app assumes scene-based lifecycle management.
* Detected by the Info.plist existence 'UIApplicationSceneManifest'.
* If this method is implemented when there is no manifest defined,
* the app will boot to a white screen.
*/
(Responder.prototype as UIApplicationDelegate).applicationConfigurationForConnectingSceneSessionOptions = function (application: UIApplication, connectingSceneSession: UISceneSession, options: UISceneConnectionOptions): UISceneConfiguration {
const config = UISceneConfiguration.configurationWithNameSessionRole('Default Configuration', connectingSceneSession.role);
config.sceneClass = UIWindowScene as any;
config.delegateClass = SceneDelegate;
return config;
};
// scene session destruction handling
(Responder.prototype as UIApplicationDelegate).applicationDidDiscardSceneSessions = function (application: UIApplication, sceneSessions: NSSet<UISceneSession>): void {
// Note: we could emit an event here if needed
// console.log('Scene sessions discarded:', sceneSessions.count);
};
}
@NativeClass
class SceneDelegate extends UIResponder implements UIWindowSceneDelegate {
static ObjCProtocols = [UIWindowSceneDelegate];
private _window: UIWindow;
private _scene: UIWindowScene;
get window(): UIWindow {
return this._window;
}
set window(value: UIWindow) {
this._window = value;
}
sceneWillConnectToSessionOptions(scene: UIScene, session: UISceneSession, connectionOptions: UISceneConnectionOptions): void {
if (Trace.isEnabled()) {
Trace.write(`SceneDelegate.sceneWillConnectToSessionOptions called with role: ${session.role}`, Trace.categories.NativeLifecycle);
}
if (!(scene instanceof UIWindowScene)) {
// Scene is not a UIWindowScene, ignoring
return;
}
this._scene = scene;
// Create window for this scene
this._window = UIWindow.alloc().initWithWindowScene(scene);
// Store the window scene for this window
Application.ios._setWindowForScene(this._window, scene);
// Set up the window content
Application.ios._setupWindowForScene(this._window, scene);
// Notify that scene will connect
Application.ios.notify({
eventName: SceneEvents.sceneWillConnect,
object: Application.ios,
scene: scene,
window: this._window,
connectionOptions: connectionOptions,
} as SceneEventData);
if (scene === Application.ios.getPrimaryScene()) {
// primary scene, activate right away
this._window.makeKeyAndVisible();
} else {
// For secondary scenes, emit an event to allow developers to set up custom content for the window
Application.ios.notify({
eventName: SceneEvents.sceneContentSetup,
object: Application.ios,
scene: scene,
window: this._window,
connectionOptions: connectionOptions,
} as SceneEventData);
}
// If this is the first scene, trigger app startup
if (!Application.ios.getPrimaryScene()) {
Application.ios._notifySceneAppStarted();
}
}
sceneDidBecomeActive(scene: UIScene): void {
// This will be handled by the notification observer in iOSApplication
// The notification system will automatically trigger sceneDidActivate
}
sceneWillResignActive(scene: UIScene): void {
// Notify that scene will resign active
Application.ios.notify({
eventName: SceneEvents.sceneWillResignActive,
object: Application.ios,
scene: scene,
} as SceneEventData);
}
sceneWillEnterForeground(scene: UIScene): void {
// This will be handled by the notification observer in iOSApplication
}
sceneDidEnterBackground(scene: UIScene): void {
// This will be handled by the notification observer in iOSApplication
}
sceneDidDisconnect(scene: UIScene): void {
// This will be handled by the notification observer in iOSApplication
}
}
// ensure available globally
global.SceneDelegate = SceneDelegate;
export class iOSApplication extends ApplicationCommon {
private _delegate: UIApplicationDelegate;
private _delegateHandlers = new Map<string, Array<Function>>();
private _rootView: View;
private _sceneDelegate: UIWindowSceneDelegate;
private _windowSceneMap = new Map<UIScene, UIWindow>();
private _primaryScene: UIWindowScene | null = null;
private _openedScenesById = new Map<string, UIWindowScene>();
displayedOnce = false;
displayedLinkTarget: CADisplayLinkTarget;
@@ -115,6 +259,15 @@ export class iOSApplication extends ApplicationCommon {
this.addNotificationObserver(UIApplicationWillTerminateNotification, this.willTerminate.bind(this));
this.addNotificationObserver(UIApplicationDidReceiveMemoryWarningNotification, this.didReceiveMemoryWarning.bind(this));
this.addNotificationObserver(UIApplicationDidChangeStatusBarOrientationNotification, this.didChangeStatusBarOrientation.bind(this));
// Add scene lifecycle notification observers only if scenes are supported
if (this.supportsScenes()) {
this.addNotificationObserver('UISceneWillConnectNotification', this.sceneWillConnect.bind(this));
this.addNotificationObserver('UISceneDidActivateNotification', this.sceneDidActivate.bind(this));
this.addNotificationObserver('UISceneWillEnterForegroundNotification', this.sceneWillEnterForeground.bind(this));
this.addNotificationObserver('UISceneDidEnterBackgroundNotification', this.sceneDidEnterBackground.bind(this));
this.addNotificationObserver('UISceneDidDisconnectNotification', this.sceneDidDisconnect.bind(this));
}
}
getRootView(): View {
@@ -388,6 +541,11 @@ export class iOSApplication extends ApplicationCommon {
}
}
// Public method for scene-based app startup
_notifySceneAppStarted() {
this.notifyAppStarted();
}
public _onLivesync(context?: ModuleContext): void {
// Handle application root module
const isAppRootModuleChanged = context && context.path && context.path.includes(this.getMainEntry().moduleName) && context.type !== 'style';
@@ -456,19 +614,26 @@ export class iOSApplication extends ApplicationCommon {
}
}
this.setMaxRefreshRate();
// ensures window is assigned to proper window scene
setiOSWindow(this.window);
if (!getiOSWindow()) {
// if still no window, create one
setiOSWindow(UIWindow.alloc().initWithFrame(UIScreen.mainScreen.bounds));
// Only set up window if NOT using scene-based lifecycle
if (!this.supportsScenes()) {
// Traditional single-window app setup
// ensures window is assigned to proper window scene
setiOSWindow(this.window);
if (!getiOSWindow()) {
// if still no window, create one
setiOSWindow(UIWindow.alloc().initWithFrame(UIScreen.mainScreen.bounds));
}
if (!__VISIONOS__) {
this.window.backgroundColor = SDK_VERSION <= 12 || !UIColor.systemBackgroundColor ? UIColor.whiteColor : UIColor.systemBackgroundColor;
}
this.notifyAppStarted(notification);
} else {
// Scene-based app - window creation will happen in scene delegate
}
if (!__VISIONOS__) {
this.window.backgroundColor = SDK_VERSION <= 12 || !UIColor.systemBackgroundColor ? UIColor.whiteColor : UIColor.systemBackgroundColor;
}
this.notifyAppStarted(notification);
}
@profile
@@ -525,6 +690,489 @@ export class iOSApplication extends ApplicationCommon {
this.setOrientation(newOrientation);
}
// Scene lifecycle notification handlers
private sceneWillConnect(notification: NSNotification) {
const scene = notification.object as UIWindowScene;
if (!scene || !(scene instanceof UIWindowScene)) {
return;
}
// Store as primary scene if it's the first one
if (!this._primaryScene) {
this._primaryScene = scene;
}
this.notify({
eventName: SceneEvents.sceneWillConnect,
object: this,
scene: scene,
userInfo: notification.userInfo,
} as SceneEventData);
}
private sceneDidActivate(notification: NSNotification) {
const scene = notification.object as UIScene;
this.notify({
eventName: SceneEvents.sceneDidActivate,
object: this,
scene: scene,
} as SceneEventData);
// If this is the primary scene, trigger traditional app lifecycle
if (scene === this._primaryScene) {
const additionalData = {
ios: UIApplication.sharedApplication,
scene: scene,
};
this.setInBackground(false, additionalData);
this.setSuspended(false, additionalData);
if (this._rootView && !this._rootView.isLoaded) {
this._rootView.callLoaded();
}
}
}
private sceneWillEnterForeground(notification: NSNotification) {
const scene = notification.object as UIScene;
this.notify({
eventName: SceneEvents.sceneWillEnterForeground,
object: this,
scene: scene,
} as SceneEventData);
}
private sceneDidEnterBackground(notification: NSNotification) {
const scene = notification.object as UIScene;
this.notify({
eventName: SceneEvents.sceneDidEnterBackground,
object: this,
scene: scene,
} as SceneEventData);
// If this is the primary scene, trigger traditional app lifecycle
if (scene === this._primaryScene) {
const additionalData = {
ios: UIApplication.sharedApplication,
scene: scene,
};
this.setInBackground(true, additionalData);
this.setSuspended(true, additionalData);
if (this._rootView && this._rootView.isLoaded) {
this._rootView.callUnloaded();
}
}
}
private sceneDidDisconnect(notification: NSNotification) {
const scene = notification.object as UIScene;
this._removeWindowForScene(scene);
// If primary scene disconnected, clear it
if (scene === this._primaryScene) {
this._primaryScene = null;
}
if (this._primaryScene) {
if (SDK_VERSION >= 17) {
const request = UISceneSessionActivationRequest.requestWithSession(this._primaryScene.session);
UIApplication.sharedApplication.activateSceneSessionForRequestErrorHandler(request, (err: NSError) => {
if (err) {
console.log('Failed to activate primary scene:', err.localizedDescription);
}
});
} else {
UIApplication.sharedApplication.requestSceneSessionActivationUserActivityOptionsErrorHandler(this._primaryScene.session, null, null, (err: NSError) => {
if (err) {
console.log('Failed to activate primary scene (legacy):', err.localizedDescription);
}
});
}
}
this.notify({
eventName: SceneEvents.sceneDidDisconnect,
object: this,
scene: scene,
} as SceneEventData);
}
// Scene management helper methods
_setWindowForScene(window: UIWindow, scene: UIScene): void {
this._windowSceneMap.set(scene, window);
}
_removeWindowForScene(scene: UIScene): void {
this._windowSceneMap.delete(scene);
// also untrack opened scene id
try {
const s: any = scene as any;
if (s && s.session) {
const id = this._getSceneId(s as UIWindowScene);
this._openedScenesById.delete(id);
}
} catch {}
}
_getWindowForScene(scene: UIScene): UIWindow | undefined {
return this._windowSceneMap.get(scene);
}
_setupWindowForScene(window: UIWindow, scene: UIWindowScene): void {
if (!window) {
return;
}
// track opened scene
try {
const id = this._getSceneId(scene);
this._openedScenesById.set(id, scene);
} catch {}
// Set up window background
if (!__VISIONOS__) {
window.backgroundColor = SDK_VERSION <= 12 || !UIColor.systemBackgroundColor ? UIColor.whiteColor : UIColor.systemBackgroundColor;
}
// If this is the primary scene, set up the main application content
if (scene === this._primaryScene || !this._primaryScene) {
this._primaryScene = scene;
if (!getiOSWindow()) {
setiOSWindow(window);
}
// Set up the window content for the primary scene
this.setWindowContent();
}
}
get sceneDelegate(): UIWindowSceneDelegate {
if (!this._sceneDelegate) {
this._sceneDelegate = SceneDelegate.new() as UIWindowSceneDelegate;
}
return this._sceneDelegate;
}
set sceneDelegate(value: UIWindowSceneDelegate) {
this._sceneDelegate = value;
}
/**
* Multi-window support
*/
/**
* Opens a new window with the specified data.
* @param data The data to pass to the new window.
*/
openWindow(data: Record<any, any>) {
if (!supportsMultipleScenes()) {
console.log('Cannot create a new scene - not supported on this device.');
return;
}
try {
const app = UIApplication.sharedApplication;
// iOS 17+
if (SDK_VERSION >= 17) {
// Create a new scene activation request with proper role
let request: UISceneSessionActivationRequest;
try {
// Use the correct factory method to create request with role
// Based on the type definitions, this is the proper way
request = UISceneSessionActivationRequest.requestWithRole(UIWindowSceneSessionRoleApplication);
// Note: may be useful to allow user defined activity type through optional string typed data in future
const activity = NSUserActivity.alloc().initWithActivityType(`${NSBundle.mainBundle.bundleIdentifier}.scene`);
activity.userInfo = dataSerialize(data);
request.userActivity = activity;
// Set proper options with requesting scene
const options = UISceneActivationRequestOptions.new();
// Note: explore secondary windows spawning other windows
// and if this context needs to change in those cases
const mainWindow = Application.ios.getPrimaryWindow();
options.requestingScene = mainWindow?.windowScene;
/**
* Note: This does not work in testing but worth exploring further sometime
* regarding the size/dimensions of opened secondary windows.
* The initial size is ultimately determined by the system
* based on available space and user context.
*/
// Get the size restrictions from the window scene
// const sizeRestrictions = (options.requestingScene as UIWindowScene).sizeRestrictions;
// // Set your minimum and maximum dimensions
// sizeRestrictions.minimumSize = CGSizeMake(320, 400);
// sizeRestrictions.maximumSize = CGSizeMake(600, 800);
request.options = options;
} catch (roleError) {
console.log('Error creating request:', roleError);
return;
}
app.activateSceneSessionForRequestErrorHandler(request, (error) => {
if (error) {
console.log('Error creating new scene (iOS 17+):', error);
// Log additional debugging info
if (error.userInfo) {
console.error(`Error userInfo: ${error.userInfo.description}`);
}
// Handle specific error types
if (error.localizedDescription.includes('role') && error.localizedDescription.includes('nil')) {
this.createSceneWithLegacyAPI(data);
} else if (error.domain === 'FBSWorkspaceErrorDomain' && error.code === 2) {
this.createSceneWithLegacyAPI(data);
}
}
});
}
// iOS 13-16 - Use the legacy requestSceneSessionActivationUserActivityOptionsErrorHandler method
else if (SDK_VERSION >= 13 && SDK_VERSION < 17) {
app.requestSceneSessionActivationUserActivityOptionsErrorHandler(
null, // session
null, // userActivity
null, // options
(error) => {
if (error) {
console.log('Error creating new scene (legacy):', error);
}
},
);
}
// Fallback for older iOS versions or unsupported configurations
else {
console.log('Neither new nor legacy scene activation methods are available');
}
} catch (error) {
console.error('Error requesting new scene:', error);
}
}
/**
* Closes a secondary window/scene.
* Usage examples:
* - Application.ios.closeWindow() // best-effort close of a non-primary scene
* - Application.ios.closeWindow(button) // from a tap handler within the scene
* - Application.ios.closeWindow(window)
* - Application.ios.closeWindow(scene)
* - Application.ios.closeWindow('scene-id')
*/
public closeWindow(target?: View | UIWindow | UIWindowScene | string): void {
if (!__APPLE__) {
return;
}
try {
const scene = this._resolveScene(target);
if (!scene) {
console.log('closeWindow: No scene resolved for target');
return;
}
// Don't allow closing the primary scene
if (scene === this._primaryScene) {
console.log('closeWindow: Refusing to close the primary scene');
return;
}
const session = scene.session;
if (!session) {
console.log('closeWindow: Scene has no session to destroy');
return;
}
const app = UIApplication.sharedApplication;
if (app.requestSceneSessionDestructionOptionsErrorHandler) {
app.requestSceneSessionDestructionOptionsErrorHandler(session, null, (error: NSError) => {
if (error) {
console.log('closeWindow: destruction error', error);
} else {
// clean up tracked id
const id = this._getSceneId(scene);
this._openedScenesById.delete(id);
}
});
} else {
console.info('closeWindow: Scene destruction API not available on this iOS version');
}
} catch (err) {
console.log('closeWindow: Unexpected error', err);
}
}
getAllWindows(): UIWindow[] {
return Array.from(this._windowSceneMap.values());
}
getAllScenes(): UIScene[] {
return Array.from(this._windowSceneMap.keys());
}
getWindowScenes(): UIWindowScene[] {
return this.getAllScenes().filter((scene) => scene instanceof UIWindowScene) as UIWindowScene[];
}
getPrimaryWindow(): UIWindow {
if (this._primaryScene) {
return this._getWindowForScene(this._primaryScene) || getiOSWindow();
}
return getiOSWindow();
}
getPrimaryScene(): UIWindowScene | null {
return this._primaryScene;
}
// Scene lifecycle management
supportsScenes(): boolean {
return supportsScenes();
}
supportsMultipleScenes(): boolean {
return supportsMultipleScenes();
}
isUsingSceneLifecycle(): boolean {
return this.supportsScenes() && this._windowSceneMap.size > 0;
}
// Call this to set up scene-based configuration
configureForScenes(): void {
if (!this.supportsScenes()) {
console.warn('Scene-based lifecycle is only supported on iOS 13+ iPad or visionOS with multi-scene enabled apps.');
return;
}
// Additional scene configuration can be added here
// For now, the notification observers are already set up in the constructor
}
// Stable scene id for lookups
private _getSceneId(scene: UIWindowScene): string {
try {
if (!scene) {
return 'Unknown';
}
// Prefer session persistentIdentifier when available (stable across lifetime)
const session = scene.session;
const persistentId = session && session.persistentIdentifier;
if (persistentId) {
return `${persistentId}`;
}
// Fallbacks
if (scene.hash != null) {
return `${scene.hash}`;
}
const desc = scene.description;
if (desc) {
return `${desc}`;
}
} catch (err) {
// ignore
}
return 'Unknown';
}
// Resolve a UIWindowScene from various input types
private _resolveScene(target?: any): UIWindowScene | null {
if (!__APPLE__) {
return null;
}
if (!target) {
// Try to pick a non-primary foreground active scene, else last known scene
const scenes = this.getWindowScenes?.() || [];
const nonPrimary = scenes.filter((s) => s !== this._primaryScene);
return nonPrimary[0] || scenes[0] || null;
}
// If a View was passed, derive its window.scene
if (target && typeof target === 'object') {
// UIWindowScene
if ((target as UIWindowScene).session && (target as UIWindowScene).activationState !== undefined) {
return target as UIWindowScene;
}
// UIWindow
if ((target as UIWindow).windowScene) {
return (target as UIWindow).windowScene;
}
// NativeScript View
if ((target as View)?.nativeViewProtected) {
const uiView = (target as View).nativeViewProtected as UIView;
const win = uiView?.window as UIWindow;
return win?.windowScene || null;
}
}
// String id lookup
if (typeof target === 'string') {
if (this._openedScenesById.has(target)) {
return this._openedScenesById.get(target);
}
// Try matching by persistentIdentifier or hash among known scenes
const scenes = this.getWindowScenes?.() || [];
for (const s of scenes) {
const sid = this._getSceneId(s);
if (sid === target) {
return s;
}
}
}
return null;
}
private createSceneWithLegacyAPI(data: Record<any, any>) {
const windowScene = this.window?.windowScene;
if (!windowScene) {
return;
}
// Create user activity for the new scene
const userActivity = NSUserActivity.alloc().initWithActivityType(`${NSBundle.mainBundle.bundleIdentifier}.scene`);
userActivity.userInfo = dataSerialize(data);
// Use the legacy API
const options = UISceneActivationRequestOptions.new();
options.requestingScene = windowScene;
UIApplication.sharedApplication.requestSceneSessionActivationUserActivityOptionsErrorHandler(
null, // session - null for new scene
userActivity,
options,
(error: NSError) => {
if (error) {
console.error(`Legacy scene API failed: ${error.localizedDescription}`);
}
},
);
}
/**
* Creates a simple view controller with a NativeScript view for a scene window.
* @param window The UIWindow to set content for
* @param view The NativeScript View to set as root content
*/
setWindowRootView(window: UIWindow, view: View): void {
if (!window || !view) {
return;
}
if (view.ios) {
window.rootViewController = view.viewController;
window.makeKeyAndVisible();
} else {
console.warn('View does not have a native iOS implementation');
}
}
get ios() {
// ensures Application.ios is defined when running on iOS
return this;

View File

@@ -917,7 +917,6 @@ export class View extends ViewCommon {
if (!navigationBar) return;
if (typeof value === 'string') {
console.log('here:', value);
navigationBar.barStyle = value === 'dark' ? UIBarStyle.Black : UIBarStyle.Default;
} else {
navigationBar.barStyle = value;

View File

@@ -16,6 +16,14 @@ global.__APPLE__ = true;
global.__COMMONJS__ = false;
global.WeakRef.prototype.get = global.WeakRef.prototype.deref;
global.NativeClass = function () {};
global.NSBundle = {
mainBundle: {
bundleIdentifier: 'test',
objectForInfoDictionaryKey(key: string) {
return true;
},
},
};
global.NSTimer = class NSTimer {};
global.NSObject = class NSObject {
static new() {
@@ -88,6 +96,7 @@ global.UIInterfaceOrientation = {
LandscapeLeft: 4,
LandscapeRight: 3,
};
global.UIWindowSceneDelegate = function () {};
const cgColors = { CGColor: 1 };
global.UIColor = {
alloc() {

View File

@@ -181,7 +181,7 @@ export default function (config: Config, env: IWebpackEnv = _env): Config {
class FixSourceMapUrlPlugin {
apply(compiler) {
compiler.hooks.emit.tap('FixSourceMapUrlPlugin', (compilation) => {
const leadingCharacter = process.platform === "win32" ? "/":"";
const leadingCharacter = process.platform === 'win32' ? '/' : '';
Object.keys(compilation.assets).forEach((filename) => {
if (filename.endsWith('.mjs') || filename.endsWith('.js')) {
const asset = compilation.assets[filename];