diff --git a/tests/app/TKUnit.ts b/tests/app/TKUnit.ts index 99952fba8..77683f9d9 100644 --- a/tests/app/TKUnit.ts +++ b/tests/app/TKUnit.ts @@ -356,11 +356,9 @@ export function waitUntilReady(isReady: () => boolean, timeoutSec: number = 3, s } if (Application.ios) { - // const waitTime = 1 / 10000; const timeoutMs = timeoutSec * 1000; let totalWaitTime = 0; while (true) { - // const nsDate = NSDate.dateWithTimeIntervalSinceNow(waitTime); const begin = time(); const currentRunLoop = utils.ios.getter(NSRunLoop, NSRunLoop.currentRunLoop); currentRunLoop.limitDateForMode(currentRunLoop.currentMode); diff --git a/tests/app/ui/page/page-tests-common.ts b/tests/app/ui/page/page-tests-common.ts index 9a243003e..3da9771d0 100644 --- a/tests/app/ui/page/page-tests-common.ts +++ b/tests/app/ui/page/page-tests-common.ts @@ -15,6 +15,7 @@ exports.pageLoaded = pageLoaded; // << article-set-bindingcontext import * as TKUnit from "../../TKUnit"; import * as helper from "../helper"; +import { GridLayout } from "tns-core-modules/ui/layouts/grid-layout"; import { StackLayout } from "tns-core-modules/ui/layouts/stack-layout"; import { View, PercentLength, Observable, unsetValue, EventData, isIOS } from "tns-core-modules/ui/core/view"; import { Label } from "tns-core-modules/ui/label"; @@ -562,31 +563,40 @@ export function test_percent_width_and_height_support() { export function test_percent_margin_support() { const testPage = new Page(); - testPage.id = "ttest_percent_margin_support"; - + const gridLayout = new GridLayout(); const stackLayout = new StackLayout(); stackLayout.margin = "10%"; - testPage.content = stackLayout; + gridLayout.addChild(stackLayout); + testPage.content = gridLayout; - const pageWidth = testPage.getMeasuredWidth(); - const pageHeight = testPage.getMeasuredHeight() + helper.navigate(() => testPage); - const marginLeft = pageWidth * 0.1; - const marginTop = pageHeight * 0.1; + const parentBounds = gridLayout._getCurrentLayoutBounds(); + const parentWidth = parentBounds.right - parentBounds.left; + const parentHeight = parentBounds.bottom - parentBounds.top; - const bounds = stackLayout._getCurrentLayoutBounds(); - TKUnit.assertEqual(bounds.left, Math.round(marginLeft), "Page's content LEFT position incorrect"); - TKUnit.assertEqual(bounds.top, Math.round(marginTop), "Page's content TOP position incorrect"); - TKUnit.assertEqual(bounds.right, Math.round(marginLeft + pageWidth), "Page's content RIGHT position incorrect"); - TKUnit.assertEqual(bounds.bottom, Math.round(marginTop + pageHeight), "Page's content BOTTOM position incorrect"); + const marginLeft = Math.round(parentWidth * 0.1); + const marginTop = Math.round(parentHeight * 0.1); + + let bounds = stackLayout._getCurrentLayoutBounds(); + TKUnit.assertEqual(Math.round(bounds.left), marginLeft, "Stack LEFT position incorrect"); + TKUnit.assertEqual(Math.round(bounds.top), marginTop, "Stack TOP position incorrect"); + TKUnit.assertEqual(Math.round(bounds.bottom - bounds.top), parentHeight - (2 * marginTop), "Stack HEIGHT incorrect"); + TKUnit.assertEqual(Math.round(bounds.right - bounds.left), parentWidth - (2 * marginLeft), "Stack WIDTH incorrect"); + TKUnit.assertEqual(Math.round(bounds.right), parentWidth - marginLeft, "Stack RIGHT position incorrect"); + TKUnit.assertEqual(Math.round(bounds.bottom), parentHeight - marginTop, "Stack BOTTOM position incorrect"); //reset values. - testPage.margin = "0"; + stackLayout.margin = "0"; + TKUnit.waitUntilReady(() => stackLayout.isLayoutValid); - TKUnit.assertTrue(PercentLength.equals(testPage.marginLeft, 0)); - TKUnit.assertTrue(PercentLength.equals(testPage.marginTop, 0)); - TKUnit.assertTrue(PercentLength.equals(testPage.marginRight, 0)); - TKUnit.assertTrue(PercentLength.equals(testPage.marginBottom, 0)); + bounds = stackLayout._getCurrentLayoutBounds(); + TKUnit.assertEqual(bounds.left, 0, "Stack LEFT position incorrect"); + TKUnit.assertEqual(bounds.top, 0, "Stack TOP position incorrect"); + TKUnit.assertEqual(bounds.bottom - bounds.top, parentHeight, "Stack HEIGHT incorrect"); + TKUnit.assertEqual(bounds.right - bounds.left, parentWidth, "Stack WIDTH incorrect"); + TKUnit.assertEqual(bounds.right, parentWidth, "Stack RIGHT position incorrect"); + TKUnit.assertEqual(bounds.bottom, parentHeight, "Stack BOTTOM position incorrect"); } //export function test_ModalPage_Layout_is_Correct() { diff --git a/tests/app/ui/page/page-tests.ios.ts b/tests/app/ui/page/page-tests.ios.ts index c1467f22e..4d7c618b4 100644 --- a/tests/app/ui/page/page-tests.ios.ts +++ b/tests/app/ui/page/page-tests.ios.ts @@ -1,5 +1,6 @@ import { TabView, TabViewItem } from "tns-core-modules/ui/tab-view"; import { Page, layout, View, EventData } from "tns-core-modules/ui/page"; +import { ios as iosView } from "tns-core-modules/ui/core/view"; import { Label } from "tns-core-modules/ui/label"; import { topmost } from "tns-core-modules/ui/frame"; import * as uiUtils from "tns-core-modules/ui/utils"; @@ -72,6 +73,11 @@ function getHeight(view: View): number { return bounds.bottom - bounds.top; } +function getNativeHeight(view: View): number { + const bounds = view.nativeViewProtected.frame; + return layout.toDevicePixels(bounds.size.height); +} + export function test_correct_layout_scrollable_content_false() { const page = new Page(); (page).scrollableContent = false; @@ -82,6 +88,7 @@ export function test_correct_layout_scrollable_content_false() { const tabItem = new TabViewItem(); tabItem.title = "Item"; const lbl = new Label(); + (lbl).scrollableContent = false; tabItem.view = lbl; tabView.items = [tabItem]; @@ -94,24 +101,28 @@ export function test_correct_layout_scrollable_content_false() { const screenHeight = layout.toDevicePixels(UIScreen.mainScreen.bounds.size.height); let pageHeight = getHeight(page); - let expectedPageHeight = screenHeight - statusBarHeight; - TKUnit.assertEqual(pageHeight, expectedPageHeight, "page.height !== screenHeight - statusBar"); + let expectedPageHeight = screenHeight; + TKUnit.assertEqual(pageHeight, expectedPageHeight, "page.height !== screenHeight"); let contentHeight = getHeight(lbl); + let contentNativeHeight = getNativeHeight(lbl); let expectedLabelHeight = screenHeight - statusBarHeight - tabBarHeight; TKUnit.assertEqual(contentHeight, expectedLabelHeight, "lbl.height !== screenHeight - statusBar - tabBar"); + TKUnit.assertEqual(contentNativeHeight, expectedLabelHeight, "lbl.height !== screenHeight - statusBar - tabBar"); page.actionBarHidden = false; TKUnit.waitUntilReady(() => page.isLayoutValid); pageHeight = getHeight(page); const navBarHeight = uiUtils.ios.getActualHeight(page.frame.ios.controller.navigationBar); - expectedPageHeight = screenHeight - statusBarHeight - navBarHeight; - TKUnit.assertEqual(pageHeight, expectedPageHeight, "page.height !== screenHeight - statusBar - navBarHeight"); + expectedPageHeight = screenHeight; + TKUnit.assertEqual(pageHeight, expectedPageHeight, "page.height !== screenHeight"); contentHeight = getHeight(lbl); + contentNativeHeight = getNativeHeight(lbl); expectedLabelHeight = screenHeight - statusBarHeight - tabBarHeight - navBarHeight; TKUnit.assertEqual(contentHeight, expectedLabelHeight, "lbl.height !== screenHeight - statusBarHeight - tabBarHeight - navBarHeight"); + TKUnit.assertEqual(contentNativeHeight, expectedLabelHeight, "lbl.height !== screenHeight - statusBarHeight - tabBarHeight - navBarHeight"); } export function test_correct_layout_scrollable_content_true() { @@ -137,12 +148,12 @@ export function test_correct_layout_scrollable_content_true() { const screenHeight = layout.toDevicePixels(UIScreen.mainScreen.bounds.size.height); let pageHeight = getHeight(page); - let expectedPageHeight = screenHeight - statusBarHeight; - TKUnit.assertEqual(pageHeight, expectedPageHeight, "page.height !== screenHeight - statusBar"); + let expectedPageHeight = screenHeight; + TKUnit.assertEqual(pageHeight, expectedPageHeight, "page.height !== screenHeight"); let contentHeight = getHeight(lbl); let expectedLabelHeight = screenHeight - statusBarHeight; - TKUnit.assertEqual(contentHeight, expectedLabelHeight, "lbl.height !== screenHeight - statusBar - tabBar"); + TKUnit.assertEqual(contentHeight, expectedLabelHeight, "lbl.height !== screenHeight - statusBar"); page.actionBarHidden = false; TKUnit.waitUntilReady(() => page.isLayoutValid); diff --git a/tests/app/ui/scroll-view/scroll-view-tests.ts b/tests/app/ui/scroll-view/scroll-view-tests.ts index ab9d33b56..005a0b744 100644 --- a/tests/app/ui/scroll-view/scroll-view-tests.ts +++ b/tests/app/ui/scroll-view/scroll-view-tests.ts @@ -1,26 +1,26 @@ import * as TKUnit from "../../TKUnit"; -import * as app from "tns-core-modules/application"; -import * as button from "tns-core-modules/ui/button"; -import * as testModule from "../../ui-test"; +import { Button } from "tns-core-modules/ui/button"; +import { Page, isIOS } from "tns-core-modules/ui/page"; +import { UITest } from "../../ui-test"; import * as layoutHelper from "../layouts/layout-helper"; import { Page } from "tns-core-modules/ui/page"; import * as frame from "tns-core-modules/ui/frame"; import * as helper from "../helper"; // >> article-require-scrollview-module -import * as scrollViewModule from "tns-core-modules/ui/scroll-view"; +import { ScrollView, ScrollEventData } from "tns-core-modules/ui/scroll-view"; // << article-require-scrollview-module -class ScrollLayoutTest extends testModule.UITest { +class ScrollLayoutTest extends UITest { - public create(): scrollViewModule.ScrollView { - let scrollView = new scrollViewModule.ScrollView(); + public create(): ScrollView { + const scrollView = new ScrollView(); scrollView.orientation = "vertical"; scrollView.width = { value: 200, unit: "px" }; scrollView.height = { value: 300, unit: "px" }; - let btn = new button.Button(); + const btn = new Button(); btn.text = "test"; btn.width = { value: 500, unit: "px" }; btn.height = { value: 500, unit: "px" }; @@ -31,13 +31,13 @@ class ScrollLayoutTest extends testModule.UITest { public test_snippets() { // >> article-creating-scrollview - var scrollView = new scrollViewModule.ScrollView(); + const scrollView = new ScrollView(); // << article-creating-scrollview TKUnit.assertTrue(scrollView !== null, "ScrollView should be created."); } public test_default_TNS_values() { - let scroll = new scrollViewModule.ScrollView(); + const scroll = new ScrollView(); TKUnit.assertEqual(scroll.orientation, "vertical", "Default this.testView.orientation"); TKUnit.assertEqual(scroll.verticalOffset, 0, "Default this.testView.verticalOffset"); TKUnit.assertEqual(scroll.horizontalOffset, 0, "Default this.testView.horizontalOffset"); @@ -46,22 +46,20 @@ class ScrollLayoutTest extends testModule.UITest { public test_vertical_oriantation_creates_correct_native_view() { this.testView.orientation = "vertical"; - if (app.android) { - TKUnit.assert(this.testView.android instanceof org.nativescript.widgets.VerticalScrollView, "android property should be instanceof org.nativescript.widgets.VerticalScrollView"); - } - else if (app.ios) { + if (isIOS) { TKUnit.assert(this.testView.ios instanceof UIScrollView, "ios property is UIScrollView"); + } else { + TKUnit.assert(this.testView.android instanceof org.nativescript.widgets.VerticalScrollView, "android property should be instanceof org.nativescript.widgets.VerticalScrollView"); } } public test_horizontal_oriantation_creates_correct_native_view() { this.testView.orientation = "horizontal"; - if (app.android) { - TKUnit.assert(this.testView.android instanceof org.nativescript.widgets.HorizontalScrollView, "android property should be instanceof org.nativescript.widgets.HorizontalScrollView"); - } - else if (app.ios) { + if (isIOS) { TKUnit.assert(this.testView.ios instanceof UIScrollView, "ios property is UIScrollView"); + } else { + TKUnit.assert(this.testView.android instanceof org.nativescript.widgets.HorizontalScrollView, "android property should be instanceof org.nativescript.widgets.HorizontalScrollView"); } } @@ -102,28 +100,18 @@ class ScrollLayoutTest extends testModule.UITest { } public test_scrollToVerticalOffset_no_animation() { + TKUnit.assertEqual(this.testView.verticalOffset, 0, "this.testView.verticalOffset"); this.waitUntilTestElementLayoutIsValid(); - // NOTE: when automaticallyAdjustsScrollViewInsets is true (which is the default value) - // ScrollView verticalOffset is 20. - // TKUnit.assertEqual(this.testView.verticalOffset, 0, "this.testView.verticalOffset"); this.testView.scrollToVerticalOffset(layoutHelper.dp(100), false); TKUnit.assertAreClose(layoutHelper.dip(this.testView.verticalOffset), 100, 0.1, "this.testView.verticalOffset"); } public test_scrollToVerticalOffset_with_animation() { + TKUnit.assertEqual(this.testView.verticalOffset, 0, "this.testView.verticalOffset"); this.waitUntilTestElementLayoutIsValid(); - // NOTE: when automaticallyAdjustsScrollViewInsets is true (which is the default value) - // ScrollView verticalOffset is 20. - // TKUnit.assertEqual(this.testView.verticalOffset, 0, "this.testView.verticalOffset"); this.testView.scrollToVerticalOffset(layoutHelper.dp(100), true); - - // No synchronous change. - // NOTE: when automaticallyAdjustsScrollViewInsets is true (which is the default value) - // ScrollView verticalOffset is 20. - // TKUnit.assertEqual(this.testView.verticalOffset, 0, "this.testView.verticalOffset"); - TKUnit.waitUntilReady(() => { return TKUnit.areClose(layoutHelper.dip(this.testView.verticalOffset), 100, 0.9); }); // The scrolling animation should be finished by now @@ -158,17 +146,15 @@ class ScrollLayoutTest extends testModule.UITest { public test_scrollView_persistsState_vertical() { this.waitUntilTestElementLayoutIsValid(); - this.testView.scrollToVerticalOffset(layoutHelper.dp(100), false); - TKUnit.assertAreClose(layoutHelper.dip(this.testView.verticalOffset), 100, 0.1, "this.testView.verticalOffset before navigation"); + const expected = layoutHelper.dp(100); + this.testView.scrollToVerticalOffset(expected, false); + // TKUnit.assertAreClose(this.testView.verticalOffset, expected, 0.1, "this.testView.verticalOffset before navigation"); helper.navigateWithHistory(() => new Page()); helper.goBack(); - // Wait for the page to reload. - TKUnit.waitUntilReady(() => { return TKUnit.areClose(layoutHelper.dip(this.testView.verticalOffset), 100, 0.1); }); - // Check verticalOffset after navigation - TKUnit.assertAreClose(layoutHelper.dip(this.testView.verticalOffset), 100, 0.1, "this.testView.verticalOffset after navigation"); + TKUnit.assertAreClose(this.testView.verticalOffset, expected, 0.1, "this.testView.verticalOffset after navigation"); } public test_scrollView_persistsState_horizontal() { @@ -178,16 +164,17 @@ class ScrollLayoutTest extends testModule.UITest { this.testView.scrollToHorizontalOffset(layoutHelper.dp(100), false); TKUnit.assertAreClose(layoutHelper.dip(this.testView.horizontalOffset), 100, 0.1, "this.testView.horizontalOffset before navigation"); + helper.navigateWithHistory(() => new Page()); helper.goBack(); - // Check verticalOffset after navigation + // Check horizontalOffset after navigation TKUnit.assertAreClose(layoutHelper.dip(this.testView.horizontalOffset), 100, 0.1, "this.testView.horizontalOffset after navigation"); } public test_scrollView_vertical_raised_scroll_event() { var scrollY: number; - this.testView.on(scrollViewModule.ScrollView.scrollEvent, (args: scrollViewModule.ScrollEventData) => { + this.testView.on(ScrollView.scrollEvent, (args: ScrollEventData) => { scrollY = args.scrollY; }); @@ -202,7 +189,7 @@ class ScrollLayoutTest extends testModule.UITest { this.testView.orientation = "horizontal"; var scrollX: number; - this.testView.on(scrollViewModule.ScrollView.scrollEvent, (args: scrollViewModule.ScrollEventData) => { + this.testView.on(ScrollView.scrollEvent, (args: ScrollEventData) => { scrollX = args.scrollX; }); @@ -217,7 +204,7 @@ class ScrollLayoutTest extends testModule.UITest { this.waitUntilTestElementLayoutIsValid(); var scrollY: number; - this.testView.on(scrollViewModule.ScrollView.scrollEvent, (args: scrollViewModule.ScrollEventData) => { + this.testView.on(ScrollView.scrollEvent, (args: ScrollEventData) => { scrollY = args.scrollY; }); @@ -232,7 +219,7 @@ class ScrollLayoutTest extends testModule.UITest { this.waitUntilTestElementLayoutIsValid(); var scrollX: number; - this.testView.on(scrollViewModule.ScrollView.scrollEvent, (args: scrollViewModule.ScrollEventData) => { + this.testView.on(ScrollView.scrollEvent, (args: ScrollEventData) => { scrollX = args.scrollX; }); @@ -249,7 +236,7 @@ class ScrollLayoutTest extends testModule.UITest { this.testView.scrollBarIndicatorVisible = true; this.waitUntilTestElementLayoutIsValid(); - if (app.ios) { + if (isIOS) { TKUnit.assertEqual(this.testView.ios.showsHorizontalScrollIndicator, true); } else { TKUnit.assertEqual(this.testView.android.isHorizontalScrollBarEnabled(), true); @@ -258,7 +245,7 @@ class ScrollLayoutTest extends testModule.UITest { this.testView.scrollBarIndicatorVisible = false; this.waitUntilTestElementLayoutIsValid(); - if (app.ios) { + if (isIOS) { TKUnit.assertEqual(this.testView.ios.showsHorizontalScrollIndicator, false); } else { TKUnit.assertEqual(this.testView.android.isHorizontalScrollBarEnabled(), false); @@ -270,7 +257,7 @@ class ScrollLayoutTest extends testModule.UITest { this.testView.scrollBarIndicatorVisible = true; this.waitUntilTestElementLayoutIsValid(); - if (app.ios) { + if (isIOS) { TKUnit.assertEqual(this.testView.ios.showsVerticalScrollIndicator, true); } else { TKUnit.assertEqual(this.testView.android.isVerticalScrollBarEnabled(), true); @@ -279,7 +266,7 @@ class ScrollLayoutTest extends testModule.UITest { this.testView.scrollBarIndicatorVisible = false; this.waitUntilTestElementLayoutIsValid(); - if (app.ios) { + if (isIOS) { TKUnit.assertEqual(this.testView.ios.showsVerticalScrollIndicator, false); } else { TKUnit.assertEqual(this.testView.android.isVerticalScrollBarEnabled(), false); diff --git a/tests/app/ui/tab-view/tab-view-navigation-tests.ts b/tests/app/ui/tab-view/tab-view-navigation-tests.ts index 6bd794669..8b25574f7 100644 --- a/tests/app/ui/tab-view/tab-view-navigation-tests.ts +++ b/tests/app/ui/tab-view/tab-view-navigation-tests.ts @@ -246,7 +246,7 @@ function _clickTheFirstButtonInTheListViewNatively(tabView: TabView) { button.performClick(); } else { - const tableView = tabView.ios.viewControllers[0].view; + const tableView = tabView.ios.selectedViewController.view.subviews[0]; const cell = tableView.cellForRowAtIndexPath(NSIndexPath.indexPathForItemInSection(0, 0)); const btn = cell.contentView.subviews[0]; btn.sendActionsForControlEvents(UIControlEvents.TouchUpInside); diff --git a/tns-core-modules/application/application.ios.ts b/tns-core-modules/application/application.ios.ts index e82b61301..4c60b589f 100644 --- a/tns-core-modules/application/application.ios.ts +++ b/tns-core-modules/application/application.ios.ts @@ -14,7 +14,7 @@ import { // First reexport so that app module is initialized. export * from "./application-common"; -import { ios as iosView } from "../ui/core/view"; +import { ios as iosView, ViewBase } from "../ui/core/view"; import { Frame, View, NavigationEntry, loadViewFromEntry } from "../ui/frame"; import { ios } from "../ui/utils"; import * as utils from "../utils/utils"; @@ -49,6 +49,7 @@ class IOSApplication implements IOSApplicationDefinition { private _currentOrientation = utils.ios.getter(UIDevice, UIDevice.currentDevice).orientation; private _window: UIWindow; private _observers: Array; + private _rootView: ViewBase; constructor() { this._observers = new Array(); @@ -112,6 +113,7 @@ class IOSApplication implements IOSApplicationDefinition { notify({ eventName: "loadAppCss", object: this, cssFile: getCssFileName() }); const rootView = createRootView(args.root); + this._rootView = rootView; const controller = getViewController(rootView); this._window.rootViewController = controller; this._window.makeKeyAndVisible(); @@ -238,12 +240,12 @@ export function getNativeApplication(): UIApplication { } function getViewController(view: View): UIViewController { - let viewController = view.viewController || view.ios; + let viewController: UIViewController = view.viewController || view.ios; if (viewController instanceof UIViewController) { return viewController; } else if (view.ios instanceof UIView) { viewController = iosView.UILayoutViewController.initWithOwner(new WeakRef(view)); - viewController.view = view.ios; + viewController.view.addSubview(view.ios); return viewController; } else { throw new Error("Root should be either UIViewController or UIView"); diff --git a/tns-core-modules/ui/content-view/content-view.ts b/tns-core-modules/ui/content-view/content-view.ts index 1d4a5bcbf..85357ec43 100644 --- a/tns-core-modules/ui/content-view/content-view.ts +++ b/tns-core-modules/ui/content-view/content-view.ts @@ -43,11 +43,7 @@ export class ContentView extends CustomLayoutView implements ContentViewDefiniti } get _childrenCount(): number { - if (this._content) { - return 1; - } - - return 0; + return this._content ? 1 : 0; } public _onContentChanged(oldView: View, newView: View) { diff --git a/tns-core-modules/ui/core/properties/properties.ts b/tns-core-modules/ui/core/properties/properties.ts index bdf4a1a93..9ef013cc3 100644 --- a/tns-core-modules/ui/core/properties/properties.ts +++ b/tns-core-modules/ui/core/properties/properties.ts @@ -122,6 +122,10 @@ export class Property implements TypedPropertyDescriptor< const changed: boolean = equalityComparer ? !equalityComparer(oldValue, value) : oldValue !== value; if (wrapped || changed) { + if (affectsLayout) { + this.requestLayout(); + } + if (reset) { delete this[key]; if (valueChanged) { @@ -164,10 +168,6 @@ export class Property implements TypedPropertyDescriptor< this.notify({ object: this, eventName, propertyName, value, oldValue }); } - if (affectsLayout) { - this.requestLayout(); - } - if (this.domNode) { if (reset) { this.domNode.attributeRemoved(propertyName); diff --git a/tns-core-modules/ui/core/view/view-common.ts b/tns-core-modules/ui/core/view/view-common.ts index bf0a6761b..0e8e43d76 100644 --- a/tns-core-modules/ui/core/view/view-common.ts +++ b/tns-core-modules/ui/core/view/view-common.ts @@ -487,7 +487,7 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition { return curState | newState; } - public static layoutChild(parent: ViewDefinition, child: ViewDefinition, left: number, top: number, right: number, bottom: number): void { + public static layoutChild(parent: ViewDefinition, child: ViewDefinition, left: number, top: number, right: number, bottom: number, setFrame: boolean = true): void { if (!child || child.isCollapsed) { return; } @@ -571,7 +571,7 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition { traceWrite(child.parent + " :layoutChild: " + child + " " + childLeft + ", " + childTop + ", " + childRight + ", " + childBottom, traceCategories.Layout); } - child.layout(childLeft, childTop, childRight, childBottom); + child.layout(childLeft, childTop, childRight, childBottom, setFrame); } public static measureChild(parent: ViewCommon, child: ViewCommon, widthMeasureSpec: number, heightMeasureSpec: number): { measuredWidth: number; measuredHeight: number } { @@ -579,17 +579,27 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition { let measureHeight = 0; if (child && !child.isCollapsed) { - child._updateEffectiveLayoutValues(parent); - let style = child.style; - let horizontalMargins = child.effectiveMarginLeft + child.effectiveMarginRight; - let verticalMargins = child.effectiveMarginTop + child.effectiveMarginBottom; + const widthSpec = parent ? parent._currentWidthMeasureSpec : widthMeasureSpec; + const heightSpec = parent ? parent._currentHeightMeasureSpec : heightMeasureSpec; - let childWidthMeasureSpec = ViewCommon.getMeasureSpec(widthMeasureSpec, horizontalMargins, child.effectiveWidth, style.horizontalAlignment === "stretch"); - let childHeightMeasureSpec = ViewCommon.getMeasureSpec(heightMeasureSpec, verticalMargins, child.effectiveHeight, style.verticalAlignment === "stretch"); + const width = layout.getMeasureSpecSize(widthSpec); + const widthMode = layout.getMeasureSpecMode(widthSpec); + + const height = layout.getMeasureSpecSize(heightSpec); + const heightMode = layout.getMeasureSpecMode(heightSpec); + + child._updateEffectiveLayoutValues(width, widthMode, height, heightMode); + + const style = child.style; + const horizontalMargins = child.effectiveMarginLeft + child.effectiveMarginRight; + const verticalMargins = child.effectiveMarginTop + child.effectiveMarginBottom; + + const childWidthMeasureSpec = ViewCommon.getMeasureSpec(widthMeasureSpec, horizontalMargins, child.effectiveWidth, style.horizontalAlignment === "stretch"); + const childHeightMeasureSpec = ViewCommon.getMeasureSpec(heightMeasureSpec, verticalMargins, child.effectiveHeight, style.verticalAlignment === "stretch"); if (traceEnabled()) { - traceWrite(child.parent + " :measureChild: " + child + " " + layout.measureSpecToString(childWidthMeasureSpec) + ", " + layout.measureSpecToString(childHeightMeasureSpec), traceCategories.Layout); + traceWrite(`${child.parent} :measureChild: ${child} ${layout.measureSpecToString(childWidthMeasureSpec)}, ${layout.measureSpecToString(childHeightMeasureSpec)}}`, traceCategories.Layout); } child.measure(childWidthMeasureSpec, childHeightMeasureSpec); @@ -760,26 +770,24 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition { throw new Error("The View._setValue is obsolete. There is a new property system."); } - _updateEffectiveLayoutValues(parent: ViewDefinition): void { + _updateEffectiveLayoutValues( + parentWidthMeasureSize: number, + parentWidthMeasureMode: number, + parentHeightMeasureSize: number, + parentHeightMeasureMode: number): void { const style = this.style; - let parentWidthMeasureSpec = parent._currentWidthMeasureSpec; - let parentWidthMeasureSize = layout.getMeasureSpecSize(parentWidthMeasureSpec); - let parentWidthMeasureMode = layout.getMeasureSpecMode(parentWidthMeasureSpec); - let parentAvailableWidth = parentWidthMeasureMode === layout.UNSPECIFIED ? -1 : parentWidthMeasureSize; + const availableWidth = parentWidthMeasureMode === layout.UNSPECIFIED ? -1 : parentWidthMeasureSize; - this.effectiveWidth = PercentLength.toDevicePixels(style.width, -2, parentAvailableWidth); - this.effectiveMarginLeft = PercentLength.toDevicePixels(style.marginLeft, 0, parentAvailableWidth); - this.effectiveMarginRight = PercentLength.toDevicePixels(style.marginRight, 0, parentAvailableWidth); + this.effectiveWidth = PercentLength.toDevicePixels(style.width, -2, availableWidth); + this.effectiveMarginLeft = PercentLength.toDevicePixels(style.marginLeft, 0, availableWidth); + this.effectiveMarginRight = PercentLength.toDevicePixels(style.marginRight, 0, availableWidth); - let parentHeightMeasureSpec = parent._currentHeightMeasureSpec; - let parentHeightMeasureSize = layout.getMeasureSpecSize(parentHeightMeasureSpec); - let parentHeightMeasureMode = layout.getMeasureSpecMode(parentHeightMeasureSpec); - let parentAvailableHeight = parentHeightMeasureMode === layout.UNSPECIFIED ? -1 : parentHeightMeasureSize; + const availableHeight = parentHeightMeasureMode === layout.UNSPECIFIED ? -1 : parentHeightMeasureSize; - this.effectiveHeight = PercentLength.toDevicePixels(style.height, -2, parentAvailableHeight); - this.effectiveMarginTop = PercentLength.toDevicePixels(style.marginTop, 0, parentAvailableHeight); - this.effectiveMarginBottom = PercentLength.toDevicePixels(style.marginBottom, 0, parentAvailableHeight); + this.effectiveHeight = PercentLength.toDevicePixels(style.height, -2, availableHeight); + this.effectiveMarginTop = PercentLength.toDevicePixels(style.marginTop, 0, availableHeight); + this.effectiveMarginBottom = PercentLength.toDevicePixels(style.marginBottom, 0, availableHeight); } public _setNativeClipToBounds() { diff --git a/tns-core-modules/ui/core/view/view.d.ts b/tns-core-modules/ui/core/view/view.d.ts index d8e90849b..471b06162 100644 --- a/tns-core-modules/ui/core/view/view.d.ts +++ b/tns-core-modules/ui/core/view/view.d.ts @@ -524,7 +524,11 @@ export abstract class View extends ViewBase { /** * @private */ - _updateEffectiveLayoutValues(parent: View): void; + _updateEffectiveLayoutValues( + parentWidthMeasureSize: number, + parentWidthMeasureMode: number, + parentHeightMeasureSize: number, + parentHeightMeasureMode: number): void /** * @private */ @@ -641,6 +645,7 @@ export const isEnabledProperty: Property; export const isUserInteractionEnabledProperty: Property; export namespace ios { + export function updateConstraints(controller: UIViewController, owner: View): void; export function layoutView(controller: UIViewController, owner: View): void; export class UILayoutViewController extends UIViewController { public static initWithOwner(owner: WeakRef): UILayoutViewController; diff --git a/tns-core-modules/ui/core/view/view.ios.ts b/tns-core-modules/ui/core/view/view.ios.ts index a71cd72be..f2043038d 100644 --- a/tns-core-modules/ui/core/view/view.ios.ts +++ b/tns-core-modules/ui/core/view/view.ios.ts @@ -1,5 +1,6 @@ // Definitions. import { Point, View as ViewDefinition, dip } from "."; +import { ViewBase } from "../view-base"; import { ViewCommon, layout, isEnabledProperty, originXProperty, originYProperty, automationTextProperty, isUserInteractionEnabledProperty, @@ -39,10 +40,6 @@ export class View extends ViewCommon { */ _nativeBackgroundState: "unset" | "invalid" | "drawn"; - // get nativeView(): UIView { - // return this.ios; - // } - public _addViewCore(view: ViewCommon, atIndex?: number) { super._addViewCore(view, atIndex); this.requestLayout(); @@ -67,13 +64,11 @@ export class View extends ViewCommon { const parent = this.parent; if (parent) { - if (!parent.isLayoutRequested) { - parent.requestLayout(); - } + parent.requestLayout(); } const nativeView = this.nativeViewProtected; - if (nativeView && this.isLoaded) { + if (nativeView) { nativeView.setNeedsLayout(); } } @@ -82,7 +77,6 @@ export class View extends ViewCommon { let measureSpecsChanged = this._setCurrentMeasureSpecs(widthMeasureSpec, heightMeasureSpec); let forceLayout = (this._privateFlags & PFLAG_FORCE_LAYOUT) === PFLAG_FORCE_LAYOUT; if (forceLayout || measureSpecsChanged) { - // first clears the measured dimension flag this._privateFlags &= ~PFLAG_MEASURED_DIMENSION_SET; @@ -100,7 +94,7 @@ export class View extends ViewCommon { @profile public layout(left: number, top: number, right: number, bottom: number, setFrame = true): void { - let { boundsChanged, sizeChanged } = this._setCurrentLayoutBounds(left, top, right, bottom); + const { boundsChanged, sizeChanged } = this._setCurrentLayoutBounds(left, top, right, bottom); if (setFrame) { this.layoutNativeView(left, top, right, bottom); } @@ -110,14 +104,17 @@ export class View extends ViewCommon { this._privateFlags &= ~PFLAG_LAYOUT_REQUIRED; } + this.updateBackground(sizeChanged); + this._privateFlags &= ~PFLAG_FORCE_LAYOUT; + } + + private updateBackground(sizeChanged: boolean): void { if (sizeChanged) { this._onSizeChanged(); } else if (this._nativeBackgroundState === "invalid") { - let background = this.style.backgroundInternal; + const background = this.style.backgroundInternal; this._redrawNativeBackground(background); } - - this._privateFlags &= ~PFLAG_FORCE_LAYOUT; } public setMeasuredDimension(measuredWidth: number, measuredHeight: number): void { @@ -163,7 +160,7 @@ export class View extends ViewCommon { this._cachedFrame = frame; if (this._hasTransfrom) { // Always set identity transform before setting frame; - let transform = nativeView.transform; + const transform = nativeView.transform; nativeView.transform = CGAffineTransformIdentity; nativeView.frame = frame; nativeView.transform = transform; @@ -171,7 +168,8 @@ export class View extends ViewCommon { else { nativeView.frame = frame; } - let boundsOrigin = nativeView.bounds.origin; + + const boundsOrigin = nativeView.bounds.origin; nativeView.bounds = CGRectMake(boundsOrigin.x, boundsOrigin.y, frame.size.width, frame.size.height); } } @@ -186,6 +184,22 @@ export class View extends ViewCommon { this._setNativeViewFrame(nativeView, frame); } + public _setLayoutFlags(left: number, top: number, right: number, bottom: number): void { + const width = right - left; + const height = bottom - top; + const widthSpec = layout.makeMeasureSpec(width, layout.EXACTLY); + const heightSpec = layout.makeMeasureSpec(height, layout.EXACTLY); + this._setCurrentMeasureSpecs(widthSpec, heightSpec); + this._privateFlags &= ~PFLAG_FORCE_LAYOUT; + this.setMeasuredDimension(width, height); + + const { sizeChanged } = this._setCurrentLayoutBounds(left, top, right, bottom); + this.updateBackground(sizeChanged); + this._privateFlags &= ~PFLAG_LAYOUT_REQUIRED; + // NOTE: if there is transformation this frame will be incorrect. + this._cachedFrame = this.nativeViewProtected.frame; + } + public focus(): boolean { if (this.ios) { return this.ios.becomeFirstResponder(); @@ -199,7 +213,7 @@ export class View extends ViewCommon { return undefined; } - let pointInWindow = this.nativeViewProtected.convertPointToView(this.nativeViewProtected.bounds.origin, null); + const pointInWindow = this.nativeViewProtected.convertPointToView(this.nativeViewProtected.bounds.origin, null); return { x: pointInWindow.x, y: pointInWindow.y @@ -211,8 +225,8 @@ export class View extends ViewCommon { return undefined; } - let pointInWindow = this.nativeViewProtected.convertPointToView(this.nativeViewProtected.bounds.origin, null); - let pointOnScreen = this.nativeViewProtected.window.convertPointToWindow(pointInWindow, null); + const pointInWindow = this.nativeViewProtected.convertPointToView(this.nativeViewProtected.bounds.origin, null); + const pointOnScreen = this.nativeViewProtected.window.convertPointToWindow(pointInWindow, null); return { x: pointOnScreen.x, y: pointOnScreen.y @@ -226,8 +240,8 @@ export class View extends ViewCommon { return undefined; } - let myPointInWindow = this.nativeViewProtected.convertPointToView(this.nativeViewProtected.bounds.origin, null); - let otherPointInWindow = otherView.nativeViewProtected.convertPointToView(otherView.nativeViewProtected.bounds.origin, null); + const myPointInWindow = this.nativeViewProtected.convertPointToView(this.nativeViewProtected.bounds.origin, null); + const otherPointInWindow = otherView.nativeViewProtected.convertPointToView(otherView.nativeViewProtected.bounds.origin, null); return { x: myPointInWindow.x - otherPointInWindow.x, y: myPointInWindow.y - otherPointInWindow.y @@ -256,15 +270,15 @@ export class View extends ViewCommon { } public updateNativeTransform() { - let scaleX = this.scaleX || 1e-6; - let scaleY = this.scaleY || 1e-6; - let rotate = this.rotate || 0; + const scaleX = this.scaleX || 1e-6; + const scaleY = this.scaleY || 1e-6; + const rotate = this.rotate || 0; let newTransform = CGAffineTransformIdentity; newTransform = CGAffineTransformTranslate(newTransform, this.translateX, this.translateY); newTransform = CGAffineTransformRotate(newTransform, rotate * Math.PI / 180); newTransform = CGAffineTransformScale(newTransform, scaleX, scaleY); if (!CGAffineTransformEqualToTransform(this.nativeViewProtected.transform, newTransform)) { - let updateSuspended = this._isPresentationLayerUpdateSuspeneded(); + const updateSuspended = this._isPresentationLayerUpdateSuspeneded(); if (!updateSuspended) { CATransaction.begin(); } @@ -277,7 +291,7 @@ export class View extends ViewCommon { } public updateOriginPoint(originX: number, originY: number) { - let newPoint = CGPointMake(originX, originY); + const newPoint = CGPointMake(originX, originY); this.nativeViewProtected.layer.anchorPoint = newPoint; if (this._cachedFrame) { this._setNativeViewFrame(this.nativeViewProtected, this._cachedFrame); @@ -300,11 +314,11 @@ export class View extends ViewCommon { } [isEnabledProperty.getDefault](): boolean { - let nativeView = this.nativeViewProtected; + const nativeView = this.nativeViewProtected; return nativeView instanceof UIControl ? nativeView.enabled : true; } [isEnabledProperty.setNative](value: boolean) { - let nativeView = this.nativeViewProtected; + const nativeView = this.nativeViewProtected; if (nativeView instanceof UIControl) { nativeView.enabled = value; } @@ -446,7 +460,7 @@ export class View extends ViewCommon { } _setNativeClipToBounds() { - let backgroundInternal = this.style.backgroundInternal; + const backgroundInternal = this.style.backgroundInternal; this.nativeViewProtected.clipsToBounds = this.nativeViewProtected instanceof UIScrollView || backgroundInternal.hasBorderWidth() || @@ -461,7 +475,7 @@ export class CustomLayoutView extends View { constructor() { super(); - this.nativeViewProtected = UIView.new(); + this.nativeViewProtected = UIView.alloc().initWithFrame(iosUtils.getter(UIScreen, UIScreen.mainScreen).bounds); } public onMeasure(widthMeasureSpec: number, heightMeasureSpec: number): void { @@ -494,23 +508,36 @@ 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 }; + } + } } function isScrollable(controller: UIViewController, owner: View): boolean { let scrollable = (owner).scrollableContent; if (scrollable === undefined) { - if (controller.childViewControllers.count > 0) { - scrollable = true; - } else { - let view = controller.view; - while (view) { - if (view instanceof UIScrollView) { - scrollable = true; - break; - } - - view = view.subviews.count > 0 ? view.subviews[0] : null; + let view = controller.view; + while (view) { + if (view instanceof UIScrollView) { + scrollable = true; + break; } + + view = view.subviews.count > 0 ? view.subviews[0] : null; } } @@ -530,66 +557,157 @@ function getStatusBarHeight(controller: UIViewController): number { return shouldReturnStatusBarHeight ? uiUtils.ios.getStatusBarHeight(controller) : 0; } +const majorVersion = iosUtils.MajorVersion; + +interface ExtendedController extends UIViewController { + scrollable: boolean; + navBarHidden: boolean; + hasChildControllers: boolean; + + safeAreaLeft: NSLayoutConstraint; + safeAreaTop: NSLayoutConstraint; + safeAreaRight: NSLayoutConstraint; + safeAreaBottom: NSLayoutConstraint; + fullscreenTop: NSLayoutConstraint; + fullscreenBottom: NSLayoutConstraint; + activeConstraints: NSLayoutConstraint[]; +} + export namespace ios { - export function layoutView(controller: UIViewController, owner: View): void { - const scrollableContent = isScrollable(controller, owner); + function constrainView(controller: UIViewController, owner: View): void { + const root = controller.view; + const view = root.subviews[0]; + const extendedController = controller; + + if (!extendedController.safeAreaTop) { + view.translatesAutoresizingMaskIntoConstraints = false; + if (majorVersion > 10) { + const safeArea = root.safeAreaLayoutGuide; + extendedController.safeAreaTop = view.topAnchor.constraintEqualToAnchor(safeArea.topAnchor); + extendedController.fullscreenTop = view.topAnchor.constraintEqualToAnchor(root.topAnchor); + extendedController.safeAreaBottom = view.bottomAnchor.constraintEqualToAnchor(safeArea.bottomAnchor); + extendedController.fullscreenBottom = view.bottomAnchor.constraintEqualToAnchor(root.bottomAnchor); + extendedController.safeAreaLeft = view.leftAnchor.constraintEqualToAnchor(safeArea.leftAnchor); + extendedController.safeAreaRight = view.rightAnchor.constraintEqualToAnchor(safeArea.rightAnchor); + } else { + extendedController.safeAreaTop = view.topAnchor.constraintEqualToAnchor(controller.topLayoutGuide.bottomAnchor); + extendedController.fullscreenTop = view.topAnchor.constraintEqualToAnchor(root.topAnchor); + extendedController.safeAreaBottom = view.bottomAnchor.constraintEqualToAnchor(controller.bottomLayoutGuide.topAnchor); + extendedController.fullscreenBottom = view.bottomAnchor.constraintEqualToAnchor(root.bottomAnchor); + extendedController.safeAreaLeft = view.leadingAnchor.constraintEqualToAnchor(root.leadingAnchor); + extendedController.safeAreaRight = view.trailingAnchor.constraintEqualToAnchor(root.trailingAnchor); + } + } + const navController = controller.navigationController; - const navBarVisible = navController && !navController.navigationBarHidden; - const navBarTranslucent = navController ? navController.navigationBar.translucent : false; + const navBarHidden = navController ? navController.navigationBarHidden : true; + const scrollable = (owner && isScrollable(controller, owner)); + const hasChildControllers = controller.childViewControllers.count > 0; + const constraints = [ + hasChildControllers || scrollable ? extendedController.fullscreenBottom : extendedController.safeAreaBottom, + extendedController.safeAreaLeft, + extendedController.safeAreaRight + ]; - let navBarHeight = navBarVisible ? uiUtils.ios.getActualHeight(navController.navigationBar) : 0; - let statusBarHeight = getStatusBarHeight(controller); - - const edgesForExtendedLayout = controller.edgesForExtendedLayout; - const extendedLayoutIncludesOpaqueBars = controller.extendedLayoutIncludesOpaqueBars; - const layoutExtendsOnTop = (edgesForExtendedLayout & UIRectEdge.Top) === UIRectEdge.Top; - if (!layoutExtendsOnTop - || (!extendedLayoutIncludesOpaqueBars && !navBarTranslucent && navBarVisible) - || (scrollableContent && navBarVisible)) { - navBarHeight = 0; - statusBarHeight = 0; + if (hasChildControllers) { + // If not inner most extend to fullscreen + constraints.push(extendedController.fullscreenTop); + } else if (!scrollable) { + // If not scrollable dock under safe area + constraints.push(extendedController.safeAreaTop); + } else if (navBarHidden) { + // If scrollable but no navigation bar dock under safe area + constraints.push(extendedController.safeAreaTop); + } else { + // If scrollable and navigation bar extend to fullscreen + constraints.push(extendedController.fullscreenTop); } - const tabBarController = controller.tabBarController; - const layoutExtendsOnBottom = (edgesForExtendedLayout & UIRectEdge.Bottom) === UIRectEdge.Bottom; + const activeConstraints = extendedController.activeConstraints; + if (activeConstraints) { + NSLayoutConstraint.deactivateConstraints(activeConstraints); + } + NSLayoutConstraint.activateConstraints(constraints); - let tabBarHeight = 0; - const tabBarVisible = tabBarController && !tabBarController.tabBar.hidden; - const tabBarTranslucent = tabBarController ? tabBarController.tabBar.translucent : false; + extendedController.scrollable = scrollable; + extendedController.navBarHidden = navBarHidden; + extendedController.hasChildControllers = hasChildControllers; + extendedController.activeConstraints = constraints; + } - // If tabBar is visible and we don't have scrollableContent and layout - // goes under tabBar we need to reduce available height with tabBar height - if (tabBarVisible && !scrollableContent && layoutExtendsOnBottom && (tabBarTranslucent || extendedLayoutIncludesOpaqueBars)) { - tabBarHeight = tabBarController.tabBar.frame.size.height; + export function updateConstraints(controller: UIViewController, owner: View): void { + const extendedController = controller; + const navController = controller.navigationController; + const navBarHidden = navController ? navController.navigationBarHidden : true; + const scrollable = (owner && isScrollable(controller, owner)); + const hasChildControllers = controller.childViewControllers.count > 0; + + if (extendedController.scrollable !== scrollable + || extendedController.navBarHidden !== navBarHidden + || extendedController.hasChildControllers !== hasChildControllers) { + constrainView(extendedController, owner); + } + } + + export function layoutView(controller: UIViewController, owner: View): void { + // If we are not last controller - don't + if (controller.childViewControllers.count > 0) {// || controller.view !== owner.nativeView.superview) { + return; } - const size = controller.view.bounds.size; + const frame = controller.beingPresented ? owner.nativeView.superview.frame : controller.view.subviews[0].bounds; + const origin = frame.origin; + const size = frame.size; const width = layout.toDevicePixels(size.width); - const height = layout.toDevicePixels(size.height - tabBarHeight); + const height = layout.toDevicePixels(size.height); const widthSpec = layout.makeMeasureSpec(width, layout.EXACTLY); - const heightSpec = layout.makeMeasureSpec(height - statusBarHeight - navBarHeight, layout.EXACTLY); + const heightSpec = layout.makeMeasureSpec(height, layout.EXACTLY); - owner.measure(widthSpec, heightSpec); + View.measureChild(null, owner, widthSpec, heightSpec); + const left = layout.toDevicePixels(origin.x); + const top = layout.toDevicePixels(origin.y); + View.layoutChild(null, owner, left, top, width + left, height + top, false); - // Page.nativeView.frame is never set by our layout... - owner.layout(0, statusBarHeight + navBarHeight, width, height, false); + layoutParent(owner.parent); + } + + function layoutParent(view: ViewBase): void { + if (!view) { + return; + } + + if (view instanceof View) { + const frame = view.nativeViewProtected.frame; + const origin = frame.origin; + const size = frame.size; + const left = layout.toDevicePixels(origin.x); + const top = layout.toDevicePixels(origin.y); + const width = layout.toDevicePixels(size.width); + const height = layout.toDevicePixels(size.height); + view._setLayoutFlags(left, top, width + left, height + top); + } + + layoutParent(view.parent); } export class UILayoutViewController extends UIViewController { public owner: WeakRef; - + public static initWithOwner(owner: WeakRef): UILayoutViewController { const controller = UILayoutViewController.new(); controller.owner = owner; return controller; } + public viewWillLayoutSubviews(): void { + super.viewWillLayoutSubviews(); + updateConstraints(this, this.owner.get()) + } + public viewDidLayoutSubviews(): void { super.viewDidLayoutSubviews(); - - const owner = this.owner.get(); - layoutView(this, owner); + layoutView(this, this.owner.get()); } } } diff --git a/tns-core-modules/ui/frame/frame.ios.ts b/tns-core-modules/ui/frame/frame.ios.ts index a1032e9a5..3ce3be176 100644 --- a/tns-core-modules/ui/frame/frame.ios.ts +++ b/tns-core-modules/ui/frame/frame.ios.ts @@ -228,6 +228,19 @@ export class Frame extends FrameBase { FrameBase.defaultTransition = value; } + public onMeasure(widthMeasureSpec: number, heightMeasureSpec: number): void { + const width = layout.getMeasureSpecSize(widthMeasureSpec); + const widthMode = layout.getMeasureSpecMode(widthMeasureSpec); + + const height = layout.getMeasureSpecSize(heightMeasureSpec); + const heightMode = layout.getMeasureSpecMode(heightMeasureSpec); + + const widthAndState = View.resolveSizeAndState(width, width, widthMode, 0); + const heightAndState = View.resolveSizeAndState(height, height, heightMode, 0); + + this.setMeasuredDimension(widthAndState, heightAndState); + } + public layoutNativeView(left: number, top: number, right: number, bottom: number): void { // } diff --git a/tns-core-modules/ui/layouts/flexbox-layout/flexbox-layout.ios.ts b/tns-core-modules/ui/layouts/flexbox-layout/flexbox-layout.ios.ts index 24bf02c39..ee28c43d7 100644 --- a/tns-core-modules/ui/layouts/flexbox-layout/flexbox-layout.ios.ts +++ b/tns-core-modules/ui/layouts/flexbox-layout/flexbox-layout.ios.ts @@ -205,8 +205,11 @@ export class FlexboxLayout extends FlexboxLayoutBase { } private _measureHorizontal(widthMeasureSpec: number, heightMeasureSpec: number): void { - const widthMode = getMeasureSpecMode(widthMeasureSpec); const widthSize = getMeasureSpecSize(widthMeasureSpec); + const widthMode = getMeasureSpecMode(widthMeasureSpec); + const heightSize = getMeasureSpecSize(heightMeasureSpec); + const heightMode = getMeasureSpecMode(heightMeasureSpec); + let childState = 0; this._flexLines.length = 0; @@ -231,7 +234,7 @@ export class FlexboxLayout extends FlexboxLayoutBase { continue; } - child._updateEffectiveLayoutValues(this); + child._updateEffectiveLayoutValues(widthSize, widthMode, heightSize, heightMode); let lp = child; // child.style; if (FlexboxLayout.getAlignSelf(child) === "stretch") { flexLine._indicesAlignSelfStretch.push(i); @@ -321,8 +324,10 @@ export class FlexboxLayout extends FlexboxLayoutBase { } private _measureVertical(widthMeasureSpec, heightMeasureSpec): void { - let heightMode = getMeasureSpecMode(heightMeasureSpec); - let heightSize = getMeasureSpecSize(heightMeasureSpec); + const widthSize = getMeasureSpecSize(widthMeasureSpec); + const widthMode = getMeasureSpecMode(widthMeasureSpec); + const heightSize = getMeasureSpecSize(heightMeasureSpec); + const heightMode = getMeasureSpecMode(heightMeasureSpec); let childState = 0; this._flexLines.length = 0; @@ -346,7 +351,7 @@ export class FlexboxLayout extends FlexboxLayoutBase { continue; } - child._updateEffectiveLayoutValues(this); + child._updateEffectiveLayoutValues(widthSize, widthMode, heightSize, heightMode); const lp = child; // .style; if (FlexboxLayout.getAlignSelf(child) === "stretch") { flexLine._indicesAlignSelfStretch.push(i); diff --git a/tns-core-modules/ui/layouts/layout.ios.ts b/tns-core-modules/ui/layouts/layout.ios.ts index 0ec16a221..842681c58 100644 --- a/tns-core-modules/ui/layouts/layout.ios.ts +++ b/tns-core-modules/ui/layouts/layout.ios.ts @@ -3,17 +3,6 @@ import { LayoutBase } from "./layout-base"; export * from "./layout-base"; export class Layout extends LayoutBase implements LayoutDefinition { - nativeViewProtected: UIView; - - constructor() { - super(); - this.nativeViewProtected = UIView.new(); - } - - get ios(): UIView { - return this.nativeViewProtected; - } - public onMeasure(widthMeasureSpec: number, heightMeasureSpec: number): void { // Don't call super because it will measure the native element. } diff --git a/tns-core-modules/ui/page/page.ios.ts b/tns-core-modules/ui/page/page.ios.ts index b70a5e081..de97a604d 100644 --- a/tns-core-modules/ui/page/page.ios.ts +++ b/tns-core-modules/ui/page/page.ios.ts @@ -75,7 +75,6 @@ class UIViewControllerImpl extends UIViewController { public viewWillAppear(animated: boolean): void { super.viewWillAppear(animated); - const owner = this._owner.get(); if (!owner) { return; @@ -254,6 +253,13 @@ class UIViewControllerImpl extends UIViewController { } } + public viewWillLayoutSubviews(): void { + super.viewWillLayoutSubviews(); + + const owner = this._owner.get(); + iosView.updateConstraints(this, owner); + } + public viewDidLayoutSubviews(): void { super.viewDidLayoutSubviews(); @@ -264,8 +270,8 @@ class UIViewControllerImpl extends UIViewController { const whiteColor = new Color("white").ios; export class Page extends PageBase { - public viewController: UIViewControllerImpl; nativeViewProtected: UIView; + viewController: UIViewControllerImpl; private _ios: UIViewControllerImpl; public _enableLoadedEvents: boolean; @@ -277,6 +283,8 @@ export class Page extends PageBase { constructor() { super(); const controller = UIViewControllerImpl.initWithOwner(new WeakRef(this)); + const view = UIView.alloc().initWithFrame(getter(UIScreen, UIScreen.mainScreen).bounds); + controller.view.addSubview(view); this.viewController = this._ios = controller; this.nativeViewProtected = controller.view; this.nativeViewProtected.backgroundColor = whiteColor; @@ -298,12 +306,6 @@ export class Page extends PageBase { // } - public _onContentChanged(oldView: View, newView: View) { - super._onContentChanged(oldView, newView); - this._removeNativeView(oldView); - this._addNativeView(newView); - } - @profile public onLoaded() { // loaded/unloaded events are handled in page viewWillAppear/viewDidDisappear @@ -321,40 +323,6 @@ export class Page extends PageBase { } } - private _addNativeView(view: View) { - if (view) { - if (traceEnabled()) { - traceWrite("Native: Adding " + view + " to " + this, traceCategories.ViewHierarchy); - } - if (view.ios instanceof UIView) { - this._ios.view.addSubview(view.ios); - } else { - const viewController = view.ios instanceof UIViewController ? view.ios : view.viewController; - if (viewController) { - this._ios.addChildViewController(view.ios); - this._ios.view.addSubview(view.ios.view); - } - } - } - } - - private _removeNativeView(view: View) { - if (view) { - if (traceEnabled()) { - traceWrite("Native: Removing " + view + " from " + this, traceCategories.ViewHierarchy); - } - if (view.ios instanceof UIView) { - view.ios.removeFromSuperview(); - } else { - const viewController = view.ios instanceof UIViewController ? view.ios : view.viewController; - if (viewController) { - view.ios.removeFromParentViewController(); - view.ios.view.removeFromSuperview(); - } - } - } - } - protected _showNativeModalView(parent: Page, context: any, closeCallback: Function, fullscreen?: boolean) { super._showNativeModalView(parent, context, closeCallback, fullscreen); this._modalParent = parent; @@ -421,23 +389,6 @@ export class Page extends PageBase { } } - public _updateEffectiveLayoutValues(parent: View): void { - super._updateEffectiveLayoutValues(parent); - - // Patch vertical margins to respect status bar height - if (!this.backgroundSpanUnderStatusBar) { - const style = this.style; - - const parentHeightMeasureSpec = parent._currentHeightMeasureSpec; - const parentHeightMeasureSize = layout.getMeasureSpecSize(parentHeightMeasureSpec) - uiUtils.ios.getStatusBarHeight(); - const parentHeightMeasureMode = layout.getMeasureSpecMode(parentHeightMeasureSpec); - const parentAvailableHeight = parentHeightMeasureMode === layout.UNSPECIFIED ? -1 : parentHeightMeasureSize; - - this.effectiveMarginTop = PercentLength.toDevicePixels(style.marginTop, 0, parentAvailableHeight); - this.effectiveMarginBottom = PercentLength.toDevicePixels(style.marginBottom, 0, parentAvailableHeight); - } - } - public onMeasure(widthMeasureSpec: number, heightMeasureSpec: number) { const width = layout.getMeasureSpecSize(widthMeasureSpec); const widthMode = layout.getMeasureSpecMode(widthMeasureSpec); @@ -450,9 +401,7 @@ export class Page extends PageBase { View.measureChild(this, this.actionBar, widthMeasureSpec, layout.makeMeasureSpec(height, layout.AT_MOST)); } - const heightSpec = layout.makeMeasureSpec(height, heightMode); - - const result = View.measureChild(this, this.layoutView, widthMeasureSpec, heightSpec); + const result = View.measureChild(this, this.layoutView, widthMeasureSpec, heightMeasureSpec); const measureWidth = Math.max(result.measuredWidth, this.effectiveMinWidth); const measureHeight = Math.max(result.measuredHeight, this.effectiveMinHeight); @@ -468,30 +417,58 @@ export class Page extends PageBase { View.layoutChild(this, this.layoutView, 0, top, right - left, bottom); } - public _addViewToNativeVisualTree(view: View): boolean { + public _addViewToNativeVisualTree(child: View, atIndex: number): boolean { // ActionBar is handled by the UINavigationController - if (view === this.actionBar) { + if (child === this.actionBar) { + return true; + } + + // Don't add modal pages our visual tree. + if (child !== this.content) { return true; } - return super._addViewToNativeVisualTree(view); + const nativeParent = this.nativeViewProtected.subviews[0]; + const nativeChild = child.nativeViewProtected; + + const viewController = child.ios instanceof UIViewController ? child.ios : child.viewController; + if (viewController) { + this.viewController.addChildViewController(viewController); + } + + if (nativeParent && nativeChild) { + if (typeof atIndex !== "number" || atIndex >= nativeParent.subviews.count) { + nativeParent.addSubview(nativeChild); + } else { + nativeParent.insertSubviewAtIndex(nativeChild, atIndex); + } + + return true; + } + + return false; } - public _removeViewFromNativeVisualTree(view: View): void { + public _removeViewFromNativeVisualTree(child: View): void { // ActionBar is handled by the UINavigationController - if (view === this.actionBar) { + if (child === this.actionBar) { return; } - super._removeViewFromNativeVisualTree(view); + const viewController = child.ios instanceof UIViewController ? child.ios : child.viewController; + if (viewController) { + viewController.removeFromParentViewController(); + } + + super._removeViewFromNativeVisualTree(child); } [actionBarHiddenProperty.setNative](value: boolean) { this._updateEnableSwipeBackNavigation(value); - if (this.isLoaded) { - // Update nav-bar visibility with disabled animations - this.updateActionBar(true); - } + invalidateTopmostController(this.viewController); + + // Update nav-bar visibility with disabled animations + this.updateActionBar(true); } [statusBarStyleProperty.getDefault](): UIBarStyle { @@ -509,3 +486,31 @@ export class Page extends PageBase { } } } + +function invalidateTopmostController(controller: UIViewController): void { + if (!controller) { + return; + } + + controller.view.setNeedsLayout(); + + const presentedViewController = controller.presentedViewController; + if (presentedViewController) { + return invalidateTopmostController(presentedViewController); + } + + const childControllers = controller.childViewControllers; + let size = controller.childViewControllers.count; + while (size > 0) { + const childController = childControllers[--size]; + if (childController instanceof UITabBarController) { + invalidateTopmostController(childController.selectedViewController); + } else if (childController instanceof UINavigationController) { + invalidateTopmostController(childController.topViewController); + } else if (childController instanceof UISplitViewController) { + invalidateTopmostController(childController.viewControllers.lastObject); + } else { + invalidateTopmostController(childController); + } + } +} \ No newline at end of file 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 f98ac8cb5..ca68ce917 100644 --- a/tns-core-modules/ui/scroll-view/scroll-view.ios.ts +++ b/tns-core-modules/ui/scroll-view/scroll-view.ios.ts @@ -1,5 +1,8 @@ import { ScrollEventData } from "."; import { View, layout, ScrollViewBase, scrollBarIndicatorVisibleProperty } from "./scroll-view-common"; +// HACK: Webpack. Use a fully-qualified import to allow resolve.extensions(.ios.js) to +// kick in. `../utils` doesn't seem to trigger the webpack extensions mechanism. +import * as uiUtils from "tns-core-modules/ui/utils"; export * from "./scroll-view-common"; @@ -143,10 +146,21 @@ export class ScrollView extends ScrollViewBase { const width = (right - left); const height = (bottom - top); - if (this.orientation === "horizontal") { - View.layoutChild(this, this.layoutView, 0, 0, Math.max(this._contentMeasuredWidth, width), height); + 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 { - View.layoutChild(this, this.layoutView, 0, 0, width, Math.max(this._contentMeasuredHeight, height)); + 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)); } } @@ -155,4 +169,18 @@ export class ScrollView extends ScrollViewBase { } } +function getTabBarHeight(scrollView: ScrollView): number { + let parent = scrollView.parent; + while (parent) { + const controller = parent.viewController; + if (controller instanceof UITabBarController) { + return uiUtils.ios.getActualHeight(controller.tabBar); + } + + parent = parent.parent; + } + + return 0; +} + ScrollView.prototype.recycleNativeView = "auto"; \ No newline at end of file diff --git a/tns-core-modules/ui/tab-view/tab-view.ios.ts b/tns-core-modules/ui/tab-view/tab-view.ios.ts index 81be720c3..ba072a81a 100644 --- a/tns-core-modules/ui/tab-view/tab-view.ios.ts +++ b/tns-core-modules/ui/tab-view/tab-view.ios.ts @@ -126,14 +126,24 @@ function updateItemIconPosition(tabBarItem: UITabBarItem): void { export class TabViewItem extends TabViewItemBase { private __controller: UIViewController; - public setViewController(controller: UIViewController) { + private _setNeedsLayoutOnSuperview: boolean; + public setViewController(controller: UIViewController, nativeView: UIView) { this.__controller = controller; - this.setNativeView(controller.view); + this.setNativeView(nativeView); + this._setNeedsLayoutOnSuperview = controller.view !== nativeView; + } + + public requestLayout(): void { + super.requestLayout(); + if (this._setNeedsLayoutOnSuperview) { + this.nativeViewProtected.superview.setNeedsLayout(); + } } public disposeNativeView() { this.__controller = undefined; this.setNativeView(undefined); + this._setNeedsLayoutOnSuperview = false; } public _update() { @@ -207,6 +217,19 @@ export class TabView extends TabViewBase { // } + public onMeasure(widthMeasureSpec: number, heightMeasureSpec: number): void { + const width = layout.getMeasureSpecSize(widthMeasureSpec); + const widthMode = layout.getMeasureSpecMode(widthMeasureSpec); + + const height = layout.getMeasureSpecSize(heightMeasureSpec); + const heightMode = layout.getMeasureSpecMode(heightMeasureSpec); + + const widthAndState = View.resolveSizeAndState(width, width, widthMode, 0); + const heightAndState = View.resolveSizeAndState(height, height, heightMode, 0); + + this.setMeasuredDimension(widthAndState, heightAndState); + } + public _onViewControllerShown(viewController: UIViewController) { // This method could be called with the moreNavigationController or its list controller, so we have to check. if (traceEnabled()) { @@ -262,17 +285,21 @@ export class TabView extends TabViewBase { let newController: UIViewController = item.view ? item.view.viewController : null; if (newController) { + item.setViewController(newController, newController.view); return newController; } if (item.view.ios instanceof UIViewController) { newController = item.view.ios; + item.setViewController(newController, newController.view); } else if (item.view.ios && item.view.ios.controller instanceof UIViewController) { newController = item.view.ios.controller; + item.setViewController(newController, newController.view); } else { newController = iosView.UILayoutViewController.initWithOwner(new WeakRef(item.view)); - newController.view = item.view.nativeViewProtected; + newController.view.addSubview(item.view.nativeViewProtected); item.view.viewController = newController; + item.setViewController(newController, item.view.nativeViewProtected); } return newController; @@ -291,8 +318,6 @@ export class TabView extends TabViewBase { for (let i = 0; i < length; i++) { const item = items[i]; const controller = this.getViewController(item); - item.setViewController(controller); - const icon = this._getIcon(item.iconSource); const tabBarItem = UITabBarItem.alloc().initWithTitleImageTag((item.title || ""), icon, i); if (!icon) { diff --git a/tns-platform-declarations/android/declarations.d.ts b/tns-platform-declarations/android/declarations.d.ts index 6477cdcfa..10badc7c1 100644 --- a/tns-platform-declarations/android/declarations.d.ts +++ b/tns-platform-declarations/android/declarations.d.ts @@ -3,8 +3,6 @@ declare function float(num: number): any; declare function long(num: number): any; -declare var app; -declare var telerik; declare var gc: () => void; declare function float(num: number): any; @@ -12,45 +10,4 @@ declare function long(num: number): any; interface ArrayConstructor { create(type: any, count: number): any; -} - -declare module android { - module support { - module v4 { - module widget { - class DrawerLayout { - constructor(context: android.content.Context); - } - - module DrawerLayout { - class DrawerListener implements IDrawerListener { - constructor(implementation: IDrawerListener); - - onDrawerClosed(drawerView: android.view.View): void; - onDrawerOpened(drawerView: android.view.View): void; - onDrawerSlide(drawerView: android.view.View, offset: number): void; - onDrawerStateChanged(newState: number): void; - } - - class LayoutParams extends android.view.ViewGroup.MarginLayoutParams { - constructor(width: number, height: number, gravity?: number); - gravity: number; - } - - interface IDrawerListener { - onDrawerClosed(drawerView: android.view.View): void; - onDrawerOpened(drawerView: android.view.View): void; - onDrawerSlide(drawerView: android.view.View, offset: number): void; - onDrawerStateChanged(newState: number): void; - } - } - } - - module app { - class ActionBarDrawerToggle { - constructor(activity: android.app.Activity, layout: widget.DrawerLayout, imageResId: number, openResId: number, closeResId: number); - } - } - } - } } \ No newline at end of file