diff --git a/apps/app/ui-tests-app/scroll-view/layout-outside-scroll-view-model.ts b/apps/app/ui-tests-app/scroll-view/layout-outside-scroll-view-model.ts new file mode 100644 index 000000000..5bd171325 --- /dev/null +++ b/apps/app/ui-tests-app/scroll-view/layout-outside-scroll-view-model.ts @@ -0,0 +1,29 @@ +import { Observable } from "tns-core-modules/data/observable"; +import { ScrollView } from "tns-core-modules/ui/scroll-view"; + +export class LayoutOutsideScrollViewModel extends Observable { + content: string = "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium," + + "totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. " + + "Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos " + + "qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, " + + "adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. " + + "Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? " + + "Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, " + + "vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?"; + isVisible: boolean = true; + + onChangeVisibility() { + this.isVisible = !this.isVisible; + this.notifyPropertyChange("isVisible", this.isVisible); + } + + onScrollToBottom(args) { + const scrollView = args.object.page.getViewById("scroll-view"); + scrollView.scrollToVerticalOffset(scrollView.scrollableHeight, false); + } + + onScrollToTop(args) { + const scrollView = args.object.page.getViewById("scroll-view"); + scrollView.scrollToVerticalOffset(0, false); + } +} diff --git a/apps/app/ui-tests-app/scroll-view/layout-outside-scroll.ts b/apps/app/ui-tests-app/scroll-view/layout-outside-scroll.ts new file mode 100644 index 000000000..fe1b1c325 --- /dev/null +++ b/apps/app/ui-tests-app/scroll-view/layout-outside-scroll.ts @@ -0,0 +1,11 @@ +import { EventData as ObservableEventData } from "tns-core-modules/data/observable"; +import { Page } from "tns-core-modules/ui/page"; +import { LayoutOutsideScrollViewModel } from "./layout-outside-scroll-view-model"; + +var viewModel = new LayoutOutsideScrollViewModel(); + +export function pageLoaded(args: ObservableEventData) { + var page = args.object; + + page.bindingContext = viewModel; +} diff --git a/apps/app/ui-tests-app/scroll-view/layout-outside-scroll.xml b/apps/app/ui-tests-app/scroll-view/layout-outside-scroll.xml new file mode 100644 index 000000000..8cd686cd4 --- /dev/null +++ b/apps/app/ui-tests-app/scroll-view/layout-outside-scroll.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/app/ui-tests-app/scroll-view/main-page.ts b/apps/app/ui-tests-app/scroll-view/main-page.ts index d1377c7ee..a2b6a5dd8 100644 --- a/apps/app/ui-tests-app/scroll-view/main-page.ts +++ b/apps/app/ui-tests-app/scroll-view/main-page.ts @@ -16,5 +16,6 @@ export function loadExamples() { examples.set("safe-area-sub-element", "scroll-view/safe-area-sub-element"); examples.set("safe-area-images", "scroll-view/safe-area-images"); examples.set("safe-area-images-overflow", "scroll-view/safe-area-images-overflow"); + examples.set("layout-outside-scroll", "scroll-view/layout-outside-scroll"); return examples; } \ No newline at end of file diff --git a/tns-core-modules/ui/core/view/view.ios.ts b/tns-core-modules/ui/core/view/view.ios.ts index 6186a27eb..d44e59823 100644 --- a/tns-core-modules/ui/core/view/view.ios.ts +++ b/tns-core-modules/ui/core/view/view.ios.ts @@ -789,36 +789,32 @@ export namespace ios { } 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 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 && inWindowLeft <= safeAreaPosition.left) { + if (position.left && inWindowPosition.left <= safeAreaPosition.left) { adjustedPosition.left = fullscreenPosition.left; } - if (position.top && inWindowTop <= safeAreaPosition.top) { + if (position.top && inWindowPosition.top <= safeAreaPosition.top) { adjustedPosition.top = fullscreenPosition.top; } - if (inWindowRight < fullscreenPosition.right && inWindowRight >= safeAreaPosition.right + fullscreenPosition.left) { - adjustedPosition.right += fullscreenPosition.right - inWindowRight; + if (inWindowPosition.right < fullscreenPosition.right && inWindowPosition.right >= safeAreaPosition.right + fullscreenPosition.left) { + adjustedPosition.right += fullscreenPosition.right - inWindowPosition.right; } - if (inWindowBottom < fullscreenPosition.bottom && inWindowBottom >= safeAreaPosition.bottom + fullscreenPosition.top) { - adjustedPosition.bottom += fullscreenPosition.bottom - inWindowBottom; + 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)); @@ -849,18 +845,16 @@ export namespace ios { layoutParent(view.parent); } - function getAvailableSpaceFromParent(view: View): { safeArea: CGRect, fullscreen: CGRect } { + function getAvailableSpaceFromParent(view: View, frame: CGRect): { safeArea: CGRect, fullscreen: CGRect, inWindow: CGRect } { if (!view) { return; } - let fullscreen = null; - let safeArea = null; + let scrollView = null; + let viewControllerView = null; if (view.viewController) { - const nativeView = view.viewController.view; - safeArea = nativeView.safeAreaLayoutGuide.layoutFrame; - fullscreen = nativeView.frame; + viewControllerView = view.viewController.view; } else { let parent = view.parent as View; while (parent && !parent.viewController && !(parent.nativeViewProtected instanceof UIScrollView)) { @@ -868,18 +862,37 @@ export namespace ios { } if (parent.nativeViewProtected instanceof UIScrollView) { - const nativeView = parent.nativeViewProtected; - const insets = nativeView.safeAreaInsets; - safeArea = CGRectMake(insets.left, insets.top, nativeView.contentSize.width - insets.left - insets.right, nativeView.contentSize.height - insets.top - insets.bottom); - fullscreen = CGRectMake(0, 0, nativeView.contentSize.width, nativeView.contentSize.height); + scrollView = parent.nativeViewProtected; } else if (parent.viewController) { - const nativeView = parent.viewController.view; - safeArea = nativeView.safeAreaLayoutGuide.layoutFrame; - fullscreen = nativeView.frame; + viewControllerView = parent.viewController.view; } } - return { safeArea: safeArea, fullscreen: fullscreen } + 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 {