mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
chore: remove critical circular dependencies (#8114)
* chore: remove critical circular dependencies * chore: fix tslint errors * chore: remove platform specific types from interfaces * chore: update unit tests polyfills * fix: incorrect null check * chore: update api.md file * test: improve test case * chore: apply comments * test: avoid page style leaks in tests
This commit is contained in:
committed by
Alexander Vakrilov
parent
5b647bd809
commit
0ffc790d82
@@ -8,6 +8,7 @@ import {
|
||||
booleanConverter, EventData, getEventOrGestureName, InheritedProperty, layout,
|
||||
Property, ShowModalOptions, traceCategories, traceEnabled, traceWrite, ViewBase
|
||||
} from "../view-base";
|
||||
import { ViewHelper } from "./view-helper";
|
||||
|
||||
import { HorizontalAlignment, VerticalAlignment, Visibility, Length, PercentLength } from "../../styling/style-properties";
|
||||
|
||||
@@ -806,193 +807,19 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
|
||||
public abstract layoutNativeView(left: number, top: number, right: number, bottom: number): void;
|
||||
|
||||
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.AT_MOST:
|
||||
if (specSize < size) {
|
||||
result = Math.ceil(specSize) | layout.MEASURED_STATE_TOO_SMALL;
|
||||
}
|
||||
break;
|
||||
|
||||
case layout.EXACTLY:
|
||||
result = Math.ceil(specSize);
|
||||
break;
|
||||
}
|
||||
|
||||
return result | (childMeasuredState & layout.MEASURED_STATE_MASK);
|
||||
return ViewHelper.resolveSizeAndState(size, specSize, specMode, childMeasuredState);
|
||||
}
|
||||
|
||||
public static combineMeasuredStates(curState: number, newState): number {
|
||||
return curState | newState;
|
||||
return ViewHelper.combineMeasuredStates(curState, newState);
|
||||
}
|
||||
|
||||
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 childStyle = child.style;
|
||||
|
||||
let childTop: number;
|
||||
let childLeft: number;
|
||||
|
||||
let childWidth = child.getMeasuredWidth();
|
||||
let childHeight = child.getMeasuredHeight();
|
||||
|
||||
let effectiveMarginTop = child.effectiveMarginTop;
|
||||
let effectiveMarginBottom = child.effectiveMarginBottom;
|
||||
|
||||
let vAlignment: VerticalAlignment;
|
||||
if (child.effectiveHeight >= 0 && childStyle.verticalAlignment === "stretch") {
|
||||
vAlignment = "middle";
|
||||
}
|
||||
else {
|
||||
vAlignment = childStyle.verticalAlignment;
|
||||
}
|
||||
|
||||
switch (vAlignment) {
|
||||
case "top":
|
||||
childTop = top + effectiveMarginTop;
|
||||
break;
|
||||
|
||||
case "middle":
|
||||
childTop = top + (bottom - top - childHeight + (effectiveMarginTop - effectiveMarginBottom)) / 2;
|
||||
break;
|
||||
|
||||
case "bottom":
|
||||
childTop = bottom - childHeight - effectiveMarginBottom;
|
||||
break;
|
||||
|
||||
case "stretch":
|
||||
default:
|
||||
childTop = top + effectiveMarginTop;
|
||||
childHeight = bottom - top - (effectiveMarginTop + effectiveMarginBottom);
|
||||
break;
|
||||
}
|
||||
|
||||
let effectiveMarginLeft = child.effectiveMarginLeft;
|
||||
let effectiveMarginRight = child.effectiveMarginRight;
|
||||
|
||||
let hAlignment: HorizontalAlignment;
|
||||
if (child.effectiveWidth >= 0 && childStyle.horizontalAlignment === "stretch") {
|
||||
hAlignment = "center";
|
||||
}
|
||||
else {
|
||||
hAlignment = childStyle.horizontalAlignment;
|
||||
}
|
||||
|
||||
switch (hAlignment) {
|
||||
case "left":
|
||||
childLeft = left + effectiveMarginLeft;
|
||||
break;
|
||||
|
||||
case "center":
|
||||
childLeft = left + (right - left - childWidth + (effectiveMarginLeft - effectiveMarginRight)) / 2;
|
||||
break;
|
||||
|
||||
case "right":
|
||||
childLeft = right - childWidth - effectiveMarginRight;
|
||||
break;
|
||||
|
||||
case "stretch":
|
||||
default:
|
||||
childLeft = left + effectiveMarginLeft;
|
||||
childWidth = right - left - (effectiveMarginLeft + effectiveMarginRight);
|
||||
break;
|
||||
}
|
||||
|
||||
let childRight = Math.round(childLeft + childWidth);
|
||||
let childBottom = Math.round(childTop + childHeight);
|
||||
childLeft = Math.round(childLeft);
|
||||
childTop = Math.round(childTop);
|
||||
|
||||
if (traceEnabled()) {
|
||||
traceWrite(child.parent + " :layoutChild: " + child + " " + childLeft + ", " + childTop + ", " + childRight + ", " + childBottom, traceCategories.Layout);
|
||||
}
|
||||
|
||||
child.layout(childLeft, childTop, childRight, childBottom, setFrame);
|
||||
ViewHelper.layoutChild(parent, child, left, top, right, bottom);
|
||||
}
|
||||
|
||||
public static measureChild(parent: ViewCommon, child: ViewCommon, 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;
|
||||
|
||||
const width = layout.getMeasureSpecSize(widthSpec);
|
||||
const widthMode = layout.getMeasureSpecMode(widthSpec);
|
||||
|
||||
const height = layout.getMeasureSpecSize(heightSpec);
|
||||
const heightMode = layout.getMeasureSpecMode(heightSpec);
|
||||
|
||||
child._updateEffectiveLayoutValues(width, widthMode, height, heightMode);
|
||||
|
||||
const style = child.style;
|
||||
const horizontalMargins = child.effectiveMarginLeft + child.effectiveMarginRight;
|
||||
const verticalMargins = child.effectiveMarginTop + child.effectiveMarginBottom;
|
||||
|
||||
const childWidthMeasureSpec = ViewCommon.getMeasureSpec(widthMeasureSpec, horizontalMargins, child.effectiveWidth, style.horizontalAlignment === "stretch");
|
||||
const childHeightMeasureSpec = ViewCommon.getMeasureSpec(heightMeasureSpec, verticalMargins, child.effectiveHeight, style.verticalAlignment === "stretch");
|
||||
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`${child.parent} :measureChild: ${child} ${layout.measureSpecToString(childWidthMeasureSpec)}, ${layout.measureSpecToString(childHeightMeasureSpec)}}`, traceCategories.Layout);
|
||||
}
|
||||
|
||||
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
|
||||
measureWidth = Math.round(child.getMeasuredWidth() + horizontalMargins);
|
||||
measureHeight = Math.round(child.getMeasuredHeight() + verticalMargins);
|
||||
}
|
||||
|
||||
return { measuredWidth: measureWidth, measuredHeight: measureHeight };
|
||||
}
|
||||
|
||||
private static getMeasureSpec(parentSpec: number, margins: number, childLength: number, stretched: boolean): number {
|
||||
const parentLength = layout.getMeasureSpecSize(parentSpec);
|
||||
const parentSpecMode = layout.getMeasureSpecMode(parentSpec);
|
||||
|
||||
let resultSize: number;
|
||||
let resultMode: number;
|
||||
|
||||
// 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;
|
||||
|
||||
// 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 ViewHelper.measureChild(parent, child, widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
|
||||
_setCurrentMeasureSpecs(widthMeasureSpec: number, heightMeasureSpec: number): boolean {
|
||||
|
||||
5
nativescript-core/ui/core/view/view-helper/package.json
Normal file
5
nativescript-core/ui/core/view/view-helper/package.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "view-helper",
|
||||
"main": "view-helper",
|
||||
"types": "view-helper.d.ts"
|
||||
}
|
||||
207
nativescript-core/ui/core/view/view-helper/view-helper-common.ts
Normal file
207
nativescript-core/ui/core/view/view-helper/view-helper-common.ts
Normal file
@@ -0,0 +1,207 @@
|
||||
// Types
|
||||
import { View as ViewDefinition } from "..";
|
||||
import {
|
||||
HorizontalAlignment as HorizontalAlignmentDefinition,
|
||||
VerticalAlignment as VerticalAlignmentDefinition
|
||||
} from "../../../styling/style-properties";
|
||||
|
||||
// Requires
|
||||
import { layout } from "../../../../utils/utils";
|
||||
import {
|
||||
isEnabled as traceEnabled,
|
||||
categories as traceCategories,
|
||||
write as traceWrite
|
||||
} 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;
|
||||
|
||||
if (child && !child.isCollapsed) {
|
||||
|
||||
const widthSpec = parent ? parent._currentWidthMeasureSpec : widthMeasureSpec;
|
||||
const heightSpec = parent ? parent._currentHeightMeasureSpec : heightMeasureSpec;
|
||||
|
||||
const width = layout.getMeasureSpecSize(widthSpec);
|
||||
const widthMode = layout.getMeasureSpecMode(widthSpec);
|
||||
|
||||
const height = layout.getMeasureSpecSize(heightSpec);
|
||||
const heightMode = layout.getMeasureSpecMode(heightSpec);
|
||||
|
||||
child._updateEffectiveLayoutValues(width, widthMode, height, heightMode);
|
||||
|
||||
const style = child.style;
|
||||
const horizontalMargins = child.effectiveMarginLeft + child.effectiveMarginRight;
|
||||
const verticalMargins = child.effectiveMarginTop + child.effectiveMarginBottom;
|
||||
|
||||
const childWidthMeasureSpec = ViewHelper.getMeasureSpec(widthMeasureSpec, horizontalMargins, child.effectiveWidth, style.horizontalAlignment === "stretch");
|
||||
const childHeightMeasureSpec = ViewHelper.getMeasureSpec(heightMeasureSpec, verticalMargins, child.effectiveHeight, style.verticalAlignment === "stretch");
|
||||
|
||||
if (traceEnabled()) {
|
||||
traceWrite(`${child.parent} :measureChild: ${child} ${layout.measureSpecToString(childWidthMeasureSpec)}, ${layout.measureSpecToString(childHeightMeasureSpec)}}`, traceCategories.Layout);
|
||||
}
|
||||
|
||||
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
|
||||
measureWidth = Math.round(child.getMeasuredWidth() + horizontalMargins);
|
||||
measureHeight = Math.round(child.getMeasuredHeight() + verticalMargins);
|
||||
}
|
||||
|
||||
return { measuredWidth: measureWidth, measuredHeight: measureHeight };
|
||||
}
|
||||
|
||||
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 childStyle = child.style;
|
||||
|
||||
let childTop: number;
|
||||
let childLeft: number;
|
||||
|
||||
let childWidth = child.getMeasuredWidth();
|
||||
let childHeight = child.getMeasuredHeight();
|
||||
|
||||
let effectiveMarginTop = child.effectiveMarginTop;
|
||||
let effectiveMarginBottom = child.effectiveMarginBottom;
|
||||
|
||||
let vAlignment: VerticalAlignmentDefinition;
|
||||
if (child.effectiveHeight >= 0 && childStyle.verticalAlignment === "stretch") {
|
||||
vAlignment = "middle";
|
||||
}
|
||||
else {
|
||||
vAlignment = childStyle.verticalAlignment;
|
||||
}
|
||||
|
||||
switch (vAlignment) {
|
||||
case "top":
|
||||
childTop = top + effectiveMarginTop;
|
||||
break;
|
||||
|
||||
case "middle":
|
||||
childTop = top + (bottom - top - childHeight + (effectiveMarginTop - effectiveMarginBottom)) / 2;
|
||||
break;
|
||||
|
||||
case "bottom":
|
||||
childTop = bottom - childHeight - effectiveMarginBottom;
|
||||
break;
|
||||
|
||||
case "stretch":
|
||||
default:
|
||||
childTop = top + effectiveMarginTop;
|
||||
childHeight = bottom - top - (effectiveMarginTop + effectiveMarginBottom);
|
||||
break;
|
||||
}
|
||||
|
||||
let effectiveMarginLeft = child.effectiveMarginLeft;
|
||||
let effectiveMarginRight = child.effectiveMarginRight;
|
||||
|
||||
let hAlignment: HorizontalAlignmentDefinition;
|
||||
if (child.effectiveWidth >= 0 && childStyle.horizontalAlignment === "stretch") {
|
||||
hAlignment = "center";
|
||||
}
|
||||
else {
|
||||
hAlignment = childStyle.horizontalAlignment;
|
||||
}
|
||||
|
||||
switch (hAlignment) {
|
||||
case "left":
|
||||
childLeft = left + effectiveMarginLeft;
|
||||
break;
|
||||
|
||||
case "center":
|
||||
childLeft = left + (right - left - childWidth + (effectiveMarginLeft - effectiveMarginRight)) / 2;
|
||||
break;
|
||||
|
||||
case "right":
|
||||
childLeft = right - childWidth - effectiveMarginRight;
|
||||
break;
|
||||
|
||||
case "stretch":
|
||||
default:
|
||||
childLeft = left + effectiveMarginLeft;
|
||||
childWidth = right - left - (effectiveMarginLeft + effectiveMarginRight);
|
||||
break;
|
||||
}
|
||||
|
||||
let childRight = Math.round(childLeft + childWidth);
|
||||
let childBottom = Math.round(childTop + childHeight);
|
||||
childLeft = Math.round(childLeft);
|
||||
childTop = Math.round(childTop);
|
||||
|
||||
if (traceEnabled()) {
|
||||
traceWrite(child.parent + " :layoutChild: " + child + " " + childLeft + ", " + childTop + ", " + childRight + ", " + childBottom, traceCategories.Layout);
|
||||
}
|
||||
|
||||
child.layout(childLeft, childTop, childRight, childBottom, setFrame);
|
||||
}
|
||||
|
||||
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.AT_MOST:
|
||||
if (specSize < size) {
|
||||
result = Math.ceil(specSize) | layout.MEASURED_STATE_TOO_SMALL;
|
||||
}
|
||||
break;
|
||||
|
||||
case layout.EXACTLY:
|
||||
result = Math.ceil(specSize);
|
||||
break;
|
||||
}
|
||||
|
||||
return result | (childMeasuredState & layout.MEASURED_STATE_MASK);
|
||||
}
|
||||
|
||||
public static combineMeasuredStates(curState: number, newState): number {
|
||||
return curState | newState;
|
||||
}
|
||||
|
||||
private static getMeasureSpec(parentSpec: number, margins: number, childLength: number, stretched: boolean): number {
|
||||
const parentLength = layout.getMeasureSpecSize(parentSpec);
|
||||
const parentSpecMode = layout.getMeasureSpecMode(parentSpec);
|
||||
|
||||
let resultSize: number;
|
||||
let resultMode: number;
|
||||
|
||||
// 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;
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./view-helper-common";
|
||||
63
nativescript-core/ui/core/view/view-helper/view-helper.d.ts
vendored
Normal file
63
nativescript-core/ui/core/view/view-helper/view-helper.d.ts
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
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 };
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
public static combineMeasuredStates(curState: number, newState): number;
|
||||
}
|
||||
|
||||
export namespace ios {
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
365
nativescript-core/ui/core/view/view-helper/view-helper.ios.ts
Normal file
365
nativescript-core/ui/core/view/view-helper/view-helper.ios.ts
Normal file
@@ -0,0 +1,365 @@
|
||||
// Types
|
||||
import { View } from "..";
|
||||
|
||||
// Requires
|
||||
import { ViewHelper } from "./view-helper-common";
|
||||
import {
|
||||
ios as iosUtils,
|
||||
layout
|
||||
} from "../../../../utils/utils";
|
||||
import {
|
||||
isEnabled as traceEnabled,
|
||||
messageType as traceMessageType,
|
||||
categories as traceCategories,
|
||||
write as traceWrite
|
||||
} from "../../../../trace";
|
||||
|
||||
export * from "./view-helper-common";
|
||||
|
||||
const majorVersion = iosUtils.MajorVersion;
|
||||
|
||||
export namespace ios {
|
||||
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) {
|
||||
traceWrite(`safeAreaLayoutGuide during layout of ${owner}. Creating fallback constraints, but layout might be wrong.`,
|
||||
traceCategories.Layout, traceMessageType.error);
|
||||
|
||||
layoutGuide = initLayoutGuide(controller);
|
||||
}
|
||||
const safeArea = layoutGuide.layoutFrame;
|
||||
let position = ios.getPositionFromFrame(safeArea);
|
||||
const safeAreaSize = safeArea.size;
|
||||
|
||||
const hasChildViewControllers = controller.childViewControllers.count > 0;
|
||||
if (hasChildViewControllers) {
|
||||
const fullscreen = controller.view.frame;
|
||||
position = ios.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 = ios.getPositionFromFrame(frame);
|
||||
const adjustedFrame = ios.getFrameFromPosition(position, insets);
|
||||
|
||||
if (traceEnabled()) {
|
||||
traceWrite(this + " :shrinkToSafeArea: " + JSON.stringify(ios.getPositionFromFrame(adjustedFrame)), traceCategories.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 = ios.getPositionFromFrame(frame);
|
||||
const safeAreaPosition = ios.getPositionFromFrame(safeArea);
|
||||
const fullscreenPosition = ios.getPositionFromFrame(fullscreen);
|
||||
const inWindowPosition = ios.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 (traceEnabled()) {
|
||||
traceWrite(view + " :expandBeyondSafeArea: " + JSON.stringify(ios.getPositionFromFrame(adjustedFrame)), traceCategories.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(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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
// Definitions.
|
||||
// Types.
|
||||
import { Point, View as ViewDefinition, dip } from ".";
|
||||
import { ViewBase } from "../view-base";
|
||||
|
||||
// Requires
|
||||
import {
|
||||
ViewCommon, layout, isEnabledProperty, originXProperty, originYProperty, automationTextProperty, isUserInteractionEnabledProperty,
|
||||
traceEnabled, traceWrite, traceCategories, traceError, traceMessageType, ShowModalOptions
|
||||
} from "./view-common";
|
||||
|
||||
import { ios } from "./view-helper";
|
||||
import { ios as iosBackground, Background } from "../../styling/background";
|
||||
import { ios as iosUtils } from "../../../utils/utils";
|
||||
import {
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
import { profile } from "../../../profiling";
|
||||
|
||||
export * from "./view-common";
|
||||
export { ios };
|
||||
|
||||
const PFLAG_FORCE_LAYOUT = 1;
|
||||
const PFLAG_MEASURED_DIMENSION_SET = 1 << 1;
|
||||
@@ -26,7 +27,7 @@ const PFLAG_LAYOUT_REQUIRED = 1 << 2;
|
||||
|
||||
const majorVersion = iosUtils.MajorVersion;
|
||||
|
||||
export class View extends ViewCommon {
|
||||
export class View extends ViewCommon implements ViewDefinition {
|
||||
nativeViewProtected: UIView;
|
||||
viewController: UIViewController;
|
||||
private _popoverPresentationDelegate: ios.UIPopoverPresentationControllerDelegateImp;
|
||||
@@ -215,6 +216,21 @@ export class View extends ViewCommon {
|
||||
this._setNativeViewFrame(nativeView, frame);
|
||||
}
|
||||
|
||||
public _layoutParent() {
|
||||
if (this.nativeViewProtected) {
|
||||
const frame = this.nativeViewProtected.frame;
|
||||
const origin = frame.origin;
|
||||
const size = frame.size;
|
||||
const left = layout.toDevicePixels(origin.x);
|
||||
const top = layout.toDevicePixels(origin.y);
|
||||
const width = layout.toDevicePixels(size.width);
|
||||
const height = layout.toDevicePixels(size.height);
|
||||
this._setLayoutFlags(left, top, width + left, height + top);
|
||||
}
|
||||
|
||||
super._layoutParent();
|
||||
}
|
||||
|
||||
public _setLayoutFlags(left: number, top: number, right: number, bottom: number): void {
|
||||
const width = right - left;
|
||||
const height = bottom - top;
|
||||
@@ -399,11 +415,11 @@ export class View extends ViewCommon {
|
||||
|
||||
this._setupAsRootView({});
|
||||
|
||||
super._showNativeModalView(parentWithController, options);
|
||||
super._showNativeModalView(<ViewCommon>parentWithController, options);
|
||||
let controller = this.viewController;
|
||||
if (!controller) {
|
||||
const nativeView = this.ios || this.nativeViewProtected;
|
||||
controller = ios.UILayoutViewController.initWithOwner(new WeakRef(this));
|
||||
controller = <UIViewController>ios.UILayoutViewController.initWithOwner(new WeakRef(this));
|
||||
|
||||
if (nativeView instanceof UIView) {
|
||||
controller.view.addSubview(nativeView);
|
||||
@@ -654,7 +670,7 @@ export class View extends ViewCommon {
|
||||
private _setupPopoverControllerDelegate(controller: UIViewController, parent: View) {
|
||||
const popoverPresentationController = controller.popoverPresentationController;
|
||||
this._popoverPresentationDelegate = ios.UIPopoverPresentationControllerDelegateImp.initWithOwnerAndCallback(new WeakRef(this), this._closeModalCallback);
|
||||
popoverPresentationController.delegate = this._popoverPresentationDelegate;
|
||||
popoverPresentationController.delegate = <UIPopoverPresentationControllerDelegate>this._popoverPresentationDelegate;
|
||||
const view = parent.nativeViewProtected;
|
||||
// Note: sourceView and sourceRect are needed to specify the anchor location for the popover.
|
||||
// Note: sourceView should be the button triggering the modal. If it the Page the popover might appear "behind" the page content
|
||||
@@ -664,7 +680,7 @@ export class View extends ViewCommon {
|
||||
|
||||
private _setupAdaptiveControllerDelegate(controller: UIViewController) {
|
||||
this._adaptivePresentationDelegate = ios.UIAdaptivePresentationControllerDelegateImp.initWithOwnerAndCallback(new WeakRef(this), this._closeModalCallback);
|
||||
controller.presentationController.delegate = this._adaptivePresentationDelegate;
|
||||
controller.presentationController.delegate = <UIAdaptivePresentationControllerDelegate>this._adaptivePresentationDelegate;
|
||||
}
|
||||
}
|
||||
View.prototype._nativeBackgroundState = "unset";
|
||||
@@ -722,366 +738,3 @@ export class CustomLayoutView extends ContainerView {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export namespace ios {
|
||||
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) {
|
||||
traceWrite(`safeAreaLayoutGuide during layout of ${owner}. Creating fallback constraints, but layout might be wrong.`,
|
||||
traceCategories.Layout, traceMessageType.error);
|
||||
|
||||
layoutGuide = initLayoutGuide(controller);
|
||||
}
|
||||
const safeArea = layoutGuide.layoutFrame;
|
||||
let position = ios.getPositionFromFrame(safeArea);
|
||||
const safeAreaSize = safeArea.size;
|
||||
|
||||
const hasChildViewControllers = controller.childViewControllers.count > 0;
|
||||
if (hasChildViewControllers) {
|
||||
const fullscreen = controller.view.frame;
|
||||
position = ios.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);
|
||||
|
||||
View.measureChild(null, owner, widthSpec, heightSpec);
|
||||
View.layoutChild(null, owner, position.left, position.top, position.right, position.bottom);
|
||||
|
||||
layoutParent(owner.parent);
|
||||
}
|
||||
|
||||
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 = ios.getPositionFromFrame(frame);
|
||||
const adjustedFrame = ios.getFrameFromPosition(position, insets);
|
||||
|
||||
if (traceEnabled()) {
|
||||
traceWrite(this + " :shrinkToSafeArea: " + JSON.stringify(ios.getPositionFromFrame(adjustedFrame)), traceCategories.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 = ios.getPositionFromFrame(frame);
|
||||
const safeAreaPosition = ios.getPositionFromFrame(safeArea);
|
||||
const fullscreenPosition = ios.getPositionFromFrame(fullscreen);
|
||||
const inWindowPosition = ios.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 (traceEnabled()) {
|
||||
traceWrite(view + " :expandBeyondSafeArea: " + JSON.stringify(ios.getPositionFromFrame(adjustedFrame)), traceCategories.Layout);
|
||||
}
|
||||
|
||||
return adjustedFrame;
|
||||
}
|
||||
|
||||
function layoutParent(view: ViewBase): void {
|
||||
if (!view) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (view instanceof View && view.nativeViewProtected) {
|
||||
const frame = view.nativeViewProtected.frame;
|
||||
const origin = frame.origin;
|
||||
const size = frame.size;
|
||||
const left = layout.toDevicePixels(origin.x);
|
||||
const top = layout.toDevicePixels(origin.y);
|
||||
const width = layout.toDevicePixels(size.width);
|
||||
const height = layout.toDevicePixels(size.height);
|
||||
view._setLayoutFlags(left, top, width + left, height + top);
|
||||
}
|
||||
|
||||
layoutParent(view.parent);
|
||||
}
|
||||
|
||||
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(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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user