mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-15 11:01:21 +08:00
fix(ios): resize of scrollview content breaks layout (#6965)
This commit is contained in:
@ -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 = <ScrollView>args.object.page.getViewById("scroll-view");
|
||||||
|
scrollView.scrollToVerticalOffset(scrollView.scrollableHeight, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
onScrollToTop(args) {
|
||||||
|
const scrollView = <ScrollView>args.object.page.getViewById("scroll-view");
|
||||||
|
scrollView.scrollToVerticalOffset(0, false);
|
||||||
|
}
|
||||||
|
}
|
11
apps/app/ui-tests-app/scroll-view/layout-outside-scroll.ts
Normal file
11
apps/app/ui-tests-app/scroll-view/layout-outside-scroll.ts
Normal file
@ -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 = <Page>args.object;
|
||||||
|
|
||||||
|
page.bindingContext = viewModel;
|
||||||
|
}
|
19
apps/app/ui-tests-app/scroll-view/layout-outside-scroll.xml
Normal file
19
apps/app/ui-tests-app/scroll-view/layout-outside-scroll.xml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<Page xmlns="http://schemas.nativescript.org/tns.xsd" loaded="pageLoaded" class="page">
|
||||||
|
<ScrollView id="scroll-view">
|
||||||
|
<StackLayout>
|
||||||
|
<GridLayout rows="auto, auto, auto" backgroundColor="lightgray">
|
||||||
|
<Button row="0" text="Change w/ Visibility" tap="{{ onChangeVisibility }}"></Button>
|
||||||
|
<Button row="1" text="Scroll To Bottom" tap="{{ onScrollToBottom }}"></Button>
|
||||||
|
<Label row="2" visibility="{{ isVisible ? 'visible' : 'collapsed' }}" text="{{ content }}" color="black"></Label>
|
||||||
|
</GridLayout>
|
||||||
|
|
||||||
|
<GridLayout height="2000" backgroundColor="yellow"></GridLayout>
|
||||||
|
|
||||||
|
<GridLayout rows="auto, auto, auto" backgroundColor="lightgray">
|
||||||
|
<Button row="0" text="Change w/ Visibility" tap="{{ onChangeVisibility }}"></Button>
|
||||||
|
<Button row="1" text="Scroll To Top" tap="{{ onScrollToTop }}"></Button>
|
||||||
|
<Label row="2" visibility="{{ isVisible ? 'visible' : 'collapsed' }}" text="{{ content }}" color="black"></Label>
|
||||||
|
</GridLayout>
|
||||||
|
</StackLayout>
|
||||||
|
</ScrollView>
|
||||||
|
</Page>
|
@ -16,5 +16,6 @@ export function loadExamples() {
|
|||||||
examples.set("safe-area-sub-element", "scroll-view/safe-area-sub-element");
|
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", "scroll-view/safe-area-images");
|
||||||
examples.set("safe-area-images-overflow", "scroll-view/safe-area-images-overflow");
|
examples.set("safe-area-images-overflow", "scroll-view/safe-area-images-overflow");
|
||||||
|
examples.set("layout-outside-scroll", "scroll-view/layout-outside-scroll");
|
||||||
return examples;
|
return examples;
|
||||||
}
|
}
|
@ -789,36 +789,32 @@ export namespace ios {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function expandBeyondSafeArea(view: View, frame: CGRect): CGRect {
|
export function expandBeyondSafeArea(view: View, frame: CGRect): CGRect {
|
||||||
const locationInWindow = view.getLocationInWindow();
|
const availableSpace = getAvailableSpaceFromParent(view, frame);
|
||||||
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 safeArea = availableSpace.safeArea;
|
||||||
const fullscreen = availableSpace.fullscreen;
|
const fullscreen = availableSpace.fullscreen;
|
||||||
|
const inWindow = availableSpace.inWindow;
|
||||||
|
|
||||||
const position = ios.getPositionFromFrame(frame);
|
const position = ios.getPositionFromFrame(frame);
|
||||||
const safeAreaPosition = ios.getPositionFromFrame(safeArea);
|
const safeAreaPosition = ios.getPositionFromFrame(safeArea);
|
||||||
const fullscreenPosition = ios.getPositionFromFrame(fullscreen);
|
const fullscreenPosition = ios.getPositionFromFrame(fullscreen);
|
||||||
|
const inWindowPosition = ios.getPositionFromFrame(inWindow);
|
||||||
|
|
||||||
const adjustedPosition = position;
|
const adjustedPosition = position;
|
||||||
|
|
||||||
if (position.left && inWindowLeft <= safeAreaPosition.left) {
|
if (position.left && inWindowPosition.left <= safeAreaPosition.left) {
|
||||||
adjustedPosition.left = fullscreenPosition.left;
|
adjustedPosition.left = fullscreenPosition.left;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (position.top && inWindowTop <= safeAreaPosition.top) {
|
if (position.top && inWindowPosition.top <= safeAreaPosition.top) {
|
||||||
adjustedPosition.top = fullscreenPosition.top;
|
adjustedPosition.top = fullscreenPosition.top;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inWindowRight < fullscreenPosition.right && inWindowRight >= safeAreaPosition.right + fullscreenPosition.left) {
|
if (inWindowPosition.right < fullscreenPosition.right && inWindowPosition.right >= safeAreaPosition.right + fullscreenPosition.left) {
|
||||||
adjustedPosition.right += fullscreenPosition.right - inWindowRight;
|
adjustedPosition.right += fullscreenPosition.right - inWindowPosition.right;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inWindowBottom < fullscreenPosition.bottom && inWindowBottom >= safeAreaPosition.bottom + fullscreenPosition.top) {
|
if (inWindowPosition.bottom < fullscreenPosition.bottom && inWindowPosition.bottom >= safeAreaPosition.bottom + fullscreenPosition.top) {
|
||||||
adjustedPosition.bottom += fullscreenPosition.bottom - inWindowBottom;
|
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));
|
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);
|
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) {
|
if (!view) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let fullscreen = null;
|
let scrollView = null;
|
||||||
let safeArea = null;
|
let viewControllerView = null;
|
||||||
|
|
||||||
if (view.viewController) {
|
if (view.viewController) {
|
||||||
const nativeView = view.viewController.view;
|
viewControllerView = view.viewController.view;
|
||||||
safeArea = nativeView.safeAreaLayoutGuide.layoutFrame;
|
|
||||||
fullscreen = nativeView.frame;
|
|
||||||
} else {
|
} else {
|
||||||
let parent = view.parent as View;
|
let parent = view.parent as View;
|
||||||
while (parent && !parent.viewController && !(parent.nativeViewProtected instanceof UIScrollView)) {
|
while (parent && !parent.viewController && !(parent.nativeViewProtected instanceof UIScrollView)) {
|
||||||
@ -868,18 +862,37 @@ export namespace ios {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (parent.nativeViewProtected instanceof UIScrollView) {
|
if (parent.nativeViewProtected instanceof UIScrollView) {
|
||||||
const nativeView = parent.nativeViewProtected;
|
scrollView = 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);
|
|
||||||
} else if (parent.viewController) {
|
} else if (parent.viewController) {
|
||||||
const nativeView = parent.viewController.view;
|
viewControllerView = parent.viewController.view;
|
||||||
safeArea = nativeView.safeAreaLayoutGuide.layoutFrame;
|
|
||||||
fullscreen = nativeView.frame;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
export class UILayoutViewController extends UIViewController {
|
||||||
|
Reference in New Issue
Block a user