@nativescript/core
Version:
A JavaScript library providing an easy to use api for interacting with iOS and Android platform APIs.
212 lines • 9 kB
JavaScript
import { IOSHelper } from '../ui/core/view/view-helper';
import { SDK_VERSION } from '../utils/constants';
import { CoreTypes } from '../core-types';
import { Trace } from '../trace';
import { NativeWindow } from './native-window-common';
/**
* iOS implementation of NativeWindow.
* Wraps a UIWindow and, when the app is scene-based, the UIWindowScene hosting it.
*/
export class IOSNativeWindow extends NativeWindow {
constructor(scene, window, id, isPrimary = false, role = 'application') {
super(id, isPrimary, role);
/**
* @internal – set while a scene session destruction request is in flight, so the
* following scene disconnect is read as a close rather than a detach.
*/
this._closeRequested = false;
// Only an id taken from the scene's session can be matched back to that session.
// Hand-minted ids ('main', 'embedded-main') are ids all the same, so their mere
// presence says nothing.
const sessionId = scene?.session?.persistentIdentifier;
this._hasSessionIdentity = !!sessionId && id === `${sessionId}`;
this._scene = scene;
this._window = window;
}
/**
* @internal – bind a new scene/window pair to this window session after a detach.
*/
_reattach(scene, uiWindow) {
this._scene = scene;
this._window = uiWindow;
this._setState('attached');
}
get ios() {
return {
scene: this._scene,
uiWindow: this._window,
};
}
/**
* Platform-specific: set the view as root content of this UIWindow.
*/
_setNativeContent(view) {
const controller = this._getViewController(view);
this._setViewControllerView(view);
if (this.role === 'embedded') {
// The host app owns this UIWindow: its rootViewController and key/visible state
// are not ours to change, so the content is handed over as a view controller.
NativeScriptEmbedder.sharedInstance().delegate?.presentNativeScriptApp(controller);
}
else {
const haveController = this._window.rootViewController !== null;
this._window.rootViewController = controller;
if (!haveController) {
this._window.makeKeyAndVisible();
}
}
// Listen for trait collection changes per-window
view.on(IOSHelper.traitCollectionColorAppearanceChangedEvent, () => {
const userInterfaceStyle = controller.traitCollection.userInterfaceStyle;
this._setSystemAppearance(this._getSystemAppearanceValue(userInterfaceStyle));
});
view.on(IOSHelper.traitCollectionLayoutDirectionChangedEvent, () => {
const layoutDirection = controller.traitCollection.layoutDirection;
this._setLayoutDirection(this._getLayoutDirectionValue(layoutDirection));
});
}
/**
* Close this window/scene.
*/
close() {
if (this.isPrimary) {
console.log('NativeWindow: Cannot close the primary window.');
return;
}
const session = this._scene?.session;
if (!session) {
console.log('NativeWindow: Scene has no session to destroy.');
return;
}
const app = UIApplication.sharedApplication;
if (app.requestSceneSessionDestructionOptionsErrorHandler) {
this._closeRequested = true;
app.requestSceneSessionDestructionOptionsErrorHandler(session, null, (error) => {
if (error) {
this._closeRequested = false;
console.log('NativeWindow: Error destroying scene session:', error.localizedDescription);
}
});
}
else {
console.log('NativeWindow: Scene destruction API not available on this iOS version.');
}
}
// --- Platform getters ---
_getOrientation() {
if (__VISIONOS__) {
return this._getOrientationValue(NativeScriptEmbedder.sharedInstance().windowScene?.interfaceOrientation);
}
if (this._scene) {
return this._getOrientationValue(this._scene.interfaceOrientation);
}
return this._getOrientationValue(UIApplication.sharedApplication.statusBarOrientation);
}
_getSystemAppearance() {
if (!__VISIONOS__ && SDK_VERSION <= 11) {
return null;
}
const rootVC = this._window?.rootViewController;
if (!rootVC) {
return null;
}
return this._getSystemAppearanceValue(rootVC.traitCollection.userInterfaceStyle);
}
_getLayoutDirection() {
const rootVC = this._window?.rootViewController;
if (!rootVC) {
return null;
}
return this._getLayoutDirectionValue(rootVC.traitCollection.layoutDirection);
}
// --- Value converters ---
_getOrientationValue(orientation) {
switch (orientation) {
case 3 /* UIInterfaceOrientation.LandscapeRight */:
case 4 /* UIInterfaceOrientation.LandscapeLeft */:
return 'landscape';
case 2 /* UIInterfaceOrientation.PortraitUpsideDown */:
case 1 /* UIInterfaceOrientation.Portrait */:
return 'portrait';
case 0 /* UIInterfaceOrientation.Unknown */:
default:
return 'unknown';
}
}
_getSystemAppearanceValue(userInterfaceStyle) {
switch (userInterfaceStyle) {
case 2 /* UIUserInterfaceStyle.Dark */:
return 'dark';
case 1 /* UIUserInterfaceStyle.Light */:
case 0 /* UIUserInterfaceStyle.Unspecified */:
default:
return 'light';
}
}
_getLayoutDirectionValue(layoutDirection) {
switch (layoutDirection) {
case 1 /* UITraitEnvironmentLayoutDirection.RightToLeft */:
return CoreTypes.LayoutDirection.rtl;
case 0 /* UITraitEnvironmentLayoutDirection.LeftToRight */:
default:
return CoreTypes.LayoutDirection.ltr;
}
}
// --- ViewController helpers ---
_getViewController(rootView) {
let viewController = rootView.viewController || rootView.ios;
if (!(viewController instanceof UIViewController)) {
viewController = IOSHelper.UILayoutViewController.initWithOwner(new WeakRef(rootView));
rootView.viewController = viewController;
}
return viewController;
}
_setViewControllerView(view) {
const viewController = view.viewController || view.ios;
const nativeView = view.ios || view.nativeViewProtected;
if (!nativeView || !viewController) {
throw new Error('Root should be either UIViewController or UIView');
}
if (viewController instanceof IOSHelper.UILayoutViewController) {
viewController.view.addSubview(nativeView);
}
}
_onReleaseRootView(rootView) {
rootView.off(IOSHelper.traitCollectionColorAppearanceChangedEvent);
rootView.off(IOSHelper.traitCollectionLayoutDirectionChangedEvent);
// An embedded window belongs to the host app, so its rootViewController is not ours
// to clear — `_setNativeContent` never set it in the first place.
if (this.role !== 'embedded' && this._window?.rootViewController) {
// The controller's view is moving into another window's hierarchy; leaving it
// installed here has UIKit holding a controller it no longer hosts.
this._window.rootViewController = null;
}
}
_onDestroy() {
// The trait collection listeners live on the root view, so they have to go
// before the base drops the reference to it.
if (this._rootView) {
this._rootView.off(IOSHelper.traitCollectionColorAppearanceChangedEvent);
this._rootView.off(IOSHelper.traitCollectionLayoutDirectionChangedEvent);
}
super._onDestroy();
this._scene = null;
this._window = null;
}
/**
* The window identity of a scene: the session persistent identifier, which iOS keeps
* across a disconnect and hands back when it reconnects the same session.
*
* Returns `undefined` when the scene carries no session identity — such a window gets
* a minted id and will not be recognised on reconnect.
*/
static getSceneId(scene) {
const persistentIdentifier = scene?.session?.persistentIdentifier;
if (persistentIdentifier) {
return `${persistentIdentifier}`;
}
Trace.write('NativeWindow: scene has no session persistentIdentifier; window identity will not survive a reconnect.', Trace.categories.NativeLifecycle, Trace.messageType.error);
return undefined;
}
}
//# sourceMappingURL=native-window.ios.js.map