@nativescript/core
Version:
A JavaScript library providing an easy to use api for interacting with iOS and Android platform APIs.
596 lines • 27 kB
JavaScript
// Definitions.
import { NavigationType } from '../frame';
// Types.
import { View, IOSHelper } from '../core/view';
import { PageBase, actionBarHiddenProperty, statusBarStyleProperty } from './page-common';
import { profile } from '../../profiling';
import { iOSNativeHelper, layout } from '../../utils';
import { getLastFocusedViewOnPage, isAccessibilityServiceEnabled } from '../../accessibility';
import { SharedTransition } from '../transition/shared-transition';
export * from './page-common';
const ENTRY = '_entry';
const DELEGATE = '_delegate';
const TRANSITION = '_transition';
const NON_ANIMATED_TRANSITION = 'non-animated';
const majorVersion = iOSNativeHelper.MajorVersion;
function isBackNavigationTo(page, entry) {
const frame = page.frame;
if (!frame) {
return false;
}
// if executing context is null here this most probably means back navigation through iOS back button
const navigationContext = frame._executingContext || {
navigationType: NavigationType.back,
};
const isReplace = navigationContext.navigationType === NavigationType.replace;
if (isReplace) {
return false;
}
if (frame.navigationQueueIsEmpty()) {
return true;
}
const navigationQueue = frame._navigationQueue;
for (let i = 0; i < navigationQueue.length; i++) {
if (navigationQueue[i].entry === entry) {
return navigationQueue[i].navigationType === NavigationType.back;
}
}
return false;
}
function isBackNavigationFrom(controller, page) {
if (!page.frame) {
return false;
}
// Controller is cleared or backstack skipped
if (controller.isBackstackCleared || controller.isBackstackSkipped) {
return false;
}
if (controller.navigationController && controller.navigationController.viewControllers.containsObject(controller)) {
return false;
}
return true;
}
var UIViewControllerImpl = /** @class */ (function (_super) {
__extends(UIViewControllerImpl, _super);
function UIViewControllerImpl() {
return _super !== null && _super.apply(this, arguments) || this;
}
Object.defineProperty(UIViewControllerImpl.prototype, "isRunningLayout", {
get: function () {
return this._isRunningLayout !== 0;
},
enumerable: true,
configurable: true
});
UIViewControllerImpl.prototype.startRunningLayout = function () {
this._isRunningLayout++;
};
UIViewControllerImpl.prototype.finishRunningLayout = function () {
this._isRunningLayout--;
this.didFirstLayout = true;
};
UIViewControllerImpl.prototype.runLayout = function (cb) {
try {
this.startRunningLayout();
cb();
}
finally {
this.finishRunningLayout();
}
};
UIViewControllerImpl.initWithOwner = function (owner) {
var controller = UIViewControllerImpl.new();
controller._owner = owner;
controller._isRunningLayout = 0;
controller.didFirstLayout = false;
return controller;
};
UIViewControllerImpl.prototype.viewDidLoad = function () {
_super.prototype.viewDidLoad.call(this);
// Unify translucent and opaque bars layout
// this.edgesForExtendedLayout = UIRectEdgeBottom;
this.extendedLayoutIncludesOpaqueBars = true;
};
UIViewControllerImpl.prototype.viewWillAppear = function (animated) {
var _a;
_super.prototype.viewWillAppear.call(this, animated);
var owner = (_a = this._owner) === null || _a === void 0 ? void 0 : _a.deref();
if (!owner) {
return;
}
var frame = this.navigationController ? this.navigationController.owner : null;
var newEntry = this[ENTRY];
// Don't raise event if currentPage was showing modal page.
if (!owner._presentedViewController && newEntry && (!frame || frame.currentPage !== owner)) {
var isBack = isBackNavigationTo(owner, newEntry);
owner.onNavigatingTo(newEntry.entry.context, isBack, newEntry.entry.bindingContext);
}
if (frame) {
frame._resolvedPage = owner;
if (owner.parent === frame) {
frame._inheritStyles(owner);
}
else {
if (!owner.parent) {
if (!frame._styleScope) {
// Make sure page will have styleScope even if frame don't.
owner._updateStyleScope();
}
frame._addView(owner);
}
else {
throw new Error('Page is already shown on another frame.');
}
}
frame._updateActionBar(owner);
}
// Set autoAdjustScrollInsets in will appear - as early as possible
IOSHelper.updateAutoAdjustScrollInsets(this, owner);
// Pages in backstack are unloaded so raise loaded here.
if (!owner.isLoaded) {
owner.callLoaded();
}
else {
// Note: Handle the case of canceled backstack navigation. (https://github.com/NativeScript/NativeScript/issues/7430)
// In this case viewWillAppear will be executed for the previous page and it will change the ActionBar
// because changes happen in an interactive transition - IOS will animate between the the states.
// If canceled - viewWillAppear will be called for the current page(which is already loaded) and we need to
// update the action bar explicitly, so that it is not left styles as the previous page.
owner.updateWithWillAppear(animated);
}
};
UIViewControllerImpl.prototype.viewDidAppear = function (animated) {
var _a, _b;
_super.prototype.viewDidAppear.call(this, animated);
var owner = (_a = this._owner) === null || _a === void 0 ? void 0 : _a.deref();
if (!owner) {
return;
}
var navigationController = this.navigationController;
var frame = navigationController ? navigationController.owner : null;
// Skip navigation events if modal page is shown.
if (!owner._presentedViewController && frame) {
var newEntry = this[ENTRY];
// frame.setCurrent(...) will reset executing context so retrieve it here
// if executing context is null here this most probably means back navigation through iOS back button
var navigationContext = frame._executingContext || {
navigationType: NavigationType.back,
};
var isReplace = navigationContext.navigationType === NavigationType.replace;
frame.setCurrent(newEntry, navigationContext.navigationType);
if (isReplace) {
var controller = newEntry.resolvedPage.ios;
if (controller) {
var animated_1 = frame._getIsAnimatedNavigation(newEntry.entry);
if (animated_1) {
controller[TRANSITION] = frame._getNavigationTransition(newEntry.entry);
}
else {
controller[TRANSITION] = {
name: NON_ANIMATED_TRANSITION,
};
}
}
}
// If page was shown with custom animation - we need to set the navigationController.delegate to the animatedDelegate.
if ((_b = frame.ios) === null || _b === void 0 ? void 0 : _b.controller) {
frame.ios.controller.delegate = this[DELEGATE];
}
frame._processNavigationQueue(owner);
if (!__VISIONOS__) {
// _processNavigationQueue will shift navigationQueue. Check canGoBack after that.
// Workaround for disabled backswipe on second custom native transition
if (frame.canGoBack()) {
var transitionState = SharedTransition.getState(owner.transitionId);
if (!(transitionState === null || transitionState === void 0 ? void 0 : transitionState.interactive)) {
// only consider when interactive transitions are not enabled
navigationController.interactivePopGestureRecognizer.delegate = navigationController;
navigationController.interactivePopGestureRecognizer.enabled = owner.enableSwipeBackNavigation;
}
}
else {
navigationController.interactivePopGestureRecognizer.enabled = false;
}
}
}
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
owner._presentedViewController = null;
}
};
UIViewControllerImpl.prototype.viewWillDisappear = function (animated) {
var _a;
_super.prototype.viewWillDisappear.call(this, animated);
var owner = (_a = this._owner) === null || _a === void 0 ? void 0 : _a.deref();
if (!owner) {
return;
}
// Cache presentedViewController if any. We don't want to raise
// navigation events in case of presenting view controller.
if (!owner._presentedViewController) {
owner._presentedViewController = this.presentedViewController;
}
var frame = owner.frame;
// Skip navigation events if we are hiding because we are about to show a modal page,
// or because we are closing a modal page,
// or because we are in tab and another controller is selected.
var tab = this.tabBarController;
if (owner.onNavigatingFrom && !owner._presentedViewController && frame && (!this.presentingViewController || frame.backStack.length > 0) && frame.currentPage === owner) {
var willSelectViewController = tab && tab._willSelectViewController;
if (!willSelectViewController || willSelectViewController === tab.selectedViewController) {
var isBack = isBackNavigationFrom(this, owner);
owner.onNavigatingFrom(isBack);
}
}
owner.updateWithWillDisappear(animated);
};
UIViewControllerImpl.prototype.viewDidDisappear = function (animated) {
var _a;
_super.prototype.viewDidDisappear.call(this, animated);
var page = (_a = this._owner) === null || _a === void 0 ? void 0 : _a.deref();
// Exit if no page or page is hiding because it shows another page modally.
if (!page || page.modal || page._presentedViewController) {
return;
}
// Forward navigation does not remove page from frame so we raise unloaded manually.
if (page.isLoaded) {
page.callUnloaded();
}
};
UIViewControllerImpl.prototype.viewWillLayoutSubviews = function () {
var _a;
_super.prototype.viewWillLayoutSubviews.call(this);
var owner = (_a = this._owner) === null || _a === void 0 ? void 0 : _a.deref();
if (owner) {
IOSHelper.updateConstraints(this, owner);
}
};
UIViewControllerImpl.prototype.viewSafeAreaInsetsDidChange = function () {
var _this = this;
var _a;
_super.prototype.viewSafeAreaInsetsDidChange.call(this);
if (this.isRunningLayout || !this.didFirstLayout) {
return;
}
var owner = (_a = this._owner) === null || _a === void 0 ? void 0 : _a.deref();
if (owner) {
this.runLayout(function () { return IOSHelper.layoutView(_this, owner); });
}
};
UIViewControllerImpl.prototype.viewDidLayoutSubviews = function () {
var _a;
this.startRunningLayout();
_super.prototype.viewDidLayoutSubviews.call(this);
var owner = (_a = this._owner) === null || _a === void 0 ? void 0 : _a.deref();
if (owner) {
// layout(owner.actionBar)
// layout(owner.content)
if (majorVersion >= 11) {
// Handle nested Page safe area insets application.
// A Page is nested if its Frame has a parent.
// If the Page is nested, cross check safe area insets on top and bottom with Frame parent.
var frame = owner.parent;
// There is a legacy scenario where Page is not in a Frame - the root of a Modal View, so it has no parent.
var frameParent = frame && frame.parent;
// Handle Angular scenario where TabView is in a ProxyViewContainer
// It is possible to wrap components in ProxyViewContainers indefinitely
// Not using instanceof ProxyViewContainer to avoid circular dependency
// TODO: Try moving UIViewControllerImpl out of page module
while (frameParent && !frameParent.nativeViewProtected) {
frameParent = frameParent.parent;
}
if (frameParent) {
var parentPageInsetsTop = frameParent.nativeViewProtected.safeAreaInsets.top;
var parentPageInsetsBottom = frameParent.nativeViewProtected.safeAreaInsets.bottom;
var currentInsetsTop = this.view.safeAreaInsets.top;
var currentInsetsBottom = this.view.safeAreaInsets.bottom;
// Safe area insets include additional safe area insets too, so subtract old values
if (this.additionalSafeAreaInsets) {
currentInsetsTop -= this.additionalSafeAreaInsets.top;
currentInsetsBottom -= this.additionalSafeAreaInsets.bottom;
}
var additionalInsetsTop = Math.max(parentPageInsetsTop - currentInsetsTop, 0);
var additionalInsetsBottom = Math.max(parentPageInsetsBottom - currentInsetsBottom, 0);
if (additionalInsetsTop > 0 || additionalInsetsBottom > 0) {
var additionalInsets = new UIEdgeInsets({
top: additionalInsetsTop,
left: 0,
bottom: additionalInsetsBottom,
right: 0,
});
this.additionalSafeAreaInsets = additionalInsets;
}
else {
this.additionalSafeAreaInsets = null;
}
}
}
IOSHelper.layoutView(this, owner);
}
this.finishRunningLayout();
};
// Mind implementation for other controllerss
UIViewControllerImpl.prototype.traitCollectionDidChange = function (previousTraitCollection) {
var _a;
_super.prototype.traitCollectionDidChange.call(this, previousTraitCollection);
if (majorVersion >= 13) {
var owner = (_a = this._owner) === null || _a === void 0 ? void 0 : _a.deref();
if (owner && this.traitCollection.hasDifferentColorAppearanceComparedToTraitCollection && this.traitCollection.hasDifferentColorAppearanceComparedToTraitCollection(previousTraitCollection)) {
owner.notify({
eventName: IOSHelper.traitCollectionColorAppearanceChangedEvent,
object: owner,
});
}
}
};
Object.defineProperty(UIViewControllerImpl.prototype, "preferredStatusBarStyle", {
// TODO: a11y
// public accessibilityPerformEscape() {
// const owner = this._owner.get();
// if (!owner) {
// return false;
// }
// console.log('page accessibilityPerformEscape');
// if (owner.onAccessibilityPerformEscape) {
// const result = owner.onAccessibilityPerformEscape();
// return result;
// } else {
// return false;
// }
// }
// @ts-ignore
get: function () {
var _a;
var owner = (_a = this._owner) === null || _a === void 0 ? void 0 : _a.deref();
if (owner) {
return owner.statusBarStyle === 'dark' ? UIStatusBarStyle.LightContent : UIStatusBarStyle.Default;
}
else {
return UIStatusBarStyle.Default;
}
},
enumerable: true,
configurable: true
});
__decorate([
profile,
__metadata("design:type", Function),
__metadata("design:paramtypes", [Boolean]),
__metadata("design:returntype", void 0)
], UIViewControllerImpl.prototype, "viewDidAppear", null);
__decorate([
profile,
__metadata("design:type", Function),
__metadata("design:paramtypes", [Boolean]),
__metadata("design:returntype", void 0)
], UIViewControllerImpl.prototype, "viewWillDisappear", null);
__decorate([
profile,
__metadata("design:type", Function),
__metadata("design:paramtypes", [Boolean]),
__metadata("design:returntype", void 0)
], UIViewControllerImpl.prototype, "viewDidDisappear", null);
return UIViewControllerImpl;
}(UIViewController));
export class Page extends PageBase {
constructor() {
super();
this._backgroundColor = majorVersion <= 12 && !UIColor.systemBackgroundColor ? UIColor.whiteColor : UIColor.systemBackgroundColor;
const controller = UIViewControllerImpl.initWithOwner(new WeakRef(this));
controller.view.backgroundColor = this._backgroundColor;
this.viewController = this._ios = controller;
}
createNativeView() {
return this.viewController.view;
}
disposeNativeView() {
this.viewController = null;
this._ios = null;
super.disposeNativeView();
}
// @ts-ignore
get ios() {
return this._ios;
}
layoutNativeView(left, top, right, bottom) {
//
}
_setNativeViewFrame(nativeView, frame) {
//
}
_shouldDelayLayout() {
return this.frame && this.frame._animationInProgress;
}
onLoaded() {
super.onLoaded();
if (this.hasActionBar) {
this.actionBar.update();
}
}
updateWithWillAppear(animated) {
// this method is important because it allows plugins to react to modal page close
// for example allowing updating status bar background color
if (this.hasActionBar) {
this.actionBar.update();
}
this.updateStatusBar();
}
updateWithWillDisappear(animated) {
// this method is important because it allows plugins to react to modal page close
// for example allowing updating status bar background color
}
updateStatusBar() {
this._updateStatusBarStyle(this.statusBarStyle);
}
_updateStatusBarStyle(value) {
const frame = this.frame;
if (frame && value) {
const navigationController = frame.ios.controller;
const navigationBar = navigationController.navigationBar;
navigationBar.barStyle = value === 'dark' ? 1 /* UIBarStyle.Black */ : 0 /* UIBarStyle.Default */;
}
}
_updateEnableSwipeBackNavigation(enabled) {
const navController = this._ios.navigationController;
if (this.frame && navController && navController.interactivePopGestureRecognizer) {
// Make sure we don't set true if cannot go back
enabled = enabled && this.frame.canGoBack();
navController.interactivePopGestureRecognizer.enabled = enabled;
}
}
onMeasure(widthMeasureSpec, heightMeasureSpec) {
const width = layout.getMeasureSpecSize(widthMeasureSpec);
const widthMode = layout.getMeasureSpecMode(widthMeasureSpec);
const height = layout.getMeasureSpecSize(heightMeasureSpec);
const heightMode = layout.getMeasureSpecMode(heightMeasureSpec);
if (this.hasActionBar && this.frame && this.frame._getNavBarVisible(this)) {
const { width, height } = this.actionBar._getActualSize;
const widthSpec = layout.makeMeasureSpec(width, layout.EXACTLY);
const heightSpec = layout.makeMeasureSpec(height, layout.EXACTLY);
View.measureChild(this, this.actionBar, widthSpec, heightSpec);
}
const result = View.measureChild(this, this.layoutView, widthMeasureSpec, heightMeasureSpec);
const measureWidth = Math.max(result.measuredWidth, this.effectiveMinWidth);
const measureHeight = Math.max(result.measuredHeight, this.effectiveMinHeight);
const widthAndState = View.resolveSizeAndState(measureWidth, width, widthMode, 0);
const heightAndState = View.resolveSizeAndState(measureHeight, height, heightMode, 0);
this.setMeasuredDimension(widthAndState, heightAndState);
}
onLayout(left, top, right, bottom) {
if (this.hasActionBar) {
const { width: actionBarWidth, height: actionBarHeight } = this.actionBar._getActualSize;
View.layoutChild(this, this.actionBar, 0, 0, actionBarWidth, actionBarHeight);
}
const insets = this.getSafeAreaInsets();
if (majorVersion <= 10) {
// iOS 10 and below don't have safe area insets API,
// there we need only the top inset on the Page
insets.top = layout.round(layout.toDevicePixels(this.viewController.view.safeAreaLayoutGuide.layoutFrame.origin.y));
}
const childLeft = 0 + insets.left;
const childTop = 0 + insets.top;
const childRight = right - insets.right;
const childBottom = bottom - insets.bottom;
View.layoutChild(this, this.layoutView, childLeft, childTop, childRight, childBottom);
}
_addViewToNativeVisualTree(child, atIndex) {
// ActionBar is handled by the UINavigationController
if (this.hasActionBar && child === this.actionBar) {
return true;
}
const nativeParent = this.nativeViewProtected;
const nativeChild = child.nativeViewProtected;
const viewController = child.ios instanceof UIViewController ? child.ios : child.viewController;
if (viewController) {
// Adding modal controllers to as child will make app freeze.
if (this.viewController.presentedViewController === viewController) {
return true;
}
this.viewController.addChildViewController(viewController);
}
if (nativeParent && nativeChild) {
if (typeof atIndex !== 'number' || atIndex >= nativeParent.subviews.count) {
nativeParent.addSubview(nativeChild);
}
else {
nativeParent.insertSubviewAtIndex(nativeChild, atIndex);
}
return true;
}
return false;
}
_removeViewFromNativeVisualTree(child) {
// ActionBar is handled by the UINavigationController
if (this.hasActionBar && child === this.actionBar) {
return;
}
const viewController = child.ios instanceof UIViewController ? child.ios : child.viewController;
if (viewController) {
viewController.removeFromParentViewController();
}
super._removeViewFromNativeVisualTree(child);
}
[actionBarHiddenProperty.setNative](value) {
this._updateEnableSwipeBackNavigation(value);
// Invalidate all inner controller.
invalidateTopmostController(this.viewController);
const frame = this.frame;
if (frame) {
// Update nav-bar visibility with disabled animations
frame._updateActionBar(this, true);
}
}
[statusBarStyleProperty.getDefault]() {
return 0 /* UIBarStyle.Default */;
}
[statusBarStyleProperty.setNative](value) {
const frame = this.frame;
if (frame) {
const navigationBar = frame.ios.controller.navigationBar;
if (typeof value === 'string') {
navigationBar.barStyle = value === 'dark' ? 1 /* UIBarStyle.Black */ : 0 /* UIBarStyle.Default */;
}
else {
navigationBar.barStyle = value;
}
}
}
accessibilityScreenChanged(refocus = false) {
if (!isAccessibilityServiceEnabled()) {
return;
}
if (refocus) {
const lastFocusedView = getLastFocusedViewOnPage(this);
if (lastFocusedView) {
const uiView = lastFocusedView.nativeViewProtected;
if (uiView) {
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, uiView);
return;
}
}
}
if (this.actionBarHidden) {
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, this.nativeViewProtected);
return;
}
if (this.accessibilityLabel) {
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, this.nativeViewProtected);
return;
}
if (this.actionBar.accessibilityLabel || this.actionBar.title) {
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, this.actionBar.nativeView);
return;
}
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, this.nativeViewProtected);
}
}
function invalidateTopmostController(controller) {
if (!controller) {
return;
}
controller.view.setNeedsLayout();
const presentedViewController = controller.presentedViewController;
if (presentedViewController) {
return invalidateTopmostController(presentedViewController);
}
const childControllers = controller.childViewControllers;
let size = controller.childViewControllers.count;
while (size > 0) {
const childController = childControllers[--size];
if (childController instanceof UITabBarController) {
invalidateTopmostController(childController.selectedViewController);
}
else if (childController instanceof UINavigationController) {
invalidateTopmostController(childController.topViewController);
}
else if (childController instanceof UISplitViewController) {
invalidateTopmostController(childController.viewControllers.lastObject);
}
else {
invalidateTopmostController(childController);
}
}
}
//# sourceMappingURL=index.ios.js.map