chore: format

This commit is contained in:
Nathan Walker
2020-07-05 14:57:06 -07:00
parent 8f2900ad0b
commit da3c0b973d
379 changed files with 71587 additions and 54582 deletions

View File

@@ -1,63 +1,108 @@
import { View } from "..";
export class ViewHelper {
/**
* Measure a child by taking into account its margins and a given measureSpecs.
* @param parent This parameter is not used. You can pass null.
* @param child The view to be measured.
* @param measuredWidth The measured width that the parent layout specifies for this view.
* @param measuredHeight The measured height that the parent layout specifies for this view.
*/
public static measureChild(parent: View, child: View, widthMeasureSpec: number, heightMeasureSpec: number): { measuredWidth: number; measuredHeight: number };
/**
* Measure a child by taking into account its margins and a given measureSpecs.
* @param parent This parameter is not used. You can pass null.
* @param child The view to be measured.
* @param measuredWidth The measured width that the parent layout specifies for this view.
* @param measuredHeight The measured height that the parent layout specifies for this view.
*/
public static measureChild(
parent: View,
child: View,
widthMeasureSpec: number,
heightMeasureSpec: number
): { measuredWidth: number; measuredHeight: number };
/**
* Layout a child by taking into account its margins, horizontal and vertical alignments and a given bounds.
* @param parent This parameter is not used. You can pass null.
* @param left Left position, relative to parent
* @param top Top position, relative to parent
* @param right Right position, relative to parent
* @param bottom Bottom position, relative to parent
*/
public static layoutChild(parent: View, child: View, left: number, top: number, right: number, bottom: number): void;
/**
* Layout a child by taking into account its margins, horizontal and vertical alignments and a given bounds.
* @param parent This parameter is not used. You can pass null.
* @param left Left position, relative to parent
* @param top Top position, relative to parent
* @param right Right position, relative to parent
* @param bottom Bottom position, relative to parent
*/
public static layoutChild(
parent: View,
child: View,
left: number,
top: number,
right: number,
bottom: number
): void;
/**
* Utility to reconcile a desired size and state, with constraints imposed
* by a MeasureSpec. Will take the desired size, unless a different size
* is imposed by the constraints. The returned value is a compound integer,
* with the resolved size in the MEASURED_SIZE_MASK bits and
* optionally the bit MEASURED_STATE_TOO_SMALL set if the resulting
* size is smaller than the size the view wants to be.
*/
public static resolveSizeAndState(size: number, specSize: number, specMode: number, childMeasuredState: number): number;
/**
* Utility to reconcile a desired size and state, with constraints imposed
* by a MeasureSpec. Will take the desired size, unless a different size
* is imposed by the constraints. The returned value is a compound integer,
* with the resolved size in the MEASURED_SIZE_MASK bits and
* optionally the bit MEASURED_STATE_TOO_SMALL set if the resulting
* size is smaller than the size the view wants to be.
*/
public static resolveSizeAndState(
size: number,
specSize: number,
specMode: number,
childMeasuredState: number
): number;
public static combineMeasuredStates(curState: number, newState): number;
public static combineMeasuredStates(curState: number, newState): number;
}
export namespace IOSHelper {
/**
* String value used when hooking to traitCollectionColorAppearanceChangedEvent event.
*/
export const traitCollectionColorAppearanceChangedEvent: string;
/**
* String value used when hooking to traitCollectionColorAppearanceChangedEvent event.
*/
export const traitCollectionColorAppearanceChangedEvent: string;
/**
* Returns a view with viewController or undefined if no such found along the view's parent chain.
* @param view The view form which to start the search.
*/
export function getParentWithViewController(view: View): View
export function updateAutoAdjustScrollInsets(controller: any /* UIViewController */, owner: View): void
export function updateConstraints(controller: any /* UIViewController */, owner: View): void;
export function layoutView(controller: any /* UIViewController */, owner: View): void;
export function getPositionFromFrame(frame: any /* CGRect */): { left, top, right, bottom };
export function getFrameFromPosition(position: { left, top, right, bottom }, insets?: { left, top, right, bottom }): any /* CGRect */;
export function shrinkToSafeArea(view: View, frame: any /* CGRect */): any /* CGRect */;
export function expandBeyondSafeArea(view: View, frame: any /* CGRect */): any /* CGRect */;
export class UILayoutViewController {
public static initWithOwner(owner: WeakRef<View>): UILayoutViewController;
}
export class UIAdaptivePresentationControllerDelegateImp {
public static initWithOwnerAndCallback(owner: WeakRef<View>, whenClosedCallback: Function): UIAdaptivePresentationControllerDelegateImp;
}
export class UIPopoverPresentationControllerDelegateImp {
public static initWithOwnerAndCallback(owner: WeakRef<View>, whenClosedCallback: Function): UIPopoverPresentationControllerDelegateImp;
}
/**
* Returns a view with viewController or undefined if no such found along the view's parent chain.
* @param view The view form which to start the search.
*/
export function getParentWithViewController(view: View): View;
export function updateAutoAdjustScrollInsets(
controller: any /* UIViewController */,
owner: View
): void;
export function updateConstraints(
controller: any /* UIViewController */,
owner: View
): void;
export function layoutView(
controller: any /* UIViewController */,
owner: View
): void;
export function getPositionFromFrame(
frame: any /* CGRect */
): { left; top; right; bottom };
export function getFrameFromPosition(
position: { left; top; right; bottom },
insets?: { left; top; right; bottom }
): any; /* CGRect */
export function shrinkToSafeArea(
view: View,
frame: any /* CGRect */
): any; /* CGRect */
export function expandBeyondSafeArea(
view: View,
frame: any /* CGRect */
): any; /* CGRect */
export class UILayoutViewController {
public static initWithOwner(
owner: WeakRef<View>
): UILayoutViewController;
}
export class UIAdaptivePresentationControllerDelegateImp {
public static initWithOwnerAndCallback(
owner: WeakRef<View>,
whenClosedCallback: Function
): UIAdaptivePresentationControllerDelegateImp;
}
export class UIPopoverPresentationControllerDelegateImp {
public static initWithOwnerAndCallback(
owner: WeakRef<View>,
whenClosedCallback: Function
): UIPopoverPresentationControllerDelegateImp;
}
}

