diff --git a/tns-core-modules/ui/core/view/view-common.ts b/tns-core-modules/ui/core/view/view-common.ts index 2ceb7cf9a..993bbf33a 100644 --- a/tns-core-modules/ui/core/view/view-common.ts +++ b/tns-core-modules/ui/core/view/view-common.ts @@ -641,7 +641,7 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition { } public abstract onMeasure(widthMeasureSpec: number, heightMeasureSpec: number): void; - public abstract onLayout(left: number, top: number, right: number, bottom: number): void; + public abstract onLayout(left: number, top: number, right: number, bottom: number, insetLeft?: number, insetTop?: number): void; public abstract layoutNativeView(left: number, top: number, right: number, bottom: number): void; public static resolveSizeAndState(size: number, specSize: number, specMode: number, childMeasuredState: number): number { @@ -879,6 +879,14 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition { return undefined; } + public getFullscreenArea(): any { + return undefined; + } + + public getSafeArea(): any { + return undefined; + } + public getLocationInWindow(): Point { return undefined; } diff --git a/tns-core-modules/ui/core/view/view.d.ts b/tns-core-modules/ui/core/view/view.d.ts index ceeaf8796..4af2b049b 100644 --- a/tns-core-modules/ui/core/view/view.d.ts +++ b/tns-core-modules/ui/core/view/view.d.ts @@ -404,7 +404,7 @@ export abstract class View extends ViewBase { * @param right Right position, relative to parent * @param bottom Bottom position, relative to parent */ - public onLayout(left: number, top: number, right: number, bottom: number): void; + public onLayout(left: number, top: number, right: number, bottom: number, insetLeft?: number, insetTop?: number): void; /** * This method must be called by onMeasure(int, int) to store the measured width and measured height. Failing to do so will trigger an exception at measurement time. @@ -527,6 +527,16 @@ export abstract class View extends ViewBase { */ public createAnimation(options: AnimationDefinition): Animation; + /** + * Returns the iOS safe area frame of the closest parent with UIViewController. + */ + public getSafeArea(): any; + + /** + * Returns the iOS frame of the closest parent with UIViewController. + */ + public getFullscreenArea(): any; + /** * Returns the location of this view in the window coordinate system. */ @@ -609,7 +619,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 diff --git a/tns-core-modules/ui/core/view/view.ios.ts b/tns-core-modules/ui/core/view/view.ios.ts index a1a68d7b9..06bea2ffa 100644 --- a/tns-core-modules/ui/core/view/view.ios.ts +++ b/tns-core-modules/ui/core/view/view.ios.ts @@ -90,7 +90,15 @@ export class View extends ViewCommon { } if (boundsChanged || (this._privateFlags & PFLAG_LAYOUT_REQUIRED) === PFLAG_LAYOUT_REQUIRED) { - this.onLayout(left, top, right, bottom); + let insetLeft = 0; + let insetTop = 0; + + if (this.nativeViewProtected.safeAreaInsets) { + insetLeft = layout.toDevicePixels(this.nativeViewProtected.safeAreaInsets.left); + insetTop = layout.toDevicePixels(this.nativeViewProtected.safeAreaInsets.top); + } + + this.onLayout(left, top, right, bottom, insetLeft, insetTop); this._privateFlags &= ~PFLAG_LAYOUT_REQUIRED; } @@ -138,7 +146,7 @@ export class View extends ViewCommon { this.setMeasuredDimension(widthAndState, heightAndState); } - public onLayout(left: number, top: number, right: number, bottom: number): void { + public onLayout(left: number, top: number, right: number, bottom: number, insetLeft?: number, insetTop?: number): void { // } @@ -159,8 +167,30 @@ export class View extends ViewCommon { nativeView.frame = frame; } - const boundsOrigin = nativeView.bounds.origin; - nativeView.bounds = CGRectMake(boundsOrigin.x, boundsOrigin.y, frame.size.width, frame.size.height); + if (nativeView.safeAreaInsets) { + const leftInset = layout.toDevicePixels(nativeView.safeAreaInsets.left); + const topInset = layout.toDevicePixels(nativeView.safeAreaInsets.top); + + const left = layout.toDevicePixels(frame.origin.x); + const top = layout.toDevicePixels(frame.origin.y); + const right = layout.toDevicePixels(frame.origin.x + frame.size.width); + const bottom = layout.toDevicePixels(frame.origin.y + frame.size.height); + if (leftInset || topInset) { + const frameNew = CGRectMake(layout.toDeviceIndependentPixels(left + leftInset), layout.toDeviceIndependentPixels(top + topInset), layout.toDeviceIndependentPixels(right - left), layout.toDeviceIndependentPixels(bottom - top)); + nativeView.frame = frameNew; + const boundsOrigin = nativeView.bounds.origin; + nativeView.bounds = CGRectMake(boundsOrigin.x, boundsOrigin.y, frameNew.size.width, frameNew.size.height); + } + else { + const boundsOrigin = nativeView.bounds.origin; + nativeView.bounds = CGRectMake(boundsOrigin.x, boundsOrigin.y, frame.size.width, frame.size.height); + } + } + else { + const boundsOrigin = nativeView.bounds.origin; + nativeView.bounds = CGRectMake(boundsOrigin.x, boundsOrigin.y, frame.size.width, frame.size.height); + } + this._raiseLayoutChangedEvent(); this._isLaidOut = true; } else if (!this._isLaidOut) { @@ -203,6 +233,16 @@ export class View extends ViewCommon { return false; } + public getFullscreenArea(): any { + const parentWithController = ios.getParentWithViewController(this); + return parentWithController.viewController.view.frame; + } + + public getSafeArea(): any { + const parentWithController = ios.getParentWithViewController(this); + return parentWithController.viewController.view.safeAreaLayoutGuide.layoutFrame; + } + public getLocationInWindow(): Point { if (!this.nativeViewProtected || !this.nativeViewProtected.window) { return undefined; @@ -688,34 +728,44 @@ export namespace ios { fullscreenSize.height -= (statusBarHeight + navBarHeight); } - left = safeOrigin.x; - width = safeAreaSize.width; + // left = safeOrigin.x; + // width = safeAreaSize.width; + // top = safeOrigin.y; + // height = 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 = fullscreenOrigin.x; + width = fullscreenSize.width; + top = fullscreenOrigin.y; + height = fullscreenSize.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 safeAreaWidth = layout.toDevicePixels(safeAreaSize.width); + const safeAreaHeight = 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, left, top, width + left, height + top); diff --git a/tns-core-modules/ui/layouts/grid-layout/grid-layout.ios.ts b/tns-core-modules/ui/layouts/grid-layout/grid-layout.ios.ts index e1c405ff1..10ea92980 100644 --- a/tns-core-modules/ui/layouts/grid-layout/grid-layout.ios.ts +++ b/tns-core-modules/ui/layouts/grid-layout/grid-layout.ios.ts @@ -156,11 +156,11 @@ export class GridLayout extends GridLayoutBase { this.setMeasuredDimension(widthSizeAndState, heightSizeAndState); } - public onLayout(left: number, top: number, right: number, bottom: number): void { + public onLayout(left: number, top: number, right: number, bottom: number, insetLeft?: number, insetTop?: number): void { super.onLayout(left, top, right, bottom); - let paddingLeft = this.effectiveBorderLeftWidth + this.effectivePaddingLeft; - let paddingTop = this.effectiveBorderTopWidth + this.effectivePaddingTop; + let paddingLeft = this.effectiveBorderLeftWidth + this.effectivePaddingLeft + insetLeft; + let paddingTop = this.effectiveBorderTopWidth + this.effectivePaddingTop + insetTop; this.columnOffsets.length = 0; this.rowOffsets.length = 0; diff --git a/tns-core-modules/ui/layouts/layout-base.ios.ts b/tns-core-modules/ui/layouts/layout-base.ios.ts index 900c1200c..23561971f 100644 --- a/tns-core-modules/ui/layouts/layout-base.ios.ts +++ b/tns-core-modules/ui/layouts/layout-base.ios.ts @@ -1,5 +1,5 @@ -import { - LayoutBaseCommon, clipToBoundsProperty, isPassThroughParentEnabledProperty, View +import { + LayoutBaseCommon, clipToBoundsProperty, isPassThroughParentEnabledProperty, View, layout } from "./layout-base-common"; export * from "./layout-base-common"; @@ -29,6 +29,75 @@ export class LayoutBase extends LayoutBaseCommon { super._setNativeClipToBounds(); } } + + public _setNativeViewFrame(nativeView: UIView, frame: CGRect) { + // if (!CGRectEqualToRect(nativeView.frame, frame)) { + // if (traceEnabled()) { + // traceWrite(this + ", Native setFrame: = " + NSStringFromCGRect(frame), traceCategories.Layout); + // } + // this._cachedFrame = frame; + // if (this._hasTransfrom) { + // // Always set identity transform before setting frame; + // const transform = nativeView.transform; + // nativeView.transform = CGAffineTransformIdentity; + // nativeView.frame = frame; + // nativeView.transform = transform; + // } + // else { + // nativeView.frame = frame; + // } + + nativeView.frame = frame; + + const safeArea = this.getSafeArea(); + const fullscreen = this.getFullscreenArea(); + const locationOnScreen = this.getLocationOnScreen(); + const onScreenLeft = layout.toDevicePixels(layout.round(locationOnScreen.x)); + const onScreenTop = layout.toDevicePixels(layout.round(locationOnScreen.y)); + + let left = layout.toDevicePixels(frame.origin.x); + let top = layout.toDevicePixels(frame.origin.y); + let width = layout.toDevicePixels(frame.size.width); + let height = layout.toDevicePixels(frame.size.height); + + let newLeft = left; + let newTop = top; + let newWidth = width; + let newHeight = height; + + if (onScreenLeft <= layout.toDevicePixels(safeArea.origin.x)) { + newLeft = layout.toDevicePixels(fullscreen.origin.x); + newWidth = width + onScreenLeft; + } + + if (onScreenTop <= layout.toDevicePixels(safeArea.origin.y)) { + newTop = layout.toDevicePixels(fullscreen.origin.y); + newHeight = height + onScreenTop; + } + + if (onScreenLeft + width >= layout.toDevicePixels(safeArea.origin.x) + layout.toDevicePixels(safeArea.size.width)) { + newWidth = newWidth + (layout.toDevicePixels(fullscreen.size.width) - (onScreenLeft + width)); + } + + if (onScreenTop + height >= layout.toDevicePixels(safeArea.origin.y) + layout.toDevicePixels(safeArea.size.height)) { + newHeight = newHeight + (layout.toDevicePixels(fullscreen.size.height) - (onScreenTop + height)); + } + + const frameNew = CGRectMake(layout.toDeviceIndependentPixels(newLeft), layout.toDeviceIndependentPixels(newTop), layout.toDeviceIndependentPixels(newWidth), layout.toDeviceIndependentPixels(newHeight)); + nativeView.frame = frameNew; + + // if (leftInset || topInset) { + // const frameNew = CGRectMake(layout.toDeviceIndependentPixels(left), layout.toDeviceIndependentPixels(top), layout.toDeviceIndependentPixels(right - left + leftInset), layout.toDeviceIndependentPixels(bottom - top + topInset)); + // nativeView.frame = frameNew; + // const boundsOrigin = nativeView.bounds.origin; + // nativeView.bounds = CGRectMake(boundsOrigin.x, boundsOrigin.y, frameNew.size.width, frameNew.size.height); + // } + // else { + const boundsOrigin = nativeView.bounds.origin; + nativeView.bounds = CGRectMake(boundsOrigin.x, boundsOrigin.y, frameNew.size.width, frameNew.size.height); + // } + // } + } [clipToBoundsProperty.getDefault](): boolean { return false; diff --git a/tns-core-modules/ui/layouts/stack-layout/stack-layout.ios.ts b/tns-core-modules/ui/layouts/stack-layout/stack-layout.ios.ts index eddf716c1..712ad25c0 100644 --- a/tns-core-modules/ui/layouts/stack-layout/stack-layout.ios.ts +++ b/tns-core-modules/ui/layouts/stack-layout/stack-layout.ios.ts @@ -81,25 +81,26 @@ export class StackLayout extends StackLayoutBase { this.setMeasuredDimension(widthAndState, heightAndState); } - public onLayout(left: number, top: number, right: number, bottom: number): void { + public onLayout(left: number, top: number, right: number, bottom: number, insetLeft?: number, insetTop?: number): void { super.onLayout(left, top, right, bottom); if (this.orientation === "vertical") { - this.layoutVertical(left, top, right, bottom); + this.layoutVertical(left, top, right, bottom, insetLeft, insetTop); } else { this.layoutHorizontal(left, top, right, bottom); } } - private layoutVertical(left: number, top: number, right: number, bottom: number): void { - const paddingLeft = this.effectiveBorderLeftWidth + this.effectivePaddingLeft; - const paddingTop = this.effectiveBorderTopWidth + this.effectivePaddingTop; + private layoutVertical(left: number, top: number, right: number, bottom: number, insetLeft?: number, insetTop?: number): void { + const paddingLeft = this.effectiveBorderLeftWidth + this.effectivePaddingLeft + insetLeft; + const paddingTop = this.effectiveBorderTopWidth + this.effectivePaddingTop + insetTop; const paddingRight = this.effectiveBorderRightWidth + this.effectivePaddingRight; const paddingBottom = this.effectiveBorderBottomWidth + this.effectivePaddingBottom; let childTop: number; let childLeft: number = paddingLeft; let childRight = right - left - paddingRight; + // let childRight = right - paddingRight; switch (this.verticalAlignment) { case VerticalAlignment.MIDDLE: