UNPKG

@nativescript/core

Version:

A JavaScript library providing an easy to use api for interacting with iOS and Android platform APIs.

344 lines • 17.9 kB
// Requires import { ViewHelper } from './view-helper-common'; import { SDK_VERSION } from '../../../../utils/constants'; import { ios as iOSUtils, layout } from '../../../../utils'; import { Trace } from '../../../../trace'; export * from './view-helper-common'; export const AndroidHelper = 0; var UILayoutViewController = /** @class */ (function (_super) { __extends(UILayoutViewController, _super); function UILayoutViewController() { return _super !== null && _super.apply(this, arguments) || this; } UILayoutViewController.initWithOwner = function (owner) { var controller = UILayoutViewController.new(); controller.owner = owner; return controller; }; UILayoutViewController.prototype.viewDidLoad = function () { _super.prototype.viewDidLoad.call(this); // Unify translucent and opaque bars layout // this.edgesForExtendedLayout = UIRectEdgeBottom; this.extendedLayoutIncludesOpaqueBars = true; }; UILayoutViewController.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); } }; UILayoutViewController.prototype.viewDidLayoutSubviews = function () { var _a; _super.prototype.viewDidLayoutSubviews.call(this); var owner = (_a = this.owner) === null || _a === void 0 ? void 0 : _a.deref(); if (owner) { if (iOSUtils.MajorVersion >= 11) { // Handle nested UILayoutViewController safe area application. // Currently, UILayoutViewController can be nested only in a TabView. // The TabView itself is handled by the OS, so we check the TabView's parent (usually a Page, but can be a Layout). var tabViewItem = owner.parent; var tabView = tabViewItem && tabViewItem.parent; var parent = tabView && tabView.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 UILayoutViewController out of view module while (parent && !parent.nativeViewProtected) { parent = parent.parent; } if (parent) { var parentPageInsetsTop = parent.nativeViewProtected.safeAreaInsets.top; var parentPageInsetsBottom = parent.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); } }; UILayoutViewController.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; } IOSHelper.updateAutoAdjustScrollInsets(this, owner); if (!owner.isLoaded && !owner.parent) { owner.callLoaded(); } }; UILayoutViewController.prototype.viewDidDisappear = function (animated) { var _a; _super.prototype.viewDidDisappear.call(this, animated); var owner = (_a = this.owner) === null || _a === void 0 ? void 0 : _a.deref(); if (owner && owner.isLoaded && !owner.parent) { owner.callUnloaded(); } }; // Mind implementation for other controllers UILayoutViewController.prototype.traitCollectionDidChange = function (previousTraitCollection) { var _a; _super.prototype.traitCollectionDidChange.call(this, previousTraitCollection); if (iOSUtils.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, }); } } }; return UILayoutViewController; }(UIViewController)); var UIAdaptivePresentationControllerDelegateImp = /** @class */ (function (_super) { __extends(UIAdaptivePresentationControllerDelegateImp, _super); function UIAdaptivePresentationControllerDelegateImp() { return _super !== null && _super.apply(this, arguments) || this; } UIAdaptivePresentationControllerDelegateImp.initWithOwnerAndCallback = function (owner, whenClosedCallback) { var instance = _super.new.call(this); instance.owner = owner; instance.closedCallback = whenClosedCallback; return instance; }; UIAdaptivePresentationControllerDelegateImp.prototype.presentationControllerDidDismiss = function (presentationController) { var _a; var owner = (_a = this.owner) === null || _a === void 0 ? void 0 : _a.deref(); if (owner && typeof this.closedCallback === 'function') { this.closedCallback(); } }; UIAdaptivePresentationControllerDelegateImp.ObjCProtocols = [UIAdaptivePresentationControllerDelegate]; return UIAdaptivePresentationControllerDelegateImp; }(NSObject)); var UIPopoverPresentationControllerDelegateImp = /** @class */ (function (_super) { __extends(UIPopoverPresentationControllerDelegateImp, _super); function UIPopoverPresentationControllerDelegateImp() { return _super !== null && _super.apply(this, arguments) || this; } UIPopoverPresentationControllerDelegateImp.initWithOwnerAndCallback = function (owner, whenClosedCallback) { var instance = _super.new.call(this); instance.owner = owner; instance.closedCallback = whenClosedCallback; return instance; }; UIPopoverPresentationControllerDelegateImp.prototype.popoverPresentationControllerDidDismissPopover = function (popoverPresentationController) { var _a; var owner = (_a = this.owner) === null || _a === void 0 ? void 0 : _a.deref(); if (owner && typeof this.closedCallback === 'function') { this.closedCallback(); } }; UIPopoverPresentationControllerDelegateImp.ObjCProtocols = [UIPopoverPresentationControllerDelegate]; return UIPopoverPresentationControllerDelegateImp; }(NSObject)); export class IOSHelper { static getParentWithViewController(view) { while (view && !view.viewController) { view = view.parent; } // Note: Might return undefined if no parent with viewController is found return view; } static updateAutoAdjustScrollInsets(controller, owner) { if (!__VISIONOS__ && SDK_VERSION <= 10) { owner._automaticallyAdjustsScrollViewInsets = false; // This API is deprecated, but has no alternative for <= iOS 10 // Defaults to true and results to appliyng the insets twice together with our logic // for iOS 11+ we use the contentInsetAdjustmentBehavior property in scrollview // https://developer.apple.com/documentation/uikit/uiviewcontroller/1621372-automaticallyadjustsscrollviewin controller.automaticallyAdjustsScrollViewInsets = false; } } /** * This method simulates the iOS 11+ safeAreaLayoutGuide property and its constraints for older versions. * * @param controller * @param owner */ static updateConstraints(controller, owner) { if (!__VISIONOS__ && SDK_VERSION <= 10) { if (!controller.view.safeAreaLayoutGuide) { IOSHelper.initLayoutGuide(controller); } } } /** * This method simulates the iOS 11+ safeAreaLayoutGuide property for older versions. * * @param controller */ static initLayoutGuide(controller) { const rootView = controller.view; if (!rootView.safeAreaLayoutGuide) { const layoutGuide = UILayoutGuide.new(); rootView.addLayoutGuide(layoutGuide); NSLayoutConstraint.activateConstraints([layoutGuide.topAnchor.constraintEqualToAnchor(controller.topLayoutGuide.bottomAnchor), layoutGuide.bottomAnchor.constraintEqualToAnchor(controller.bottomLayoutGuide.topAnchor), layoutGuide.leadingAnchor.constraintEqualToAnchor(rootView.leadingAnchor), layoutGuide.trailingAnchor.constraintEqualToAnchor(rootView.trailingAnchor)]); rootView.safeAreaLayoutGuide = layoutGuide; } return rootView.safeAreaLayoutGuide; } static layoutView(controller, owner) { let layoutGuide = controller.view.safeAreaLayoutGuide; if (!layoutGuide) { Trace.write(`safeAreaLayoutGuide during layout of ${owner}. Creating fallback constraints, but layout might be wrong.`, Trace.categories.Layout, Trace.messageType.error); layoutGuide = IOSHelper.initLayoutGuide(controller); } const safeArea = layoutGuide.layoutFrame; let position = IOSHelper.getPositionFromFrame(safeArea); const safeAreaSize = safeArea.size; const hasChildViewControllers = controller.childViewControllers.count > 0; if (hasChildViewControllers) { const fullscreen = controller.view.frame; position = IOSHelper.getPositionFromFrame(fullscreen); } const safeAreaWidth = layout.round(layout.toDevicePixels(safeAreaSize.width)); const safeAreaHeight = layout.round(layout.toDevicePixels(safeAreaSize.height)); const widthSpec = layout.makeMeasureSpec(safeAreaWidth, layout.EXACTLY); const heightSpec = layout.makeMeasureSpec(safeAreaHeight, layout.EXACTLY); ViewHelper.measureChild(null, owner, widthSpec, heightSpec); ViewHelper.layoutChild(null, owner, position.left, position.top, position.right, position.bottom); if (owner.parent) { owner.parent._layoutParent(); } } static getPositionFromFrame(frame) { const left = layout.round(layout.toDevicePixels(frame.origin.x)); const top = layout.round(layout.toDevicePixels(frame.origin.y)); const right = layout.round(layout.toDevicePixels(frame.origin.x + frame.size.width)); const bottom = layout.round(layout.toDevicePixels(frame.origin.y + frame.size.height)); return { left, right, top, bottom }; } static getFrameFromPosition(position, insets) { insets = insets || { left: 0, top: 0, right: 0, bottom: 0 }; const left = layout.toDeviceIndependentPixels(position.left + insets.left); const top = layout.toDeviceIndependentPixels(position.top + insets.top); let width = layout.toDeviceIndependentPixels(position.right - position.left - insets.left - insets.right); let height = layout.toDeviceIndependentPixels(position.bottom - position.top - insets.top - insets.bottom); if (width < 0) { width = 0; } if (height < 0) { height = 0; } return CGRectMake(left, top, width, height); } static shrinkToSafeArea(view, frame) { const insets = view.getSafeAreaInsets(); if (insets.left || insets.top) { const position = IOSHelper.getPositionFromFrame(frame); const adjustedFrame = IOSHelper.getFrameFromPosition(position, insets); if (Trace.isEnabled()) { Trace.write(`${view} :shrinkToSafeArea: ${JSON.stringify(IOSHelper.getPositionFromFrame(adjustedFrame))}`, Trace.categories.Layout); } return adjustedFrame; } return null; } static expandBeyondSafeArea(view, frame) { const availableSpace = IOSHelper.getAvailableSpaceFromParent(view, frame); const safeArea = availableSpace.safeArea; const fullscreen = availableSpace.fullscreen; const inWindow = availableSpace.inWindow; const position = IOSHelper.getPositionFromFrame(frame); const safeAreaPosition = IOSHelper.getPositionFromFrame(safeArea); const fullscreenPosition = IOSHelper.getPositionFromFrame(fullscreen); const inWindowPosition = IOSHelper.getPositionFromFrame(inWindow); const adjustedPosition = position; if (position.left && inWindowPosition.left <= safeAreaPosition.left) { adjustedPosition.left = fullscreenPosition.left; } if (position.top && inWindowPosition.top <= safeAreaPosition.top) { adjustedPosition.top = fullscreenPosition.top; } if (inWindowPosition.right < fullscreenPosition.right && inWindowPosition.right >= safeAreaPosition.right + fullscreenPosition.left) { adjustedPosition.right += fullscreenPosition.right - inWindowPosition.right; } if (inWindowPosition.bottom < fullscreenPosition.bottom && inWindowPosition.bottom >= safeAreaPosition.bottom + fullscreenPosition.top) { adjustedPosition.bottom += fullscreenPosition.bottom - inWindowPosition.bottom; } const adjustedFrame = CGRectMake(layout.toDeviceIndependentPixels(adjustedPosition.left), layout.toDeviceIndependentPixels(adjustedPosition.top), layout.toDeviceIndependentPixels(adjustedPosition.right - adjustedPosition.left), layout.toDeviceIndependentPixels(adjustedPosition.bottom - adjustedPosition.top)); if (Trace.isEnabled()) { Trace.write(view + ' :expandBeyondSafeArea: ' + JSON.stringify(IOSHelper.getPositionFromFrame(adjustedFrame)), Trace.categories.Layout); } return adjustedFrame; } static getAvailableSpaceFromParent(view, frame) { if (!view) { return; } let scrollView = null; let viewControllerView = null; if (view.viewController) { viewControllerView = view.viewController.view; } else { let parent = view.parent; while (parent && !parent.viewController && !(parent.nativeViewProtected instanceof UIScrollView)) { parent = parent.parent; } if (parent.nativeViewProtected instanceof UIScrollView) { scrollView = parent.nativeViewProtected; } else if (parent.viewController) { viewControllerView = parent.viewController.view; } } let fullscreen = null; let safeArea = null; let controllerInWindow = { x: 0, y: 0 }; if (viewControllerView) { safeArea = viewControllerView.safeAreaLayoutGuide.layoutFrame; fullscreen = viewControllerView.frame; controllerInWindow = viewControllerView.convertPointToView(viewControllerView.bounds.origin, null); } else if (scrollView) { const insets = scrollView.safeAreaInsets; safeArea = CGRectMake(insets.left, insets.top, scrollView.contentSize.width - insets.left - insets.right, scrollView.contentSize.height - insets.top - insets.bottom); fullscreen = CGRectMake(0, 0, scrollView.contentSize.width, scrollView.contentSize.height); } // We take into account the controller position inside the window. // for example with a bottomsheet the controller will be "offset" const locationInWindow = view.getLocationInWindow(); let inWindowLeft = locationInWindow.x - controllerInWindow.x; let inWindowTop = locationInWindow.y - controllerInWindow.y; if (scrollView) { inWindowLeft += scrollView.contentOffset.x; inWindowTop += scrollView.contentOffset.y; } const inWindow = CGRectMake(inWindowLeft, inWindowTop, frame.size.width, frame.size.height); return { safeArea: safeArea, fullscreen: fullscreen, inWindow: inWindow, }; } } IOSHelper.traitCollectionColorAppearanceChangedEvent = 'traitCollectionColorAppearanceChanged'; IOSHelper.UILayoutViewController = UILayoutViewController; IOSHelper.UIAdaptivePresentationControllerDelegateImp = UIAdaptivePresentationControllerDelegateImp; IOSHelper.UIPopoverPresentationControllerDelegateImp = UIPopoverPresentationControllerDelegateImp; //# sourceMappingURL=index.ios.js.map