iOS owner pattern changed to use WeakRef in order to prevent memory leaks.

Optimized list-view-tests.
Added time per test.
This commit is contained in:
hshristov
2015-10-22 10:26:59 +03:00
parent 9c0ac9e471
commit f72d79e65a
22 changed files with 863 additions and 791 deletions

View File

@ -29,12 +29,23 @@ export interface TestInfoEntry {
testTimeout: number;
}
export function time(): number {
if (global.android) {
return java.lang.System.nanoTime() / 1000000; // 1 ms = 1000000 ns
}
else {
return CACurrentMediaTime() * 1000;
}
}
export var write = function write(message: string, type?: number) {
//console.log(message);
trace.write(message, trace.categories.Test, type);
}
var runTest = function (testInfo: TestInfoEntry) {
let start = time();
let duration;
try {
if (testInfo.instance) {
testInfo.testFunc.apply(testInfo.instance);
@ -44,13 +55,15 @@ var runTest = function (testInfo: TestInfoEntry) {
}
if (testInfo.isTest) {
write("--- [" + testInfo.testName + "] OK", trace.messageType.info);
duration = time() - start;
write("--- [" + testInfo.testName + "] OK, duration: " + duration, trace.messageType.info);
testInfo.isPassed = true;
}
}
catch (e) {
if (testInfo.isTest) {
write("--- [" + testInfo.testName + "] FAILED: " + e.message, trace.messageType.error);
duration = time() - start;
write("--- [" + testInfo.testName + "] FAILED: " + e.message + ", duration: " + duration, trace.messageType.error);
testInfo.isPassed = false;
testInfo.errorMessage = e.message;
}
@ -78,7 +91,7 @@ function runAsync(testInfo: TestInfoEntry, recursiveIndex: number, testTimeout?:
var error;
var isDone = false;
var handle;
var testStartTime = new Date().getTime();
var testStartTime = time();
//write("--- [" + testInfo.testName + "] Started at: " + testStartTime, trace.messageType.info);
var doneCallback = function (e: Error) {
if (e) {
@ -93,23 +106,25 @@ function runAsync(testInfo: TestInfoEntry, recursiveIndex: number, testTimeout?:
testTimeout = defaultTimeout;
}
let duration;
var checkFinished = function () {
duration = time() - testStartTime;
if (isDone) {
write("--- [" + testInfo.testName + "] OK", trace.messageType.info);
write("--- [" + testInfo.testName + "] OK, duration: " + duration, trace.messageType.info);
//write("--- [" + testInfo.testName + "] took: " + (new Date().getTime() - testStartTime), trace.messageType.info);
testInfo.isPassed = true;
runTests(testsQueue, recursiveIndex + 1);
}
else if (error) {
write("--- [" + testInfo.testName + "] FAILED: " + error.message, trace.messageType.error);
write("--- [" + testInfo.testName + "] FAILED: " + error.message + ", duration: " + duration, trace.messageType.error);
//write("--- [" + testInfo.testName + "] took: " + (new Date().getTime() - testStartTime), trace.messageType.info);
testInfo.errorMessage = error.message;
runTests(testsQueue, recursiveIndex + 1);
}
else {
var testEndTime = new Date().getTime();
var testEndTime = time();
if (testEndTime - testStartTime > testTimeout) {
write("--- [" + testInfo.testName + "] TIMEOUT", trace.messageType.error);
write("--- [" + testInfo.testName + "] TIMEOUT, duration: " + duration, trace.messageType.error);
//write("--- [" + testInfo.testName + "] took: " + (testEndTime - testStartTime), trace.messageType.info);
testInfo.errorMessage = "Test timeout.";
runTests(testsQueue, recursiveIndex + 1);

View File

@ -128,24 +128,15 @@ function printRunTestStats() {
}
}
function time(): number {
if (platform.device.os === platform.platformNames.android) {
return java.lang.System.nanoTime() / 1000000; // 1 ms = 1000000 ns
}
else if (platform.device.os === platform.platformNames.ios) {
return CACurrentMediaTime() * 1000;
}
}
function startLog(): void {
let testsName: string = this.name;
TKUnit.write("START " + testsName + " TESTS.", trace.messageType.info);
this.start = time();
this.start = TKUnit.time();
}
function log(): void {
let testsName: string = this.name;
let duration = time() - this.start;
let duration = TKUnit.time() - this.start;
TKUnit.write(testsName + " COMPLETED for " + duration, trace.messageType.info);
}

View File

@ -1,4 +1,5 @@
import TKUnit = require("../../TKUnit");
import testModule = require("../../ui-test");
import TKUnit = require("../../TKUnit");
import testRunner = require("../../testRunner");
import app = require("application");
import helper = require("../helper");
@ -68,39 +69,32 @@ import labelModule = require("ui/label");
var ASYNC = 0.2;
var FEW_ITEMS = [0, 1, 2];
var MANY_ITEMS = [];
var MANY_ITEMS = new Array<number>(100);
for (var i = 0; i < 100; i++) {
MANY_ITEMS[i] = i;
MANY_ITEMS.push(i);
}
export function test_default_TNS_values() {
// <snippet module="ui/list-view" title="list-view">
// ### Creating a ListView
// ``` JavaScript
var listView = new listViewModule.ListView();
// ```
// </snippet>
export class ListViewTest extends testModule.UITest<listViewModule.ListView> {
public create(): listViewModule.ListView {
return new listViewModule.ListView();
}
public test_default_TNS_values() {
// <snippet module="ui/list-view" title="list-view">
// ### Creating a ListView
// ``` JavaScript
var listView = new listViewModule.ListView();
// ```
// </snippet>
function testAction(views: Array<viewModule.View>) {
TKUnit.assertEqual(listView.isScrolling, false, "Default listView.isScrolling");
TKUnit.assert(types.isUndefined(listView.items), "Default listView.items should be undefined");
}
if (app.android) {
TKUnit.assert(listView.android instanceof android.widget.ListView, "android property is android.widget.ListView");
}
else if (app.ios) {
TKUnit.assert(listView.ios instanceof UITableView, "ios property is UITableView");
}
public test_set_items_to_array_loads_all_items(done) {
var listView = this.testView;
};
helper.buildUIAndRunTest(listView, testAction);
}
export function test_set_items_to_array_loads_all_items() {
var listView = new listViewModule.ListView();
function testAction(views: Array<viewModule.View>) {
var indexes = {};
// <snippet module="ui/list-view" title="list-view">
// ### Using ListView with Array
@ -117,26 +111,35 @@ export function test_set_items_to_array_loads_all_items() {
//<hide>
indexes[args.index] = true;
if (args.index === (colors.length - 1)) {
try {
if (app.android) {
TKUnit.assert(listView.android instanceof android.widget.ListView, "android property is android.widget.ListView");
}
else if (app.ios) {
TKUnit.assert(listView.ios instanceof UITableView, "ios property is UITableView");
}
TKUnit.assert(indexes[0], "itemLoading not called for index 0");
TKUnit.assert(indexes[1], "itemLoading not called for index 1");
TKUnit.assert(indexes[2], "itemLoading not called for index 2");
done(null);
}
catch (e) {
done(e);
}
}
//</hide>
});
// ```
// </snippet>
}
TKUnit.wait(ASYNC);
TKUnit.assert(indexes[0], "itemLoading not called for index 0");
TKUnit.assert(indexes[1], "itemLoading not called for index 1");
TKUnit.assert(indexes[2], "itemLoading not called for index 2");
};
public test_set_native_item_exposed() {
let listView = this.testView;
helper.buildUIAndRunTest(listView, testAction);
}
export function test_set_native_item_exposed() {
var listView = new listViewModule.ListView();
function testAction(views: Array<viewModule.View>) {
var indexes = {};
var colors = ["red", "green", "blue"];
let indexes = {};
let colors = ["red", "green", "blue"];
listView.items = colors;
listView.on(listViewModule.ListView.itemLoadingEvent, function (args: listViewModule.ItemEventData) {
if (platform.device.os === platform.platformNames.ios) {
@ -146,7 +149,8 @@ export function test_set_native_item_exposed() {
}
});
TKUnit.wait(ASYNC);
TKUnit.waitUntilReady(() => { return this.getNativeViewCount(listView) === listView.items.length; }, ASYNC);
for (var item in indexes) {
if (platform.device.os === platform.platformNames.ios) {
TKUnit.assert(indexes[item] instanceof UITableViewCell, "itemLoading not called for index " + item);
@ -154,36 +158,27 @@ export function test_set_native_item_exposed() {
TKUnit.assert(indexes[item] instanceof android.view.ViewGroup, "itemLoading not called for index " + item);
}
}
};
}
helper.buildUIAndRunTest(listView, testAction);
}
export function test_set_items_to_array_creates_native_views() {
var listView = new listViewModule.ListView();
listView.on(listViewModule.ListView.itemLoadingEvent, loadViewWithItemNumber);
function testAction(views: Array<viewModule.View>) {
public test_set_items_to_array_creates_native_views() {
var listView = this.testView;
listView.on(listViewModule.ListView.itemLoadingEvent, this.loadViewWithItemNumber);
listView.items = FEW_ITEMS;
TKUnit.wait(ASYNC);
TKUnit.assertEqual(getNativeViewCount(listView), FEW_ITEMS.length, "Native views count.");
};
TKUnit.waitUntilReady(() => { return this.getNativeViewCount(listView) === listView.items.length; }, ASYNC);
TKUnit.assertEqual(this.getNativeViewCount(listView), FEW_ITEMS.length, "Native views count.");
}
helper.buildUIAndRunTest(listView, testAction);
}
public test_refresh_after_adding_items_to_array_loads_new_items() {
export function test_refresh_after_adding_items_to_array_loads_new_items() {
var listView = this.testView;
listView.on(listViewModule.ListView.itemLoadingEvent, this.loadViewWithItemNumber);
var listView = new listViewModule.ListView();
listView.on(listViewModule.ListView.itemLoadingEvent, loadViewWithItemNumber);
function testAction(views: Array<viewModule.View>) {
var colors = ["red", "green", "blue"];
listView.items = colors;
TKUnit.waitUntilReady(() => { return this.getNativeViewCount(listView) === listView.items.length; }, ASYNC);
TKUnit.assertEqual(this.getNativeViewCount(listView), colors.length, "Native views count.");
TKUnit.wait(ASYNC);
TKUnit.assertEqual(getNativeViewCount(listView), colors.length, "Native views count.");
// <snippet module="ui/list-view" title="list-view">
// > Note, that changing the array after the list view is shown will not update the UI.
// You can force-update the UI using the refresh() method.
@ -193,19 +188,14 @@ export function test_refresh_after_adding_items_to_array_loads_new_items() {
listView.refresh();
// ```
// </snippet>
TKUnit.wait(ASYNC);
TKUnit.assertEqual(getNativeViewCount(listView), colors.length, "Native views count.");
};
TKUnit.waitUntilReady(() => { return this.getNativeViewCount(listView) === listView.items.length; }, ASYNC);
TKUnit.assertEqual(this.getNativeViewCount(listView), colors.length, "Native views count.");
}
helper.buildUIAndRunTest(listView, testAction);
}
export function test_refresh_reloads_all_items() {
var listView = new listViewModule.ListView();
function testAction(views: Array<viewModule.View>) {
var indexes = {};
var testStarted = false;
public test_refresh_reloads_all_items() {
let listView = this.testView;
let indexes = {};
let completed = false;
listView.items = FEW_ITEMS;
listView.on(listViewModule.ListView.itemLoadingEvent, function (args: listViewModule.ItemEventData) {
if (!args.view) {
@ -213,79 +203,66 @@ export function test_refresh_reloads_all_items() {
}
(<labelModule.Label>args.view).text = "item " + args.index;
if (testStarted) {
indexes[args.index] = true;
}
indexes[args.index] = indexes[args.index] ? indexes[args.index] + 1 : 1;
completed = args.index === (listView.items.length - 1);
});
TKUnit.wait(ASYNC);
testStarted = true;
TKUnit.waitUntilReady(() => { return completed; }, ASYNC);
TKUnit.assertEqual(indexes[0], 1, "itemLoading called more than once");
TKUnit.assertEqual(indexes[1], 1, "itemLoading called more than once");
TKUnit.assertEqual(indexes[2], 1, "itemLoading called more than once");
completed = false;
listView.refresh();
TKUnit.wait(ASYNC);
TKUnit.assert(indexes[0], "itemLoading not called for index 0");
TKUnit.assert(indexes[1], "itemLoading not called for index 1");
TKUnit.assert(indexes[2], "itemLoading not called for index 2");
};
TKUnit.waitUntilReady(() => { return completed; }, ASYNC);
TKUnit.assertEqual(indexes[0], 2, "itemLoading not called for index 0");
TKUnit.assertEqual(indexes[1], 2, "itemLoading not called for index 1");
TKUnit.assertEqual(indexes[2], 2, "itemLoading not called for index 2");
}
helper.buildUIAndRunTest(listView, testAction);
}
public test_set_itmes_to_null_clears_native_items() {
var listView = this.testView;
listView.on(listViewModule.ListView.itemLoadingEvent, this.loadViewWithItemNumber);
export function test_set_itmes_to_null_clears_native_items() {
var listView = new listViewModule.ListView();
listView.on(listViewModule.ListView.itemLoadingEvent, loadViewWithItemNumber);
function testAction(views: Array<viewModule.View>) {
listView.items = FEW_ITEMS;
TKUnit.wait(ASYNC);
TKUnit.assertEqual(getNativeViewCount(listView), FEW_ITEMS.length, "Native views count.");
TKUnit.waitUntilReady(() => { return this.getNativeViewCount(listView) === listView.items.length; }, ASYNC);
TKUnit.assertEqual(this.getNativeViewCount(listView), FEW_ITEMS.length, "Native views count.");
listView.items = null;
TKUnit.wait(ASYNC);
TKUnit.assertEqual(getNativeViewCount(listView), 0, "Native views count.");
};
TKUnit.waitUntilReady(() => { return this.getNativeViewCount(listView) === 0; }, ASYNC);
TKUnit.assertEqual(this.getNativeViewCount(listView), 0, "Native views count.");
}
helper.buildUIAndRunTest(listView, testAction);
}
public test_set_itmes_to_undefiend_clears_native_items() {
var listView = this.testView;
listView.on(listViewModule.ListView.itemLoadingEvent, this.loadViewWithItemNumber);
export function test_set_itmes_to_undefiend_clears_native_items() {
var listView = new listViewModule.ListView();
listView.on(listViewModule.ListView.itemLoadingEvent, loadViewWithItemNumber);
function testAction(views: Array<viewModule.View>) {
listView.items = FEW_ITEMS;
TKUnit.wait(ASYNC);
TKUnit.assertEqual(getNativeViewCount(listView), FEW_ITEMS.length, "Native views count.");
TKUnit.waitUntilReady(() => { return this.getNativeViewCount(listView) === listView.items.length; }, ASYNC);
TKUnit.assertEqual(this.getNativeViewCount(listView), FEW_ITEMS.length, "Native views count.");
listView.items = undefined;
TKUnit.wait(ASYNC);
TKUnit.assertEqual(getNativeViewCount(listView), 0, "Native views count.");
};
TKUnit.waitUntilReady(() => { return this.getNativeViewCount(listView) === 0; }, ASYNC);
TKUnit.assertEqual(this.getNativeViewCount(listView), 0, "Native views count.");
}
helper.buildUIAndRunTest(listView, testAction);
}
public test_set_itmes_to_different_source_loads_new_items() {
var listView = this.testView;
listView.on(listViewModule.ListView.itemLoadingEvent, this.loadViewWithItemNumber);
export function test_set_itmes_to_different_source_loads_new_items() {
var listView = new listViewModule.ListView();
listView.on(listViewModule.ListView.itemLoadingEvent, loadViewWithItemNumber);
function testAction(views: Array<viewModule.View>) {
listView.items = [1, 2, 3];
TKUnit.wait(ASYNC);
TKUnit.assertEqual(getNativeViewCount(listView), 3, "Native views count.");
TKUnit.waitUntilReady(() => { return this.getNativeViewCount(listView) === listView.items.length; }, ASYNC);
TKUnit.assertEqual(this.getNativeViewCount(listView), 3, "Native views count.");
listView.items = ["a", "b", "c", "d"];
TKUnit.wait(ASYNC);
TKUnit.assertEqual(getNativeViewCount(listView), 4, "Native views count.");
};
TKUnit.waitUntilReady(() => { return this.getNativeViewCount(listView) === listView.items.length; }, ASYNC);
TKUnit.assertEqual(this.getNativeViewCount(listView), 4, "Native views count.");
}
helper.buildUIAndRunTest(listView, testAction);
}
public test_set_items_to_observable_array_loads_all_items() {
var listView = this.testView;
export function test_set_items_to_observable_array_loads_all_items() {
var listView = new listViewModule.ListView();
function testAction(views: Array<viewModule.View>) {
var indexes = {};
// <snippet module="ui/list-view" title="list-view">
// ### Using ListView with ObservableArray
@ -304,25 +281,21 @@ export function test_set_items_to_observable_array_loads_all_items() {
// ```
// </snippet>
TKUnit.wait(ASYNC);
TKUnit.waitUntilReady(() => { return this.getNativeViewCount(listView) === listView.items.length; }, ASYNC);
TKUnit.assert(indexes[0], "itemLoading not called for index 0");
TKUnit.assert(indexes[1], "itemLoading not called for index 1");
TKUnit.assert(indexes[2], "itemLoading not called for index 2");
};
}
helper.buildUIAndRunTest(listView, testAction);
}
public test_add_to_observable_array_refreshes_the_listview() {
var listView = this.testView;
listView.on(listViewModule.ListView.itemLoadingEvent, this.loadViewWithItemNumber);
export function test_add_to_observable_array_refreshes_the_listview() {
var listView = new listViewModule.ListView();
listView.on(listViewModule.ListView.itemLoadingEvent, loadViewWithItemNumber);
function testAction(views: Array<viewModule.View>) {
var colors = new observableArray.ObservableArray(["red", "green", "blue"]);
listView.items = colors;
TKUnit.wait(ASYNC);
TKUnit.assertEqual(getNativeViewCount(listView), 3, "getNativeViewCount");
TKUnit.waitUntilReady(() => { return this.getNativeViewCount(listView) === listView.items.length; }, ASYNC);
TKUnit.assertEqual(this.getNativeViewCount(listView), 3, "getNativeViewCount");
// <snippet module="ui/list-view" title="list-view">
// > When using ObservableArray the list view will be automatically updated when items are added or removed form the array.
@ -331,60 +304,46 @@ export function test_add_to_observable_array_refreshes_the_listview() {
//// The ListView will be updated automatically.
// ```
// </snippet>
TKUnit.wait(ASYNC);
TKUnit.assertEqual(getNativeViewCount(listView), 4, "getNativeViewCount");
TKUnit.waitUntilReady(() => { return this.getNativeViewCount(listView) === listView.items.length; }, ASYNC);
TKUnit.assertEqual(this.getNativeViewCount(listView), 4, "getNativeViewCount");
}
};
public test_remove_from_observable_array_refreshes_the_listview() {
var listView = this.testView;
var data = new observableArray.ObservableArray([1, 2, 3]);
helper.buildUIAndRunTest(listView, testAction);
}
export function test_remove_from_observable_array_refreshes_the_listview() {
var listView = new listViewModule.ListView();
var data = new observableArray.ObservableArray([1, 2, 3]);
function testAction(views: Array<viewModule.View>) {
listView.items = data;
listView.on(listViewModule.ListView.itemLoadingEvent, loadViewWithItemNumber);
listView.on(listViewModule.ListView.itemLoadingEvent, this.loadViewWithItemNumber);
TKUnit.wait(ASYNC);
TKUnit.assertEqual(getNativeViewCount(listView), 3, "getNativeViewCount");
TKUnit.waitUntilReady(() => { return this.getNativeViewCount(listView) === listView.items.length; }, ASYNC);
TKUnit.assertEqual(this.getNativeViewCount(listView), 3, "getNativeViewCount");
data.pop();
TKUnit.wait(ASYNC);
TKUnit.assertEqual(getNativeViewCount(listView), 2, "getNativeViewCount");
TKUnit.waitUntilReady(() => { return this.getNativeViewCount(listView) === listView.items.length; }, ASYNC);
TKUnit.assertEqual(this.getNativeViewCount(listView), 2, "getNativeViewCount");
}
};
public test_splice_observable_array_refreshes_the_listview() {
var listView = this.testView;
var data = new observableArray.ObservableArray(["a", "b", "c"]);
helper.buildUIAndRunTest(listView, testAction);
}
export function test_splice_observable_array_refreshes_the_listview() {
var listView = new listViewModule.ListView();
var data = new observableArray.ObservableArray(["a", "b", "c"]);
function testAction(views: Array<viewModule.View>) {
listView.items = data;
listView.on(listViewModule.ListView.itemLoadingEvent, loadViewWithItemNumber);
listView.on(listViewModule.ListView.itemLoadingEvent, this.loadViewWithItemNumber);
TKUnit.wait(ASYNC);
TKUnit.assertEqual(getNativeViewCount(listView), 3, "getNativeViewCount");
TKUnit.waitUntilReady(() => { return this.getNativeViewCount(listView) === listView.items.length; }, ASYNC);
TKUnit.assertEqual(this.getNativeViewCount(listView), 3, "getNativeViewCount");
// Remove the first 2 elements and add
data.splice(0, 2, "d", "e", "f");
TKUnit.wait(ASYNC);
TKUnit.assertEqual(getNativeViewCount(listView), 4, "getNativeViewCount");
TKUnit.waitUntilReady(() => { return this.getNativeViewCount(listView) === listView.items.length; }, ASYNC);
TKUnit.assertEqual(this.getNativeViewCount(listView), 4, "getNativeViewCount");
}
};
public test_nativeTap_is_raised() {
var listView = this.testView;
listView.on(listViewModule.ListView.itemLoadingEvent, this.loadViewWithItemNumber);
listView.items = FEW_ITEMS;
helper.buildUIAndRunTest(listView, testAction);
}
export function test_nativeTap_is_raised() {
var listView = new listViewModule.ListView();
listView.on(listViewModule.ListView.itemLoadingEvent, loadViewWithItemNumber);
listView.items = FEW_ITEMS;
function testAction(views: Array<viewModule.View>) {
var nativeTapRaised = false;
var itemIndex = -1;
/* tslint:disable:no-unused-variable */
@ -405,23 +364,19 @@ export function test_nativeTap_is_raised() {
// ```
// </snippet>
/* tslint:enable:no-unused-variable */
TKUnit.wait(ASYNC);
performNativeItemTap(listView, 1);
TKUnit.waitUntilReady(() => { return this.getNativeViewCount(listView) === listView.items.length; }, ASYNC);
this.performNativeItemTap(listView, 1);
TKUnit.assert(nativeTapRaised, "itemTap not raised.");
TKUnit.assertEqual(itemIndex, 1, "tappedItemIndex");
};
}
helper.buildUIAndRunTest(listView, testAction);
}
public test_loadMoreItems_raised_when_showing_few_items() {
var listView = this.testView;
export function test_loadMoreItems_raised_when_showing_few_items() {
var listView = new listViewModule.ListView();
function testAction(views: Array<viewModule.View>) {
var loadMoreItemsCount = 0;
listView.items = FEW_ITEMS;
listView.on(listViewModule.ListView.itemLoadingEvent, loadViewWithItemNumber);
listView.on(listViewModule.ListView.itemLoadingEvent, this.loadViewWithItemNumber);
// <snippet module="ui/list-view" title="list-view">
// ### LoadMoreItems event
// The event will be raised when the ListView is scrolled so that the last item is visible.
@ -435,36 +390,35 @@ export function test_loadMoreItems_raised_when_showing_few_items() {
});
// ```
// </snippet>
TKUnit.wait(ASYNC);
TKUnit.waitUntilReady(() => { return this.getNativeViewCount(listView) === listView.items.length; }, ASYNC);
TKUnit.assertEqual(loadMoreItemsCount, 1, "loadMoreItemsCount");
};
}
helper.buildUIAndRunTest(listView, testAction);
}
public test_loadMoreItems_not_raised_when_showing_many_items() {
var listView = this.testView;
listView.on(listViewModule.ListView.itemLoadingEvent, this.loadViewWithItemNumber);
export function test_loadMoreItems_not_raised_when_showing_many_items() {
var listView = new listViewModule.ListView();
listView.on(listViewModule.ListView.itemLoadingEvent, loadViewWithItemNumber);
function testAction(views: Array<viewModule.View>) {
var loadMoreItemsCount = 0;
listView.items = MANY_ITEMS;
listView.on(listViewModule.ListView.loadMoreItemsEvent, function (data: observable.EventData) {
loadMoreItemsCount++;
});
TKUnit.wait(ASYNC);
// We can't use waitUntilReady because we don't know what to wait for.
if (platform.device.os === platform.platformNames.android) {
this.waitUntilTestElementLayoutIsValid();
}
else {
TKUnit.wait(ASYNC);
}
TKUnit.assertEqual(loadMoreItemsCount, 0, "loadMoreItemsCount");
};
}
helper.buildUIAndRunTest(listView, testAction);
}
public test_loadMoreItems_is_raised_when_scroll_to_last_item() {
var listView = this.testView;
listView.on(listViewModule.ListView.itemLoadingEvent, this.loadViewWithItemNumber);
export function test_loadMoreItems_is_raised_when_scroll_to_last_item() {
var listView = new listViewModule.ListView();
listView.on(listViewModule.ListView.itemLoadingEvent, loadViewWithItemNumber);
function testAction(views: Array<viewModule.View>) {
var loadMoreItemsCount = 0;
listView.items = MANY_ITEMS;
listView.on(listViewModule.ListView.loadMoreItemsEvent, function (data: observable.EventData) {
@ -473,89 +427,82 @@ export function test_loadMoreItems_is_raised_when_scroll_to_last_item() {
listView.scrollToIndex(MANY_ITEMS.length - 1);
TKUnit.wait(ASYNC);
// We can't use waitUntilReady because we don't know what to wait for.
if (platform.device.os === platform.platformNames.android) {
this.waitUntilTestElementLayoutIsValid();
}
else {
TKUnit.wait(ASYNC);
}
TKUnit.assert(loadMoreItemsCount > 0, "loadMoreItemsCount");
};
}
helper.buildUIAndRunTest(listView, testAction);
}
public test_usingAppLevelConvertersInListViewItems() {
var listView = this.testView;
export function test_usingAppLevelConvertersInListViewItems() {
var listView = new listViewModule.ListView();
var dateConverter = function (value, format) {
var result = format;
var day = value.getDate();
result = result.replace("DD", day < 10 ? "0" + day : day);
var month = value.getMonth() + 1;
result = result.replace("MM", month < 10 ? "0" + month : month);
result = result.replace("YYYY", value.getFullYear());
return result;
};
var dateConverter = function (value, format) {
var result = format;
var day = value.getDate();
result = result.replace("DD", day < 10 ? "0" + day : day);
var month = value.getMonth() + 1;
result = result.replace("MM", month < 10 ? "0" + month : month);
result = result.replace("YYYY", value.getFullYear());
return result;
};
app.resources["dateConverter"] = dateConverter;
app.resources["dateConverter"] = dateConverter;
var data = new observableArray.ObservableArray();
data.push({ date: new Date(2020, 2, 7) });
var data = new observableArray.ObservableArray();
data.push({ date: new Date(2020, 2, 7) });
function testAction(views: Array<viewModule.View>) {
listView.itemTemplate = "<Label id=\"testLabel\" text=\"{{ date, date | dateConverter('DD.MM.YYYY') }}\" />";
listView.items = data;
TKUnit.wait(ASYNC);
var nativeElementText = getTextFromNativeElementAt(listView, 0);
TKUnit.waitUntilReady(() => { return this.getNativeViewCount(listView) === listView.items.length; }, ASYNC);
var nativeElementText = this.getTextFromNativeElementAt(listView, 0);
TKUnit.assertEqual(nativeElementText, "07.03.2020", "native element text");
};
}
helper.buildUIAndRunTest(listView, testAction);
}
public test_BindingListViewToASimpleArray() {
var listView = this.testView;
export function test_BindingListViewToASimpleArray() {
var listView = new listViewModule.ListView();
function testAction(views: Array<viewModule.View>) {
listView.itemTemplate = "<Label id=\"testLabel\" text=\"{{ $value }}\" />";
listView.items = [1, 2, 3];
TKUnit.wait(ASYNC);
var firstNativeElementText = getTextFromNativeElementAt(listView, 0);
var secondNativeElementText = getTextFromNativeElementAt(listView, 1);
var thirdNativeElementText = getTextFromNativeElementAt(listView, 2);
TKUnit.waitUntilReady(() => { return this.getNativeViewCount(listView) === listView.items.length; }, ASYNC);
var firstNativeElementText = this.getTextFromNativeElementAt(listView, 0);
var secondNativeElementText = this.getTextFromNativeElementAt(listView, 1);
var thirdNativeElementText = this.getTextFromNativeElementAt(listView, 2);
TKUnit.assertEqual(firstNativeElementText, "1", "first element text");
TKUnit.assertEqual(secondNativeElementText, "2", "second element text");
TKUnit.assertEqual(thirdNativeElementText, "3", "third element text");
}
helper.buildUIAndRunTest(listView, testAction);
}
public test_BindingListViewToASimpleArrayWithExpression() {
var listView = this.testView;
export function test_BindingListViewToASimpleArrayWithExpression() {
var listView = new listViewModule.ListView();
function testAction(views: Array<viewModule.View>) {
listView.itemTemplate = "<Label id=\"testLabel\" text=\"{{ $value, $value + ' some static text' }}\" />";
listView.items = [1, 2, 3];
TKUnit.wait(ASYNC);
var firstNativeElementText = getTextFromNativeElementAt(listView, 0);
var secondNativeElementText = getTextFromNativeElementAt(listView, 1);
var thirdNativeElementText = getTextFromNativeElementAt(listView, 2);
TKUnit.waitUntilReady(() => { return this.getNativeViewCount(listView) === listView.items.length; }, ASYNC);
var firstNativeElementText = this.getTextFromNativeElementAt(listView, 0);
var secondNativeElementText = this.getTextFromNativeElementAt(listView, 1);
var thirdNativeElementText = this.getTextFromNativeElementAt(listView, 2);
TKUnit.assertEqual(firstNativeElementText, "1 some static text", "first element text");
TKUnit.assertEqual(secondNativeElementText, "2 some static text", "second element text");
TKUnit.assertEqual(thirdNativeElementText, "3 some static text", "third element text");
}
helper.buildUIAndRunTest(listView, testAction);
}
public test_bindingToParentObject() {
var listView = this.testView;
var expectedValue = "parentTestValue";
export function test_bindingToParentObject() {
var listView = new listViewModule.ListView();
var expectedValue = "parentTestValue";
function testAction(views: Array<viewModule.View>) {
var listViewModel = new observable.Observable();
listViewModel.set("items", [1, 2, 3]);
listViewModel.set("parentTestProp", expectedValue);
@ -563,24 +510,21 @@ export function test_bindingToParentObject() {
listView.bind({ sourceProperty: "items", targetProperty: "items" });
listView.itemTemplate = "<Label id=\"testLabel\" text=\"{{ $parents[ListView].parentTestProp }}\" />";
TKUnit.wait(ASYNC);
var firstNativeElementText = getTextFromNativeElementAt(listView, 0);
var secondNativeElementText = getTextFromNativeElementAt(listView, 1);
var thirdNativeElementText = getTextFromNativeElementAt(listView, 2);
TKUnit.waitUntilReady(() => { return this.getNativeViewCount(listView) === listView.items.length; }, ASYNC);
var firstNativeElementText = this.getTextFromNativeElementAt(listView, 0);
var secondNativeElementText = this.getTextFromNativeElementAt(listView, 1);
var thirdNativeElementText = this.getTextFromNativeElementAt(listView, 2);
TKUnit.assertEqual(firstNativeElementText, expectedValue, "first element text");
TKUnit.assertEqual(secondNativeElementText, expectedValue, "second element text");
TKUnit.assertEqual(thirdNativeElementText, expectedValue, "third element text");
}
helper.buildUIAndRunTest(listView, testAction);
}
public test_bindingToParentObjectWithSpacesInIndexer() {
var listView = this.testView;
var expectedValue = "parentTestValue";
export function test_bindingToParentObjectWithSpacesInIndexer() {
var listView = new listViewModule.ListView();
var expectedValue = "parentTestValue";
function testAction(views: Array<viewModule.View>) {
var listViewModel = new observable.Observable();
listViewModel.set("items", [1, 2, 3]);
listViewModel.set("parentTestProp", expectedValue);
@ -588,31 +532,27 @@ export function test_bindingToParentObjectWithSpacesInIndexer() {
listView.bind({ sourceProperty: "items", targetProperty: "items" });
listView.itemTemplate = "<Label id=\"testLabel\" text=\"{{ $parents[ ListView ].parentTestProp }}\" />";
TKUnit.wait(ASYNC);
var firstNativeElementText = getTextFromNativeElementAt(listView, 0);
var secondNativeElementText = getTextFromNativeElementAt(listView, 1);
var thirdNativeElementText = getTextFromNativeElementAt(listView, 2);
TKUnit.waitUntilReady(() => { return this.getNativeViewCount(listView) === listView.items.length; }, ASYNC);
var firstNativeElementText = this.getTextFromNativeElementAt(listView, 0);
var secondNativeElementText = this.getTextFromNativeElementAt(listView, 1);
var thirdNativeElementText = this.getTextFromNativeElementAt(listView, 2);
TKUnit.assertEqual(firstNativeElementText, expectedValue, "first element text");
TKUnit.assertEqual(secondNativeElementText, expectedValue, "second element text");
TKUnit.assertEqual(thirdNativeElementText, expectedValue, "third element text");
}
helper.buildUIAndRunTest(listView, testAction);
}
public test_ConverterIsCalledJustOnce_onAddingItemsToListView() {
var listView = this.testView;
var converterCalledCounter = 0;
export function test_ConverterIsCalledJustOnce_onAddingItemsToListView() {
var listView = new listViewModule.ListView();
var converterCalledCounter = 0;
var testConverter = function (value) {
converterCalledCounter++;
return value;
}
var testConverter = function (value) {
converterCalledCounter++;
return value;
}
app.resources["testConverter"] = testConverter;
app.resources["testConverter"] = testConverter;
function testAction(views: Array<viewModule.View>) {
var listViewModel = new observable.Observable();
listViewModel.set("items", [1, 2, 3]);
listView.bindingContext = listViewModel;
@ -620,7 +560,7 @@ export function test_ConverterIsCalledJustOnce_onAddingItemsToListView() {
listView.bind({ sourceProperty: "items", targetProperty: "items" });
listView.itemTemplate = "<Label id=\"testLabel\" text=\"{{ $value, $value | testConverter }}\" />";
TKUnit.wait(ASYNC);
TKUnit.waitUntilReady(() => { return this.getNativeViewCount(listView) === listView.items.length; }, ASYNC);
if (utils.ios && utils.ios.MajorVersion < 8) {
TKUnit.assertEqual(converterCalledCounter, listViewModel.get("items").length * 2, "Converter should be called once for every item.");
@ -630,127 +570,163 @@ export function test_ConverterIsCalledJustOnce_onAddingItemsToListView() {
}
}
helper.buildUIAndRunTest(listView, testAction);
}
export function test_no_memory_leak_when_items_is_regular_array(done) {
var createFunc = function (): listViewModule.ListView {
var listView = new listViewModule.ListView();
listView.items = FEW_ITEMS;
return listView;
};
helper.buildUIWithWeakRefAndInteract(createFunc, (list) => {
TKUnit.assert(list.isLoaded, "ListView should be loaded here");
}, done);
}
export function test_no_memory_leak_when_items_is_observable_array(done) {
// Keep the reference to the observable array to test the weakEventListener
var colors = new observableArray.ObservableArray(["red", "green", "blue"]);
var createFunc = function (): listViewModule.ListView {
var listView = new listViewModule.ListView();
listView.items = colors;
return listView;
};
helper.buildUIWithWeakRefAndInteract(createFunc, (list) => {
TKUnit.assert(list.isLoaded, "ListView should be loaded here");
}, done);
}
function loadViewWithItemNumber(args: listViewModule.ItemEventData) {
if (!args.view) {
args.view = new labelModule.Label();
public test_no_memory_leak_when_items_is_regular_array() {
let weakRef = new WeakRef<listViewModule.ListView>(this.testView);
weakRef.get().items = FEW_ITEMS;
this.waitUntilTestElementIsLoaded();
TKUnit.assertTrue(weakRef.get().isLoaded, "ListView should be loaded here");
this.assertNoMemoryLeak(weakRef);
}
(<labelModule.Label>args.view).text = "item " + args.index;
}
function getTextFromNativeElementAt(listView: listViewModule.ListView, index: number): string {
if (listView.android) {
var nativeElement = listView.android.getChildAt(index);
if (nativeElement instanceof android.view.ViewGroup) {
return (<android.widget.TextView>((<any>nativeElement).getChildAt(0))).getText() + "";
public test_no_memory_leak_when_items_is_observable_array() {
// Keep the reference to the observable array to test the weakEventListener
var colors = new observableArray.ObservableArray(["red", "green", "blue"]);
let weakRef = new WeakRef<listViewModule.ListView>(this.testView);
weakRef.get().items = colors;
this.waitUntilTestElementIsLoaded();
TKUnit.assertTrue(weakRef.get().isLoaded, "ListView should be loaded here");
this.assertNoMemoryLeak(weakRef);
}
private test_view_in_itemLoading_is_not_collected_prematurely() {
let weakRef: WeakRef<labelModule.Label>;
let handler = function (args: listViewModule.ItemEventData) {
let lbl = new labelModule.Label();
lbl.text = args.index.toString();
weakRef = new WeakRef(lbl);
args.view = lbl;
let listView: listViewModule.ListView = <listViewModule.ListView>args.object;
listView.off("itemLoading", handler);
};
this.testView.on("itemLoading", handler);
this.testView.items = [1];
TKUnit.waitUntilReady(() => { return this.getNativeViewCount(this.testView) === this.testView.items.length; }, ASYNC);
if (platform.device.os === platform.platformNames.ios) {
// Could cause GC on the next call.
// NOTE: Don't replace this with forceGC();
new ArrayBuffer(4 * 1024 * 1024);
}
return (<android.widget.TextView>nativeElement).getText() + "";
utils.GC();
TKUnit.assert(weakRef.get(), weakRef.get() + " died prematurely!");
}
else if (listView.ios) {
if (types.isFunction(listView.ios.visibleCells)) {
return listView.ios.visibleCells()[index].contentView.subviews[0].text + "";
private assertNoMemoryLeak(weakRef: WeakRef<listViewModule.ListView>) {
this.tearDown();
TKUnit.waitUntilReady(() => {
if (platform.device.os === platform.platformNames.ios) {
// Could cause GC on the next call.
// NOTE: Don't replace this with forceGC();
new ArrayBuffer(4 * 1024 * 1024);
}
utils.GC();
return !weakRef.get();
});
TKUnit.assert(!weakRef.get(), weakRef.get() + " leaked!");
}
private loadViewWithItemNumber(args: listViewModule.ItemEventData) {
if (!args.view) {
args.view = new labelModule.Label();
}
(<labelModule.Label>args.view).text = "item " + args.index;
}
private getTextFromNativeElementAt(listView: listViewModule.ListView, index: number): string {
if (listView.android) {
var nativeElement = listView.android.getChildAt(index);
if (nativeElement instanceof android.view.ViewGroup) {
return (<android.widget.TextView>((<any>nativeElement).getChildAt(0))).getText() + "";
}
return (<android.widget.TextView>nativeElement).getText() + "";
}
else if (listView.ios) {
if (types.isFunction(listView.ios.visibleCells)) {
return listView.ios.visibleCells()[index].contentView.subviews[0].text + "";
}
else {
return listView.ios.visibleCells[index].contentView.subviews[0].text + "";
}
}
}
private getNativeViewCount(listView: listViewModule.ListView): number {
if (listView.android) {
return listView.android.getChildCount();
}
else if (listView.ios) {
if (types.isFunction(listView.ios.visibleCells)) {
return listView.ios.visibleCells().count;
}
else {
return (<any>listView.ios.visibleCells).count;
}
}
else {
return listView.ios.visibleCells[index].contentView.subviews[0].text + "";
throw new Error("Cannot get native view count");
}
}
}
function getNativeViewCount(listView: listViewModule.ListView): number {
if (listView.android) {
return listView.android.getChildCount();
}
else if (listView.ios) {
if (types.isFunction(listView.ios.visibleCells)) {
return listView.ios.visibleCells().count;
private performNativeItemTap(listView: listViewModule.ListView, index: number): void {
if (listView.android) {
listView.android.performItemClick(listView.android.getChildAt(index), index, listView.android.getAdapter().getItemId(index));
}
else if (listView.ios) {
// Calling selectRowAtIndexPathAnimatedScrollPosition will not tiger [Will|Did]SelectRowAtIndexPath callbacks.
listView.ios.delegate.tableViewWillSelectRowAtIndexPath(listView.ios, NSIndexPath.indexPathForItemInSection(index, 0));
}
else {
return (<any>listView.ios.visibleCells).count;
throw new Error("Cannot perform native item tap");
}
}
else {
throw new Error("Cannot get native view count");
}
//public test_LoadedUnloaded() {
// var listView = this.testView;
// var vm = {
// loadedCount: 0,
// unloadedCount: 0,
// onViewLoaded: function onViewLoaded(args) {
// this.loadedCount++;
// console.log(args.object._domId + " LOADED");
// },
// onViewUnloaded: function onViewUnloaded(args) {
// this.unloadedCount++;
// console.log(args.object._domId + " UNLOADED");
// }
// };
// listView.itemTemplate = "<Label text=\"{{ $value }}\" loaded=\"{{ onViewLoaded }}\" unloaded=\"{{ onViewUnloaded }}\"/>";
// listView.bindingContext = vm;
// var count = 10;
// var modifier = listView.ios ? 1 : 0; // iOS has one fake measure cell that raises loaded.
// var generate = function (count: number): observableArray.ObservableArray<string> {
// var items = new observableArray.ObservableArray<string>();
// for (var i = 0; i < count; i++) {
// items.push("Item " + i);
// }
// return items;
// }
// function testAction(views: Array<viewModule.View>) {
// listView.items = generate(count);
// TKUnit.wait(ASYNC);
// frame.topmost().navigate("pages/navigation/pageB");
// TKUnit.wait(ASYNC);
// frame.topmost().goBack();
// TKUnit.assertEqual(vm.loadedCount, count + modifier, "Loaded Count");
// TKUnit.assertEqual(vm.unloadedCount, count, "Unloaded Count");
// }
// helper.buildUIAndRunTest(listView, testAction);
//}
}
function performNativeItemTap(listView: listViewModule.ListView, index: number): void {
if (listView.android) {
listView.android.performItemClick(listView.android.getChildAt(index), index, listView.android.getAdapter().getItemId(index));
}
else if (listView.ios) {
// Calling selectRowAtIndexPathAnimatedScrollPosition will not tiger [Will|Did]SelectRowAtIndexPath callbacks.
listView.ios.delegate.tableViewWillSelectRowAtIndexPath(listView.ios, NSIndexPath.indexPathForItemInSection(index, 0));
}
else {
throw new Error("Cannot perform native item tap");
}
}
//export function test_LoadedUnloaded() {
// var listView = new listViewModule.ListView();
// var vm = {
// loadedCount: 0,
// unloadedCount: 0,
// onViewLoaded: function onViewLoaded(args) {
// this.loadedCount++;
// console.log(args.object._domId + " LOADED");
// },
// onViewUnloaded: function onViewUnloaded(args) {
// this.unloadedCount++;
// console.log(args.object._domId + " UNLOADED");
// }
// };
// listView.itemTemplate = "<Label text=\"{{ $value }}\" loaded=\"{{ onViewLoaded }}\" unloaded=\"{{ onViewUnloaded }}\"/>";
// listView.bindingContext = vm;
// var count = 10;
// var modifier = listView.ios ? 1 : 0; // iOS has one fake measure cell that raises loaded.
// var generate = function (count: number): observableArray.ObservableArray<string> {
// var items = new observableArray.ObservableArray<string>();
// for (var i = 0; i < count; i++) {
// items.push("Item " + i);
// }
// return items;
// }
// function testAction(views: Array<viewModule.View>) {
// listView.items = generate(count);
// TKUnit.wait(ASYNC);
// frame.topmost().navigate("pages/navigation/pageB");
// TKUnit.wait(ASYNC);
// frame.topmost().goBack();
// TKUnit.assertEqual(vm.loadedCount, count + modifier, "Loaded Count");
// TKUnit.assertEqual(vm.unloadedCount, count, "Unloaded Count");
// }
// helper.buildUIAndRunTest(listView, testAction);
//}
export function createTestCase(): ListViewTest {
return new ListViewTest();
}

View File

@ -165,10 +165,10 @@ class ScrollLayoutTest extends testModule.UITest<scrollViewModule.ScrollView> {
// No synchronous change.
TKUnit.assertEqual(this.testView.verticalOffset, 0, "this.testView.verticalOffset");
TKUnit.waitUntilReady(() => { return TKUnit.areClose(this.testView.verticalOffset, 100, 0.1); });
TKUnit.waitUntilReady(() => { return TKUnit.areClose(this.testView.verticalOffset, 100, 0.9); });
// The scrolling animation should be finished by now
TKUnit.assertAreClose(this.testView.verticalOffset, 100, 0.1, "this.testView.verticalOffset");
TKUnit.assertAreClose(this.testView.verticalOffset, 100, 0.9, "this.testView.verticalOffset");
}
public test_scrollToHorizontalOffset_no_animation() {
@ -206,10 +206,10 @@ class ScrollLayoutTest extends testModule.UITest<scrollViewModule.ScrollView> {
// No synchronous change.
TKUnit.assertEqual(this.testView.horizontalOffset, 0, "this.testView.horizontalOffset");
TKUnit.waitUntilReady(() => { return TKUnit.areClose(this.testView.horizontalOffset, 100, 0.1); });
TKUnit.waitUntilReady(() => { return TKUnit.areClose(this.testView.horizontalOffset, 100, 0.9); });
// The scrolling animation should be finished by now
TKUnit.assertAreClose(this.testView.horizontalOffset, 100, 0.1, "this.testView.horizontalOffset");
TKUnit.assertAreClose(this.testView.horizontalOffset, 100, 0.9, "this.testView.horizontalOffset");
}
public test_scrollView_persistsState_vertical() {

View File

@ -1,23 +1,24 @@
import definition = require("fps-meter/fps-native");
class FrameHandlerImpl extends NSObject {
static new(): FrameHandlerImpl {
return <FrameHandlerImpl>super.new();
}
private _owner: FPSCallback;
private _owner: WeakRef<FPSCallback>;
public initWithOwner(owner: FPSCallback): FrameHandlerImpl {
this._owner = owner;
return this;
public static initWithOwner(owner: WeakRef<FPSCallback>): FrameHandlerImpl {
let handler = <FrameHandlerImpl>FrameHandlerImpl.new();
handler._owner = owner;
return handler;
}
public handleFrame(sender: CADisplayLink): void {
this._owner._handleFrame(sender);
let owner = this._owner.get();
if (owner) {
owner._handleFrame(sender);
}
}
public static ObjCExposedMethods = {
"handleFrame": { returns: interop.types.void, params: [ CADisplayLink ] }
"handleFrame": { returns: interop.types.void, params: [CADisplayLink] }
};
}
@ -30,7 +31,7 @@ export class FPSCallback implements definition.FPSCallback {
constructor(onFrame: (currentTimeMillis: number) => void) {
this.onFrame = onFrame;
this.impl = FrameHandlerImpl.new().initWithOwner(this);
this.impl = FrameHandlerImpl.initWithOwner(new WeakRef(this));
this.displayLink = CADisplayLink.displayLinkWithTargetSelector(this.impl, "handleFrame");
this.displayLink.paused = true;

View File

@ -51,7 +51,7 @@ export class ActionBar extends common.ActionBar {
// Set back button text
if (previousController) {
if (this.navigationButton) {
var tapHandler = TapBarItemHandlerImpl.new().initWithOwner(this.navigationButton);
var tapHandler = TapBarItemHandlerImpl.initWithOwner(new WeakRef(this.navigationButton));
var barButtonItem = UIBarButtonItem.alloc().initWithTitleStyleTargetAction(this.navigationButton.text + "", UIBarButtonItemStyle.UIBarButtonItemStylePlain, tapHandler, "tap");
previousController.navigationItem.backBarButtonItem = barButtonItem;
}
@ -112,7 +112,7 @@ export class ActionBar extends common.ActionBar {
}
private createBarButtonItem(item: dts.ActionItem): UIBarButtonItem {
var tapHandler = TapBarItemHandlerImpl.new().initWithOwner(item);
var tapHandler = TapBarItemHandlerImpl.initWithOwner(new WeakRef(item));
// associate handler with menuItem or it will get collected by JSC.
(<any>item).handler = tapHandler;
@ -129,7 +129,7 @@ export class ActionBar extends common.ActionBar {
return barButtonItem;
}
public _onTitlePropertyChanged() {
if (!this.page) {
return;
@ -141,7 +141,7 @@ export class ActionBar extends common.ActionBar {
private _navigationBarHeight: number = 0;
public onMeasure(widthMeasureSpec: number, heightMeasureSpec: number) {
let width = utils.layout.getMeasureSpecSize(widthMeasureSpec);
let widthMode = utils.layout.getMeasureSpecMode(widthMeasureSpec);
@ -186,19 +186,19 @@ export class ActionBar extends common.ActionBar {
}
class TapBarItemHandlerImpl extends NSObject {
static new(): TapBarItemHandlerImpl {
return <TapBarItemHandlerImpl>super.new();
}
private _owner: WeakRef<dts.ActionItemBase>;
private _owner: dts.ActionItemBase;
public initWithOwner(owner: dts.ActionItemBase): TapBarItemHandlerImpl {
this._owner = owner;
return this;
public static initWithOwner(owner: WeakRef<dts.ActionItemBase>): TapBarItemHandlerImpl {
let handler = <TapBarItemHandlerImpl>TapBarItemHandlerImpl.new();
handler._owner = owner;
return handler;
}
public tap(args) {
this._owner._raiseTap();
let owner = this._owner.get();
if (owner) {
owner._raiseTap();
}
}
public static ObjCExposedMethods = {

View File

@ -2,19 +2,19 @@
import stateChanged = require("ui/core/control-state-change");
class TapHandlerImpl extends NSObject {
static new(): TapHandlerImpl {
return <TapHandlerImpl>super.new();
}
private _owner: WeakRef<Button>;
private _owner: Button;
public initWithOwner(owner: Button): TapHandlerImpl {
this._owner = owner;
return this;
public static initWithOwner(owner: WeakRef<Button>): TapHandlerImpl {
let handler = <TapHandlerImpl>TapHandlerImpl.new();
handler._owner = owner;
return handler;
}
public tap(args) {
this._owner._emit(common.Button.tapEvent);
let owner = this._owner.get();
if (owner) {
owner._emit(common.Button.tapEvent);
}
}
public static ObjCExposedMethods = {
@ -33,7 +33,7 @@ export class Button extends common.Button {
super();
this._ios = UIButton.buttonWithType(UIButtonType.UIButtonTypeSystem);
this._tapHandler = TapHandlerImpl.new().initWithOwner(this);
this._tapHandler = TapHandlerImpl.initWithOwner(new WeakRef(this));
this._ios.addTargetActionForControlEvents(this._tapHandler, "tap", UIControlEvents.UIControlEventTouchUpInside);
this._stateChangedHandler = new stateChanged.ControlStateChangeListener(this._ios, (s: string) => {

View File

@ -71,7 +71,7 @@ export class DatePicker extends common.DatePicker {
this._ios = new UIDatePicker();
this._ios.datePickerMode = UIDatePickerMode.UIDatePickerModeDate;
this._changeHandler = UIDatePickerChangeHandlerImpl.new().initWithOwner(this);
this._changeHandler = UIDatePickerChangeHandlerImpl.initWithOwner(new WeakRef(this));
this._ios.addTargetActionForControlEvents(this._changeHandler, "valueChanged", UIControlEvents.UIControlEventValueChanged);
}
@ -81,30 +81,33 @@ export class DatePicker extends common.DatePicker {
}
class UIDatePickerChangeHandlerImpl extends NSObject {
static new(): UIDatePickerChangeHandlerImpl {
return <UIDatePickerChangeHandlerImpl>super.new();
}
private _owner: DatePicker;
private _owner: WeakRef<DatePicker>;
public initWithOwner(owner: DatePicker): UIDatePickerChangeHandlerImpl {
this._owner = owner;
return this;
public static initWithOwner(owner: WeakRef<DatePicker>): UIDatePickerChangeHandlerImpl {
let impl = <UIDatePickerChangeHandlerImpl>UIDatePickerChangeHandlerImpl.new();
impl._owner = owner;
return impl;
}
public valueChanged(sender: UIDatePicker) {
var comps = NSCalendar.currentCalendar().componentsFromDate(NSCalendarUnit.NSCalendarUnitYear | NSCalendarUnit.NSCalendarUnitMonth | NSCalendarUnit.NSCalendarUnitDay, sender.date);
if (comps.year !== this._owner.year) {
this._owner._onPropertyChangedFromNative(common.DatePicker.yearProperty, comps.year);
let owner = this._owner.get();
if (!owner) {
return;
}
if (comps.month !== this._owner.month) {
this._owner._onPropertyChangedFromNative(common.DatePicker.monthProperty, comps.month);
if (comps.year !== owner.year) {
owner._onPropertyChangedFromNative(common.DatePicker.yearProperty, comps.year);
}
if (comps.day !== this._owner.day) {
this._owner._onPropertyChangedFromNative(common.DatePicker.dayProperty, comps.day);
if (comps.month !== owner.month) {
owner._onPropertyChangedFromNative(common.DatePicker.monthProperty, comps.month);
}
if (comps.day !== owner.day) {
owner._onPropertyChangedFromNative(common.DatePicker.dayProperty, comps.day);
}
}

View File

@ -235,32 +235,42 @@ export class Frame extends frameCommon.Frame {
class UINavigationControllerImpl extends UINavigationController implements UINavigationControllerDelegate {
public static ObjCProtocols = [UINavigationControllerDelegate];
private _owner: Frame;
private _owner: WeakRef<Frame>;
public static initWithOwner(owner: Frame): UINavigationControllerImpl {
public static initWithOwner(owner: WeakRef<Frame>): UINavigationControllerImpl {
var controller = <UINavigationControllerImpl>UINavigationControllerImpl.new();
controller._owner = owner;
return controller;
}
get owner(): Frame {
return this._owner;
return this._owner.get();
}
public viewDidLoad(): void {
this._owner.onLoaded();
let owner = this._owner.get();
if (owner) {
owner.onLoaded();
}
}
public viewDidLayoutSubviews(): void {
trace.write(this._owner + " viewDidLayoutSubviews, isLoaded = " + this._owner.isLoaded, trace.categories.ViewHierarchy);
this._owner._updateLayout();
let owner = this._owner.get();
if (owner) {
trace.write(this._owner + " viewDidLayoutSubviews, isLoaded = " + owner.isLoaded, trace.categories.ViewHierarchy);
owner._updateLayout();
}
}
public navigationControllerWillShowViewControllerAnimated(navigationController: UINavigationController, viewController: UIViewController, animated: boolean): void {
// In this method we need to layout the new page otherwise page will be shown empty and update after that which is bad UX.
var frame = this._owner;
var newEntry: definition.BackstackEntry = viewController[ENTRY];
var newPage = newEntry.resolvedPage;
let frame = this._owner.get();
if (!frame) {
return;
}
let newEntry: definition.BackstackEntry = viewController[ENTRY];
let newPage = newEntry.resolvedPage;
if (!newPage.parent) {
if (!frame._currentEntry) {
// First navigation
@ -282,14 +292,18 @@ class UINavigationControllerImpl extends UINavigationController implements UINav
}
public navigationControllerDidShowViewControllerAnimated(navigationController: UINavigationController, viewController: UIViewController, animated: boolean): void {
var frame: Frame = this._owner;
var backStack = frame.backStack;
var currentEntry = backStack.length > 0 ? backStack[backStack.length - 1] : null;
var newEntry: definition.BackstackEntry = viewController[ENTRY];
let frame = this._owner.get();
if (!frame) {
return;
}
let backStack = frame.backStack;
let currentEntry = backStack.length > 0 ? backStack[backStack.length - 1] : null;
let newEntry: definition.BackstackEntry = viewController[ENTRY];
// This code check if navigation happened through UI (e.g. back button or swipe gesture).
// When calling goBack on frame isBack will be false.
var isBack: boolean = currentEntry && newEntry === currentEntry;
let isBack: boolean = currentEntry && newEntry === currentEntry;
if (isBack) {
try {
frame._shouldSkipNativePop = true;
@ -300,7 +314,7 @@ class UINavigationControllerImpl extends UINavigationController implements UINav
}
}
var page = frame.currentPage;
let page = frame.currentPage;
if (page && !navigationController.viewControllers.containsObject(page.ios)) {
frame._removeView(page);
}
@ -308,7 +322,7 @@ class UINavigationControllerImpl extends UINavigationController implements UINav
frame._navigateToEntry = null;
frame._currentEntry = newEntry;
var newPage = newEntry.resolvedPage;
let newPage = newEntry.resolvedPage;
// In iOS we intentionally delay the raising of the 'loaded' event so both platforms behave identically.
// The loaded event must be raised AFTER the page is part of the windows hierarchy and
@ -337,7 +351,7 @@ class iOSFrame implements definition.iOSFrame {
private _navBarVisibility: string;
constructor(owner: Frame) {
this._controller = UINavigationControllerImpl.initWithOwner(owner);
this._controller = UINavigationControllerImpl.initWithOwner(new WeakRef(owner));
this._controller.delegate = this._controller;
this._controller.automaticallyAdjustsScrollViewInsets = false;
//this.showNavigationBar = false;
@ -356,8 +370,9 @@ class iOSFrame implements definition.iOSFrame {
this._showNavigationBar = value;
this._controller.navigationBarHidden = !value;
if (change) {
this._controller.owner.requestLayout();
let owner = this._controller.owner;
if (owner && change) {
owner.requestLayout();
}
}

View File

@ -7,28 +7,26 @@ import trace = require("trace");
global.moduleMerge(common, exports);
class UIGestureRecognizerImpl extends NSObject {
static new(): UIGestureRecognizerImpl {
return <UIGestureRecognizerImpl>super.new();
}
private _owner: GesturesObserver;
private _owner: WeakRef<GesturesObserver>;
private _type: any;
private _callback: Function;
private _context: any;
public initWithOwnerTypeCallback(owner: GesturesObserver, type: any, callback?: Function, thisArg?: any): UIGestureRecognizerImpl {
this._owner = owner;
this._type = type;
public static initWithOwnerTypeCallback(owner: WeakRef<GesturesObserver>, type: any, callback?: Function, thisArg?: any): UIGestureRecognizerImpl {
let handler = <UIGestureRecognizerImpl>UIGestureRecognizerImpl.new();
handler._owner = owner;
handler._type = type;
if (callback) {
this._callback = callback;
handler._callback = callback;
}
if (thisArg) {
this._context = thisArg;
handler._context = thisArg;
}
return this;
return handler;
}
public static ObjCExposedMethods = {
@ -36,17 +34,18 @@ class UIGestureRecognizerImpl extends NSObject {
};
public recognize(recognizer: UIGestureRecognizer): void {
var callback = this._callback ? this._callback : this._owner.callback;
var type = this._type;
var target = this._owner.target;
let owner = this._owner.get();
let callback = this._callback ? this._callback : (owner ? owner.callback : null);
let typeParam = this._type;
let target = owner ? owner.target : undefined;
var args = {
type: type,
type: typeParam,
view: target,
ios: recognizer,
android: undefined,
object: target,
eventName: definition.toString(type),
eventName: definition.toString(typeParam),
};
if (callback) {
@ -209,7 +208,7 @@ export class GesturesObserver extends common.GesturesObserver {
}
function _createUIGestureRecognizerTarget(owner: GesturesObserver, type: definition.GestureTypes, callback?: (args: definition.GestureEventData) => void, thisArg?: any): any {
return UIGestureRecognizerImpl.new().initWithOwnerTypeCallback(owner, type, callback, thisArg);
return UIGestureRecognizerImpl.initWithOwnerTypeCallback(new WeakRef(owner), type, callback, thisArg);
}
interface RecognizerCache {

View File

@ -24,35 +24,44 @@ function onTextWrapPropertyChanged(data: dependencyObservable.PropertyChangeData
global.moduleMerge(common, exports);
class UILabelImpl extends UILabel {
static new(): UILabelImpl {
return <UILabelImpl>super.new();
}
private _owner: Label;
private _owner: WeakRef<Label>;
public initWithOwner(owner: Label): UILabelImpl {
this._owner = owner;
return this;
public static initWithOwner(owner: WeakRef<Label>): UILabelImpl {
let labelImpl = <UILabelImpl>UILabelImpl.new();
labelImpl._owner = owner;
return labelImpl;
}
public textRectForBoundsLimitedToNumberOfLines(bounds: CGRect, numberOfLines: number): CGRect {
var rect = super.textRectForBoundsLimitedToNumberOfLines(bounds, numberOfLines);
var textRect = CGRectMake(
- (this._owner.borderWidth + this._owner.style.paddingLeft),
- (this._owner.borderWidth + this._owner.style.paddingTop),
rect.size.width + (this._owner.borderWidth + this._owner.style.paddingLeft + this._owner.style.paddingRight + this._owner.borderWidth),
rect.size.height + (this._owner.borderWidth + this._owner.style.paddingTop + this._owner.style.paddingBottom + this._owner.borderWidth)
let rect = super.textRectForBoundsLimitedToNumberOfLines(bounds, numberOfLines);
let owner = this._owner.get();
if (owner) {
let size = rect.size;
rect = CGRectMake(
- (owner.borderWidth + owner.style.paddingLeft),
- (owner.borderWidth + owner.style.paddingTop),
size.width + (owner.borderWidth + owner.style.paddingLeft + owner.style.paddingRight + owner.borderWidth),
size.height + (owner.borderWidth + owner.style.paddingTop + owner.style.paddingBottom + owner.borderWidth)
);
return textRect;
}
return rect;
}
public drawTextInRect(rect: CGRect): void {
var textRect = CGRectMake(
(this._owner.borderWidth + this._owner.style.paddingLeft),
(this._owner.borderWidth + this._owner.style.paddingTop),
rect.size.width - (this._owner.borderWidth + this._owner.style.paddingLeft + this._owner.style.paddingRight + this._owner.borderWidth),
rect.size.height - (this._owner.borderWidth + this._owner.style.paddingTop + this._owner.style.paddingBottom + this._owner.borderWidth)
);
let owner = this._owner.get();
let textRect: CGRect;
let size = rect.size;
if (owner) {
textRect = CGRectMake((owner.borderWidth + owner.style.paddingLeft), (owner.borderWidth + owner.style.paddingTop),
size.width - (owner.borderWidth + owner.style.paddingLeft + owner.style.paddingRight + owner.borderWidth),
size.height - (owner.borderWidth + owner.style.paddingTop + owner.style.paddingBottom + owner.borderWidth));
}
else {
textRect = CGRectMake(0, 0, size.width, size.height);
}
super.drawTextInRect(textRect);
}
}
@ -63,8 +72,7 @@ export class Label extends common.Label {
constructor(options?: definition.Options) {
super(options);
//this._ios = new UILabel();
this._ios = UILabelImpl.new().initWithOwner(this);
this._ios = UILabelImpl.initWithOwner(new WeakRef(this));
this._ios.userInteractionEnabled = true;
}

View File

@ -13,13 +13,8 @@ export class ListPicker extends common.ListPicker {
super();
this._ios = new UIPickerView();
var dataSource = ListPickerDataSource.new().initWithOwner(this);
this._dataSource = dataSource;
this._ios.dataSource = this._dataSource;
this._delegate = ListPickerDelegateImpl.new().initWithOwner(this);
this._ios.dataSource = this._dataSource = ListPickerDataSource.initWithOwner(new WeakRef(this));
this._delegate = ListPickerDelegateImpl.initWithOwner(new WeakRef(this));
}
public onLoaded() {
@ -55,16 +50,13 @@ export class ListPicker extends common.ListPicker {
class ListPickerDataSource extends NSObject implements UIPickerViewDataSource {
public static ObjCProtocols = [UIPickerViewDataSource];
private _owner: WeakRef<ListPicker>;
static new(): ListPickerDataSource {
return <ListPickerDataSource>super.new();
}
private _owner: ListPicker;
public initWithOwner(owner: ListPicker): ListPickerDataSource {
this._owner = owner;
return this;
public static initWithOwner(owner: WeakRef<ListPicker>): ListPickerDataSource {
let dataSource = <ListPickerDataSource>ListPickerDataSource.new();
dataSource._owner = owner;
return dataSource;
}
public numberOfComponentsInPickerView(pickerView: UIPickerView) {
@ -72,35 +64,35 @@ class ListPickerDataSource extends NSObject implements UIPickerViewDataSource {
}
public pickerViewNumberOfRowsInComponent(pickerView: UIPickerView, component: number) {
return this._owner.items ? this._owner.items.length : 0;
let owner = this._owner.get();
return (owner && owner.items) ? owner.items.length : 0;
}
}
class ListPickerDelegateImpl extends NSObject implements UIPickerViewDelegate {
public static ObjCProtocols = [UIPickerViewDelegate];
static new(): ListPickerDelegateImpl {
return <ListPickerDelegateImpl>super.new();
}
private _owner: WeakRef<ListPicker>;
private _owner: ListPicker;
public initWithOwner(owner: ListPicker): ListPickerDelegateImpl {
this._owner = owner;
return this;
public static initWithOwner(owner: WeakRef<ListPicker>): ListPickerDelegateImpl {
let delegate = <ListPickerDelegateImpl>ListPickerDelegateImpl.new();
delegate._owner = owner;
return delegate;
}
public pickerViewTitleForRowForComponent(pickerView: UIPickerView, row: number, component: number): string {
if (this._owner) {
return this._owner._getItemAsString(row);
let owner = this._owner.get();
if (owner) {
return owner._getItemAsString(row);
}
return row.toString();
}
public pickerViewDidSelectRowInComponent(pickerView: UIPickerView, row: number, component: number): void {
if (this._owner) {
this._owner._onPropertyChangedFromNative(common.ListPicker.selectedIndexProperty, row);
let owner = this._owner.get();
if (owner) {
owner._onPropertyChangedFromNative(common.ListPicker.selectedIndexProperty, row);
}
}
}

View File

@ -18,13 +18,25 @@ global.moduleMerge(common, exports);
var infinity = utils.layout.makeMeasureSpec(0, utils.layout.UNSPECIFIED);
class ListViewCell extends UITableViewCell {
static new(): ListViewCell {
return <ListViewCell>super.new();
public willMoveToSuperview(newSuperview: UIView): void {
let parent: ListView = <ListView>(this.view ? this.view.parent : null);
// When inside ListView and there is no newSuperview this cell is
// removed from native visual tree so we remove it from our tree too.
if (parent && !newSuperview) {
parent._removeContainer(this);
}
}
public get view(): view.View {
return this.owner ? this.owner.get() : null
}
public owner: WeakRef<view.View>;
}
function notifyForItemAtIndex(listView: definition.ListView, cell: any, eventName: string, indexPath: NSIndexPath) {
var args = <definition.ItemEventData>{ eventName: eventName, object: listView, index: indexPath.row, view: cell.view, ios: cell, android: undefined };
function notifyForItemAtIndex(listView: definition.ListView, cell: any, view: view.View, eventName: string, indexPath: NSIndexPath) {
let args = <definition.ItemEventData>{ eventName: eventName, object: listView, index: indexPath.row, view: view, ios: cell, android: undefined };
listView.notify(args);
return args;
}
@ -32,35 +44,35 @@ function notifyForItemAtIndex(listView: definition.ListView, cell: any, eventNam
class DataSource extends NSObject implements UITableViewDataSource {
public static ObjCProtocols = [UITableViewDataSource];
static new(): DataSource {
return <DataSource>super.new();
}
private _owner: WeakRef<ListView>;
private _owner: ListView;
public initWithOwner(owner: ListView): DataSource {
this._owner = owner;
return this;
public static initWithOwner(owner: WeakRef<ListView>): DataSource {
let dataSource = <DataSource>DataSource.new();
dataSource._owner = owner;
return dataSource;
}
public tableViewNumberOfRowsInSection(tableView: UITableView, section: number) {
return this._owner.items ? this._owner.items.length : 0;
let owner = this._owner.get();
return (owner && owner.items) ? owner.items.length : 0;
}
public tableViewCellForRowAtIndexPath(tableView: UITableView, indexPath: NSIndexPath): UITableViewCell {
// We call this method because ...ForIndexPath calls tableViewHeightForRowAtIndexPath immediately (before we can prepare and measure it).
var cell = tableView.dequeueReusableCellWithIdentifier(CELLIDENTIFIER) || ListViewCell.new();
this._owner._prepareCell(cell, indexPath);
let cell = <ListViewCell>(tableView.dequeueReusableCellWithIdentifier(CELLIDENTIFIER) || ListViewCell.new());
let owner = this._owner.get();
if (owner) {
owner._prepareCell(cell, indexPath);
var cellView: view.View = cell.view;
if (cellView) {
// Arrange cell views. We do it here instead of _layoutCell because _layoutCell is called
// from 'tableViewHeightForRowAtIndexPath' method too (in iOS 7.1) and we don't want to arrange the fake cell.
var width = utils.layout.getMeasureSpecSize(this._owner.widthMeasureSpec);
var cellHeight = this._owner.getHeight(indexPath.row);
view.View.layoutChild(this._owner, cellView, 0, 0, width, cellHeight);
let cellView: view.View = cell.view;
if (cellView) {
// Arrange cell views. We do it here instead of _layoutCell because _layoutCell is called
// from 'tableViewHeightForRowAtIndexPath' method too (in iOS 7.1) and we don't want to arrange the fake cell.
let width = utils.layout.getMeasureSpecSize(owner.widthMeasureSpec);
let cellHeight = owner.getHeight(indexPath.row);
view.View.layoutChild(owner, cellView, 0, 0, width, cellHeight);
}
}
return cell;
}
}
@ -68,45 +80,52 @@ class DataSource extends NSObject implements UITableViewDataSource {
class UITableViewDelegateImpl extends NSObject implements UITableViewDelegate {
public static ObjCProtocols = [UITableViewDelegate];
static new(): UITableViewDelegateImpl {
return <UITableViewDelegateImpl>super.new();
}
private _owner: WeakRef<ListView>;
private _measureCell: ListViewCell;
private _owner: ListView;
private _measureCell: UITableViewCell;
public initWithOwner(owner: ListView): UITableViewDelegateImpl {
this._owner = owner;
return this;
public static initWithOwner(owner: WeakRef<ListView>): UITableViewDelegateImpl {
let delegate = <UITableViewDelegateImpl>UITableViewDelegateImpl.new();
delegate._owner = owner;
return delegate;
}
public tableViewWillDisplayCellForRowAtIndexPath(tableView: UITableView, cell: UITableViewCell, indexPath: NSIndexPath) {
if (indexPath.row === this._owner.items.length - 1) {
this._owner.notify(<observable.EventData>{ eventName: LOADMOREITEMS, object: this._owner });
let owner = this._owner.get();
if (owner && (indexPath.row === owner.items.length - 1)) {
owner.notify(<observable.EventData>{ eventName: LOADMOREITEMS, object: owner });
}
}
public tableViewWillSelectRowAtIndexPath(tableView: UITableView, indexPath: NSIndexPath): NSIndexPath {
var cell = tableView.cellForRowAtIndexPath(indexPath);
notifyForItemAtIndex(this._owner, cell, ITEMTAP, indexPath);
let cell = <ListViewCell>tableView.cellForRowAtIndexPath(indexPath);
let owner = this._owner.get();
if (owner) {
notifyForItemAtIndex(owner, cell, cell.view, ITEMTAP, indexPath);
}
cell.highlighted = false;
return indexPath;
}
public tableViewHeightForRowAtIndexPath(tableView: UITableView, indexPath: NSIndexPath): number {
var height = undefined;
if (utils.ios.MajorVersion >= 8) {
height = this._owner.getHeight(indexPath.row);
let owner = this._owner.get();
if (!owner) {
return 44;
}
let height = undefined;
if (utils.ios.MajorVersion >= 8) {
height = owner.getHeight(indexPath.row);
}
if (utils.ios.MajorVersion < 8 || height === undefined) {
// in iOS 7.1 (or iOS8+ after call to scrollToRowAtIndexPath:atScrollPosition:animated:) this method is called before tableViewCellForRowAtIndexPath so we need fake cell to measure its content.
var cell = this._measureCell;
let cell = this._measureCell;
if (!cell) {
this._measureCell = tableView.dequeueReusableCellWithIdentifier(CELLIDENTIFIER) || ListViewCell.new();
cell = this._measureCell;
}
height = this._owner._prepareCell(cell, indexPath);
height = owner._prepareCell(cell, indexPath);
}
return height;
@ -134,7 +153,7 @@ export class ListView extends common.ListView {
private _heights: Array<number>;
private _preparingCell: boolean = false;
private _isDataDirty: boolean = false;
private _map: Map<ListViewCell, view.View>;
widthMeasureSpec: number = 0;
constructor() {
@ -145,14 +164,10 @@ export class ListView extends common.ListView {
this._ios.autoresizingMask = UIViewAutoresizing.UIViewAutoresizingNone;
this._ios.estimatedRowHeight = DEFAULT_HEIGHT;
var dataSource = DataSource.new().initWithOwner(this);
this._dataSource = dataSource;
this._ios.dataSource = this._dataSource;
this._delegate = UITableViewDelegateImpl.new().initWithOwner(this);
this._ios.dataSource = this._dataSource = DataSource.initWithOwner(new WeakRef(this));
this._delegate = UITableViewDelegateImpl.initWithOwner(new WeakRef(this));
this._heights = new Array<number>();
this._map = new Map<ListViewCell, view.View>();
}
public onLoaded() {
@ -225,22 +240,34 @@ export class ListView extends common.ListView {
return 0;
}
public _prepareCell(tableCell: UITableViewCell, indexPath: NSIndexPath): number {
var cell: any = tableCell;
var cellHeight: number;
public _prepareCell(cell: ListViewCell, indexPath: NSIndexPath): number {
let cellHeight: number;
try {
this._preparingCell = true;
if (!cell.view) {
cell.view = this._getItemTemplateContent(indexPath.row);
let view = cell.view;
if (!view) {
view = this._getItemTemplateContent(indexPath.row);
}
var args = notifyForItemAtIndex(this, cell, ITEMLOADING, indexPath);
var view = cell.view = args.view || this._getDefaultItemContent(indexPath.row);
let args = notifyForItemAtIndex(this, cell, view, ITEMLOADING, indexPath);
view = args.view || this._getDefaultItemContent(indexPath.row);
// If cell is reused be have old content - remove it first.
if (!cell.view) {
cell.owner = new WeakRef(view);
}
else if (cell.view !== view) {
this._removeContainer(cell);
(<UIView>cell.view._nativeView).removeFromSuperview();
cell.owner = new WeakRef(view);
}
this._prepareItem(view, indexPath.row);
if (view && !view.parent && view.ios) {
cell.contentView.addSubview(view.ios);
this._map.set(cell, view);
// We expect that views returned from itemLoading are new (e.g. not reused).
if (view && !view.parent && view._nativeView) {
cell.contentView.addSubview(view._nativeView);
this._addView(view);
}
@ -251,4 +278,9 @@ export class ListView extends common.ListView {
}
return cellHeight;
}
public _removeContainer(cell: ListViewCell): void {
this._removeView(cell.view)
this._map.delete(cell);
}
}

View File

@ -11,35 +11,30 @@ import observable = require("data/observable");
global.moduleMerge(pageCommon, exports);
class UIViewControllerImpl extends UIViewController {
static new(): UIViewControllerImpl {
return <UIViewControllerImpl>super.new();
}
private _owner: Page;
private _owner: WeakRef<Page>;
public initWithOwner(owner: Page): UIViewControllerImpl {
this._owner = owner;
this.automaticallyAdjustsScrollViewInsets = false;
return this;
}
public didRotateFromInterfaceOrientation(fromInterfaceOrientation: number) {
trace.write(this._owner + " didRotateFromInterfaceOrientation(" + fromInterfaceOrientation + ")", trace.categories.ViewHierarchy);
}
public viewDidLoad() {
trace.write(this._owner + " viewDidLoad", trace.categories.ViewHierarchy);
public static initWithOwner(owner: WeakRef<Page>): UIViewControllerImpl {
let controller = <UIViewControllerImpl>UIViewControllerImpl.new();
controller._owner = owner;
controller.automaticallyAdjustsScrollViewInsets = false;
return controller;
}
public viewDidLayoutSubviews() {
trace.write(this._owner + " viewDidLayoutSubviews, isLoaded = " + this._owner.isLoaded, trace.categories.ViewHierarchy);
if (!this._owner.isLoaded) {
let owner = this._owner.get();
if (!owner) {
return;
}
trace.write(owner + " viewDidLayoutSubviews, isLoaded = " + owner.isLoaded, trace.categories.ViewHierarchy);
if (!owner.isLoaded) {
return;
}
if (this._owner._isModal) {
if (owner._isModal) {
let isTablet = device.deviceType === DeviceType.Tablet;
let isFullScreen = !this._owner._UIModalPresentationFormSheet || !isTablet;
let isFullScreen = !owner._UIModalPresentationFormSheet || !isTablet;
let frame = isFullScreen ? UIScreen.mainScreen().bounds : this.view.frame;
let origin = frame.origin;
let size = frame.size;
@ -62,7 +57,7 @@ class UIViewControllerImpl extends UIViewController {
let bottom = height;
let statusBarHeight = uiUtils.ios.getStatusBarHeight();
let statusBarVisible = !UIApplication.sharedApplication().statusBarHidden;
let backgroundSpanUnderStatusBar = this._owner.backgroundSpanUnderStatusBar;
let backgroundSpanUnderStatusBar = owner.backgroundSpanUnderStatusBar;
if (statusBarVisible && !backgroundSpanUnderStatusBar) {
height -= statusBarHeight;
}
@ -70,9 +65,9 @@ class UIViewControllerImpl extends UIViewController {
let widthSpec = utils.layout.makeMeasureSpec(width, mode);
let heightSpec = utils.layout.makeMeasureSpec(height, mode);
View.measureChild(null, this._owner, widthSpec, heightSpec);
View.measureChild(null, owner, widthSpec, heightSpec);
let top = ((backgroundSpanUnderStatusBar && isFullScreen) || utils.ios.MajorVersion < 8 || !isFullScreen) ? 0 : statusBarHeight;
View.layoutChild(null, this._owner, 0, top, width, bottom);
View.layoutChild(null, owner, 0, top, width, bottom);
if (utils.ios.MajorVersion < 8) {
if (!backgroundSpanUnderStatusBar && (!isTablet || isFullScreen)) {
@ -88,31 +83,41 @@ class UIViewControllerImpl extends UIViewController {
trace.write(this._owner + ", native frame = " + NSStringFromCGRect(this.view.frame), trace.categories.Layout);
}
else {
this._owner._updateLayout();
owner._updateLayout();
}
}
public viewWillAppear() {
trace.write(this._owner + " viewWillAppear", trace.categories.Navigation);
this._owner._enableLoadedEvents = true;
let owner = this._owner.get();
if (!owner) {
return;
}
trace.write(owner + " viewWillAppear", trace.categories.Navigation);
owner._enableLoadedEvents = true;
// In iOS we intentionally delay the raising of the 'loaded' event so both platforms behave identically.
// The loaded event must be raised AFTER the page is part of the windows hierarchy and
// frame.topmost().currentPage is set to the page instance.
// https://github.com/NativeScript/NativeScript/issues/779
if (!this._owner._isModal) {
this._owner._delayLoadedEvent = true;
if (!owner._isModal) {
owner._delayLoadedEvent = true;
}
this._owner.onLoaded();
this._owner._enableLoadedEvents = false;
owner.onLoaded();
owner._enableLoadedEvents = false;
}
public viewDidDisappear() {
trace.write(this._owner + " viewDidDisappear", trace.categories.Navigation);
this._owner._enableLoadedEvents = true;
this._owner.onUnloaded();
this._owner._enableLoadedEvents = false;
let owner = this._owner.get();
if (!owner) {
return;
}
trace.write(owner + " viewDidDisappear", trace.categories.Navigation);
owner._enableLoadedEvents = true;
owner.onUnloaded();
owner._enableLoadedEvents = false;
}
}
@ -125,7 +130,7 @@ export class Page extends pageCommon.Page {
constructor(options?: definition.Options) {
super(options);
this._ios = UIViewControllerImpl.new().initWithOwner(this);
this._ios = UIViewControllerImpl.initWithOwner(new WeakRef(this));
}
public requestLayout(): void {

View File

@ -57,24 +57,26 @@ global.moduleMerge(common, exports);
class UISearchBarDelegateImpl extends NSObject implements UISearchBarDelegate {
public static ObjCProtocols = [UISearchBarDelegate];
static new(): UISearchBarDelegateImpl {
return <UISearchBarDelegateImpl>super.new();
}
private _owner: SearchBar;
private _owner: WeakRef<SearchBar>;
private _searchText: string;
public initWithOwner(owner: SearchBar): UISearchBarDelegateImpl {
this._owner = owner;
return this;
public static initWithOwner(owner: WeakRef<SearchBar>): UISearchBarDelegateImpl {
let delegate = <UISearchBarDelegateImpl>UISearchBarDelegateImpl.new();
delegate._owner = owner;
return delegate;
}
public searchBarTextDidChange(searchBar: UISearchBar, searchText: string) {
this._owner._onPropertyChangedFromNative(common.SearchBar.textProperty, searchText);
let owner = this._owner.get();
if (!owner) {
return;
}
owner._onPropertyChangedFromNative(common.SearchBar.textProperty, searchText);
// This code is needed since sometimes searchBarCancelButtonClicked is not called!
if (searchText === "" && this._searchText !== searchText) {
this._owner._emit(common.SearchBar.clearEvent);
owner._emit(common.SearchBar.clearEvent);
}
this._searchText = searchText;
@ -82,12 +84,22 @@ class UISearchBarDelegateImpl extends NSObject implements UISearchBarDelegate {
public searchBarCancelButtonClicked(searchBar: UISearchBar) {
searchBar.resignFirstResponder();
this._owner._emit(common.SearchBar.clearEvent);
let owner = this._owner.get();
if (!owner) {
return;
}
owner._emit(common.SearchBar.clearEvent);
}
public searchBarSearchButtonClicked(searchBar: UISearchBar) {
searchBar.resignFirstResponder();
this._owner._emit(common.SearchBar.submitEvent);
let owner = this._owner.get();
if (!owner) {
return;
}
owner._emit(common.SearchBar.submitEvent);
}
}
@ -101,7 +113,7 @@ export class SearchBar extends common.SearchBar {
this._ios = new UISearchBar();
this._delegate = UISearchBarDelegateImpl.new().initWithOwner(this);
this._delegate = UISearchBarDelegateImpl.initWithOwner(new WeakRef(this));
}
public onLoaded() {
@ -121,7 +133,7 @@ export class SearchBar extends common.SearchBar {
get ios(): UISearchBar {
return this._ios;
}
}
private static findTextField(view: UIView) {
for (let i = 0, l = view.subviews.count; i < l; i++) {

View File

@ -86,7 +86,7 @@ export class SegmentedBar extends common.SegmentedBar {
super();
this._ios = UISegmentedControl.new();
this._selectionHandler = SelectionHandlerImpl.new().initWithOwner(this);
this._selectionHandler = SelectionHandlerImpl.initWithOwner(new WeakRef(this));
this._ios.addTargetActionForControlEvents(this._selectionHandler, "selected", UIControlEvents.UIControlEventValueChanged);
}
@ -96,22 +96,23 @@ export class SegmentedBar extends common.SegmentedBar {
}
class SelectionHandlerImpl extends NSObject {
static new(): SelectionHandlerImpl {
return <SelectionHandlerImpl>super.new();
}
private _owner: SegmentedBar;
private _owner: WeakRef<SegmentedBar>;
public initWithOwner(owner: SegmentedBar): SelectionHandlerImpl {
this._owner = owner;
return this;
public static initWithOwner(owner: WeakRef<SegmentedBar>): SelectionHandlerImpl {
let handler = <SelectionHandlerImpl>SelectionHandlerImpl.new();
handler._owner = owner;
return handler;
}
public selected(sender: UISegmentedControl) {
this._owner.selectedIndex = sender.selectedSegmentIndex;
let owner = this._owner.get();
if (owner) {
owner.selectedIndex = sender.selectedSegmentIndex;
}
}
public static ObjCExposedMethods = {
"selected": { returns: interop.types.void, params: [UISegmentedControl] }
};
}
}

View File

@ -25,19 +25,20 @@ function onMaxValuePropertyChanged(data: dependencyObservable.PropertyChangeData
global.moduleMerge(common, exports);
class SliderChangeHandlerImpl extends NSObject {
static new(): SliderChangeHandlerImpl {
return <SliderChangeHandlerImpl>super.new();
private _owner: WeakRef<Slider>;
public static initWithOwner(owner: WeakRef<Slider>): SliderChangeHandlerImpl {
let handler = <SliderChangeHandlerImpl>SliderChangeHandlerImpl.new();
handler._owner = owner;
return handler;
}
private _owner: Slider;
public initWithOwner(owner: Slider): SliderChangeHandlerImpl {
this._owner = owner;
return this;
}
public sliderValueChanged(sender: UISlider) {
this._owner._onPropertyChangedFromNative(common.Slider.valueProperty, sender.value);
let owner = this._owner.get();
if (owner) {
owner._onPropertyChangedFromNative(common.Slider.valueProperty, sender.value);
}
}
public static ObjCExposedMethods = {
@ -57,7 +58,7 @@ export class Slider extends common.Slider {
this._ios.minimumValue = 0;
this._ios.maximumValue = this.maxValue;
this._changeHandler = SliderChangeHandlerImpl.new().initWithOwner(this);
this._changeHandler = SliderChangeHandlerImpl.initWithOwner(new WeakRef(this));
this._ios.addTargetActionForControlEvents(this._changeHandler, "sliderValueChanged", UIControlEvents.UIControlEventValueChanged);
}

View File

@ -13,19 +13,20 @@ function onCheckedPropertyChanged(data: dependencyObservable.PropertyChangeData)
global.moduleMerge(common, exports);
class SwitchChangeHandlerImpl extends NSObject {
static new(): SwitchChangeHandlerImpl {
return <SwitchChangeHandlerImpl>super.new();
}
private _owner: Switch;
private _owner: WeakRef<Switch>;
public initWithOwner(owner: Switch): SwitchChangeHandlerImpl {
this._owner = owner;
return this;
public static initWithOwner(owner: WeakRef<Switch>): SwitchChangeHandlerImpl {
let handler = <SwitchChangeHandlerImpl>SwitchChangeHandlerImpl.new();
handler._owner = owner;
return handler;
}
public valueChanged(sender: UISwitch) {
this._owner._onPropertyChangedFromNative(common.Switch.checkedProperty, sender.on);
let owner = this._owner.get();
if (owner) {
owner._onPropertyChangedFromNative(common.Switch.checkedProperty, sender.on);
}
}
public static ObjCExposedMethods = {
@ -41,7 +42,7 @@ export class Switch extends common.Switch {
super();
this._ios = new UISwitch();
this._handler = SwitchChangeHandlerImpl.new().initWithOwner(this);
this._handler = SwitchChangeHandlerImpl.initWithOwner(new WeakRef(this));
this._ios.addTargetActionForControlEvents(this._handler, "valueChanged", UIControlEvents.UIControlEventValueChanged);
}

View File

@ -13,22 +13,21 @@ import color = require("color");
global.moduleMerge(common, exports);
class UITabBarControllerImpl extends UITabBarController {
static new(): UITabBarControllerImpl {
return <UITabBarControllerImpl>super.new();
}
private _owner: TabView;
private _owner: WeakRef<TabView>;
public initWithOwner(owner: TabView): UITabBarControllerImpl {
this._owner = owner;
return this;
public static initWithOwner(owner: WeakRef<TabView>): UITabBarControllerImpl {
let handler = <UITabBarControllerImpl>UITabBarControllerImpl.new();
handler._owner = owner;
return handler;
}
public viewDidLayoutSubviews(): void {
trace.write("TabView.UITabBarControllerClass.viewDidLayoutSubviews();", trace.categories.Debug);
super.viewDidLayoutSubviews();
if (this._owner.isLoaded) {
this._owner._updateLayout();
let owner = this._owner.get();
if (owner && owner.isLoaded) {
owner._updateLayout();
}
}
}
@ -36,42 +35,42 @@ class UITabBarControllerImpl extends UITabBarController {
class UITabBarControllerDelegateImpl extends NSObject implements UITabBarControllerDelegate {
public static ObjCProtocols = [UITabBarControllerDelegate];
static new(): UITabBarControllerDelegateImpl {
return <UITabBarControllerDelegateImpl>super.new();
}
private _owner: WeakRef<TabView>;
private _owner: TabView;
public initWithOwner(owner: TabView): UITabBarControllerDelegateImpl {
this._owner = owner;
return this;
public static initWithOwner(owner: WeakRef<TabView>): UITabBarControllerDelegateImpl {
let delegate = <UITabBarControllerDelegateImpl>UITabBarControllerDelegateImpl.new();
delegate._owner = owner;
return delegate;
}
public tabBarControllerDidSelectViewController(tabBarController: UITabBarController, viewController: UIViewController): void {
trace.write("TabView.UITabBarControllerDelegateClass.tabBarControllerDidSelectViewController(" + tabBarController + ", " + viewController + ");", trace.categories.Debug);
this._owner._onViewControllerShown(viewController);
let owner = this._owner.get();
if (owner) {
owner._onViewControllerShown(viewController);
}
}
}
class UINavigationControllerDelegateImpl extends NSObject implements UINavigationControllerDelegate {
public static ObjCProtocols = [UINavigationControllerDelegate];
static new(): UINavigationControllerDelegateImpl {
return <UINavigationControllerDelegateImpl>super.new();
}
private _owner: WeakRef<TabView>;
private _owner: TabView;
public initWithOwner(owner: TabView): UINavigationControllerDelegateImpl {
this._owner = owner;
return this;
public static initWithOwner(owner: WeakRef<TabView>): UINavigationControllerDelegateImpl {
let delegate = <UINavigationControllerDelegateImpl>UINavigationControllerDelegateImpl.new();
delegate._owner = owner;
return delegate;
}
navigationControllerDidShowViewControllerAnimated(navigationController: UINavigationController, viewController: UIViewController, animated: boolean): void {
trace.write("TabView.UINavigationControllerDelegateClass.navigationControllerDidShowViewControllerAnimated(" + navigationController + ", " + viewController + ", " + animated + ");", trace.categories.Debug);
// We don't need Edit button in More screen.
navigationController.navigationBar.topItem.rightBarButtonItem = null;
this._owner._onViewControllerShown(viewController);
let owner = this._owner.get();
if (owner) {
owner._onViewControllerShown(viewController);
}
}
}
@ -117,11 +116,9 @@ export class TabView extends common.TabView {
constructor() {
super();
this._ios = UITabBarControllerImpl.new().initWithOwner(this);
this._delegate = UITabBarControllerDelegateImpl.new().initWithOwner(this);
this._moreNavigationControllerDelegate = UINavigationControllerDelegateImpl.new().initWithOwner(this);
this._ios = UITabBarControllerImpl.initWithOwner(new WeakRef(this));
this._delegate = UITabBarControllerDelegateImpl.initWithOwner(new WeakRef(this));
this._moreNavigationControllerDelegate = UINavigationControllerDelegateImpl.initWithOwner(new WeakRef(this));
//This delegate is set on the last line of _addTabs method.
}

View File

@ -16,85 +16,99 @@ global.moduleMerge(common, exports);
class UITextFieldDelegateImpl extends NSObject implements UITextFieldDelegate {
public static ObjCProtocols = [UITextFieldDelegate];
static new(): UITextFieldDelegateImpl {
return <UITextFieldDelegateImpl>super.new();
private _owner: WeakRef<TextField>;
private firstEdit: boolean;
public static initWithOwner(owner: WeakRef<TextField>): UITextFieldDelegateImpl {
let delegate = <UITextFieldDelegateImpl>UITextFieldDelegateImpl.new();
delegate._owner = owner;
return delegate;
}
private _owner: TextField;
private firstEdit: boolean;
public initWithOwner(owner: TextField): UITextFieldDelegateImpl {
this._owner = owner;
return this;
}
public textFieldShouldBeginEditing(textField: UITextField): boolean {
this.firstEdit = true;
return this._owner.editable;
let owner = this._owner.get();
if (owner) {
return owner.editable;
}
return true;
}
public textFieldDidEndEditing(textField: UITextField) {
if (this._owner.updateTextTrigger === enums.UpdateTextTrigger.focusLost) {
this._owner._onPropertyChangedFromNative(textBase.TextBase.textProperty, textField.text);
}
let owner = this._owner.get();
if (owner) {
if (owner.updateTextTrigger === enums.UpdateTextTrigger.focusLost) {
owner._onPropertyChangedFromNative(textBase.TextBase.textProperty, textField.text);
}
this._owner.dismissSoftInput();
owner.dismissSoftInput();
}
}
public textFieldShouldClear(textField: UITextField) {
this.firstEdit = false;
this._owner._onPropertyChangedFromNative(textBase.TextBase.textProperty, "");
let owner = this._owner.get();
if (owner) {
owner._onPropertyChangedFromNative(textBase.TextBase.textProperty, "");
}
return true;
}
public textFieldShouldReturn(textField: UITextField): boolean {
// Called when the user presses the return button.
this._owner.dismissSoftInput();
this._owner.notify({ eventName: TextField.returnPressEvent, object: this._owner });
let owner = this._owner.get();
if (owner) {
owner.dismissSoftInput();
owner.notify({ eventName: TextField.returnPressEvent, object: owner });
}
return true;
}
public textFieldShouldChangeCharactersInRangeReplacementString(textField: UITextField, range: NSRange, replacementString: string): boolean {
if (this._owner.updateTextTrigger === enums.UpdateTextTrigger.textChanged) {
if (textField.secureTextEntry && this.firstEdit) {
this._owner._onPropertyChangedFromNative(textBase.TextBase.textProperty, replacementString);
}
else {
var newText = NSString.alloc().initWithString(textField.text).stringByReplacingCharactersInRangeWithString(range, replacementString);
this._owner._onPropertyChangedFromNative(textBase.TextBase.textProperty, newText);
let owner = this._owner.get();
if (owner) {
if (owner.updateTextTrigger === enums.UpdateTextTrigger.textChanged) {
if (textField.secureTextEntry && this.firstEdit) {
owner._onPropertyChangedFromNative(textBase.TextBase.textProperty, replacementString);
}
else {
let newText = NSString.alloc().initWithString(textField.text).stringByReplacingCharactersInRangeWithString(range, replacementString);
owner._onPropertyChangedFromNative(textBase.TextBase.textProperty, newText);
}
}
}
this.firstEdit = false;
this.firstEdit = false;
return true;
}
}
class UITextFieldImpl extends UITextField {
static new(): UITextFieldImpl {
return <UITextFieldImpl>super.new();
}
private _owner: TextField;
private _owner: WeakRef<TextField>;
public initWithOwner(owner: TextField): UITextFieldImpl {
this._owner = owner;
return this;
public static initWithOwner(owner: WeakRef<TextField>): UITextFieldImpl {
let handler = <UITextFieldImpl>UITextFieldImpl.new();
handler._owner = owner;
return handler;
}
private _getTextRectForBounds(bounds: CGRect): CGRect {
if (!this._owner) {
let owner = this._owner ? this._owner.get() : null;
if (!owner) {
return bounds;
}
return CGRectMake(
this._owner.borderWidth + this._owner.style.paddingLeft,
this._owner.borderWidth + this._owner.style.paddingTop,
bounds.size.width - (this._owner.borderWidth + this._owner.style.paddingLeft + this._owner.style.paddingRight + this._owner.borderWidth),
bounds.size.height - (this._owner.borderWidth + this._owner.style.paddingTop + this._owner.style.paddingBottom + this._owner.borderWidth)
);
let size = bounds.size;
return CGRectMake(owner.borderWidth + owner.style.paddingLeft, owner.borderWidth + owner.style.paddingTop,
size.width - (owner.borderWidth + owner.style.paddingLeft + owner.style.paddingRight + owner.borderWidth),
size.height - (owner.borderWidth + owner.style.paddingTop + owner.style.paddingBottom + owner.borderWidth)
);
}
public textRectForBounds(bounds: CGRect): CGRect {
return this._getTextRectForBounds(bounds);
}
@ -111,8 +125,8 @@ export class TextField extends common.TextField {
constructor() {
super();
this._ios = UITextFieldImpl.new().initWithOwner(this);
this._delegate = UITextFieldDelegateImpl.new().initWithOwner(this);
this._ios = UITextFieldImpl.initWithOwner(new WeakRef(this));
this._delegate = UITextFieldDelegateImpl.initWithOwner(new WeakRef(this));
}
public onLoaded() {

View File

@ -38,7 +38,7 @@ export class TimePicker extends common.TimePicker {
this._ios = new UIDatePicker();
this._ios.datePickerMode = UIDatePickerMode.UIDatePickerModeTime;
this._changeHandler = UITimePickerChangeHandlerImpl.new().initWithOwner(this);
this._changeHandler = UITimePickerChangeHandlerImpl.initWithOwner(new WeakRef(this));
this._ios.addTargetActionForControlEvents(this._changeHandler, "valueChanged", UIControlEvents.UIControlEventValueChanged);
}
@ -48,30 +48,33 @@ export class TimePicker extends common.TimePicker {
}
class UITimePickerChangeHandlerImpl extends NSObject {
static new(): UITimePickerChangeHandlerImpl {
return <UITimePickerChangeHandlerImpl>super.new();
}
private _owner: TimePicker;
private _owner: WeakRef<TimePicker>;
public initWithOwner(owner: TimePicker): UITimePickerChangeHandlerImpl {
this._owner = owner;
return this;
public static initWithOwner(owner: WeakRef<TimePicker>): UITimePickerChangeHandlerImpl {
let handler = <UITimePickerChangeHandlerImpl>UITimePickerChangeHandlerImpl.new();
handler._owner = owner;
return handler;
}
public valueChanged(sender: UIDatePicker) {
var comps = NSCalendar.currentCalendar().componentsFromDate(NSCalendarUnit.NSCalendarUnitHour | NSCalendarUnit.NSCalendarUnitMinute, sender.date);
if (comps.hour !== this._owner.hour) {
this._owner._onPropertyChangedFromNative(common.TimePicker.hourProperty, comps.hour);
let owner = this._owner.get();
if (!owner) {
return;
}
if (comps.minute !== this._owner.minute) {
this._owner._onPropertyChangedFromNative(common.TimePicker.minuteProperty, comps.minute);
var comps = NSCalendar.currentCalendar().componentsFromDate(NSCalendarUnit.NSCalendarUnitHour | NSCalendarUnit.NSCalendarUnitMinute, sender.date);
if (comps.hour !== owner.hour) {
owner._onPropertyChangedFromNative(common.TimePicker.hourProperty, comps.hour);
}
if (comps.minute !== owner.minute) {
owner._onPropertyChangedFromNative(common.TimePicker.minuteProperty, comps.minute);
}
}
public static ObjCExposedMethods = {
'valueChanged': { returns: interop.types.void, params: [UIDatePicker] }
};
}
}

View File

@ -6,21 +6,19 @@ global.moduleMerge(common, exports);
class UIWebViewDelegateImpl extends NSObject implements UIWebViewDelegate {
public static ObjCProtocols = [UIWebViewDelegate];
static new(): UIWebViewDelegateImpl {
return <UIWebViewDelegateImpl>super.new();
}
private _owner: WeakRef<WebView>;
private _owner: WebView;
public initWithOwner(owner: WebView): UIWebViewDelegateImpl {
this._owner = owner;
return this;
public static initWithOwner(owner: WeakRef<WebView>): UIWebViewDelegateImpl {
let delegate = <UIWebViewDelegateImpl>UIWebViewDelegateImpl.new();
delegate._owner = owner;
return delegate;
}
public webViewShouldStartLoadWithRequestNavigationType(webView: UIWebView, request: NSURLRequest, navigationType: number) {
if (request.URL) {
let owner = this._owner.get();
if (owner && request.URL) {
trace.write("UIWebViewDelegateClass.webViewShouldStartLoadWithRequestNavigationType(" + request.URL.absoluteString + ", " + navigationType + ")", trace.categories.Debug);
this._owner._onLoadStarted(request.URL.absoluteString);
owner._onLoadStarted(request.URL.absoluteString);
}
return true;
@ -32,17 +30,25 @@ class UIWebViewDelegateImpl extends NSObject implements UIWebViewDelegate {
public webViewDidFinishLoad(webView: UIWebView) {
trace.write("UIWebViewDelegateClass.webViewDidFinishLoad(" + webView.request.URL + ")", trace.categories.Debug);
this._owner._onLoadFinished(webView.request.URL.absoluteString);
let owner = this._owner.get();
if (owner) {
owner._onLoadFinished(webView.request.URL.absoluteString);
}
}
public webViewDidFailLoadWithError(webView: UIWebView, error: NSError) {
var url = this._owner.url;
if (webView.request && webView.request.URL) {
url = webView.request.URL.absoluteString;
}
let owner = this._owner.get();
if (owner) {
var url = owner.url;
if (webView.request && webView.request.URL) {
url = webView.request.URL.absoluteString;
}
trace.write("UIWebViewDelegateClass.webViewDidFailLoadWithError(" + error.localizedDescription + ")", trace.categories.Debug);
this._owner._onLoadFinished(url, error.localizedDescription);
trace.write("UIWebViewDelegateClass.webViewDidFailLoadWithError(" + error.localizedDescription + ")", trace.categories.Debug);
if (owner) {
owner._onLoadFinished(url, error.localizedDescription);
}
}
}
}
@ -54,7 +60,7 @@ export class WebView extends common.WebView {
super();
this._ios = new UIWebView();
this._delegate = UIWebViewDelegateImpl.new().initWithOwner(this);
this._delegate = UIWebViewDelegateImpl.initWithOwner(new WeakRef(this));
}
public onLoaded() {