View File

@@ -3,362 +3,518 @@ import { View } from "..";
// Requires
import { ViewHelper } from "./view-helper-common";
import {
ios as iosUtils,
layout
} from "../../../../utils/utils";
import {
Trace
} from "../../../../trace";
import { ios as iosUtils, layout } from "../../../../utils/utils";
import { Trace } from "../../../../trace";
export * from "./view-helper-common";
const majorVersion = iosUtils.MajorVersion;
export namespace IOSHelper {
export const traitCollectionColorAppearanceChangedEvent = "traitCollectionColorAppearanceChanged";
export function getParentWithViewController(view: View): View {
while (view && !view.viewController) {
view = view.parent as View;
}
// Note: Might return undefined if no parent with viewController is found
return view;
}
export function updateAutoAdjustScrollInsets(controller: UIViewController, owner: View): void {
if (majorVersion <= 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;
}
}
export function updateConstraints(controller: UIViewController, owner: View): void {
if (majorVersion <= 10) {
const layoutGuide = initLayoutGuide(controller);
(<any>controller.view).safeAreaLayoutGuide = layoutGuide;
}
}
function initLayoutGuide(controller: UIViewController) {
const rootView = controller.view;
const layoutGuide = UILayoutGuide.alloc().init();
rootView.addLayoutGuide(layoutGuide);
NSLayoutConstraint.activateConstraints(<any>[
layoutGuide.topAnchor.constraintEqualToAnchor(controller.topLayoutGuide.bottomAnchor),
layoutGuide.bottomAnchor.constraintEqualToAnchor(controller.bottomLayoutGuide.topAnchor),
layoutGuide.leadingAnchor.constraintEqualToAnchor(rootView.leadingAnchor),
layoutGuide.trailingAnchor.constraintEqualToAnchor(rootView.trailingAnchor)
]);
return layoutGuide;
}
export function layoutView(controller: UIViewController, owner: View): void {
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 = 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();
}
}
export function getPositionFromFrame(frame: CGRect): { left, top, right, bottom } {
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 };
}
export function getFrameFromPosition(position: { left, top, right, bottom }, insets?: { left, top, right, bottom }): CGRect {
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);
const width = layout.toDeviceIndependentPixels(position.right - position.left - insets.left - insets.right);
const height = layout.toDeviceIndependentPixels(position.bottom - position.top - insets.top - insets.bottom);
return CGRectMake(left, top, width, height);
}
export function shrinkToSafeArea(view: View, frame: CGRect): CGRect {
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(this + " :shrinkToSafeArea: " + JSON.stringify(IOSHelper.getPositionFromFrame(adjustedFrame)), Trace.categories.Layout);
}
return adjustedFrame;
}
return null;
}
export function expandBeyondSafeArea(view: View, frame: CGRect): CGRect {
const availableSpace = 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;
}
function getAvailableSpaceFromParent(view: View, frame: CGRect): { safeArea: CGRect, fullscreen: CGRect, inWindow: CGRect } {
if (!view) {
return;
}
let scrollView = null;
let viewControllerView = null;
if (view.viewController) {
viewControllerView = view.viewController.view;
} else {
let parent = view.parent as View;
while (parent && !parent.viewController && !(parent.nativeViewProtected instanceof UIScrollView)) {
parent = parent.parent as View;
}
if (parent.nativeViewProtected instanceof UIScrollView) {
scrollView = parent.nativeViewProtected;
} else if (parent.viewController) {
viewControllerView = parent.viewController.view;
}
}
let fullscreen = null;
let safeArea = null;
if (viewControllerView) {
safeArea = viewControllerView.safeAreaLayoutGuide.layoutFrame;
fullscreen = viewControllerView.frame;
}
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);
}
const locationInWindow = view.getLocationInWindow();
let inWindowLeft = locationInWindow.x;
let inWindowTop = locationInWindow.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 };
}
export class UILayoutViewController extends UIViewController {
public owner: WeakRef<View>;
public static initWithOwner(owner: WeakRef<View>): UILayoutViewController {
const controller = <UILayoutViewController>UILayoutViewController.new();
controller.owner = owner;
return controller;
}
public viewDidLoad(): void {
super.viewDidLoad();
// Unify translucent and opaque bars layout
// this.edgesForExtendedLayout = UIRectEdgeBottom;
this.extendedLayoutIncludesOpaqueBars = true;
}
public viewWillLayoutSubviews(): void {
super.viewWillLayoutSubviews();
const owner = this.owner.get();
if (owner) {
updateConstraints(this, owner);
}
}
public viewDidLayoutSubviews(): void {
super.viewDidLayoutSubviews();
const owner = this.owner.get();
if (owner) {
if (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).
const tabViewItem = owner.parent;
const tabView = tabViewItem && tabViewItem.parent;
let 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) {
const parentPageInsetsTop = parent.nativeViewProtected.safeAreaInsets.top;
const currentInsetsTop = this.view.safeAreaInsets.top;
const additionalInsetsTop = Math.max(parentPageInsetsTop - currentInsetsTop, 0);
const parentPageInsetsBottom = parent.nativeViewProtected.safeAreaInsets.bottom;
const currentInsetsBottom = this.view.safeAreaInsets.bottom;
const additionalInsetsBottom = Math.max(parentPageInsetsBottom - currentInsetsBottom, 0);
if (additionalInsetsTop > 0 || additionalInsetsBottom > 0) {
const additionalInsets = new UIEdgeInsets({ top: additionalInsetsTop, left: 0, bottom: additionalInsetsBottom, right: 0 });
this.additionalSafeAreaInsets = additionalInsets;
}
}
}
layoutView(this, owner);
}
}
public viewWillAppear(animated: boolean): void {
super.viewWillAppear(animated);
const owner = this.owner.get();
if (!owner) {
return;
}
updateAutoAdjustScrollInsets(this, owner);
if (!owner.parent) {
owner.callLoaded();
}
}
public viewDidDisappear(animated: boolean): void {
super.viewDidDisappear(animated);
const owner = this.owner.get();
if (owner && !owner.parent) {
owner.callUnloaded();
}
}
// Mind implementation for other controllers
public traitCollectionDidChange(previousTraitCollection: UITraitCollection): void {
super.traitCollectionDidChange(previousTraitCollection);
if (majorVersion >= 13) {
const owner = this.owner.get();
if (owner &&
this.traitCollection.hasDifferentColorAppearanceComparedToTraitCollection &&
this.traitCollection.hasDifferentColorAppearanceComparedToTraitCollection(previousTraitCollection)) {
owner.notify({ eventName: traitCollectionColorAppearanceChangedEvent, object: owner });
}
}
}
}
export class UIAdaptivePresentationControllerDelegateImp extends NSObject implements UIAdaptivePresentationControllerDelegate {
public static ObjCProtocols = [UIAdaptivePresentationControllerDelegate];
private owner: WeakRef<View>;
private closedCallback: Function;
public static initWithOwnerAndCallback(owner: WeakRef<View>, whenClosedCallback: Function): UIAdaptivePresentationControllerDelegateImp {
const instance = <UIAdaptivePresentationControllerDelegateImp>super.new();
instance.owner = owner;
instance.closedCallback = whenClosedCallback;
return instance;
}
public presentationControllerDidDismiss(presentationController: UIPresentationController) {
const owner = this.owner.get();
if (owner && typeof this.closedCallback === "function") {
this.closedCallback();
}
}
}
export class UIPopoverPresentationControllerDelegateImp extends NSObject implements UIPopoverPresentationControllerDelegate {
public static ObjCProtocols = [UIPopoverPresentationControllerDelegate];
private owner: WeakRef<View>;
private closedCallback: Function;
public static initWithOwnerAndCallback(owner: WeakRef<View>, whenClosedCallback: Function): UIPopoverPresentationControllerDelegateImp {
const instance = <UIPopoverPresentationControllerDelegateImp>super.new();
instance.owner = owner;
instance.closedCallback = whenClosedCallback;
return instance;
}
public popoverPresentationControllerDidDismissPopover(popoverPresentationController: UIPopoverPresentationController) {
const owner = this.owner.get();
if (owner && typeof this.closedCallback === "function") {
this.closedCallback();
}
}
}
}
export const traitCollectionColorAppearanceChangedEvent =
"traitCollectionColorAppearanceChanged";
export function getParentWithViewController(view: View): View {
while (view && !view.viewController) {
view = view.parent as View;
}
// Note: Might return undefined if no parent with viewController is found
return view;
}
export function updateAutoAdjustScrollInsets(
controller: UIViewController,
owner: View
): void {
if (majorVersion <= 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;
}
}
export function updateConstraints(
controller: UIViewController,
owner: View
): void {
if (majorVersion <= 10) {
const layoutGuide = initLayoutGuide(controller);
(<any>controller.view).safeAreaLayoutGuide = layoutGuide;
}
}
function initLayoutGuide(controller: UIViewController) {
const rootView = controller.view;
const layoutGuide = UILayoutGuide.alloc().init();
rootView.addLayoutGuide(layoutGuide);
NSLayoutConstraint.activateConstraints(<any>[
layoutGuide.topAnchor.constraintEqualToAnchor(
controller.topLayoutGuide.bottomAnchor
),
layoutGuide.bottomAnchor.constraintEqualToAnchor(
controller.bottomLayoutGuide.topAnchor
),
layoutGuide.leadingAnchor.constraintEqualToAnchor(
rootView.leadingAnchor
),
layoutGuide.trailingAnchor.constraintEqualToAnchor(
rootView.trailingAnchor
),
]);
return layoutGuide;
}
export function layoutView(
controller: UIViewController,
owner: View
): void {
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 = 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();
}
}
export function getPositionFromFrame(
frame: CGRect
): { left; top; right; bottom } {
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 };
}
export function getFrameFromPosition(
position: { left; top; right; bottom },
insets?: { left; top; right; bottom }
): CGRect {
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);
const width = layout.toDeviceIndependentPixels(
position.right - position.left - insets.left - insets.right
);
const height = layout.toDeviceIndependentPixels(
position.bottom - position.top - insets.top - insets.bottom
);
return CGRectMake(left, top, width, height);
}
export function shrinkToSafeArea(view: View, frame: CGRect): CGRect {
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(
this +
" :shrinkToSafeArea: " +
JSON.stringify(
IOSHelper.getPositionFromFrame(adjustedFrame)
),
Trace.categories.Layout
);
}
return adjustedFrame;
}
return null;
}
export function expandBeyondSafeArea(view: View, frame: CGRect): CGRect {
const availableSpace = 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;
}
function getAvailableSpaceFromParent(
view: View,
frame: CGRect
): { safeArea: CGRect; fullscreen: CGRect; inWindow: CGRect } {
if (!view) {
return;
}
let scrollView = null;
let viewControllerView = null;
if (view.viewController) {
viewControllerView = view.viewController.view;
} else {
let parent = view.parent as View;
while (
parent &&
!parent.viewController &&
!(parent.nativeViewProtected instanceof UIScrollView)
) {
parent = parent.parent as View;
}
if (parent.nativeViewProtected instanceof UIScrollView) {
scrollView = parent.nativeViewProtected;
} else if (parent.viewController) {
viewControllerView = parent.viewController.view;
}
}
let fullscreen = null;
let safeArea = null;
if (viewControllerView) {
safeArea = viewControllerView.safeAreaLayoutGuide.layoutFrame;
fullscreen = viewControllerView.frame;
} 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
);
}
const locationInWindow = view.getLocationInWindow();
let inWindowLeft = locationInWindow.x;
let inWindowTop = locationInWindow.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,
};
}
export class UILayoutViewController extends UIViewController {
public owner: WeakRef<View>;
public static initWithOwner(
owner: WeakRef<View>
): UILayoutViewController {
const controller = <UILayoutViewController>(
UILayoutViewController.new()
);
controller.owner = owner;
return controller;
}
public viewDidLoad(): void {
super.viewDidLoad();
// Unify translucent and opaque bars layout
// this.edgesForExtendedLayout = UIRectEdgeBottom;
this.extendedLayoutIncludesOpaqueBars = true;
}
public viewWillLayoutSubviews(): void {
super.viewWillLayoutSubviews();
const owner = this.owner.get();
if (owner) {
updateConstraints(this, owner);
}
}
public viewDidLayoutSubviews(): void {
super.viewDidLayoutSubviews();
const owner = this.owner.get();
if (owner) {
if (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).
const tabViewItem = owner.parent;
const tabView = tabViewItem && tabViewItem.parent;
let 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) {
const parentPageInsetsTop =
parent.nativeViewProtected.safeAreaInsets.top;
const currentInsetsTop = this.view.safeAreaInsets.top;
const additionalInsetsTop = Math.max(
parentPageInsetsTop - currentInsetsTop,
0
);
const parentPageInsetsBottom =
parent.nativeViewProtected.safeAreaInsets.bottom;
const currentInsetsBottom = this.view.safeAreaInsets
.bottom;
const additionalInsetsBottom = Math.max(
parentPageInsetsBottom - currentInsetsBottom,
0
);
if (
additionalInsetsTop > 0 ||
additionalInsetsBottom > 0
) {
const additionalInsets = new UIEdgeInsets({
top: additionalInsetsTop,
left: 0,
bottom: additionalInsetsBottom,
right: 0,
});
this.additionalSafeAreaInsets = additionalInsets;
}
}
}
layoutView(this, owner);
}
}
public viewWillAppear(animated: boolean): void {
super.viewWillAppear(animated);
const owner = this.owner.get();
if (!owner) {
return;
}
updateAutoAdjustScrollInsets(this, owner);
if (!owner.parent) {
owner.callLoaded();
}
}
public viewDidDisappear(animated: boolean): void {
super.viewDidDisappear(animated);
const owner = this.owner.get();
if (owner && !owner.parent) {
owner.callUnloaded();
}
}
// Mind implementation for other controllers
public traitCollectionDidChange(
previousTraitCollection: UITraitCollection
): void {
super.traitCollectionDidChange(previousTraitCollection);
if (majorVersion >= 13) {
const owner = this.owner.get();
if (
owner &&
this.traitCollection
.hasDifferentColorAppearanceComparedToTraitCollection &&
this.traitCollection.hasDifferentColorAppearanceComparedToTraitCollection(
previousTraitCollection
)
) {
owner.notify({
eventName: traitCollectionColorAppearanceChangedEvent,
object: owner,
});
}
}
}
}
export class UIAdaptivePresentationControllerDelegateImp extends NSObject
implements UIAdaptivePresentationControllerDelegate {
public static ObjCProtocols = [
UIAdaptivePresentationControllerDelegate,
];
private owner: WeakRef<View>;
private closedCallback: Function;
public static initWithOwnerAndCallback(
owner: WeakRef<View>,
whenClosedCallback: Function
): UIAdaptivePresentationControllerDelegateImp {
const instance = <UIAdaptivePresentationControllerDelegateImp>(
super.new()
);
instance.owner = owner;
instance.closedCallback = whenClosedCallback;
return instance;
}
public presentationControllerDidDismiss(
presentationController: UIPresentationController
) {
const owner = this.owner.get();
if (owner && typeof this.closedCallback === "function") {
this.closedCallback();
}
}
}
export class UIPopoverPresentationControllerDelegateImp extends NSObject
implements UIPopoverPresentationControllerDelegate {
public static ObjCProtocols = [UIPopoverPresentationControllerDelegate];
private owner: WeakRef<View>;
private closedCallback: Function;
public static initWithOwnerAndCallback(
owner: WeakRef<View>,
whenClosedCallback: Function
): UIPopoverPresentationControllerDelegateImp {
const instance = <UIPopoverPresentationControllerDelegateImp>(
super.new()
);
instance.owner = owner;
instance.closedCallback = whenClosedCallback;
return instance;
}
public popoverPresentationControllerDidDismissPopover(
popoverPresentationController: UIPopoverPresentationController
) {
const owner = this.owner.get();
if (owner && typeof this.closedCallback === "function") {
this.closedCallback();
}
}
}
}

