diff --git a/CrossPlatformModules.csproj b/CrossPlatformModules.csproj
index d8cab45cf..863c5fdb7 100644
--- a/CrossPlatformModules.csproj
+++ b/CrossPlatformModules.csproj
@@ -92,6 +92,13 @@
main-page.xml
+
+
+ main-page.xml
+
+
+ login-page.xml
+
@@ -587,6 +594,7 @@
+
Designer
@@ -594,6 +602,7 @@
+
@@ -1494,6 +1503,9 @@
PreserveNewest
+
+ PreserveNewest
+
diff --git a/application/application.ios.ts b/application/application.ios.ts
index 10f8c1c98..e5c652935 100644
--- a/application/application.ios.ts
+++ b/application/application.ios.ts
@@ -31,48 +31,7 @@ class Window extends UIWindow {
}
public layoutSubviews(): void {
- if (!this._content) {
- // TODO: Invalid setup, throw an exception?
- return;
- }
-
- var statusFrame = UIApplication.sharedApplication().statusBarFrame;
- var statusBarHeight = 0;
-
- try {
- statusBarHeight = Math.min(statusFrame.size.width, statusFrame.size.height);
- } catch (ex) {
- console.log("exception: " + ex);
- }
-
- var isLandscape = utils.ios.isLandscape();
-
- var iOSMajorVersion = utils.ios.MajorVersion;
- // in iOS 8 when in landscape statusbar is hidden.
- if (isLandscape && iOSMajorVersion > 7) {
- statusBarHeight = 0;
- }
-
- var deviceFrame = UIScreen.mainScreen().bounds;
- var size = deviceFrame.size;
- var width = size.width;
- var height = size.height;
-
- // in iOS 7 when in landscape we switch width with height because on device they don't change even when rotated.
- if (iOSMajorVersion < 8 && isLandscape) {
- width = size.height;
- height = size.width;
- }
-
- var origin = deviceFrame.origin;
- var left = origin.x;
- var top = origin.y + statusBarHeight;
-
- var widthSpec = utils.layout.makeMeasureSpec(width, utils.layout.EXACTLY);
- var heightSpec = utils.layout.makeMeasureSpec(height - statusBarHeight, utils.layout.EXACTLY);
-
- this._content.measure(widthSpec, heightSpec);
- this._content.layout(left, top, width, height);
+ utils.ios._layoutRootView(this._content);
}
}
diff --git a/apps/modal-views-demo/app.ts b/apps/modal-views-demo/app.ts
new file mode 100644
index 000000000..cb572300b
--- /dev/null
+++ b/apps/modal-views-demo/app.ts
@@ -0,0 +1,3 @@
+import application = require("application");
+application.mainModule = "main-page";
+application.start();
diff --git a/apps/modal-views-demo/login-page.ts b/apps/modal-views-demo/login-page.ts
new file mode 100644
index 000000000..ee1d6deb6
--- /dev/null
+++ b/apps/modal-views-demo/login-page.ts
@@ -0,0 +1,32 @@
+import observable = require("data/observable");
+import pages = require("ui/page");
+import textField = require("ui/text-field");
+
+var context: any;
+var closeCallback: Function;
+
+var page: pages.Page;
+var usernameTextField: textField.TextField;
+var passwordTextField: textField.TextField;
+
+export function onShownModally(args: pages.ShownModallyData) {
+ console.log("login-page.onShownModally, context: " + args.context);
+ context = args.context;
+ closeCallback = args.closeCallback;
+}
+
+export function onLoaded(args: observable.EventData) {
+ console.log("login-page.onLoaded");
+ page = args.object;
+ usernameTextField = page.getViewById("username");
+ passwordTextField = page.getViewById("password");
+}
+
+export function onUnloaded() {
+ console.log("login-page.onUnloaded");
+}
+
+export function onLoginButtonTap() {
+ console.log("login-page.onLoginButtonTap");
+ closeCallback(usernameTextField.text, passwordTextField.text);
+}
\ No newline at end of file
diff --git a/apps/modal-views-demo/login-page.xml b/apps/modal-views-demo/login-page.xml
new file mode 100644
index 000000000..0db5a18df
--- /dev/null
+++ b/apps/modal-views-demo/login-page.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/apps/modal-views-demo/main-page.ts b/apps/modal-views-demo/main-page.ts
new file mode 100644
index 000000000..056114717
--- /dev/null
+++ b/apps/modal-views-demo/main-page.ts
@@ -0,0 +1,18 @@
+import observable = require("data/observable");
+import pages = require("ui/page");
+import labelModule = require("ui/label");
+
+var page: pages.Page;
+var label: labelModule.Label;
+
+export function pageLoaded(args: observable.EventData) {
+ page = args.object;
+ label = page.getViewById("label");
+}
+
+export function onTap(args: observable.EventData) {
+ page.showModal("./modal-views-demo/login-page", "some custom context", function(username: string, password: string) {
+ console.log(username + "/" + password);
+ label.text = username + "/" + password;
+ });
+}
\ No newline at end of file
diff --git a/apps/modal-views-demo/main-page.xml b/apps/modal-views-demo/main-page.xml
new file mode 100644
index 000000000..bbfb061a0
--- /dev/null
+++ b/apps/modal-views-demo/main-page.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/apps/modal-views-demo/package.json b/apps/modal-views-demo/package.json
new file mode 100644
index 000000000..dc02b5111
--- /dev/null
+++ b/apps/modal-views-demo/package.json
@@ -0,0 +1,2 @@
+{ "name" : "modal-views-demo",
+ "main" : "app.js" }
\ No newline at end of file
diff --git a/ui/core/view.ios.ts b/ui/core/view.ios.ts
index 2b2961a21..240ef0e6c 100644
--- a/ui/core/view.ios.ts
+++ b/ui/core/view.ios.ts
@@ -171,9 +171,23 @@ export class View extends viewCommon.View {
public layoutNativeView(left: number, top: number, right: number, bottom: number): void {
var frame = CGRectMake(left, top, right - left, bottom - top);
- if (!CGRectEqualToRect(this._nativeView.frame, frame)) {
- trace.write(this + ", Native setFrame: " + NSStringFromCGRect(frame), trace.categories.Layout);
- this._nativeView.frame = frame;
+
+ // This is done because when rotated in iOS7 there is rotation applied on the first subview on the Window which is our frame.nativeView.view.
+ // If we set it it should be transformed so it is correct.
+ // When in landscape in iOS 7 there is transformation on the first subview of the window so we set frame to its subview.
+ // in iOS 8 we set frame to subview again otherwise we get clipped.
+ var nativeView: UIView;
+ if (!this.parent && this._nativeView.subviews.count > 0) {
+ trace.write(this + " has no parent. Setting frame to first child instead.", trace.categories.Layout);
+ nativeView = (this._nativeView.subviews[0]);
+ }
+ else {
+ nativeView = this._nativeView;
+ }
+
+ if (!CGRectEqualToRect(nativeView.frame, frame)) {
+ trace.write(this + ", Native setFrame: = " + NSStringFromCGRect(frame), trace.categories.Layout);
+ nativeView.frame = frame;
}
}
diff --git a/ui/frame/frame-common.ts b/ui/frame/frame-common.ts
index 65ed51862..ba3769f30 100644
--- a/ui/frame/frame-common.ts
+++ b/ui/frame/frame-common.ts
@@ -30,7 +30,7 @@ function buildEntryFromArgs(arg: any): definition.NavigationEntry {
return entry;
}
-function resolvePageFromEntry(entry: definition.NavigationEntry): pages.Page {
+export function resolvePageFromEntry(entry: definition.NavigationEntry): pages.Page {
var page: pages.Page;
if (entry.create) {
diff --git a/ui/frame/frame.ios.ts b/ui/frame/frame.ios.ts
index 6b8a5c2fc..d7b00ddfc 100644
--- a/ui/frame/frame.ios.ts
+++ b/ui/frame/frame.ios.ts
@@ -137,29 +137,6 @@ export class Frame extends frameCommon.Frame {
}
}
- public layoutNativeView(left: number, top: number, right: number, bottom: number): void {
- // We don't call super here because we set frame on our first subview.
- // This is done because when rotated in iOS7 there is rotation applied on the first subview on the Window which is our frame.nativeView.view.
- // If we set it it should be transformed so it is correct.
-
- var frame = CGRectMake(left, top, right - left, bottom - top);
- var nativeView: UIView;
-
- // When in landscape in iOS 7 there is transformation on the first subview of the window so we set frame to its subview.
- // in iOS 8 we set frame to subview again otherwise we get clipped.
- if (!this.parent && this._nativeView.subviews.count > 0) {
- nativeView = (this._nativeView.subviews[0]);
- }
- else {
- nativeView = this._nativeView;
- }
-
- if (!CGRectEqualToRect(nativeView.frame, frame)) {
- trace.write(this + ", Native setFrame: " + NSStringFromCGRect(frame), trace.categories.Layout);
- nativeView.frame = frame;
- }
- }
-
protected get navigationBarHeight(): number {
var navigationBar = this._ios.controller.navigationBar;
return (navigationBar && !this._ios.controller.navigationBarHidden) ? navigationBar.frame.size.height : 0;
diff --git a/ui/page/page-common.ts b/ui/page/page-common.ts
index 16a673caa..b0456e0d4 100644
--- a/ui/page/page-common.ts
+++ b/ui/page/page-common.ts
@@ -8,6 +8,7 @@ import fileSystemAccess = require("file-system/file-system-access");
import bindable = require("ui/core/bindable");
import dependencyObservable = require("ui/core/dependency-observable");
import enums = require("ui/enums");
+import frameCommon = require("ui/frame/frame-common");
var OPTIONS_MENU = "optionsMenu";
@@ -17,6 +18,7 @@ export module knownCollections {
export class Page extends contentView.ContentView implements dts.Page, view.AddArrayFromBuilder {
public static navigatedToEvent = "navigatedTo";
+ public static shownModallyEvent = "shownModally";
private _navigationContext: any;
@@ -114,6 +116,34 @@ export class Page extends contentView.ContentView implements dts.Page, view.AddA
this._navigationContext = undefined;
}
+ public showModal(moduleName: string, context: any, closeCallback: Function) {
+ var page = frameCommon.resolvePageFromEntry({ moduleName: moduleName });
+ (page)._showNativeModalView(this, context, closeCallback);
+ }
+
+ protected _showNativeModalView(parent: Page, context: any, closeCallback: Function) {
+ //
+ }
+
+ protected _hideNativeModalView(parent: Page) {
+ //
+ }
+
+ protected _raiseShownModallyEvent(parent: Page, context: any, closeCallback: Function) {
+ var that = this;
+ var closeProxy = function () {
+ that._hideNativeModalView(parent);
+ closeCallback.apply(undefined, arguments);
+ };
+
+ this.notify({
+ eventName: Page.shownModallyEvent,
+ object: this,
+ context: context,
+ closeCallback: closeProxy
+ });
+ }
+
public _getStyleScope(): styleScope.StyleScope {
return this._styleScope;
}
diff --git a/ui/page/page.android.ts b/ui/page/page.android.ts
index 6209919bc..04d1a7e91 100644
--- a/ui/page/page.android.ts
+++ b/ui/page/page.android.ts
@@ -1,10 +1,33 @@
import pageCommon = require("ui/page/page-common");
import definition = require("ui/page");
import trace = require("trace");
+import color = require("color");
declare var exports;
require("utils/module-merge").merge(pageCommon, exports);
+class DialogFragmentClass extends android.app.DialogFragment {
+ private _owner: Page;
+
+ constructor(owner: Page) {
+ super();
+
+ this._owner = owner;
+ return global.__native(this);
+ }
+
+ public onCreateDialog(savedInstanceState: android.os.Bundle): android.app.Dialog {
+ var dialog = new android.app.Dialog(this._owner._context);
+ dialog.requestWindowFeature(android.view.Window.FEATURE_NO_TITLE);
+ dialog.setContentView(this._owner._nativeView);
+ var window = dialog.getWindow();
+ window.setBackgroundDrawable(new android.graphics.drawable.ColorDrawable(android.graphics.Color.TRANSPARENT));
+ window.setLayout(android.view.ViewGroup.LayoutParams.FILL_PARENT, android.view.ViewGroup.LayoutParams.FILL_PARENT);
+
+ return dialog;
+ }
+};
+
export class Page extends pageCommon.Page {
private _isBackNavigation = false;
@@ -34,4 +57,31 @@ export class Page extends pageCommon.Page {
this.frame.android.activity.invalidateOptionsMenu();
}
}
+
+ /* tslint:disable */
+ private _dialogFragment: DialogFragmentClass;
+ /* tslint:enable */
+ protected _showNativeModalView(parent: Page, context: any, closeCallback: Function) {
+ if (!this.backgroundColor) {
+ this.backgroundColor = new color.Color("White");
+ }
+
+ this._onAttached(parent._context);
+ this._isAddedToNativeVisualTree = true;
+ this.onLoaded();
+
+ this._dialogFragment = new DialogFragmentClass(this);
+ this._dialogFragment.show(parent.frame.android.activity.getFragmentManager(), "dialog");
+
+ super._raiseShownModallyEvent(parent, context, closeCallback);
+ }
+
+ protected _hideNativeModalView(parent: Page) {
+ this._dialogFragment.dismissAllowingStateLoss();
+ this._dialogFragment = null;
+
+ this.onUnloaded();
+ this._isAddedToNativeVisualTree = false;
+ this._onDetached(true);
+ }
}
\ No newline at end of file
diff --git a/ui/page/page.d.ts b/ui/page/page.d.ts
index 1ef972ff4..86f8ce21f 100644
--- a/ui/page/page.d.ts
+++ b/ui/page/page.d.ts
@@ -23,6 +23,21 @@ declare module "ui/page" {
context: any;
}
+ /**
+ * Defines the data for the Page.shownModally event.
+ */
+ export interface ShownModallyData extends observable.EventData {
+ /**
+ * The context (optional, may be undefined) passed to the page when shown modally.
+ */
+ context: any;
+
+ /**
+ * A callback to call when you want to close the modally shown page. Pass in any kind of arguments and you will receive when the callback parameter of Page.showModal is executed.
+ */
+ closeCallback: Function;
+ }
+
export module knownCollections {
export var optionsMenu: string;
}
@@ -106,6 +121,24 @@ declare module "ui/page" {
*/
on(event: "navigatedTo", callback: (args: NavigatedData) => void, thisArg?: any);
+ /**
+ * String value used when hooking to shownModally event.
+ */
+ public static shownModallyEvent: string;
+
+ /**
+ * Raised when the page is shown as a modal dialog.
+ */
+ on(event: "shownModally", callback: (args: ShownModallyData) => void, thisArg?: any);
+
+ /**
+ * Shows the page contained in moduleName as a modal view.
+ * @param moduleName - The name of the page module to load starting from the application root.
+ * @param context - Any context you want to pass to the modally shown page. This same context will be available in the arguments of the Page.shownModally event handler.
+ * @param closeCallback - A function that will be called when the page is closed. Any arguments provided when calling ShownModallyData.closeCallback will be available here.
+ */
+ showModal(moduleName: string, context: any, closeCallback: Function);
+
_addArrayFromBuilder(name: string, value: Array): void;
//@private
diff --git a/ui/page/page.ios.ts b/ui/page/page.ios.ts
index 09092ac90..37ed6d440 100644
--- a/ui/page/page.ios.ts
+++ b/ui/page/page.ios.ts
@@ -3,6 +3,7 @@ import definition = require("ui/page");
import viewModule = require("ui/core/view");
import imageSource = require("image-source");
import trace = require("trace");
+import utils = require("utils/utils");
declare var exports;
require("utils/module-merge").merge(pageCommon, exports);
@@ -20,7 +21,15 @@ class UIViewControllerImpl extends UIViewController {
return this;
}
+ public didRotateFromInterfaceOrientation(fromInterfaceOrientation: number) {
+ trace.write(this._owner + " didRotateFromInterfaceOrientation(" + fromInterfaceOrientation+ ")", trace.categories.ViewHierarchy);
+ if (this._owner._isModal) {
+ utils.ios._layoutRootView(this._owner);
+ }
+ }
+
public viewDidLoad() {
+ trace.write(this._owner + " viewDidLoad", trace.categories.ViewHierarchy);
this.view.autoresizesSubviews = false;
this.view.autoresizingMask = UIViewAutoresizing.UIViewAutoresizingNone;
}
@@ -32,7 +41,6 @@ class UIViewControllerImpl extends UIViewController {
public viewWillAppear() {
trace.write(this._owner + " viewWillAppear", trace.categories.Navigation);
-
this._owner._enableLoadedEvents = true;
this._owner.onLoaded();
this._owner._enableLoadedEvents = false;
@@ -40,17 +48,16 @@ class UIViewControllerImpl extends UIViewController {
public viewDidDisappear() {
trace.write(this._owner + " viewDidDisappear", trace.categories.Navigation);
-
this._owner._enableLoadedEvents = true;
this._owner.onUnloaded();
this._owner._enableLoadedEvents = false;
}
-
}
export class Page extends pageCommon.Page {
private _ios: UIViewController;
public _enableLoadedEvents: boolean;
+ public _isModal = false;
constructor(options?: definition.Options) {
super(options);
@@ -113,7 +120,7 @@ export class Page extends pageCommon.Page {
this.populateMenuItems();
}
- populateMenuItems() {
+ public populateMenuItems() {
var items = this.optionsMenu.getItems();
var navigationItem: UINavigationItem = (this.ios).navigationItem;
@@ -139,6 +146,21 @@ export class Page extends pageCommon.Page {
navigationItem.setRightBarButtonItemsAnimated(array, true);
}
+
+ protected _showNativeModalView(parent: Page, context: any, closeCallback: Function) {
+ this._isModal = true;
+ utils.ios._layoutRootView(this);
+
+ var that = this;
+ parent.ios.presentViewControllerAnimatedCompletion(this._ios, false, function completion() {
+ that._raiseShownModallyEvent(parent, context, closeCallback);
+ });
+ }
+
+ protected _hideNativeModalView(parent: Page) {
+ parent._ios.dismissModalViewControllerAnimated(false);
+ this._isModal = false;
+ }
}
class TapBarItemHandlerImpl extends NSObject {
@@ -160,4 +182,4 @@ class TapBarItemHandlerImpl extends NSObject {
public static ObjCExposedMethods = {
"tap": { returns: interop.types.void, params: [interop.types.id] }
};
-}
\ No newline at end of file
+}
diff --git a/utils/utils.d.ts b/utils/utils.d.ts
index 058e6128b..f0e877009 100644
--- a/utils/utils.d.ts
+++ b/utils/utils.d.ts
@@ -1,5 +1,6 @@
declare module "utils/utils" {
import colorModule = require("color");
+ import view = require("ui/core/view");
/**
* Utility module related to layout.
@@ -118,6 +119,8 @@
* Gets the iOS device major version (for 8.1 will return 8).
*/
export var MajorVersion: number;
+
+ export function _layoutRootView(rootView: view.View): void;
}
/**
* An utility function that copies properties from source object to target object.
diff --git a/utils/utils.ios.ts b/utils/utils.ios.ts
index 735d32023..69dab5779 100644
--- a/utils/utils.ios.ts
+++ b/utils/utils.ios.ts
@@ -1,5 +1,6 @@
import common = require("utils/utils-common");
import colorModule = require("color");
+import view = require("ui/core/view");
// merge the exports of the common file with the exports of this file
declare var exports;
@@ -77,6 +78,50 @@ export module ios {
}
export var MajorVersion = NSString.stringWithString(UIDevice.currentDevice().systemVersion).intValue;
+
+ export function _layoutRootView(rootView: view.View) {
+ if (!rootView) {
+ return;
+ }
+
+ var statusFrame = UIApplication.sharedApplication().statusBarFrame;
+ var statusBarHeight = 0;
+
+ try {
+ statusBarHeight = Math.min(statusFrame.size.width, statusFrame.size.height);
+ } catch (ex) {
+ console.log("exception: " + ex);
+ }
+
+ var landscape = isLandscape();
+
+ var iOSMajorVersion = MajorVersion;
+ // in iOS 8 when in landscape statusbar is hidden.
+ if (landscape && iOSMajorVersion > 7) {
+ statusBarHeight = 0;
+ }
+
+ var deviceFrame = UIScreen.mainScreen().bounds;
+ var size = deviceFrame.size;
+ var width = size.width;
+ var height = size.height;
+
+ // in iOS 7 when in landscape we switch width with height because on device they don't change even when rotated.
+ if (iOSMajorVersion < 8 && landscape) {
+ width = size.height;
+ height = size.width;
+ }
+
+ var origin = deviceFrame.origin;
+ var left = origin.x;
+ var top = origin.y + statusBarHeight;
+
+ var widthSpec = layout.makeMeasureSpec(width, common.layout.EXACTLY);
+ var heightSpec = layout.makeMeasureSpec(height - statusBarHeight, common.layout.EXACTLY);
+
+ rootView.measure(widthSpec, heightSpec);
+ rootView.layout(left, top, width, height);
+ }
}
export function GC() {