From 0f7f2b969b2531680beed381ae33696b63071947 Mon Sep 17 00:00:00 2001 From: Martin Yankov Date: Mon, 23 Jul 2018 14:33:51 +0300 Subject: [PATCH] add safe area support for scroll view --- tns-core-modules/ui/core/view/view.ios.ts | 12 +- .../ui/layouts/layout-base.ios.ts | 11 +- .../ui/scroll-view/scroll-view.ios.ts | 145 ++++++++++++++---- 3 files changed, 133 insertions(+), 35 deletions(-) diff --git a/tns-core-modules/ui/core/view/view.ios.ts b/tns-core-modules/ui/core/view/view.ios.ts index 4dea4cea9..cdceb2cb1 100644 --- a/tns-core-modules/ui/core/view/view.ios.ts +++ b/tns-core-modules/ui/core/view/view.ios.ts @@ -212,7 +212,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 actualFrame = this._setNativeViewFrame(nativeView, frame); + const actualFrame = this._setNativeViewFrame(nativeView, frame) || frame; const actualLeft = Math.round(layout.toDevicePixels(actualFrame.origin.x)); const actualTop = Math.round(layout.toDevicePixels(actualFrame.origin.y)); @@ -672,8 +672,14 @@ export namespace ios { 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 { diff --git a/tns-core-modules/ui/layouts/layout-base.ios.ts b/tns-core-modules/ui/layouts/layout-base.ios.ts index 822e2a7d3..adb14c264 100644 --- a/tns-core-modules/ui/layouts/layout-base.ios.ts +++ b/tns-core-modules/ui/layouts/layout-base.ios.ts @@ -54,7 +54,7 @@ export class LayoutBase extends LayoutBaseCommon { const safeArea = this.getSafeArea(); const fullscreen = this.getFullscreenArea(); - const locationOnScreen = this.getLocationOnScreen(); + const locationOnScreen = this.getLocationInWindow(); const onScreenLeft = layout.toDevicePixels(layout.round(locationOnScreen.x)); const onScreenTop = layout.toDevicePixels(layout.round(locationOnScreen.y)); @@ -69,22 +69,21 @@ export class LayoutBase extends LayoutBaseCommon { let newWidth = width; let newHeight = height; - if (onScreenLeft <= layout.toDevicePixels(safeArea.origin.x)) { + if (left !== 0 && onScreenLeft <= layout.toDevicePixels(safeArea.origin.x)) { newLeft = layout.toDevicePixels(fullscreen.origin.x); newWidth = width + onScreenLeft; } - if (onScreenTop <= layout.toDevicePixels(safeArea.origin.y)) { + if (top !== 0 && onScreenTop <= layout.toDevicePixels(safeArea.origin.y)) { newTop = layout.toDevicePixels(fullscreen.origin.y); newHeight = height + onScreenTop; } - if (width && onScreenLeft + width >= layout.toDevicePixels(safeArea.origin.x) + layout.toDevicePixels(safeArea.size.width)) { + if (width && width < layout.toDevicePixels(fullscreen.size.width) && onScreenLeft + width >= layout.toDevicePixels(safeArea.origin.x) + layout.toDevicePixels(safeArea.size.width)) { newWidth = newWidth + (layout.toDevicePixels(fullscreen.size.width) - (onScreenLeft + width)); } - if (height && onScreenTop + height >= layout.toDevicePixels(safeArea.origin.y) + layout.toDevicePixels(safeArea.size.height)) { - // console.log(">>>>>>>> Bottom Layout: onScreenTop - " + onScreenTop + " height - " + height + " safeAreaOriginY - " + layout.toDevicePixels(safeArea.origin.y) + " safeAreaHeight - " + layout.toDevicePixels(safeArea.size.height)); + if (height && height < layout.toDevicePixels(fullscreen.size.height) && onScreenTop + height >= layout.toDevicePixels(safeArea.origin.y) + layout.toDevicePixels(safeArea.size.height)) { newHeight = newHeight + (layout.toDevicePixels(fullscreen.size.height) - (onScreenTop + height)); } diff --git a/tns-core-modules/ui/scroll-view/scroll-view.ios.ts b/tns-core-modules/ui/scroll-view/scroll-view.ios.ts index f7cfe5aa9..dc52c9ff1 100644 --- a/tns-core-modules/ui/scroll-view/scroll-view.ios.ts +++ b/tns-core-modules/ui/scroll-view/scroll-view.ios.ts @@ -124,14 +124,14 @@ 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; - } + // // `_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) { + // // 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 }; @@ -141,9 +141,9 @@ 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); + // 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); @@ -155,26 +155,119 @@ export class ScrollView extends ScrollViewBase { this.setMeasuredDimension(widthAndState, heightAndState); } - public onLayout(left: number, top: number, right: number, bottom: number): void { - const width = (right - left); - const height = (bottom - top); + public onLayout(left: number, top: number, right: number, bottom: number, insets?: {left, top, right, bottom}): void { + let width = (right - left); + let height = (bottom - 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 + this.nativeViewProtected.contentInsetAdjustmentBehavior = 2; } + // let verticalInset: number; + const nativeView = this.nativeViewProtected; + // const inset = nativeView.adjustedContentInset; + // // Prior iOS 11 + // if (inset === undefined) { + // verticalInset = 0; + // // verticalInset = -layout.toDevicePixels(nativeView.contentOffset.y); + // // verticalInset += getTabBarHeight(this); + // } else { + // verticalInset = layout.toDevicePixels(inset.bottom + inset.top); + // } + 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)); + width = Math.max(this._contentMeasuredWidth + insets.left + insets.right, width); } + else { + height = Math.max(this._contentMeasuredHeight + insets.top + insets.bottom, height); + } + + nativeView.contentSize = CGSizeMake(layout.toDeviceIndependentPixels(width), layout.toDeviceIndependentPixels(height)); + View.layoutChild(this, this.layoutView, 0, 0, width, height); + + // if (this.orientation === "horizontal") { + // nativeView.contentSize = CGSizeMake(layout.toDeviceIndependentPixels(this._contentMeasuredWidth + insets.left + insets.right), layout.toDeviceIndependentPixels(height)); + // View.layoutChild(this, this.layoutView, 0, 0, Math.max(this._contentMeasuredWidth + insets.left + insets.right, width), height); + // } else { + // nativeView.contentSize = CGSizeMake(layout.toDeviceIndependentPixels(width), layout.toDeviceIndependentPixels(this._contentMeasuredHeight + insets.top + insets.bottom)); + // View.layoutChild(this, this.layoutView, 0, 0, width, Math.max(this._contentMeasuredHeight + insets.top + insets.bottom, height)); + // } + } + + 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.getLocationInWindow(); + 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); + + if (majorVersion > 10) { + let newLeft = left; + let newTop = top; + let newWidth = width; + let newHeight = height; + + if (left !== 0 && onScreenLeft <= layout.toDevicePixels(safeArea.origin.x)) { + newLeft = layout.toDevicePixels(fullscreen.origin.x); + newWidth = width + onScreenLeft; + } + + if (top !== 0 && onScreenTop <= layout.toDevicePixels(safeArea.origin.y)) { + newTop = layout.toDevicePixels(fullscreen.origin.y); + newHeight = height + onScreenTop; + } + + if (width && width < layout.toDevicePixels(fullscreen.size.width) && onScreenLeft + width >= layout.toDevicePixels(safeArea.origin.x) + layout.toDevicePixels(safeArea.size.width)) { + newWidth = newWidth + (layout.toDevicePixels(fullscreen.size.width) - (onScreenLeft + width)); + } + + if (height && height < layout.toDevicePixels(fullscreen.size.height) && 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, nativeView.frame.size.width, nativeView.frame.size.height); + // } + // } + + return nativeView.frame; } public _onOrientationChanged() {