Layout improvements

This commit is contained in:
Hristo Hristov
2017-12-15 17:11:51 +02:00
parent 33cd058718
commit 63ab46eb2a
4 changed files with 242 additions and 122 deletions

View File

@ -118,6 +118,7 @@ class IOSApplication implements IOSApplicationDefinition {
this._rootView = rootView; this._rootView = rootView;
const controller = getViewController(rootView); const controller = getViewController(rootView);
this._window.rootViewController = controller; this._window.rootViewController = controller;
rootView._setupAsRootView({});
this._window.makeKeyAndVisible(); this._window.makeKeyAndVisible();
} }
@ -199,8 +200,7 @@ function createRootView(v?: View) {
// try to navigate to the mainEntry (if specified) // try to navigate to the mainEntry (if specified)
if (mainEntry) { if (mainEntry) {
if (createRootFrame) { if (createRootFrame) {
const frame = new Frame(); const frame = rootView = new Frame();
rootView = frame;
frame.navigate(mainEntry); frame.navigate(mainEntry);
} else { } else {
rootView = createViewFromEntry(mainEntry); rootView = createViewFromEntry(mainEntry);
@ -211,7 +211,6 @@ function createRootView(v?: View) {
} }
} }
rootView._setupAsRootView({});
return rootView; return rootView;
} }
@ -230,6 +229,7 @@ export function start(entry?: string | NavigationEntry) {
// Normal NativeScript app will need UIApplicationMain. // Normal NativeScript app will need UIApplicationMain.
UIApplicationMain(0, null, null, iosApp && iosApp.delegate ? NSStringFromClass(<any>iosApp.delegate) : NSStringFromClass(Responder)); UIApplicationMain(0, null, null, iosApp && iosApp.delegate ? NSStringFromClass(<any>iosApp.delegate) : NSStringFromClass(Responder));
} else { } else {
// TODO: this rootView should be held alive until rootController dismissViewController is called.
const rootView = createRootView(); const rootView = createRootView();
if (rootView) { if (rootView) {
// Attach to the existing iOS app // Attach to the existing iOS app
@ -238,6 +238,7 @@ export function start(entry?: string | NavigationEntry) {
const rootController = window.rootViewController; const rootController = window.rootViewController;
if (rootController) { if (rootController) {
const controller = getViewController(rootView); const controller = getViewController(rootView);
rootView._setupAsRootView({});
rootController.presentViewControllerAnimatedCompletion(controller, true, null); rootController.presentViewControllerAnimatedCompletion(controller, true, null);
} }
} }
@ -258,13 +259,17 @@ function getViewController(view: View): UIViewController {
let viewController: UIViewController = view.viewController || view.ios; let viewController: UIViewController = view.viewController || view.ios;
if (viewController instanceof UIViewController) { if (viewController instanceof UIViewController) {
return viewController; 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 { } 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 () { global.__onLiveSync = function () {

View File

@ -575,15 +575,7 @@ export class CustomLayoutView extends View {
function isScrollable(controller: UIViewController, owner: View): boolean { function isScrollable(controller: UIViewController, owner: View): boolean {
let scrollable = (<any>owner).scrollableContent; let scrollable = (<any>owner).scrollableContent;
if (scrollable === undefined) { if (scrollable === undefined) {
let view: UIView = controller.view.subviews.count > 0 ? controller.view.subviews[0] : null; const 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];
}
}
if (view instanceof UIScrollView) { if (view instanceof UIScrollView) {
scrollable = true; scrollable = true;
} }
@ -599,124 +591,213 @@ interface ExtendedController extends UIViewController {
navBarHidden: boolean; navBarHidden: boolean;
hasChildControllers: boolean; hasChildControllers: boolean;
safeAreaLeft: NSLayoutConstraint; // safeAreaLeft: NSLayoutConstraint;
safeAreaTop: NSLayoutConstraint; // safeAreaTop: NSLayoutConstraint;
safeAreaRight: NSLayoutConstraint; // safeAreaRight: NSLayoutConstraint;
safeAreaBottom: NSLayoutConstraint; // safeAreaBottom: NSLayoutConstraint;
fullscreenTop: NSLayoutConstraint; // fullscreenTop: NSLayoutConstraint;
fullscreenBottom: NSLayoutConstraint; // fullscreenBottom: NSLayoutConstraint;
activeConstraints: NSLayoutConstraint[]; // fullscreenLeft: NSLayoutConstraint;
// fullscreenRight: NSLayoutConstraint;
// activeConstraints: NSLayoutConstraint[];
} }
export namespace ios { export namespace ios {
function constrainView(controller: ExtendedController, owner: View): void { function constrainView(controller: ExtendedController, owner: View): void {
const root = controller.view; const root = controller.view;
const view = root.subviews[0];
if (!controller.safeAreaTop) { if (!root.safeAreaLayoutGuide) {
view.translatesAutoresizingMaskIntoConstraints = false; const layoutGuide = (<any>root).safeAreaLayoutGuide = UILayoutGuide.alloc().init();
if (majorVersion > 10) { root.addLayoutGuide(layoutGuide);
const safeArea = root.safeAreaLayoutGuide; // // view.translatesAutoresizingMaskIntoConstraints = false;
controller.safeAreaTop = view.topAnchor.constraintEqualToAnchor(safeArea.topAnchor); // if (majorVersion > 10) {
controller.fullscreenTop = view.topAnchor.constraintEqualToAnchor(root.topAnchor); // const safeArea = root.safeAreaLayoutGuide;
controller.safeAreaBottom = view.bottomAnchor.constraintEqualToAnchor(safeArea.bottomAnchor); // layoutGuide.topAnchor.constraintEqualToAnchor(safeArea.topAnchor);
controller.fullscreenBottom = view.bottomAnchor.constraintEqualToAnchor(root.bottomAnchor); // layoutGuide.bottomAnchor.constraintEqualToAnchor(safeArea.bottomAnchor);
controller.safeAreaLeft = view.leftAnchor.constraintEqualToAnchor(safeArea.leftAnchor); // layoutGuide.leftAnchor.constraintEqualToAnchor(safeArea.leftAnchor);
controller.safeAreaRight = view.rightAnchor.constraintEqualToAnchor(safeArea.rightAnchor); // layoutGuide.rightAnchor.constraintEqualToAnchor(safeArea.rightAnchor);
} else { // } else {
controller.safeAreaTop = view.topAnchor.constraintEqualToAnchor(controller.topLayoutGuide.bottomAnchor); NSLayoutConstraint.activateConstraints(<any>[
controller.fullscreenTop = view.topAnchor.constraintEqualToAnchor(root.topAnchor); layoutGuide.topAnchor.constraintEqualToAnchor(controller.topLayoutGuide.bottomAnchor),
controller.safeAreaBottom = view.bottomAnchor.constraintEqualToAnchor(controller.bottomLayoutGuide.topAnchor); layoutGuide.bottomAnchor.constraintEqualToAnchor(controller.bottomLayoutGuide.topAnchor),
controller.fullscreenBottom = view.bottomAnchor.constraintEqualToAnchor(root.bottomAnchor); layoutGuide.leadingAnchor.constraintEqualToAnchor(root.leadingAnchor),
controller.safeAreaLeft = view.leadingAnchor.constraintEqualToAnchor(root.leadingAnchor); layoutGuide.trailingAnchor.constraintEqualToAnchor(root.trailingAnchor)
controller.safeAreaRight = view.trailingAnchor.constraintEqualToAnchor(root.trailingAnchor); ]);
} // }
} }
const navBarHidden = controller.navBarHidden; // const view = root.subviews[0];
const scrollable = controller.scrollable;; // if (!controller.safeAreaTop) {
const hasChildControllers = controller.hasChildControllers; // view.translatesAutoresizingMaskIntoConstraints = false;
const constraints = [ // if (majorVersion > 10) {
hasChildControllers || scrollable ? controller.fullscreenBottom : controller.safeAreaBottom, // const safeArea = root.safeAreaLayoutGuide;
controller.safeAreaLeft, // controller.safeAreaTop = view.topAnchor.constraintEqualToAnchor(safeArea.topAnchor);
controller.safeAreaRight // 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) { // // check if this works
// If not inner most extend to fullscreen // const fullscreenHorizontally = controller ===
constraints.push(controller.fullscreenTop); // iosUtils.getter(UIApplication, UIApplication.sharedApplication).keyWindow.rootViewController
} else if (!scrollable) { // || !!(<any>owner).wantsFullscreen;
// 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; // const navBarHidden = controller.navBarHidden;
if (activeConstraints) { // const scrollable = controller.scrollable;;
NSLayoutConstraint.deactivateConstraints(<any>activeConstraints); // const hasChildControllers = controller.hasChildControllers;
} // const constraints = [
// hasChildControllers || scrollable ? controller.fullscreenBottom : controller.safeAreaBottom,
// fullscreenHorizontally ? controller.fullscreenLeft : controller.safeAreaLeft,
// fullscreenHorizontally ? controller.fullscreenRight : controller.safeAreaRight
// ];
NSLayoutConstraint.activateConstraints(<any>constraints); // if (hasChildControllers) {
controller.activeConstraints = constraints; // // 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(<any>activeConstraints);
// }
// NSLayoutConstraint.activateConstraints(<any>constraints);
// controller.activeConstraints = constraints;
} }
export function updateConstraints(controller: UIViewController, owner: View): void { export function updateConstraints(controller: UIViewController, owner: View): void {
const extendedController = <ExtendedController>controller; const extendedController = <ExtendedController>controller;
const navController = controller.navigationController; // const navController = controller.navigationController;
const navBarHidden = navController ? navController.navigationBarHidden : true; // const navBarHidden = navController ? navController.navigationBarHidden : true;
const scrollable = (owner && isScrollable(controller, owner)); // const scrollable = isScrollable(controller, owner);
const hasChildControllers = controller.childViewControllers.count > 0; // const hasChildControllers = controller.childViewControllers.count > 0;
if (extendedController.scrollable !== scrollable // if (extendedController.scrollable !== scrollable
|| extendedController.navBarHidden !== navBarHidden // || extendedController.navBarHidden !== navBarHidden
|| extendedController.hasChildControllers !== hasChildControllers) { // || extendedController.hasChildControllers !== hasChildControllers) {
extendedController.scrollable = scrollable; // extendedController.scrollable = scrollable;
extendedController.navBarHidden = navBarHidden; // extendedController.navBarHidden = navBarHidden;
extendedController.hasChildControllers = hasChildControllers; // extendedController.hasChildControllers = hasChildControllers;
constrainView(extendedController, owner); // constrainView(extendedController, owner);
} // }
constrainView(extendedController, owner);
} }
export function layoutView(controller: UIViewController, owner: View): void { export function layoutView(controller: UIViewController, owner: View): void {
// If we are not most inner controller - don't layout // const frame = controller.view.subviews[0].bounds;
if (controller.childViewControllers.count > 0) {
return;
}
const frame = controller.view.subviews[0].bounds; // check if this works
const origin = frame.origin; const fullscreen = controller ===
const size = frame.size; iosUtils.getter(UIApplication, UIApplication.sharedApplication).keyWindow.rootViewController;
let width = layout.toDevicePixels(size.width); // || !!(<any>owner).wantsFullscreen;
let height = layout.toDevicePixels(size.height);
if (iosUtils.MajorVersion < 11) { let left: number, top: number, width: number, height: number;
const window = controller.view.window;
if (window) { const frame = controller.view.frame;
const windowSize = window.frame.size; const fullscreenOrigin = frame.origin;
const windowInPortrait = windowSize.width < windowSize.height; const fullscreenSize = frame.size;
const viewInPortrait = width < height;
if (windowInPortrait !== viewInPortrait) { if (fullscreen) {
// NOTE: This happens on iOS <11. left = layout.toDevicePixels(fullscreenOrigin.x);
// We were not visible (probably in backstack) when orientation happened. top = layout.toDevicePixels(fullscreenOrigin.y);
// request layout so we get the new dimensions. width = layout.toDevicePixels(fullscreenSize.width);
// There is no sync way to force a layout. height = layout.toDevicePixels(fullscreenSize.height);
setTimeout(() => owner.requestLayout()); } 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 widthSpec = layout.makeMeasureSpec(width, layout.EXACTLY);
const heightSpec = layout.makeMeasureSpec(height, layout.EXACTLY); const heightSpec = layout.makeMeasureSpec(height, layout.EXACTLY);
View.measureChild(null, owner, widthSpec, heightSpec); View.measureChild(null, owner, widthSpec, heightSpec);
const left = layout.toDevicePixels(origin.x); // const left = layout.toDevicePixels(fullscreenOrigin.x);
const top = layout.toDevicePixels(origin.y); // const top = layout.toDevicePixels(fullscreenOrigin.y);
View.layoutChild(null, owner, left, top, width + left, height + top, false); View.layoutChild(null, owner, left, top, width + left, height + top);
layoutParent(owner.parent); layoutParent(owner.parent);
} }
@ -751,12 +832,34 @@ export namespace ios {
public viewWillLayoutSubviews(): void { public viewWillLayoutSubviews(): void {
super.viewWillLayoutSubviews(); super.viewWillLayoutSubviews();
updateConstraints(this, this.owner.get()) const owner = this.owner.get();
if (owner) {
updateConstraints(this, owner);
}
} }
public viewDidLayoutSubviews(): void { public viewDidLayoutSubviews(): void {
super.viewDidLayoutSubviews(); 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();
}
} }
} }
} }

View File

@ -246,16 +246,20 @@ class UIViewControllerImpl extends UIViewController {
public viewWillLayoutSubviews(): void { public viewWillLayoutSubviews(): void {
super.viewWillLayoutSubviews(); super.viewWillLayoutSubviews();
const owner = this._owner.get(); const owner = this._owner.get();
iosView.updateConstraints(this, owner); if (owner) {
iosView.updateConstraints(this, owner);
}
} }
public viewDidLayoutSubviews(): void { public viewDidLayoutSubviews(): void {
super.viewDidLayoutSubviews(); super.viewDidLayoutSubviews();
const owner = this._owner.get(); 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() { constructor() {
super(); super();
const controller = UIViewControllerImpl.initWithOwner(new WeakRef(this)); 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.viewController = this._ios = controller;
this.nativeViewProtected = controller.view; this.nativeViewProtected = controller.view;
this.nativeViewProtected.backgroundColor = whiteColor; this.nativeViewProtected.backgroundColor = whiteColor;
@ -330,7 +332,7 @@ export class Page extends PageBase {
const height = layout.getMeasureSpecSize(heightMeasureSpec); const height = layout.getMeasureSpecSize(heightMeasureSpec);
const heightMode = layout.getMeasureSpecMode(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 { width, height } = this.actionBar._getActualSize;
const widthSpec = layout.makeMeasureSpec(width, layout.EXACTLY); const widthSpec = layout.makeMeasureSpec(width, layout.EXACTLY);
const heightSpec = layout.makeMeasureSpec(height, 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) { public onLayout(left: number, top: number, right: number, bottom: number) {
const { width: actionBarWidth, height: actionBarHeight } = this.actionBar._getActualSize; const { width: actionBarWidth, height: actionBarHeight } = this.actionBar._getActualSize;
View.layoutChild(this, this.actionBar, 0, 0, actionBarWidth, actionBarHeight); 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 { public _addViewToNativeVisualTree(child: View, atIndex: number): boolean {
@ -360,7 +362,7 @@ export class Page extends PageBase {
return true; return true;
} }
const nativeParent = this.nativeViewProtected.subviews[0]; const nativeParent = this.nativeViewProtected;
const nativeChild = child.nativeViewProtected; const nativeChild = child.nativeViewProtected;
const viewController = child.ios instanceof UIViewController ? child.ios : child.viewController; const viewController = child.ios instanceof UIViewController ? child.ios : child.viewController;

View File

@ -28,13 +28,23 @@ class UITabBarControllerImpl extends UITabBarController {
return handler; return handler;
} }
@profile
public viewWillAppear(animated: boolean): void { public viewWillAppear(animated: boolean): void {
super.viewWillAppear(animated); super.viewWillAppear(animated);
const owner = this._owner.get(); const owner = this._owner.get();
if (owner && !owner.isLoaded && !owner.parent) { if (owner && !owner.parent) {
owner.callLoaded(); 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 { class UITabBarControllerDelegateImpl extends NSObject implements UITabBarControllerDelegate {