mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
feat(iOS): Safe Area Support (#6230)
This commit is contained in:
@@ -586,6 +586,7 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
|
||||
public originY: number;
|
||||
public isEnabled: boolean;
|
||||
public isUserInteractionEnabled: boolean;
|
||||
public iosOverflowSafeArea: boolean;
|
||||
|
||||
get isLayoutValid(): boolean {
|
||||
return this._isLayoutValid;
|
||||
@@ -842,7 +843,7 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
|
||||
}
|
||||
|
||||
_getCurrentLayoutBounds(): { left: number; top: number; right: number; bottom: number } {
|
||||
return { left: this._oldLeft, top: this._oldTop, right: this._oldRight, bottom: this._oldBottom };
|
||||
return { left: 0, top: 0, right: 0, bottom: 0 };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -879,6 +880,10 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public getSafeAreaInsets(): { left, top, right, bottom } {
|
||||
return { left: 0, top: 0, right: 0, bottom: 0 };
|
||||
}
|
||||
|
||||
public getLocationInWindow(): Point {
|
||||
return undefined;
|
||||
}
|
||||
@@ -1022,3 +1027,6 @@ isEnabledProperty.register(ViewCommon);
|
||||
|
||||
export const isUserInteractionEnabledProperty = new Property<ViewCommon, boolean>({ name: "isUserInteractionEnabled", defaultValue: true, valueConverter: booleanConverter });
|
||||
isUserInteractionEnabledProperty.register(ViewCommon);
|
||||
|
||||
export const iosOverflowSafeAreaProperty = new Property<ViewCommon, boolean>({ name: "iosOverflowSafeArea", defaultValue: false, valueConverter: booleanConverter });
|
||||
iosOverflowSafeAreaProperty.register(ViewCommon);
|
||||
|
||||
@@ -816,7 +816,11 @@ export class View extends ViewCommon {
|
||||
}
|
||||
}
|
||||
|
||||
export class CustomLayoutView extends View implements CustomLayoutViewDefinition {
|
||||
export class ContainerView extends View {
|
||||
public iosOverflowSafeArea: boolean;
|
||||
}
|
||||
|
||||
export class CustomLayoutView extends ContainerView implements CustomLayoutViewDefinition {
|
||||
nativeViewProtected: android.view.ViewGroup;
|
||||
|
||||
public createNativeView() {
|
||||
|
||||
33
tns-core-modules/ui/core/view/view.d.ts
vendored
33
tns-core-modules/ui/core/view/view.d.ts
vendored
@@ -344,6 +344,11 @@ export abstract class View extends ViewBase {
|
||||
*/
|
||||
isUserInteractionEnabled: boolean;
|
||||
|
||||
/**
|
||||
* Instruct container view to expand beyond the safe area. This property is iOS specific. Default value: false
|
||||
*/
|
||||
iosOverflowSafeArea: boolean;
|
||||
|
||||
/**
|
||||
* Gets is layout is valid. This is a read-only property.
|
||||
*/
|
||||
@@ -415,7 +420,6 @@ export abstract class View extends ViewBase {
|
||||
|
||||
/**
|
||||
* Called from onLayout when native view position is about to be changed.
|
||||
* @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
|
||||
@@ -427,7 +431,7 @@ export abstract class View extends ViewBase {
|
||||
* 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 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 };
|
||||
@@ -527,6 +531,11 @@ export abstract class View extends ViewBase {
|
||||
*/
|
||||
public createAnimation(options: AnimationDefinition): Animation;
|
||||
|
||||
/**
|
||||
* Returns the iOS safe area insets of this view.
|
||||
*/
|
||||
public getSafeAreaInsets(): { left, top, right, bottom };
|
||||
|
||||
/**
|
||||
* Returns the location of this view in the window coordinate system.
|
||||
*/
|
||||
@@ -609,7 +618,7 @@ export abstract class View extends ViewBase {
|
||||
* Called by layout method to cache view bounds.
|
||||
* @private
|
||||
*/
|
||||
_setCurrentLayoutBounds(left: number, top: number, right: number, bottom: number): void;
|
||||
_setCurrentLayoutBounds(left: number, top: number, right: number, bottom: number): { boundsChanged: boolean, sizeChanged: boolean };
|
||||
/**
|
||||
* Return view bounds.
|
||||
* @private
|
||||
@@ -700,10 +709,20 @@ export abstract class View extends ViewBase {
|
||||
_setValue(property: any, value: any): never;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for all UI components that are containers.
|
||||
*/
|
||||
export class ContainerView extends View {
|
||||
/**
|
||||
* Instruct container view to expand beyond the safe area. This property is iOS specific. Default value: true
|
||||
*/
|
||||
public iosOverflowSafeArea: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for all UI components that implement custom layouts.
|
||||
*/
|
||||
export class CustomLayoutView extends View {
|
||||
export class CustomLayoutView extends ContainerView {
|
||||
//@private
|
||||
/**
|
||||
* @private
|
||||
@@ -777,6 +796,7 @@ export const originXProperty: Property<View, number>;
|
||||
export const originYProperty: Property<View, number>;
|
||||
export const isEnabledProperty: Property<View, boolean>;
|
||||
export const isUserInteractionEnabledProperty: Property<View, boolean>;
|
||||
export const iosOverflowSafeAreaProperty: Property<View, boolean>;
|
||||
|
||||
export namespace ios {
|
||||
/**
|
||||
@@ -784,10 +804,13 @@ export namespace ios {
|
||||
* @param view The view form which to start the search.
|
||||
*/
|
||||
export function getParentWithViewController(view: View): View
|
||||
export function isContentScrollable(controller: any /* UIViewController */, owner: View): boolean
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
// Definitions.
|
||||
import { Point, View as ViewDefinition, dip } from ".";
|
||||
import { ViewBase } from "../view-base";
|
||||
import { booleanConverter, Property } from "../view";
|
||||
|
||||
import {
|
||||
ViewCommon, layout, isEnabledProperty, originXProperty, originYProperty, automationTextProperty, isUserInteractionEnabledProperty,
|
||||
traceEnabled, traceWrite, traceCategories, traceError, traceMessageType
|
||||
traceEnabled, traceWrite, traceCategories, traceError, traceMessageType, getAncestor
|
||||
} from "./view-common";
|
||||
|
||||
import { ios as iosBackground, Background } from "../../styling/background";
|
||||
@@ -24,6 +25,8 @@ const PFLAG_FORCE_LAYOUT = 1;
|
||||
const PFLAG_MEASURED_DIMENSION_SET = 1 << 1;
|
||||
const PFLAG_LAYOUT_REQUIRED = 1 << 2;
|
||||
|
||||
const majorVersion = iosUtils.MajorVersion;
|
||||
|
||||
export class View extends ViewCommon {
|
||||
nativeViewProtected: UIView;
|
||||
viewController: UIViewController;
|
||||
@@ -90,7 +93,15 @@ export class View extends ViewCommon {
|
||||
}
|
||||
|
||||
if (boundsChanged || (this._privateFlags & PFLAG_LAYOUT_REQUIRED) === PFLAG_LAYOUT_REQUIRED) {
|
||||
this.onLayout(left, top, right, bottom);
|
||||
let position = { left, top, right, bottom };
|
||||
if (this.nativeViewProtected && majorVersion > 10) {
|
||||
// on iOS 11+ it is possible to have a changed layout frame due to safe area insets
|
||||
// get the frame and adjust the position, so that onLayout works correctly
|
||||
const frame = this.nativeViewProtected.frame;
|
||||
position = ios.getPositionFromFrame(frame);
|
||||
}
|
||||
|
||||
this.onLayout(position.left, position.top, position.right, position.bottom);
|
||||
this._privateFlags &= ~PFLAG_LAYOUT_REQUIRED;
|
||||
}
|
||||
|
||||
@@ -139,13 +150,13 @@ export class View extends ViewCommon {
|
||||
}
|
||||
|
||||
public onLayout(left: number, top: number, right: number, bottom: number): void {
|
||||
//
|
||||
//
|
||||
}
|
||||
|
||||
public _setNativeViewFrame(nativeView: UIView, frame: CGRect) {
|
||||
public _setNativeViewFrame(nativeView: UIView, frame: CGRect): void {
|
||||
if (!CGRectEqualToRect(nativeView.frame, frame)) {
|
||||
if (traceEnabled()) {
|
||||
traceWrite(this + ", Native setFrame: = " + NSStringFromCGRect(frame), traceCategories.Layout);
|
||||
traceWrite(this + " :_setNativeViewFrame: " + JSON.stringify(ios.getPositionFromFrame(frame)), traceCategories.Layout);
|
||||
}
|
||||
this._cachedFrame = frame;
|
||||
if (this._hasTransfrom) {
|
||||
@@ -154,13 +165,19 @@ export class View extends ViewCommon {
|
||||
nativeView.transform = CGAffineTransformIdentity;
|
||||
nativeView.frame = frame;
|
||||
nativeView.transform = transform;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
nativeView.frame = frame;
|
||||
}
|
||||
|
||||
const adjustedFrame = this.applySafeAreaInsets(frame);
|
||||
if (adjustedFrame) {
|
||||
nativeView.frame = adjustedFrame;
|
||||
}
|
||||
|
||||
const boundsOrigin = nativeView.bounds.origin;
|
||||
nativeView.bounds = CGRectMake(boundsOrigin.x, boundsOrigin.y, frame.size.width, frame.size.height);
|
||||
const boundsFrame = adjustedFrame || frame;
|
||||
nativeView.bounds = CGRectMake(boundsOrigin.x, boundsOrigin.y, boundsFrame.size.width, boundsFrame.size.height);
|
||||
|
||||
this._raiseLayoutChangedEvent();
|
||||
this._isLaidOut = true;
|
||||
} else if (!this._isLaidOut) {
|
||||
@@ -183,7 +200,7 @@ export class View extends ViewCommon {
|
||||
}
|
||||
|
||||
const nativeView = this.nativeViewProtected;
|
||||
const frame = CGRectMake(layout.toDeviceIndependentPixels(left), layout.toDeviceIndependentPixels(top), layout.toDeviceIndependentPixels(right - left), layout.toDeviceIndependentPixels(bottom - top));
|
||||
const frame = ios.getFrameFromPosition({ left, top, right, bottom });
|
||||
this._setNativeViewFrame(nativeView, frame);
|
||||
}
|
||||
|
||||
@@ -211,6 +228,34 @@ export class View extends ViewCommon {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected applySafeAreaInsets(frame: CGRect): CGRect {
|
||||
if (majorVersion <= 10) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!this.iosOverflowSafeArea) {
|
||||
return ios.shrinkToSafeArea(this, frame);
|
||||
} else if (this.nativeViewProtected && this.nativeViewProtected.window) {
|
||||
return ios.expandBeyondSafeArea(this, frame);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public getSafeAreaInsets(): { left, top, right, bottom } {
|
||||
const safeAreaInsets = this.nativeViewProtected && this.nativeViewProtected.safeAreaInsets;
|
||||
let insets = { left: 0, top: 0, right: 0, bottom: 0 };
|
||||
|
||||
if (safeAreaInsets) {
|
||||
insets.left = layout.round(layout.toDevicePixels(safeAreaInsets.left));
|
||||
insets.top = layout.round(layout.toDevicePixels(safeAreaInsets.top));
|
||||
insets.right = layout.round(layout.toDevicePixels(safeAreaInsets.right));
|
||||
insets.bottom = layout.round(layout.toDevicePixels(safeAreaInsets.bottom));
|
||||
}
|
||||
|
||||
return insets;
|
||||
}
|
||||
|
||||
public getLocationInWindow(): Point {
|
||||
if (!this.nativeViewProtected || !this.nativeViewProtected.window) {
|
||||
return undefined;
|
||||
@@ -374,7 +419,7 @@ export class View extends ViewCommon {
|
||||
protected _hideNativeModalView(parent: View) {
|
||||
if (!parent || !parent.viewController) {
|
||||
traceError("Trying to hide modal view but no parent with viewController specified.")
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
const parentController = parent.viewController;
|
||||
@@ -508,6 +553,23 @@ export class View extends ViewCommon {
|
||||
}
|
||||
}
|
||||
|
||||
_getCurrentLayoutBounds(): { left: number; top: number; right: number; bottom: number } {
|
||||
const nativeView = this.nativeViewProtected;
|
||||
if (nativeView && !this.isCollapsed) {
|
||||
const frame = nativeView.frame;
|
||||
const origin = frame.origin;
|
||||
const size = frame.size;
|
||||
return {
|
||||
left: Math.round(layout.toDevicePixels(origin.x)),
|
||||
top: Math.round(layout.toDevicePixels(origin.y)),
|
||||
right: Math.round(layout.toDevicePixels(origin.x + size.width)),
|
||||
bottom: Math.round(layout.toDevicePixels(origin.y + size.height))
|
||||
};
|
||||
} else {
|
||||
return { left: 0, top: 0, right: 0, bottom: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
_redrawNativeBackground(value: UIColor | Background): void {
|
||||
let updateSuspended = this._isPresentationLayerUpdateSuspeneded();
|
||||
if (!updateSuspended) {
|
||||
@@ -540,7 +602,17 @@ export class View extends ViewCommon {
|
||||
}
|
||||
View.prototype._nativeBackgroundState = "unset";
|
||||
|
||||
export class CustomLayoutView extends View {
|
||||
export class ContainerView extends View {
|
||||
|
||||
public iosOverflowSafeArea: boolean;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.iosOverflowSafeArea = true;
|
||||
}
|
||||
}
|
||||
|
||||
export class CustomLayoutView extends ContainerView {
|
||||
|
||||
nativeViewProtected: UIView;
|
||||
|
||||
@@ -582,23 +654,6 @@ export class CustomLayoutView extends View {
|
||||
child.nativeViewProtected.removeFromSuperview();
|
||||
}
|
||||
}
|
||||
|
||||
_getCurrentLayoutBounds(): { left: number; top: number; right: number; bottom: number } {
|
||||
const nativeView = this.nativeViewProtected;
|
||||
if (nativeView && !this.isCollapsed) {
|
||||
const frame = nativeView.frame;
|
||||
const origin = frame.origin;
|
||||
const size = frame.size;
|
||||
return {
|
||||
left: layout.toDevicePixels(origin.x),
|
||||
top: layout.toDevicePixels(origin.y),
|
||||
right: layout.toDevicePixels(origin.x + size.width),
|
||||
bottom: layout.toDevicePixels(origin.y + size.height)
|
||||
};
|
||||
} else {
|
||||
return { left: 0, top: 0, right: 0, bottom: 0 };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export namespace ios {
|
||||
@@ -611,28 +666,19 @@ export namespace ios {
|
||||
return view;
|
||||
}
|
||||
|
||||
export function isContentScrollable(controller: UIViewController, owner: View): boolean {
|
||||
let scrollableContent = (<any>owner).scrollableContent;
|
||||
if (scrollableContent === undefined) {
|
||||
const view: UIView = controller.view.subviews.count > 0 ? controller.view.subviews[0] : null;
|
||||
if (view instanceof UIScrollView) {
|
||||
scrollableContent = true;
|
||||
}
|
||||
}
|
||||
|
||||
return scrollableContent === true || scrollableContent === "true";
|
||||
}
|
||||
|
||||
export function updateAutoAdjustScrollInsets(controller: UIViewController, owner: View): void {
|
||||
const scrollable = isContentScrollable(controller, owner);
|
||||
|
||||
owner._automaticallyAdjustsScrollViewInsets = scrollable;
|
||||
controller.automaticallyAdjustsScrollViewInsets = scrollable;
|
||||
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 {
|
||||
const root = controller.view;
|
||||
if (!root.safeAreaLayoutGuide) {
|
||||
if (majorVersion <= 10) {
|
||||
const layoutGuide = initLayoutGuide(controller);
|
||||
(<any>controller.view).safeAreaLayoutGuide = layoutGuide;
|
||||
}
|
||||
@@ -651,26 +697,19 @@ export namespace ios {
|
||||
return layoutGuide;
|
||||
}
|
||||
|
||||
function getStatusBarHeight(viewController?: UIViewController): number {
|
||||
const app = iosUtils.getter(UIApplication, UIApplication.sharedApplication);
|
||||
if (!app || app.statusBarHidden) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (viewController && viewController.prefersStatusBarHidden) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const statusFrame = app.statusBarFrame;
|
||||
return Math.min(statusFrame.size.width, statusFrame.size.height);
|
||||
}
|
||||
|
||||
export function layoutView(controller: UIViewController, owner: View): void {
|
||||
let left: number, top: number, width: number, height: number;
|
||||
// apply parent page additional top insets if any. The scenario is when there is a parent page with action bar.
|
||||
const parentPage = getAncestor(owner, "Page");
|
||||
if (parentPage) {
|
||||
const parentPageInsetsTop = parentPage.viewController.view.safeAreaInsets.top;
|
||||
const currentInsetsTop = controller.view.safeAreaInsets.top;
|
||||
const additionalInsetsTop = parentPageInsetsTop - currentInsetsTop;
|
||||
|
||||
const frame = controller.view.frame;
|
||||
const fullscreenOrigin = frame.origin;
|
||||
const fullscreenSize = frame.size;
|
||||
if (additionalInsetsTop > 0) {
|
||||
const additionalInsets = new UIEdgeInsets({ top: additionalInsetsTop, left: 0, bottom: 0, right: 0 });
|
||||
controller.additionalSafeAreaInsets = additionalInsets;
|
||||
}
|
||||
}
|
||||
|
||||
let layoutGuide = controller.view.safeAreaLayoutGuide;
|
||||
if (!layoutGuide) {
|
||||
@@ -680,56 +719,104 @@ export namespace ios {
|
||||
layoutGuide = initLayoutGuide(controller);
|
||||
}
|
||||
const safeArea = layoutGuide.layoutFrame;
|
||||
const safeOrigin = safeArea.origin;
|
||||
let position = ios.getPositionFromFrame(safeArea);
|
||||
const safeAreaSize = safeArea.size;
|
||||
|
||||
const navController = controller.navigationController;
|
||||
const navBarHidden = navController ? navController.navigationBarHidden : true;
|
||||
const scrollable = isContentScrollable(controller, owner);
|
||||
const hasChildControllers = controller.childViewControllers.count > 0;
|
||||
|
||||
if (!(controller.edgesForExtendedLayout & UIRectEdge.Top)) {
|
||||
const statusBarHeight = getStatusBarHeight(controller);
|
||||
const navBarHeight = controller.navigationController ? controller.navigationController.navigationBar.frame.size.height : 0;
|
||||
fullscreenOrigin.y = safeOrigin.y;
|
||||
fullscreenSize.height -= (statusBarHeight + navBarHeight);
|
||||
const hasChildViewControllers = controller.childViewControllers.count > 0;
|
||||
if (hasChildViewControllers) {
|
||||
const fullscreen = controller.view.frame;
|
||||
position = ios.getPositionFromFrame(fullscreen);
|
||||
}
|
||||
|
||||
left = safeOrigin.x;
|
||||
width = safeAreaSize.width;
|
||||
const safeAreaWidth = layout.round(layout.toDevicePixels(safeAreaSize.width));
|
||||
const safeAreaHeight = layout.round(layout.toDevicePixels(safeAreaSize.height));
|
||||
|
||||
if (hasChildControllers) {
|
||||
// If not inner most extend to fullscreen
|
||||
top = fullscreenOrigin.y;
|
||||
height = fullscreenSize.height;
|
||||
} else if (!scrollable) {
|
||||
// If not scrollable dock under safe area
|
||||
top = safeOrigin.y;
|
||||
height = safeAreaSize.height;
|
||||
} else if (navBarHidden) {
|
||||
// If scrollable but no navigation bar dock under safe area
|
||||
top = safeOrigin.y;
|
||||
height = navController ? (fullscreenSize.height - top) : safeAreaSize.height;
|
||||
} else {
|
||||
// If scrollable and navigation bar extend to fullscreen
|
||||
top = fullscreenOrigin.y;
|
||||
height = fullscreenSize.height;
|
||||
}
|
||||
|
||||
left = layout.toDevicePixels(left);
|
||||
top = layout.toDevicePixels(top);
|
||||
width = layout.toDevicePixels(width);
|
||||
height = layout.toDevicePixels(height);
|
||||
|
||||
const widthSpec = layout.makeMeasureSpec(width, layout.EXACTLY);
|
||||
const heightSpec = layout.makeMeasureSpec(height, layout.EXACTLY);
|
||||
const widthSpec = layout.makeMeasureSpec(safeAreaWidth, layout.EXACTLY);
|
||||
const heightSpec = layout.makeMeasureSpec(safeAreaHeight, layout.EXACTLY);
|
||||
|
||||
View.measureChild(null, owner, widthSpec, heightSpec);
|
||||
View.layoutChild(null, owner, left, top, width + left, height + top);
|
||||
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 locationInWindow = view.getLocationInWindow();
|
||||
const inWindowLeft = layout.round(layout.toDevicePixels(locationInWindow.x));
|
||||
const inWindowTop = layout.round(layout.toDevicePixels(locationInWindow.y));
|
||||
const inWindowRight = inWindowLeft + layout.round(layout.toDevicePixels(frame.size.width));
|
||||
const inWindowBottom = inWindowTop + layout.round(layout.toDevicePixels(frame.size.height));
|
||||
|
||||
const availableSpace = getAvailableSpaceFromParent(view);
|
||||
const safeArea = availableSpace.safeArea;
|
||||
const fullscreen = availableSpace.fullscreen;
|
||||
|
||||
const position = ios.getPositionFromFrame(frame);
|
||||
const safeAreaPosition = ios.getPositionFromFrame(safeArea);
|
||||
const fullscreenPosition = ios.getPositionFromFrame(fullscreen);
|
||||
|
||||
const adjustedPosition = position;
|
||||
|
||||
if (position.left && inWindowLeft <= safeAreaPosition.left) {
|
||||
adjustedPosition.left = fullscreenPosition.left;
|
||||
}
|
||||
|
||||
if (position.top && inWindowTop <= safeAreaPosition.top) {
|
||||
adjustedPosition.top = fullscreenPosition.top;
|
||||
}
|
||||
|
||||
if (inWindowRight < fullscreenPosition.right && inWindowRight >= safeAreaPosition.right + fullscreenPosition.left) {
|
||||
adjustedPosition.right = fullscreenPosition.right - fullscreenPosition.left;
|
||||
}
|
||||
|
||||
if (inWindowBottom < fullscreenPosition.bottom && inWindowBottom >= safeAreaPosition.bottom + fullscreenPosition.top) {
|
||||
adjustedPosition.bottom = fullscreenPosition.bottom - fullscreenPosition.top;
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -749,6 +836,38 @@ export namespace ios {
|
||||
layoutParent(view.parent);
|
||||
}
|
||||
|
||||
function getAvailableSpaceFromParent(view: View): { safeArea: CGRect, fullscreen: CGRect } {
|
||||
if (!view) {
|
||||
return;
|
||||
}
|
||||
|
||||
let fullscreen = null;
|
||||
let safeArea = null;
|
||||
|
||||
if (view.viewController) {
|
||||
const nativeView = view.viewController.view;
|
||||
safeArea = nativeView.safeAreaLayoutGuide.layoutFrame;
|
||||
fullscreen = nativeView.frame;
|
||||
} 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) {
|
||||
const nativeView = parent.nativeViewProtected;
|
||||
safeArea = nativeView.safeAreaLayoutGuide.layoutFrame;
|
||||
fullscreen = CGRectMake(0, 0, nativeView.contentSize.width, nativeView.contentSize.height);
|
||||
} else if (parent.viewController) {
|
||||
const nativeView = parent.viewController.view;
|
||||
safeArea = nativeView.safeAreaLayoutGuide.layoutFrame;
|
||||
fullscreen = nativeView.frame;
|
||||
}
|
||||
}
|
||||
|
||||
return { safeArea: safeArea, fullscreen: fullscreen }
|
||||
}
|
||||
|
||||
export class UILayoutViewController extends UIViewController {
|
||||
public owner: WeakRef<View>;
|
||||
|
||||
@@ -796,4 +915,4 @@ export namespace ios {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,13 +47,14 @@ export class AbsoluteLayout extends AbsoluteLayoutBase {
|
||||
public onLayout(left: number, top: number, right: number, bottom: number): void {
|
||||
super.onLayout(left, top, right, bottom);
|
||||
|
||||
const insets = this.getSafeAreaInsets();
|
||||
this.eachLayoutChild((child, last) => {
|
||||
|
||||
const childWidth = child.getMeasuredWidth();
|
||||
const childHeight = child.getMeasuredHeight();
|
||||
|
||||
const childLeft = this.effectiveBorderLeftWidth + this.effectivePaddingLeft + child.effectiveLeft;
|
||||
const childTop = this.effectiveBorderTopWidth + this.effectivePaddingTop + child.effectiveTop;
|
||||
const childLeft = this.effectiveBorderLeftWidth + this.effectivePaddingLeft + child.effectiveLeft + insets.left;
|
||||
const childTop = this.effectiveBorderTopWidth + this.effectivePaddingTop + child.effectiveTop + insets.top;
|
||||
const childRight = childLeft + childWidth + child.effectiveMarginLeft + child.effectiveMarginRight;
|
||||
const childBottom = childTop + childHeight + child.effectiveMarginTop + child.effectiveMarginBottom;
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ export class DockLayout extends DockLayoutBase {
|
||||
|
||||
const horizontalPaddingsAndMargins = this.effectivePaddingLeft + this.effectivePaddingRight + this.effectiveBorderLeftWidth + this.effectiveBorderRightWidth;
|
||||
const verticalPaddingsAndMargins = this.effectivePaddingTop + this.effectivePaddingBottom + this.effectiveBorderTopWidth + this.effectiveBorderBottomWidth;
|
||||
|
||||
|
||||
let remainingWidth = widthMode === layout.UNSPECIFIED ? Number.MAX_VALUE : width - horizontalPaddingsAndMargins;
|
||||
let remainingHeight = heightMode === layout.UNSPECIFIED ? Number.MAX_VALUE : height - verticalPaddingsAndMargins;
|
||||
|
||||
@@ -79,11 +79,12 @@ export class DockLayout extends DockLayoutBase {
|
||||
public onLayout(left: number, top: number, right: number, bottom: number): void {
|
||||
super.onLayout(left, top, right, bottom);
|
||||
|
||||
const horizontalPaddingsAndMargins = this.effectivePaddingLeft + this.effectivePaddingRight + this.effectiveBorderLeftWidth + this.effectiveBorderRightWidth;
|
||||
const verticalPaddingsAndMargins = this.effectivePaddingTop + this.effectivePaddingBottom + this.effectiveBorderTopWidth + this.effectiveBorderBottomWidth;
|
||||
const insets = this.getSafeAreaInsets();
|
||||
const horizontalPaddingsAndMargins = this.effectivePaddingLeft + this.effectivePaddingRight + this.effectiveBorderLeftWidth + this.effectiveBorderRightWidth + insets.left + insets.right;
|
||||
const verticalPaddingsAndMargins = this.effectivePaddingTop + this.effectivePaddingBottom + this.effectiveBorderTopWidth + this.effectiveBorderBottomWidth + insets.top + insets.bottom;
|
||||
|
||||
let childLeft = this.effectiveBorderLeftWidth + this.effectivePaddingLeft;
|
||||
let childTop = this.effectiveBorderTopWidth + this.effectivePaddingTop;
|
||||
let childLeft = this.effectiveBorderLeftWidth + this.effectivePaddingLeft + insets.left;
|
||||
let childTop = this.effectiveBorderTopWidth + this.effectivePaddingTop + insets.top;
|
||||
|
||||
let x = childLeft;
|
||||
let y = childTop;
|
||||
|
||||
@@ -943,38 +943,43 @@ export class FlexboxLayout extends FlexboxLayoutBase {
|
||||
}
|
||||
|
||||
public onLayout(left: number, top: number, right: number, bottom: number) {
|
||||
const insets = this.getSafeAreaInsets();
|
||||
|
||||
let isRtl;
|
||||
switch (this.flexDirection) {
|
||||
case FlexDirection.ROW:
|
||||
isRtl = false;
|
||||
this._layoutHorizontal(isRtl, left, top, right, bottom);
|
||||
this._layoutHorizontal(isRtl, left, top, right, bottom, insets);
|
||||
break;
|
||||
case FlexDirection.ROW_REVERSE:
|
||||
isRtl = true;
|
||||
this._layoutHorizontal(isRtl, left, top, right, bottom);
|
||||
this._layoutHorizontal(isRtl, left, top, right, bottom, insets);
|
||||
break;
|
||||
case FlexDirection.COLUMN:
|
||||
isRtl = false;
|
||||
if (this.flexWrap === FlexWrap.WRAP_REVERSE) {
|
||||
isRtl = !isRtl;
|
||||
}
|
||||
this._layoutVertical(isRtl, false, left, top, right, bottom);
|
||||
this._layoutVertical(isRtl, false, left, top, right, bottom, insets);
|
||||
break;
|
||||
case FlexDirection.COLUMN_REVERSE:
|
||||
isRtl = false;
|
||||
if (this.flexWrap === FlexWrap.WRAP_REVERSE) {
|
||||
isRtl = !isRtl;
|
||||
}
|
||||
this._layoutVertical(isRtl, true, left, top, right, bottom);
|
||||
this._layoutVertical(isRtl, true, left, top, right, bottom, insets);
|
||||
break;
|
||||
default:
|
||||
throw new Error("Invalid flex direction is set: " + this.flexDirection);
|
||||
}
|
||||
}
|
||||
|
||||
private _layoutHorizontal(isRtl: boolean, left: number, top: number, right: number, bottom: number) {
|
||||
let paddingLeft = this.effectivePaddingLeft;
|
||||
let paddingRight = this.effectivePaddingRight;
|
||||
private _layoutHorizontal(isRtl: boolean, left: number, top: number, right: number, bottom: number, insets: { left, top, right, bottom }) {
|
||||
// include insets
|
||||
let paddingLeft = this.effectivePaddingLeft + insets.left;
|
||||
let paddingTop = this.effectivePaddingTop + insets.top;
|
||||
let paddingRight = this.effectivePaddingRight + insets.right;
|
||||
let paddingBottom = this.effectivePaddingBottom + insets.bottom;
|
||||
|
||||
let childLeft;
|
||||
let currentViewIndex = 0;
|
||||
@@ -982,8 +987,9 @@ export class FlexboxLayout extends FlexboxLayoutBase {
|
||||
let height = bottom - top;
|
||||
let width = right - left;
|
||||
|
||||
let childBottom = height - this.effectivePaddingBottom;
|
||||
let childTop = this.effectivePaddingTop;
|
||||
// include insets
|
||||
let childBottom = height - paddingBottom;
|
||||
let childTop = paddingTop;
|
||||
|
||||
let childRight;
|
||||
this._flexLines.forEach((flexLine, i) => {
|
||||
@@ -997,16 +1003,16 @@ export class FlexboxLayout extends FlexboxLayoutBase {
|
||||
childRight = width - paddingRight;
|
||||
break;
|
||||
case JustifyContent.FLEX_END:
|
||||
childLeft = width - flexLine._mainSize + paddingRight;
|
||||
childRight = flexLine._mainSize - paddingLeft;
|
||||
childLeft = width - flexLine._mainSize - paddingRight;
|
||||
childRight = flexLine._mainSize + paddingLeft;
|
||||
break;
|
||||
case JustifyContent.CENTER:
|
||||
childLeft = paddingLeft + (width - flexLine._mainSize) / 2.0;
|
||||
childRight = width - paddingRight - (width - flexLine._mainSize) / 2.0;
|
||||
childLeft = paddingLeft + (width - insets.left - insets.right - flexLine._mainSize) / 2.0;
|
||||
childRight = width - paddingRight - (width - insets.left - insets.right - flexLine._mainSize) / 2.0;
|
||||
break;
|
||||
case JustifyContent.SPACE_AROUND:
|
||||
if (flexLine._itemCount !== 0) {
|
||||
spaceBetweenItem = (width - flexLine.mainSize) / flexLine._itemCount;
|
||||
spaceBetweenItem = (width - insets.left - insets.right - flexLine.mainSize) / flexLine._itemCount;
|
||||
}
|
||||
childLeft = paddingLeft + spaceBetweenItem / 2.0;
|
||||
childRight = width - paddingRight - spaceBetweenItem / 2.0;
|
||||
@@ -1014,7 +1020,7 @@ export class FlexboxLayout extends FlexboxLayoutBase {
|
||||
case JustifyContent.SPACE_BETWEEN:
|
||||
childLeft = paddingLeft;
|
||||
let denominator = flexLine.itemCount !== 1 ? flexLine.itemCount - 1 : 1.0;
|
||||
spaceBetweenItem = (width - flexLine.mainSize) / denominator;
|
||||
spaceBetweenItem = (width - insets.left - insets.right - flexLine.mainSize) / denominator;
|
||||
childRight = width - paddingRight;
|
||||
break;
|
||||
default:
|
||||
@@ -1130,12 +1136,13 @@ export class FlexboxLayout extends FlexboxLayoutBase {
|
||||
}
|
||||
}
|
||||
|
||||
private _layoutVertical(isRtl: boolean, fromBottomToTop: boolean, left: number, top: number, right: number, bottom: number) {
|
||||
let paddingTop = this.effectivePaddingTop;
|
||||
let paddingBottom = this.effectivePaddingBottom;
|
||||
private _layoutVertical(isRtl: boolean, fromBottomToTop: boolean, left: number, top: number, right: number, bottom: number, insets: { left, top, right, bottom }) {
|
||||
let paddingLeft = this.effectivePaddingLeft + insets.left;
|
||||
let paddingTop = this.effectivePaddingTop + insets.top;
|
||||
let paddingRight = this.effectivePaddingRight + insets.right;
|
||||
let paddingBottom = this.effectivePaddingBottom + insets.bottom;
|
||||
|
||||
let paddingRight = this.effectivePaddingRight;
|
||||
let childLeft = this.effectivePaddingLeft;
|
||||
let childLeft = paddingLeft;
|
||||
let currentViewIndex = 0;
|
||||
|
||||
let width = right - left;
|
||||
@@ -1157,16 +1164,16 @@ export class FlexboxLayout extends FlexboxLayoutBase {
|
||||
childBottom = height - paddingBottom;
|
||||
break;
|
||||
case JustifyContent.FLEX_END:
|
||||
childTop = height - flexLine._mainSize + paddingBottom;
|
||||
childBottom = flexLine._mainSize - paddingTop;
|
||||
childTop = height - flexLine._mainSize - paddingBottom;
|
||||
childBottom = flexLine._mainSize + paddingTop;
|
||||
break;
|
||||
case JustifyContent.CENTER:
|
||||
childTop = paddingTop + (height - flexLine._mainSize) / 2.0;
|
||||
childBottom = height - paddingBottom - (height - flexLine._mainSize) / 2.0;
|
||||
childTop = paddingTop + (height - insets.top - insets.bottom - flexLine._mainSize) / 2.0;
|
||||
childBottom = height - paddingBottom - (height - insets.top - insets.bottom - flexLine._mainSize) / 2.0;
|
||||
break;
|
||||
case JustifyContent.SPACE_AROUND:
|
||||
if (flexLine._itemCount !== 0) {
|
||||
spaceBetweenItem = (height - flexLine._mainSize) / flexLine.itemCount;
|
||||
spaceBetweenItem = (height - insets.top - insets.bottom - flexLine._mainSize) / flexLine.itemCount;
|
||||
}
|
||||
childTop = paddingTop + spaceBetweenItem / 2.0;
|
||||
childBottom = height - paddingBottom - spaceBetweenItem / 2.0;
|
||||
@@ -1174,7 +1181,7 @@ export class FlexboxLayout extends FlexboxLayoutBase {
|
||||
case JustifyContent.SPACE_BETWEEN:
|
||||
childTop = paddingTop;
|
||||
let denominator = flexLine.itemCount !== 1 ? flexLine.itemCount - 1 : 1.0;
|
||||
spaceBetweenItem = (height - flexLine.mainSize) / denominator;
|
||||
spaceBetweenItem = (height - insets.top - insets.bottom - flexLine.mainSize) / denominator;
|
||||
childBottom = height - paddingBottom;
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -159,8 +159,10 @@ export class GridLayout extends GridLayoutBase {
|
||||
public onLayout(left: number, top: number, right: number, bottom: number): void {
|
||||
super.onLayout(left, top, right, bottom);
|
||||
|
||||
let paddingLeft = this.effectiveBorderLeftWidth + this.effectivePaddingLeft;
|
||||
let paddingTop = this.effectiveBorderTopWidth + this.effectivePaddingTop;
|
||||
const insets = this.getSafeAreaInsets();
|
||||
|
||||
let paddingLeft = this.effectiveBorderLeftWidth + this.effectivePaddingLeft + insets.left;
|
||||
let paddingTop = this.effectiveBorderTopWidth + this.effectivePaddingTop + insets.top;
|
||||
|
||||
this.columnOffsets.length = 0;
|
||||
this.rowOffsets.length = 0;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import {
|
||||
LayoutBaseCommon, clipToBoundsProperty, isPassThroughParentEnabledProperty, View
|
||||
import {
|
||||
LayoutBaseCommon, clipToBoundsProperty, isPassThroughParentEnabledProperty, View
|
||||
} from "./layout-base-common";
|
||||
|
||||
export * from "./layout-base-common";
|
||||
|
||||
export class LayoutBase extends LayoutBaseCommon {
|
||||
nativeViewProtected: UIView;
|
||||
|
||||
|
||||
public addChild(child: View): void {
|
||||
super.addChild(child);
|
||||
this.requestLayout();
|
||||
@@ -29,7 +29,7 @@ export class LayoutBase extends LayoutBaseCommon {
|
||||
super._setNativeClipToBounds();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[clipToBoundsProperty.getDefault](): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -83,19 +83,21 @@ export class StackLayout extends StackLayoutBase {
|
||||
|
||||
public onLayout(left: number, top: number, right: number, bottom: number): void {
|
||||
super.onLayout(left, top, right, bottom);
|
||||
|
||||
const insets = this.getSafeAreaInsets();
|
||||
if (this.orientation === "vertical") {
|
||||
this.layoutVertical(left, top, right, bottom);
|
||||
this.layoutVertical(left, top, right, bottom, insets);
|
||||
}
|
||||
else {
|
||||
this.layoutHorizontal(left, top, right, bottom);
|
||||
this.layoutHorizontal(left, top, right, bottom, insets);
|
||||
}
|
||||
}
|
||||
|
||||
private layoutVertical(left: number, top: number, right: number, bottom: number): void {
|
||||
const paddingLeft = this.effectiveBorderLeftWidth + this.effectivePaddingLeft;
|
||||
const paddingTop = this.effectiveBorderTopWidth + this.effectivePaddingTop;
|
||||
const paddingRight = this.effectiveBorderRightWidth + this.effectivePaddingRight;
|
||||
const paddingBottom = this.effectiveBorderBottomWidth + this.effectivePaddingBottom;
|
||||
private layoutVertical(left: number, top: number, right: number, bottom: number, insets: {left, top, right, bottom}): void {
|
||||
const paddingLeft = this.effectiveBorderLeftWidth + this.effectivePaddingLeft + insets.left;
|
||||
const paddingTop = this.effectiveBorderTopWidth + this.effectivePaddingTop + insets.top;
|
||||
const paddingRight = this.effectiveBorderRightWidth + this.effectivePaddingRight + insets.right;
|
||||
const paddingBottom = this.effectiveBorderBottomWidth + this.effectivePaddingBottom + insets.bottom;
|
||||
|
||||
let childTop: number;
|
||||
let childLeft: number = paddingLeft;
|
||||
@@ -125,11 +127,11 @@ export class StackLayout extends StackLayoutBase {
|
||||
})
|
||||
}
|
||||
|
||||
private layoutHorizontal(left: number, top: number, right: number, bottom: number): void {
|
||||
const paddingLeft = this.effectiveBorderLeftWidth + this.effectivePaddingLeft;
|
||||
const paddingTop = this.effectiveBorderTopWidth + this.effectivePaddingTop;
|
||||
const paddingRight = this.effectiveBorderRightWidth + this.effectivePaddingRight;
|
||||
const paddingBottom = this.effectiveBorderBottomWidth + this.effectivePaddingBottom;
|
||||
private layoutHorizontal(left: number, top: number, right: number, bottom: number, insets: {left, top, right, bottom}): void {
|
||||
const paddingLeft = this.effectiveBorderLeftWidth + this.effectivePaddingLeft + insets.left;
|
||||
const paddingTop = this.effectiveBorderTopWidth + this.effectivePaddingTop + insets.top;
|
||||
const paddingRight = this.effectiveBorderRightWidth + this.effectivePaddingRight + insets.right;
|
||||
const paddingBottom = this.effectiveBorderBottomWidth + this.effectivePaddingBottom + insets.bottom;
|
||||
|
||||
let childTop: number = paddingTop;
|
||||
let childLeft: number;
|
||||
|
||||
@@ -122,35 +122,31 @@ export class WrapLayout extends WrapLayoutBase {
|
||||
public onLayout(left: number, top: number, right: number, bottom: number): void {
|
||||
super.onLayout(left, top, right, bottom);
|
||||
|
||||
const insets = this.getSafeAreaInsets();
|
||||
const isVertical = this.orientation === "vertical";
|
||||
const paddingLeft = this.effectiveBorderLeftWidth + this.effectivePaddingLeft;
|
||||
const paddingTop = this.effectiveBorderTopWidth + this.effectivePaddingTop;
|
||||
const paddingRight = this.effectiveBorderRightWidth + this.effectivePaddingRight;
|
||||
const paddingBottom = this.effectiveBorderBottomWidth + this.effectivePaddingBottom;
|
||||
const paddingLeft = this.effectiveBorderLeftWidth + this.effectivePaddingLeft + insets.left;
|
||||
const paddingTop = this.effectiveBorderTopWidth + this.effectivePaddingTop + insets.top;
|
||||
const paddingRight = this.effectiveBorderRightWidth + this.effectivePaddingRight + insets.right;
|
||||
const paddingBottom = this.effectiveBorderBottomWidth + this.effectivePaddingBottom + insets.bottom;
|
||||
|
||||
let childLeft = paddingLeft;
|
||||
let childTop = paddingTop;
|
||||
let childrenLength: number;
|
||||
if (isVertical) {
|
||||
childrenLength = bottom - top - paddingBottom;
|
||||
}
|
||||
else {
|
||||
childrenLength = right - left - paddingRight;
|
||||
}
|
||||
let childrenHeight = bottom - top - paddingBottom;
|
||||
let childrenWidth = right - left - paddingRight;
|
||||
let rowOrColumn = 0;
|
||||
|
||||
var rowOrColumn = 0;
|
||||
this.eachLayoutChild((child, last) => {
|
||||
// Add margins because layoutChild will sustract them.
|
||||
// * density converts them to device pixels.
|
||||
let childHeight = child.getMeasuredHeight() + child.effectiveMarginTop + child.effectiveMarginBottom;
|
||||
let childWidth = child.getMeasuredWidth() + child.effectiveMarginLeft + child.effectiveMarginRight;
|
||||
|
||||
|
||||
let length = this._lengths[rowOrColumn];
|
||||
if (isVertical) {
|
||||
childWidth = length;
|
||||
childHeight = this.effectiveItemHeight > 0 ? this.effectiveItemHeight : childHeight;
|
||||
let isFirst = childTop === paddingTop;
|
||||
if (childTop + childHeight > childrenLength) {
|
||||
if (childTop + childHeight > childrenHeight && childLeft + childWidth <= childrenWidth) {
|
||||
// Move to top.
|
||||
childTop = paddingTop;
|
||||
|
||||
@@ -165,12 +161,18 @@ export class WrapLayout extends WrapLayoutBase {
|
||||
// Take respective column width.
|
||||
childWidth = this._lengths[isFirst ? rowOrColumn - 1 : rowOrColumn];
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
if (childLeft < childrenWidth && childTop < childrenHeight) {
|
||||
View.layoutChild(this, child, childLeft, childTop, childLeft + childWidth, childTop + childHeight);
|
||||
}
|
||||
|
||||
// Move next child Top position to bottom.
|
||||
childTop += childHeight;
|
||||
} else {
|
||||
childWidth = this.effectiveItemWidth > 0 ? this.effectiveItemWidth : childWidth;
|
||||
childHeight = length;
|
||||
let isFirst = childLeft === paddingLeft;
|
||||
if (childLeft + childWidth > childrenLength) {
|
||||
if (childLeft + childWidth > childrenWidth && childTop + childHeight <= childrenHeight) {
|
||||
// Move to left.
|
||||
childLeft = paddingLeft;
|
||||
|
||||
@@ -185,15 +187,11 @@ export class WrapLayout extends WrapLayoutBase {
|
||||
// Take respective row height.
|
||||
childHeight = this._lengths[isFirst ? rowOrColumn - 1 : rowOrColumn];
|
||||
}
|
||||
}
|
||||
|
||||
View.layoutChild(this, child, childLeft, childTop, childLeft + childWidth, childTop + childHeight);
|
||||
if (childLeft < childrenWidth && childTop < childrenHeight) {
|
||||
View.layoutChild(this, child, childLeft, childTop, childLeft + childWidth, childTop + childHeight);
|
||||
}
|
||||
|
||||
if (isVertical) {
|
||||
// Move next child Top position to bottom.
|
||||
childTop += childHeight;
|
||||
}
|
||||
else {
|
||||
// Move next child Left position to right.
|
||||
childLeft += childWidth;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ListView as ListViewDefinition, ItemsSource, ItemEventData, TemplatedItemsView } from ".";
|
||||
import { CoercibleProperty, CssProperty, Style, View, Template, KeyedTemplate, Length, Property, Color, Observable, EventData, CSSType } from "../core/view";
|
||||
import { CoercibleProperty, CssProperty, Style, View, ViewBase, ContainerView, Template, KeyedTemplate, Length, Property, Color, Observable, EventData, CSSType } from "../core/view";
|
||||
import { parse, parseMultipleTemplates } from "../builder";
|
||||
import { Label } from "../label";
|
||||
import { ObservableArray, ChangedData } from "../../data/observable-array";
|
||||
@@ -19,7 +19,7 @@ export module knownMultiTemplates {
|
||||
const autoEffectiveRowHeight = -1;
|
||||
|
||||
@CSSType("ListView")
|
||||
export abstract class ListViewBase extends View implements ListViewDefinition, TemplatedItemsView {
|
||||
export abstract class ListViewBase extends ContainerView implements ListViewDefinition, TemplatedItemsView {
|
||||
public static itemLoadingEvent = "itemLoading";
|
||||
public static itemTapEvent = "itemTap";
|
||||
public static loadMoreItemsEvent = "loadMoreItems";
|
||||
|
||||
@@ -7,6 +7,7 @@ import { StackLayout } from "../layouts/stack-layout";
|
||||
import { ProxyViewContainer } from "../proxy-view-container";
|
||||
import { profile } from "../../profiling";
|
||||
import * as trace from "../../trace";
|
||||
import { ios as iosUtils } from "../../utils/utils";
|
||||
|
||||
export * from "./list-view-common";
|
||||
|
||||
@@ -22,6 +23,7 @@ interface ViewItemIndex {
|
||||
}
|
||||
|
||||
type ItemView = View & ViewItemIndex;
|
||||
const majorVersion = iosUtils.MajorVersion;
|
||||
|
||||
class ListViewCell extends UITableViewCell {
|
||||
public static initWithEmptyBackground(): ListViewCell {
|
||||
|
||||
@@ -120,7 +120,7 @@ class UIViewControllerImpl extends UIViewController {
|
||||
// Skip navigation events if modal page is shown.
|
||||
if (!owner._presentedViewController && frame) {
|
||||
const newEntry = this[ENTRY];
|
||||
|
||||
|
||||
let isBack: boolean;
|
||||
// We are on the current page which happens when navigation is canceled so isBack should be false.
|
||||
if (frame.currentPage === owner && frame._navigationQueue.length === 0) {
|
||||
@@ -312,7 +312,14 @@ export class Page extends PageBase {
|
||||
public onLayout(left: number, top: number, right: number, bottom: number) {
|
||||
const { width: actionBarWidth, height: actionBarHeight } = this.actionBar._getActualSize;
|
||||
View.layoutChild(this, this.actionBar, 0, 0, actionBarWidth, actionBarHeight);
|
||||
View.layoutChild(this, this.layoutView, left, top, right, bottom);
|
||||
|
||||
const insets = this.getSafeAreaInsets();
|
||||
|
||||
const childLeft = 0 + insets.left;
|
||||
const childTop = 0 + insets.top;
|
||||
const childRight = right - left - insets.right;
|
||||
const childBottom = bottom - top - insets.bottom;
|
||||
View.layoutChild(this, this.layoutView, childLeft, childTop, childRight, childBottom);
|
||||
}
|
||||
|
||||
public _addViewToNativeVisualTree(child: View, atIndex: number): boolean {
|
||||
@@ -330,7 +337,7 @@ export class Page extends PageBase {
|
||||
if (this.viewController.presentedViewController === viewController) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
this.viewController.addChildViewController(viewController);
|
||||
}
|
||||
|
||||
|
||||
@@ -101,7 +101,12 @@ export class Repeater extends CustomLayoutView implements RepeaterDefinition {
|
||||
}
|
||||
|
||||
public onLayout(left: number, top: number, right: number, bottom: number): void {
|
||||
View.layoutChild(this, this.itemsLayout, 0, 0, right - left, bottom - top);
|
||||
const insets = this.getSafeAreaInsets();
|
||||
const childLeft = left + insets.left;
|
||||
const childTop = top + insets.top;
|
||||
const childRight = right - insets.right;
|
||||
const childBottom = bottom - insets.bottom;
|
||||
View.layoutChild(this, this.itemsLayout, childLeft, childTop, childRight, childBottom);
|
||||
}
|
||||
|
||||
public onMeasure(widthMeasureSpec: number, heightMeasureSpec: number): void {
|
||||
|
||||
@@ -132,15 +132,6 @@ export class ScrollView extends ScrollViewBase {
|
||||
this._contentMeasuredWidth = this.effectiveMinWidth;
|
||||
this._contentMeasuredHeight = this.effectiveMinHeight;
|
||||
|
||||
// `_automaticallyAdjustsScrollViewInsets` is set to true only if the first child
|
||||
// of UIViewController (Page, TabView e.g) is UIScrollView (ScrollView, ListView e.g).
|
||||
// On iOS 11 by default UIScrollView automatically adjusts the scroll view insets, but they s
|
||||
if (majorVersion > 10 && !this.parent._automaticallyAdjustsScrollViewInsets) {
|
||||
// Disable automatic adjustment of scroll view insets when ScrollView
|
||||
// is not the first child of UIViewController.
|
||||
this.nativeViewProtected.contentInsetAdjustmentBehavior = 2;
|
||||
}
|
||||
|
||||
if (child) {
|
||||
let childSize: { measuredWidth: number; measuredHeight: number };
|
||||
if (this.orientation === "vertical") {
|
||||
@@ -149,10 +140,6 @@ export class ScrollView extends ScrollViewBase {
|
||||
childSize = View.measureChild(this, child, layout.makeMeasureSpec(0, layout.UNSPECIFIED), heightMeasureSpec);
|
||||
}
|
||||
|
||||
const w = layout.toDeviceIndependentPixels(childSize.measuredWidth);
|
||||
const h = layout.toDeviceIndependentPixels(childSize.measuredHeight);
|
||||
this.nativeViewProtected.contentSize = CGSizeMake(w, h);
|
||||
|
||||
this._contentMeasuredWidth = Math.max(childSize.measuredWidth, this.effectiveMinWidth);
|
||||
this._contentMeasuredHeight = Math.max(childSize.measuredHeight, this.effectiveMinHeight);
|
||||
}
|
||||
@@ -164,25 +151,34 @@ export class ScrollView extends ScrollViewBase {
|
||||
}
|
||||
|
||||
public onLayout(left: number, top: number, right: number, bottom: number): void {
|
||||
const width = (right - left);
|
||||
const height = (bottom - top);
|
||||
const insets = this.getSafeAreaInsets();
|
||||
let width = (right - left - insets.right - insets.left);
|
||||
let height = (bottom - top - insets.bottom - insets.top);
|
||||
|
||||
let verticalInset: number;
|
||||
const nativeView = this.nativeViewProtected;
|
||||
const inset = nativeView.adjustedContentInset;
|
||||
// Prior iOS 11
|
||||
if (inset === undefined) {
|
||||
verticalInset = -layout.toDevicePixels(nativeView.contentOffset.y);
|
||||
verticalInset += getTabBarHeight(this);
|
||||
} else {
|
||||
verticalInset = layout.toDevicePixels(inset.bottom + inset.top);
|
||||
|
||||
if (majorVersion > 10) {
|
||||
// Disable automatic adjustment of scroll view insets
|
||||
// Consider exposing this as property with all 4 modes
|
||||
// https://developer.apple.com/documentation/uikit/uiscrollview/contentinsetadjustmentbehavior
|
||||
nativeView.contentInsetAdjustmentBehavior = 2;
|
||||
}
|
||||
|
||||
let scrollWidth = width;
|
||||
let scrollHeight = height;
|
||||
if (this.orientation === "horizontal") {
|
||||
View.layoutChild(this, this.layoutView, 0, 0, Math.max(this._contentMeasuredWidth, width), height - verticalInset);
|
||||
} else {
|
||||
View.layoutChild(this, this.layoutView, 0, 0, width, Math.max(this._contentMeasuredHeight, height - verticalInset));
|
||||
scrollWidth = Math.max(this._contentMeasuredWidth + insets.left + insets.right, width);
|
||||
scrollHeight = height + insets.top + insets.bottom;
|
||||
width = Math.max(this._contentMeasuredWidth, width);
|
||||
}
|
||||
else {
|
||||
scrollHeight = Math.max(this._contentMeasuredHeight + insets.top + insets.bottom, height);
|
||||
scrollWidth = width + insets.left + insets.right;
|
||||
height = Math.max(this._contentMeasuredHeight, height);
|
||||
}
|
||||
|
||||
nativeView.contentSize = CGSizeMake(layout.toDeviceIndependentPixels(scrollWidth), layout.toDeviceIndependentPixels(scrollHeight));
|
||||
View.layoutChild(this, this.layoutView, insets.left, insets.top, insets.left + width, insets.top + height);
|
||||
}
|
||||
|
||||
public _onOrientationChanged() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { WebView as WebViewDefinition, LoadEventData, NavigationType } from ".";
|
||||
import { View, Property, EventData, CSSType } from "../core/view";
|
||||
import { ContainerView, Property, EventData, CSSType } from "../core/view";
|
||||
import { File, knownFolders, path } from "../../file-system";
|
||||
|
||||
export { File, knownFolders, path, NavigationType };
|
||||
@@ -8,7 +8,7 @@ export * from "../core/view";
|
||||
export const srcProperty = new Property<WebViewBase, string>({ name: "src" });
|
||||
|
||||
@CSSType("WebView")
|
||||
export abstract class WebViewBase extends View implements WebViewDefinition {
|
||||
export abstract class WebViewBase extends ContainerView implements WebViewDefinition {
|
||||
public static loadStartedEvent = "loadStarted";
|
||||
public static loadFinishedEvent = "loadFinished";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user