View File

@@ -1,205 +1,290 @@
// Types
import { View as ViewDefinition } from "..";
import {
HorizontalAlignment as HorizontalAlignmentDefinition,
VerticalAlignment as VerticalAlignmentDefinition
import {
HorizontalAlignment as HorizontalAlignmentDefinition,
VerticalAlignment as VerticalAlignmentDefinition,
} from "../../../styling/style-properties";
// Requires
import { layout } from "../../../../utils/utils";
import {
Trace
} from "../../../../trace";
import { Trace } from "../../../../trace";
export class ViewHelper {
public static measureChild(
parent: ViewDefinition,
child: ViewDefinition,
widthMeasureSpec: number,
heightMeasureSpec: number
): { measuredWidth: number; measuredHeight: number } {
let measureWidth = 0;
let measureHeight = 0;
public static measureChild(parent: ViewDefinition, child: ViewDefinition, widthMeasureSpec: number, heightMeasureSpec: number): { measuredWidth: number; measuredHeight: number } {
let measureWidth = 0;
let measureHeight = 0;
if (child && !child.isCollapsed) {
const widthSpec = parent
? parent._currentWidthMeasureSpec
: widthMeasureSpec;
const heightSpec = parent
? parent._currentHeightMeasureSpec
: heightMeasureSpec;
if (child && !child.isCollapsed) {
const width = layout.getMeasureSpecSize(widthSpec);
const widthMode = layout.getMeasureSpecMode(widthSpec);
const widthSpec = parent ? parent._currentWidthMeasureSpec : widthMeasureSpec;
const heightSpec = parent ? parent._currentHeightMeasureSpec : heightMeasureSpec;
const height = layout.getMeasureSpecSize(heightSpec);
const heightMode = layout.getMeasureSpecMode(heightSpec);
const width = layout.getMeasureSpecSize(widthSpec);
const widthMode = layout.getMeasureSpecMode(widthSpec);
child._updateEffectiveLayoutValues(
width,
widthMode,
height,
heightMode
);
const height = layout.getMeasureSpecSize(heightSpec);
const heightMode = layout.getMeasureSpecMode(heightSpec);
const style = child.style;
const horizontalMargins =
child.effectiveMarginLeft + child.effectiveMarginRight;
const verticalMargins =
child.effectiveMarginTop + child.effectiveMarginBottom;
child._updateEffectiveLayoutValues(width, widthMode, height, heightMode);
const childWidthMeasureSpec = ViewHelper.getMeasureSpec(
widthMeasureSpec,
horizontalMargins,
child.effectiveWidth,
style.horizontalAlignment === "stretch"
);
const childHeightMeasureSpec = ViewHelper.getMeasureSpec(
heightMeasureSpec,
verticalMargins,
child.effectiveHeight,
style.verticalAlignment === "stretch"
);
const style = child.style;
const horizontalMargins = child.effectiveMarginLeft + child.effectiveMarginRight;
const verticalMargins = child.effectiveMarginTop + child.effectiveMarginBottom;
if (Trace.isEnabled()) {
Trace.write(
`${
child.parent
} :measureChild: ${child} ${layout.measureSpecToString(
childWidthMeasureSpec
)}, ${layout.measureSpecToString(childHeightMeasureSpec)}}`,
Trace.categories.Layout
);
}
const childWidthMeasureSpec = ViewHelper.getMeasureSpec(widthMeasureSpec, horizontalMargins, child.effectiveWidth, style.horizontalAlignment === "stretch");
const childHeightMeasureSpec = ViewHelper.getMeasureSpec(heightMeasureSpec, verticalMargins, child.effectiveHeight, style.verticalAlignment === "stretch");
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
measureWidth = Math.round(
child.getMeasuredWidth() + horizontalMargins
);
measureHeight = Math.round(
child.getMeasuredHeight() + verticalMargins
);
}
if (Trace.isEnabled()) {
Trace.write(`${child.parent} :measureChild: ${child} ${layout.measureSpecToString(childWidthMeasureSpec)}, ${layout.measureSpecToString(childHeightMeasureSpec)}}`, Trace.categories.Layout);
}
return { measuredWidth: measureWidth, measuredHeight: measureHeight };
}
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
measureWidth = Math.round(child.getMeasuredWidth() + horizontalMargins);
measureHeight = Math.round(child.getMeasuredHeight() + verticalMargins);
}
public static layoutChild(
parent: ViewDefinition,
child: ViewDefinition,
left: number,
top: number,
right: number,
bottom: number,
setFrame: boolean = true
): void {
if (!child || child.isCollapsed) {
return;
}
return { measuredWidth: measureWidth, measuredHeight: measureHeight };
}
let childStyle = child.style;
public static layoutChild(parent: ViewDefinition, child: ViewDefinition, left: number, top: number, right: number, bottom: number, setFrame: boolean = true): void {
if (!child || child.isCollapsed) {
return;
}
let childTop: number;
let childLeft: number;
let childStyle = child.style;
let childWidth = child.getMeasuredWidth();
let childHeight = child.getMeasuredHeight();
let childTop: number;
let childLeft: number;
let effectiveMarginTop = child.effectiveMarginTop;
let effectiveMarginBottom = child.effectiveMarginBottom;
let childWidth = child.getMeasuredWidth();
let childHeight = child.getMeasuredHeight();
let vAlignment: VerticalAlignmentDefinition;
if (
child.effectiveHeight >= 0 &&
childStyle.verticalAlignment === "stretch"
) {
vAlignment = "middle";
} else {
vAlignment = childStyle.verticalAlignment;
}
let effectiveMarginTop = child.effectiveMarginTop;
let effectiveMarginBottom = child.effectiveMarginBottom;
switch (vAlignment) {
case "top":
childTop = top + effectiveMarginTop;
break;
let vAlignment: VerticalAlignmentDefinition;
if (child.effectiveHeight >= 0 && childStyle.verticalAlignment === "stretch") {
vAlignment = "middle";
}
else {
vAlignment = childStyle.verticalAlignment;
}
case "middle":
childTop =
top +
(bottom -
top -
childHeight +
(effectiveMarginTop - effectiveMarginBottom)) /
2;
break;
switch (vAlignment) {
case "top":
childTop = top + effectiveMarginTop;
break;
case "bottom":
childTop = bottom - childHeight - effectiveMarginBottom;
break;
case "middle":
childTop = top + (bottom - top - childHeight + (effectiveMarginTop - effectiveMarginBottom)) / 2;
break;
case "stretch":
default:
childTop = top + effectiveMarginTop;
childHeight =
bottom - top - (effectiveMarginTop + effectiveMarginBottom);
break;
}
case "bottom":
childTop = bottom - childHeight - effectiveMarginBottom;
break;
let effectiveMarginLeft = child.effectiveMarginLeft;
let effectiveMarginRight = child.effectiveMarginRight;
case "stretch":
default:
childTop = top + effectiveMarginTop;
childHeight = bottom - top - (effectiveMarginTop + effectiveMarginBottom);
break;
}
let hAlignment: HorizontalAlignmentDefinition;
if (
child.effectiveWidth >= 0 &&
childStyle.horizontalAlignment === "stretch"
) {
hAlignment = "center";
} else {
hAlignment = childStyle.horizontalAlignment;
}
let effectiveMarginLeft = child.effectiveMarginLeft;
let effectiveMarginRight = child.effectiveMarginRight;
switch (hAlignment) {
case "left":
childLeft = left + effectiveMarginLeft;
break;
let hAlignment: HorizontalAlignmentDefinition;
if (child.effectiveWidth >= 0 && childStyle.horizontalAlignment === "stretch") {
hAlignment = "center";
}
else {
hAlignment = childStyle.horizontalAlignment;
}
case "center":
childLeft =
left +
(right -
left -
childWidth +
(effectiveMarginLeft - effectiveMarginRight)) /
2;
break;
switch (hAlignment) {
case "left":
childLeft = left + effectiveMarginLeft;
break;
case "right":
childLeft = right - childWidth - effectiveMarginRight;
break;
case "center":
childLeft = left + (right - left - childWidth + (effectiveMarginLeft - effectiveMarginRight)) / 2;
break;
case "stretch":
default:
childLeft = left + effectiveMarginLeft;
childWidth =
right - left - (effectiveMarginLeft + effectiveMarginRight);
break;
}
case "right":
childLeft = right - childWidth - effectiveMarginRight;
break;
let childRight = Math.round(childLeft + childWidth);
let childBottom = Math.round(childTop + childHeight);
childLeft = Math.round(childLeft);
childTop = Math.round(childTop);
case "stretch":
default:
childLeft = left + effectiveMarginLeft;
childWidth = right - left - (effectiveMarginLeft + effectiveMarginRight);
break;
}
if (Trace.isEnabled()) {
Trace.write(
child.parent +
" :layoutChild: " +
child +
" " +
childLeft +
", " +
childTop +
", " +
childRight +
", " +
childBottom,
Trace.categories.Layout
);
}
let childRight = Math.round(childLeft + childWidth);
let childBottom = Math.round(childTop + childHeight);
childLeft = Math.round(childLeft);
childTop = Math.round(childTop);
child.layout(childLeft, childTop, childRight, childBottom, setFrame);
}
if (Trace.isEnabled()) {
Trace.write(child.parent + " :layoutChild: " + child + " " + childLeft + ", " + childTop + ", " + childRight + ", " + childBottom, Trace.categories.Layout);
}
public static resolveSizeAndState(
size: number,
specSize: number,
specMode: number,
childMeasuredState: number
): number {
let result = size;
switch (specMode) {
case layout.UNSPECIFIED:
result = Math.ceil(size);
break;
child.layout(childLeft, childTop, childRight, childBottom, setFrame);
}
case layout.AT_MOST:
if (specSize < size) {
result =
Math.ceil(specSize) | layout.MEASURED_STATE_TOO_SMALL;
}
break;
public static resolveSizeAndState(size: number, specSize: number, specMode: number, childMeasuredState: number): number {
let result = size;
switch (specMode) {
case layout.UNSPECIFIED:
result = Math.ceil(size);
break;
case layout.EXACTLY:
result = Math.ceil(specSize);
break;
}
case layout.AT_MOST:
if (specSize < size) {
result = Math.ceil(specSize) | layout.MEASURED_STATE_TOO_SMALL;
}
break;
return result | (childMeasuredState & layout.MEASURED_STATE_MASK);
}
case layout.EXACTLY:
result = Math.ceil(specSize);
break;
}
public static combineMeasuredStates(curState: number, newState): number {
return curState | newState;
}
return result | (childMeasuredState & layout.MEASURED_STATE_MASK);
}
private static getMeasureSpec(
parentSpec: number,
margins: number,
childLength: number,
stretched: boolean
): number {
const parentLength = layout.getMeasureSpecSize(parentSpec);
const parentSpecMode = layout.getMeasureSpecMode(parentSpec);
public static combineMeasuredStates(curState: number, newState): number {
return curState | newState;
}
let resultSize: number;
let resultMode: number;
private static getMeasureSpec(parentSpec: number, margins: number, childLength: number, stretched: boolean): number {
const parentLength = layout.getMeasureSpecSize(parentSpec);
const parentSpecMode = layout.getMeasureSpecMode(parentSpec);
// We want a specific size... let be it.
if (childLength >= 0) {
// If mode !== UNSPECIFIED we take the smaller of parentLength and childLength
// Otherwise we will need to clip the view but this is not possible in all Android API levels.
// TODO: remove Math.min(parentLength, childLength)
resultSize =
parentSpecMode === layout.UNSPECIFIED
? childLength
: Math.min(parentLength, childLength);
resultMode = layout.EXACTLY;
} else {
switch (parentSpecMode) {
// Parent has imposed an exact size on us
case layout.EXACTLY:
resultSize = Math.max(0, parentLength - margins);
// if stretched - nativeView wants to be our size. So be it.
// else - nativeView wants to determine its own size. It can't be bigger than us.
resultMode = stretched ? layout.EXACTLY : layout.AT_MOST;
break;
let resultSize: number;
let resultMode: number;
// Parent has imposed a maximum size on us
case layout.AT_MOST:
resultSize = Math.max(0, parentLength - margins);
resultMode = layout.AT_MOST;
break;
// We want a specific size... let be it.
if (childLength >= 0) {
// If mode !== UNSPECIFIED we take the smaller of parentLength and childLength
// Otherwise we will need to clip the view but this is not possible in all Android API levels.
// TODO: remove Math.min(parentLength, childLength)
resultSize = parentSpecMode === layout.UNSPECIFIED ? childLength : Math.min(parentLength, childLength);
resultMode = layout.EXACTLY;
}
else {
switch (parentSpecMode) {
// Parent has imposed an exact size on us
case layout.EXACTLY:
resultSize = Math.max(0, parentLength - margins);
// if stretched - nativeView wants to be our size. So be it.
// else - nativeView wants to determine its own size. It can't be bigger than us.
resultMode = stretched ? layout.EXACTLY : layout.AT_MOST;
break;
// Equivalent to measure with Infinity.
case layout.UNSPECIFIED:
resultSize = 0;
resultMode = layout.UNSPECIFIED;
break;
}
}
// Parent has imposed a maximum size on us
case layout.AT_MOST:
resultSize = Math.max(0, parentLength - margins);
resultMode = layout.AT_MOST;
break;
// Equivalent to measure with Infinity.
case layout.UNSPECIFIED:
resultSize = 0;
resultMode = layout.UNSPECIFIED;
break;
}
}
return layout.makeMeasureSpec(resultSize, resultMode);
}
return layout.makeMeasureSpec(resultSize, resultMode);
}
}