From 63ab46eb2a76f3a67880d9fdc6b03fb80eaaf6c2 Mon Sep 17 00:00:00 2001 From: Hristo Hristov Date: Fri, 15 Dec 2017 17:11:51 +0200 Subject: [PATCH] Layout improvements --- .../application/application.ios.ts | 23 +- tns-core-modules/ui/core/view/view.ios.ts | 307 ++++++++++++------ tns-core-modules/ui/page/page.ios.ts | 20 +- tns-core-modules/ui/tab-view/tab-view.ios.ts | 14 +- 4 files changed, 242 insertions(+), 122 deletions(-) diff --git a/tns-core-modules/application/application.ios.ts b/tns-core-modules/application/application.ios.ts index 8d8d1d38c..4f007ec40 100644 --- a/tns-core-modules/application/application.ios.ts +++ b/tns-core-modules/application/application.ios.ts @@ -118,6 +118,7 @@ class IOSApplication implements IOSApplicationDefinition { this._rootView = rootView; const controller = getViewController(rootView); this._window.rootViewController = controller; + rootView._setupAsRootView({}); this._window.makeKeyAndVisible(); } @@ -199,8 +200,7 @@ function createRootView(v?: View) { // try to navigate to the mainEntry (if specified) if (mainEntry) { if (createRootFrame) { - const frame = new Frame(); - rootView = frame; + const frame = rootView = new Frame(); frame.navigate(mainEntry); } else { rootView = createViewFromEntry(mainEntry); @@ -211,7 +211,6 @@ function createRootView(v?: View) { } } - rootView._setupAsRootView({}); return rootView; } @@ -230,6 +229,7 @@ export function start(entry?: string | NavigationEntry) { // Normal NativeScript app will need UIApplicationMain. UIApplicationMain(0, null, null, iosApp && iosApp.delegate ? NSStringFromClass(iosApp.delegate) : NSStringFromClass(Responder)); } else { + // TODO: this rootView should be held alive until rootController dismissViewController is called. const rootView = createRootView(); if (rootView) { // Attach to the existing iOS app @@ -238,6 +238,7 @@ export function start(entry?: string | NavigationEntry) { const rootController = window.rootViewController; if (rootController) { const controller = getViewController(rootView); + rootView._setupAsRootView({}); rootController.presentViewControllerAnimatedCompletion(controller, true, null); } } @@ -258,13 +259,17 @@ function getViewController(view: View): UIViewController { 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)) as UIViewController; - viewController.view.addSubview(view.ios); - return viewController; } else { - throw new Error("Root should be either UIViewController or UIView"); + const nativeView = view.ios || view.nativeViewProtected; + if (nativeView instanceof UIView) { + viewController = iosView.UILayoutViewController.initWithOwner(new WeakRef(view)) as UIViewController; + viewController.view.addSubview(nativeView); + view.viewController = viewController; + return viewController; + } } + + throw new Error("Root should be either UIViewController or UIView"); } global.__onLiveSync = function () { @@ -273,4 +278,4 @@ global.__onLiveSync = function () { } livesync(); -} +} \ 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 fe1c11309..db7e45152 100644 --- a/tns-core-modules/ui/core/view/view.ios.ts +++ b/tns-core-modules/ui/core/view/view.ios.ts @@ -575,15 +575,7 @@ export class CustomLayoutView extends View { function isScrollable(controller: UIViewController, owner: View): boolean { let scrollable = (owner).scrollableContent; if (scrollable === undefined) { - let view: UIView = controller.view.subviews.count > 0 ? controller.view.subviews[0] : null; - if (!(controller instanceof ios.UILayoutViewController)) { - // Propbably we are PageViewController. Consider different cases - if (view && view.subviews.count > 0) { - // Take page content. - view = view.subviews[0]; - } - } - + const view: UIView = controller.view.subviews.count > 0 ? controller.view.subviews[0] : null; if (view instanceof UIScrollView) { scrollable = true; } @@ -599,124 +591,213 @@ interface ExtendedController extends UIViewController { navBarHidden: boolean; hasChildControllers: boolean; - safeAreaLeft: NSLayoutConstraint; - safeAreaTop: NSLayoutConstraint; - safeAreaRight: NSLayoutConstraint; - safeAreaBottom: NSLayoutConstraint; - fullscreenTop: NSLayoutConstraint; - fullscreenBottom: NSLayoutConstraint; - activeConstraints: NSLayoutConstraint[]; + // safeAreaLeft: NSLayoutConstraint; + // safeAreaTop: NSLayoutConstraint; + // safeAreaRight: NSLayoutConstraint; + // safeAreaBottom: NSLayoutConstraint; + // fullscreenTop: NSLayoutConstraint; + // fullscreenBottom: NSLayoutConstraint; + // fullscreenLeft: NSLayoutConstraint; + // fullscreenRight: NSLayoutConstraint; + // activeConstraints: NSLayoutConstraint[]; } export namespace ios { function constrainView(controller: ExtendedController, owner: View): void { const root = controller.view; - const view = root.subviews[0]; - if (!controller.safeAreaTop) { - view.translatesAutoresizingMaskIntoConstraints = false; - if (majorVersion > 10) { - const safeArea = root.safeAreaLayoutGuide; - controller.safeAreaTop = view.topAnchor.constraintEqualToAnchor(safeArea.topAnchor); - controller.fullscreenTop = view.topAnchor.constraintEqualToAnchor(root.topAnchor); - controller.safeAreaBottom = view.bottomAnchor.constraintEqualToAnchor(safeArea.bottomAnchor); - controller.fullscreenBottom = view.bottomAnchor.constraintEqualToAnchor(root.bottomAnchor); - controller.safeAreaLeft = view.leftAnchor.constraintEqualToAnchor(safeArea.leftAnchor); - controller.safeAreaRight = view.rightAnchor.constraintEqualToAnchor(safeArea.rightAnchor); - } else { - controller.safeAreaTop = view.topAnchor.constraintEqualToAnchor(controller.topLayoutGuide.bottomAnchor); - controller.fullscreenTop = view.topAnchor.constraintEqualToAnchor(root.topAnchor); - controller.safeAreaBottom = view.bottomAnchor.constraintEqualToAnchor(controller.bottomLayoutGuide.topAnchor); - controller.fullscreenBottom = view.bottomAnchor.constraintEqualToAnchor(root.bottomAnchor); - controller.safeAreaLeft = view.leadingAnchor.constraintEqualToAnchor(root.leadingAnchor); - controller.safeAreaRight = view.trailingAnchor.constraintEqualToAnchor(root.trailingAnchor); - } + if (!root.safeAreaLayoutGuide) { + const layoutGuide = (root).safeAreaLayoutGuide = UILayoutGuide.alloc().init(); + root.addLayoutGuide(layoutGuide); + // // view.translatesAutoresizingMaskIntoConstraints = false; + // if (majorVersion > 10) { + // const safeArea = root.safeAreaLayoutGuide; + // layoutGuide.topAnchor.constraintEqualToAnchor(safeArea.topAnchor); + // layoutGuide.bottomAnchor.constraintEqualToAnchor(safeArea.bottomAnchor); + // layoutGuide.leftAnchor.constraintEqualToAnchor(safeArea.leftAnchor); + // layoutGuide.rightAnchor.constraintEqualToAnchor(safeArea.rightAnchor); + // } else { + NSLayoutConstraint.activateConstraints([ + layoutGuide.topAnchor.constraintEqualToAnchor(controller.topLayoutGuide.bottomAnchor), + layoutGuide.bottomAnchor.constraintEqualToAnchor(controller.bottomLayoutGuide.topAnchor), + layoutGuide.leadingAnchor.constraintEqualToAnchor(root.leadingAnchor), + layoutGuide.trailingAnchor.constraintEqualToAnchor(root.trailingAnchor) + ]); + // } } - const navBarHidden = controller.navBarHidden; - const scrollable = controller.scrollable;; - const hasChildControllers = controller.hasChildControllers; - const constraints = [ - hasChildControllers || scrollable ? controller.fullscreenBottom : controller.safeAreaBottom, - controller.safeAreaLeft, - controller.safeAreaRight - ]; + // const view = root.subviews[0]; + // if (!controller.safeAreaTop) { + // view.translatesAutoresizingMaskIntoConstraints = false; + // if (majorVersion > 10) { + // const safeArea = root.safeAreaLayoutGuide; + // controller.safeAreaTop = view.topAnchor.constraintEqualToAnchor(safeArea.topAnchor); + // controller.fullscreenTop = view.topAnchor.constraintEqualToAnchor(root.topAnchor); + // controller.safeAreaBottom = view.bottomAnchor.constraintEqualToAnchor(safeArea.bottomAnchor); + // controller.fullscreenBottom = view.bottomAnchor.constraintEqualToAnchor(root.bottomAnchor); + // controller.safeAreaLeft = view.leftAnchor.constraintEqualToAnchor(safeArea.leftAnchor); + // controller.fullscreenLeft = view.leftAnchor.constraintEqualToAnchor(root.leftAnchor); + // controller.safeAreaRight = view.rightAnchor.constraintEqualToAnchor(safeArea.rightAnchor); + // controller.fullscreenRight = view.rightAnchor.constraintEqualToAnchor(root.rightAnchor); + // } else { + // controller.safeAreaTop = view.topAnchor.constraintEqualToAnchor(controller.topLayoutGuide.bottomAnchor); + // controller.fullscreenTop = view.topAnchor.constraintEqualToAnchor(root.topAnchor); + // controller.safeAreaBottom = view.bottomAnchor.constraintEqualToAnchor(controller.bottomLayoutGuide.topAnchor); + // controller.fullscreenBottom = view.bottomAnchor.constraintEqualToAnchor(root.bottomAnchor); + // controller.safeAreaLeft = view.leadingAnchor.constraintEqualToAnchor(root.leadingAnchor); + // controller.fullscreenLeft = controller.safeAreaLeft; + // controller.safeAreaRight = view.trailingAnchor.constraintEqualToAnchor(root.trailingAnchor); + // controller.fullscreenRight = controller.safeAreaRight; + // } + // } - if (hasChildControllers) { - // If not inner most extend to fullscreen - constraints.push(controller.fullscreenTop); - } else if (!scrollable) { - // If not scrollable dock under safe area - constraints.push(controller.safeAreaTop); - } else if (navBarHidden) { - // If scrollable but no navigation bar dock under safe area - constraints.push(controller.safeAreaTop); - } else { - // If scrollable and navigation bar extend to fullscreen - constraints.push(controller.fullscreenTop); - } + // // check if this works + // const fullscreenHorizontally = controller === + // iosUtils.getter(UIApplication, UIApplication.sharedApplication).keyWindow.rootViewController + // || !!(owner).wantsFullscreen; - const activeConstraints = controller.activeConstraints; - if (activeConstraints) { - NSLayoutConstraint.deactivateConstraints(activeConstraints); - } + // const navBarHidden = controller.navBarHidden; + // const scrollable = controller.scrollable;; + // const hasChildControllers = controller.hasChildControllers; + // const constraints = [ + // hasChildControllers || scrollable ? controller.fullscreenBottom : controller.safeAreaBottom, + // fullscreenHorizontally ? controller.fullscreenLeft : controller.safeAreaLeft, + // fullscreenHorizontally ? controller.fullscreenRight : controller.safeAreaRight + // ]; - NSLayoutConstraint.activateConstraints(constraints); - controller.activeConstraints = constraints; + // if (hasChildControllers) { + // // If not inner most extend to fullscreen + // constraints.push(controller.fullscreenTop); + // } else if (!scrollable) { + // // If not scrollable dock under safe area + // constraints.push(controller.safeAreaTop); + // } else if (navBarHidden) { + // // If scrollable but no navigation bar dock under safe area + // constraints.push(controller.safeAreaTop); + // } else { + // // If scrollable and navigation bar extend to fullscreen + // constraints.push(controller.fullscreenTop); + // } + + // const activeConstraints = controller.activeConstraints; + // if (activeConstraints) { + // NSLayoutConstraint.deactivateConstraints(activeConstraints); + // } + + // NSLayoutConstraint.activateConstraints(constraints); + // controller.activeConstraints = constraints; } 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; + // const navController = controller.navigationController; + // const navBarHidden = navController ? navController.navigationBarHidden : true; + // const scrollable = isScrollable(controller, owner); + // const hasChildControllers = controller.childViewControllers.count > 0; - if (extendedController.scrollable !== scrollable - || extendedController.navBarHidden !== navBarHidden - || extendedController.hasChildControllers !== hasChildControllers) { - extendedController.scrollable = scrollable; - extendedController.navBarHidden = navBarHidden; - extendedController.hasChildControllers = hasChildControllers; - constrainView(extendedController, owner); - } + // if (extendedController.scrollable !== scrollable + // || extendedController.navBarHidden !== navBarHidden + // || extendedController.hasChildControllers !== hasChildControllers) { + // extendedController.scrollable = scrollable; + // extendedController.navBarHidden = navBarHidden; + // extendedController.hasChildControllers = hasChildControllers; + // constrainView(extendedController, owner); + // } + + constrainView(extendedController, owner); } export function layoutView(controller: UIViewController, owner: View): void { - // If we are not most inner controller - don't layout - if (controller.childViewControllers.count > 0) { - return; - } + // const frame = controller.view.subviews[0].bounds; - const frame = controller.view.subviews[0].bounds; - const origin = frame.origin; - const size = frame.size; - let width = layout.toDevicePixels(size.width); - let height = layout.toDevicePixels(size.height); + // check if this works + const fullscreen = controller === + iosUtils.getter(UIApplication, UIApplication.sharedApplication).keyWindow.rootViewController; + // || !!(owner).wantsFullscreen; - if (iosUtils.MajorVersion < 11) { - const window = controller.view.window; - if (window) { - const windowSize = window.frame.size; - const windowInPortrait = windowSize.width < windowSize.height; - const viewInPortrait = width < height; - if (windowInPortrait !== viewInPortrait) { - // NOTE: This happens on iOS <11. - // We were not visible (probably in backstack) when orientation happened. - // request layout so we get the new dimensions. - // There is no sync way to force a layout. - setTimeout(() => owner.requestLayout()); - } + let left: number, top: number, width: number, height: number; + + const frame = controller.view.frame; + const fullscreenOrigin = frame.origin; + const fullscreenSize = frame.size; + + if (fullscreen) { + left = layout.toDevicePixels(fullscreenOrigin.x); + top = layout.toDevicePixels(fullscreenOrigin.y); + width = layout.toDevicePixels(fullscreenSize.width); + height = layout.toDevicePixels(fullscreenSize.height); + } else { + const safeArea = controller.view.safeAreaLayoutGuide.layoutFrame; + const safeOrigin = safeArea.origin; + const safeAreaSize = safeArea.size; + + const navController = controller.navigationController; + const navBarHidden = navController ? navController.navigationBarHidden : true; + const scrollable = isScrollable(controller, owner); + const hasChildControllers = controller.childViewControllers.count > 0; + + const safeAreaTopLength = safeOrigin.y - fullscreenOrigin.y; + const safeAreaBottomLength = fullscreenSize.height - safeAreaSize.height - safeAreaTopLength; + + left = safeOrigin.x; + width = safeAreaSize.width; + + if (hasChildControllers) { + // If not inner most extend to fullscreen + top = fullscreenOrigin.y; // constraints.push(controller.fullscreenTop); + height = fullscreenSize.height; + } else if (!scrollable) { + // If not scrollable dock under safe area + top = safeOrigin.y; + height = safeAreaSize.height; + // constraints.push(controller.safeAreaTop); + } else if (navBarHidden) { + // If scrollable but no navigation bar dock under safe area + top = safeOrigin.y; // constraints.push(controller.safeAreaTop); + // const adjusted = parentControllerAdjustedScrollViewInsets(controller); + height = safeAreaSize.height + safeAreaBottomLength; + // if () + } else { + // If scrollable and navigation bar extend to fullscreen + top = fullscreenOrigin.y; // constraints.push(controller.fullscreenTop); + height = fullscreenOrigin.y + fullscreenSize.height; } + + left = layout.toDevicePixels(left); + top = layout.toDevicePixels(top); + width = layout.toDevicePixels(width); + height = layout.toDevicePixels(height); } + // const frame = controller.view.safeAreaLayoutGuide.layoutFrame; + // const origin = frame.origin; + // const size = frame.size; + // width = layout.toDevicePixels(fullscreenSize.width); + // height = layout.toDevicePixels(fullscreenSize.height); + + // if (iosUtils.MajorVersion < 11) { + // const window = controller.view.window; + // if (window) { + // const windowSize = window.frame.size; + // const windowInPortrait = windowSize.width < windowSize.height; + // const viewInPortrait = width < height; + // if (windowInPortrait !== viewInPortrait) { + // // NOTE: This happens on iOS <11. + // // We were not visible (probably in backstack) when orientation happened. + // // request layout so we get the new dimensions. + // // There is no sync way to force a layout. + // setTimeout(() => owner.requestLayout()); + // } + // } + // } + const widthSpec = layout.makeMeasureSpec(width, layout.EXACTLY); const heightSpec = layout.makeMeasureSpec(height, layout.EXACTLY); 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); + // const left = layout.toDevicePixels(fullscreenOrigin.x); + // const top = layout.toDevicePixels(fullscreenOrigin.y); + View.layoutChild(null, owner, left, top, width + left, height + top); layoutParent(owner.parent); } @@ -751,12 +832,34 @@ export namespace ios { public viewWillLayoutSubviews(): void { super.viewWillLayoutSubviews(); - updateConstraints(this, this.owner.get()) + const owner = this.owner.get(); + if (owner) { + updateConstraints(this, owner); + } } public viewDidLayoutSubviews(): void { super.viewDidLayoutSubviews(); - layoutView(this, this.owner.get()); + const owner = this.owner.get(); + if (owner) { + layoutView(this, owner); + } + } + + public viewWillAppear(animated: boolean): void { + super.viewWillAppear(animated); + const owner = this.owner.get(); + if (owner && !owner.parent) { + owner.callLoaded(); + } + } + + public viewDidDisappear(animated: boolean): void { + super.viewDidDisappear(animated); + const owner = this.owner.get(); + if (owner && !owner.parent) { + owner.callUnloaded(); + } } } -} +} \ No newline at end of file diff --git a/tns-core-modules/ui/page/page.ios.ts b/tns-core-modules/ui/page/page.ios.ts index 0061fa69f..27ece39d0 100644 --- a/tns-core-modules/ui/page/page.ios.ts +++ b/tns-core-modules/ui/page/page.ios.ts @@ -246,16 +246,20 @@ class UIViewControllerImpl extends UIViewController { public viewWillLayoutSubviews(): void { super.viewWillLayoutSubviews(); - const owner = this._owner.get(); - iosView.updateConstraints(this, owner); + if (owner) { + iosView.updateConstraints(this, owner); + } } public viewDidLayoutSubviews(): void { super.viewDidLayoutSubviews(); - const owner = this._owner.get(); - iosView.layoutView(this, owner); + if (owner) { + // layout(owner.actionBar) + // layout(owner.content) + iosView.layoutView(this, owner); + } } } @@ -270,8 +274,6 @@ 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; @@ -330,7 +332,7 @@ export class Page extends PageBase { const height = layout.getMeasureSpecSize(heightMeasureSpec); const heightMode = layout.getMeasureSpecMode(heightMeasureSpec); - if (!this._modalParent && this.frame && this.frame._getNavBarVisible(this)) { + if (this.frame && this.frame._getNavBarVisible(this)) { const { width, height } = this.actionBar._getActualSize; const widthSpec = layout.makeMeasureSpec(width, layout.EXACTLY); const heightSpec = layout.makeMeasureSpec(height, layout.EXACTLY); @@ -351,7 +353,7 @@ export class Page extends PageBase { public onLayout(left: number, top: number, right: number, bottom: number) { const { width: actionBarWidth, height: actionBarHeight } = this.actionBar._getActualSize; View.layoutChild(this, this.actionBar, 0, 0, actionBarWidth, actionBarHeight); - View.layoutChild(this, this.layoutView, 0, 0, right - left, bottom - top); + View.layoutChild(this, this.layoutView, left, top, right, bottom); } public _addViewToNativeVisualTree(child: View, atIndex: number): boolean { @@ -360,7 +362,7 @@ export class Page extends PageBase { return true; } - const nativeParent = this.nativeViewProtected.subviews[0]; + const nativeParent = this.nativeViewProtected; const nativeChild = child.nativeViewProtected; const viewController = child.ios instanceof UIViewController ? child.ios : child.viewController; 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 6bb1afccf..a2d0dab6f 100644 --- a/tns-core-modules/ui/tab-view/tab-view.ios.ts +++ b/tns-core-modules/ui/tab-view/tab-view.ios.ts @@ -28,13 +28,23 @@ class UITabBarControllerImpl extends UITabBarController { return handler; } + @profile public viewWillAppear(animated: boolean): void { super.viewWillAppear(animated); const owner = this._owner.get(); - if (owner && !owner.isLoaded && !owner.parent) { + if (owner && !owner.parent) { owner.callLoaded(); } } + + @profile + public viewDidDisappear(animated: boolean): void { + super.viewDidDisappear(animated); + const owner = this._owner.get(); + if (owner && !owner.parent && owner.isLoaded && !this.presentedViewController) { + owner.callUnloaded(); + } + } } class UITabBarControllerDelegateImpl extends NSObject implements UITabBarControllerDelegate { @@ -288,7 +298,7 @@ export class TabView extends TabViewBase { private getViewController(item: TabViewItem): UIViewController { let newController: UIViewController = item.view ? item.view.viewController : null; - + if (newController) { item.setViewController(newController, newController.view); return newController;