@nativescript/core
Version:
A JavaScript library providing an easy to use api for interacting with iOS and Android platform APIs.
996 lines • 44 kB
JavaScript
import { ListViewBase, separatorColorProperty, itemTemplatesProperty, iosEstimatedRowHeightProperty, stickyHeaderProperty, stickyHeaderTemplateProperty, stickyHeaderHeightProperty, sectionedProperty, showSearchProperty, searchAutoHideProperty } from './list-view-common';
import { View } from '../core/view';
import { Length } from '../styling/length-shared';
import { Observable } from '../../data/observable';
import { Color } from '../../color';
import { layout } from '../../utils';
import { SDK_VERSION } from '../../utils/constants';
import { StackLayout } from '../layouts/stack-layout';
import { ProxyViewContainer } from '../proxy-view-container';
import { profile } from '../../profiling';
import { Trace } from '../../trace';
import { Builder } from '../builder';
import { Label } from '../label';
import { isFunction } from '../../utils/types';
export * from './list-view-common';
const ITEMLOADING = ListViewBase.itemLoadingEvent;
const LOADMOREITEMS = ListViewBase.loadMoreItemsEvent;
const ITEMTAP = ListViewBase.itemTapEvent;
const DEFAULT_HEIGHT = 44;
const infinity = layout.makeMeasureSpec(0, layout.UNSPECIFIED);
var ListViewCell = (function (_super) {
__extends(ListViewCell, _super);
function ListViewCell() {
return _super !== null && _super.apply(this, arguments) || this;
}
ListViewCell.initWithEmptyBackground = function () {
var cell = ListViewCell.new();
cell.backgroundColor = UIColor.clearColor;
return cell;
};
ListViewCell.prototype.initWithStyleReuseIdentifier = function (style, reuseIdentifier) {
var cell = _super.prototype.initWithStyleReuseIdentifier.call(this, style, reuseIdentifier);
cell.backgroundColor = UIColor.clearColor;
return cell;
};
ListViewCell.prototype.willMoveToSuperview = function (newSuperview) {
var parent = (this.view ? this.view.parent : null);
if (parent && !newSuperview) {
parent._removeContainer(this);
}
};
Object.defineProperty(ListViewCell.prototype, "view", {
get: function () {
return this.owner ? this.owner.deref() : null;
},
enumerable: true,
configurable: true
});
return ListViewCell;
}(UITableViewCell));
var ListViewHeaderCell = (function (_super) {
__extends(ListViewHeaderCell, _super);
function ListViewHeaderCell() {
return _super !== null && _super.apply(this, arguments) || this;
}
ListViewHeaderCell.initWithEmptyBackground = function () {
var cell = ListViewHeaderCell.new();
cell.backgroundColor = UIColor.clearColor;
return cell;
};
ListViewHeaderCell.prototype.initWithReuseIdentifier = function (reuseIdentifier) {
var cell = _super.prototype.initWithReuseIdentifier.call(this, reuseIdentifier);
cell.backgroundColor = UIColor.clearColor;
return cell;
};
ListViewHeaderCell.prototype.willMoveToSuperview = function (newSuperview) {
var parent = (this.view ? this.view.parent : null);
if (parent && !newSuperview) {
parent._removeHeaderContainer(this);
}
};
Object.defineProperty(ListViewHeaderCell.prototype, "view", {
get: function () {
return this.owner ? this.owner.deref() : null;
},
enumerable: true,
configurable: true
});
return ListViewHeaderCell;
}(UITableViewHeaderFooterView));
function notifyForItemAtIndex(listView, cell, view, eventName, indexPath) {
const args = {
eventName: eventName,
object: listView,
index: indexPath.row,
section: indexPath.section,
view: view,
ios: cell,
android: undefined,
};
listView.notify(args);
return args;
}
var DataSource = (function (_super) {
__extends(DataSource, _super);
function DataSource() {
return _super !== null && _super.apply(this, arguments) || this;
}
DataSource.initWithOwner = function (owner) {
var dataSource = DataSource.new();
dataSource._owner = owner;
return dataSource;
};
DataSource.prototype.numberOfSectionsInTableView = function (tableView) {
var _a;
var owner = (_a = this._owner) === null || _a === void 0 ? void 0 : _a.deref();
if (!owner) {
return 1;
}
var sections = owner._getSectionCount();
if (Trace.isEnabled()) {
Trace.write("ListView: numberOfSections = ".concat(sections, " (sectioned: ").concat(owner.sectioned, ")"), Trace.categories.Debug);
}
return sections;
};
DataSource.prototype.tableViewNumberOfRowsInSection = function (tableView, section) {
var _a;
var owner = (_a = this._owner) === null || _a === void 0 ? void 0 : _a.deref();
if (!owner) {
return 0;
}
var sectionItems = owner._getItemsInSection(section);
var rowCount = sectionItems ? sectionItems.length : 0;
if (Trace.isEnabled()) {
Trace.write("ListView: numberOfRows in section ".concat(section, " = ").concat(rowCount), Trace.categories.Debug);
}
return rowCount;
};
DataSource.prototype.tableViewCellForRowAtIndexPath = function (tableView, indexPath) {
var _a;
var owner = (_a = this._owner) === null || _a === void 0 ? void 0 : _a.deref();
var cell;
if (owner) {
var template = owner._getItemTemplate(indexPath.row);
cell = (tableView.dequeueReusableCellWithIdentifier(template.key) || ListViewCell.initWithEmptyBackground());
owner._prepareCell(cell, indexPath);
var cellView = cell.view;
if (cellView && cellView.isLayoutRequired) {
var width = layout.getMeasureSpecSize(owner.widthMeasureSpec);
var rowHeight = owner._effectiveRowHeight;
var cellHeight = rowHeight > 0 ? rowHeight : owner.getHeight(indexPath.row);
cellView.iosOverflowSafeAreaEnabled = false;
View.layoutChild(owner, cellView, 0, 0, width, cellHeight);
}
}
else {
cell = ListViewCell.initWithEmptyBackground();
}
return cell;
};
DataSource.ObjCProtocols = [UITableViewDataSource];
return DataSource;
}(NSObject));
var UITableViewDelegateImpl = (function (_super) {
__extends(UITableViewDelegateImpl, _super);
function UITableViewDelegateImpl() {
return _super !== null && _super.apply(this, arguments) || this;
}
UITableViewDelegateImpl.initWithOwner = function (owner) {
var delegate = UITableViewDelegateImpl.new();
delegate._owner = owner;
delegate._measureCellMap = new Map();
return delegate;
};
UITableViewDelegateImpl.prototype.tableViewWillDisplayCellForRowAtIndexPath = function (tableView, cell, indexPath) {
var _a;
var owner = (_a = this._owner) === null || _a === void 0 ? void 0 : _a.deref();
if (owner && indexPath.row === owner.items.length - 1) {
owner.notify({
eventName: LOADMOREITEMS,
object: owner,
});
}
};
UITableViewDelegateImpl.prototype.tableViewWillSelectRowAtIndexPath = function (tableView, indexPath) {
var _a;
var cell = tableView.cellForRowAtIndexPath(indexPath);
var owner = (_a = this._owner) === null || _a === void 0 ? void 0 : _a.deref();
if (owner) {
notifyForItemAtIndex(owner, cell, cell.view, ITEMTAP, indexPath);
}
return indexPath;
};
UITableViewDelegateImpl.prototype.tableViewDidSelectRowAtIndexPath = function (tableView, indexPath) {
tableView.deselectRowAtIndexPathAnimated(indexPath, true);
return indexPath;
};
UITableViewDelegateImpl.prototype.tableViewHeightForRowAtIndexPath = function (tableView, indexPath) {
var _a;
var owner = (_a = this._owner) === null || _a === void 0 ? void 0 : _a.deref();
if (!owner) {
return tableView.estimatedRowHeight;
}
var height = owner.getHeight(indexPath.row);
if (height === undefined) {
var template = owner._getItemTemplate(indexPath.row);
var cell = this._measureCellMap.get(template.key);
if (!cell) {
cell = tableView.dequeueReusableCellWithIdentifier(template.key) || ListViewCell.initWithEmptyBackground();
this._measureCellMap.set(template.key, cell);
}
height = owner._prepareCell(cell, indexPath);
}
return layout.toDeviceIndependentPixels(height);
};
UITableViewDelegateImpl.prototype.tableViewViewForHeaderInSection = function (tableView, section) {
var _a;
var owner = (_a = this._owner) === null || _a === void 0 ? void 0 : _a.deref();
if (!owner || !owner.stickyHeader || !owner.stickyHeaderTemplate) {
if (Trace.isEnabled()) {
Trace.write("ListView: No sticky header (stickyHeader: ".concat(owner === null || owner === void 0 ? void 0 : owner.stickyHeader, ", hasTemplate: ").concat(!!(owner === null || owner === void 0 ? void 0 : owner.stickyHeaderTemplate), ")"), Trace.categories.Debug);
}
return null;
}
if (Trace.isEnabled()) {
Trace.write("ListView: Creating sticky header", Trace.categories.Debug);
}
var headerReuseIdentifier = "stickyHeader";
var headerCell = tableView.dequeueReusableHeaderFooterViewWithIdentifier(headerReuseIdentifier);
if (!headerCell) {
headerCell = ListViewHeaderCell.alloc().initWithReuseIdentifier(headerReuseIdentifier);
headerCell.backgroundColor = UIColor.clearColor;
}
owner._prepareHeader(headerCell, section);
return headerCell;
};
UITableViewDelegateImpl.prototype.tableViewHeightForHeaderInSection = function (tableView, section) {
var _a;
var owner = (_a = this._owner) === null || _a === void 0 ? void 0 : _a.deref();
if (!owner || !owner.stickyHeader) {
return 0;
}
var height;
if (owner.stickyHeaderHeight === "auto") {
height = 44;
}
else {
height = layout.toDeviceIndependentPixels(Length.toDevicePixels(owner.stickyHeaderHeight, 44));
}
if (Trace.isEnabled()) {
Trace.write("ListView: Sticky header height: ".concat(height), Trace.categories.Debug);
}
return height;
};
UITableViewDelegateImpl.ObjCProtocols = [UITableViewDelegate];
return UITableViewDelegateImpl;
}(NSObject));
var UITableViewRowHeightDelegateImpl = (function (_super) {
__extends(UITableViewRowHeightDelegateImpl, _super);
function UITableViewRowHeightDelegateImpl() {
return _super !== null && _super.apply(this, arguments) || this;
}
UITableViewRowHeightDelegateImpl.initWithOwner = function (owner) {
var delegate = UITableViewRowHeightDelegateImpl.new();
delegate._owner = owner;
return delegate;
};
UITableViewRowHeightDelegateImpl.prototype.tableViewWillDisplayCellForRowAtIndexPath = function (tableView, cell, indexPath) {
var _a;
var owner = (_a = this._owner) === null || _a === void 0 ? void 0 : _a.deref();
if (owner && indexPath.row === owner.items.length - 1) {
owner.notify({
eventName: LOADMOREITEMS,
object: owner,
});
}
};
UITableViewRowHeightDelegateImpl.prototype.tableViewWillSelectRowAtIndexPath = function (tableView, indexPath) {
var _a;
var cell = tableView.cellForRowAtIndexPath(indexPath);
var owner = (_a = this._owner) === null || _a === void 0 ? void 0 : _a.deref();
if (owner) {
notifyForItemAtIndex(owner, cell, cell.view, ITEMTAP, indexPath);
}
return indexPath;
};
UITableViewRowHeightDelegateImpl.prototype.tableViewDidSelectRowAtIndexPath = function (tableView, indexPath) {
tableView.deselectRowAtIndexPathAnimated(indexPath, true);
return indexPath;
};
UITableViewRowHeightDelegateImpl.prototype.tableViewHeightForRowAtIndexPath = function (tableView, indexPath) {
var _a;
var owner = (_a = this._owner) === null || _a === void 0 ? void 0 : _a.deref();
if (!owner) {
return tableView.estimatedRowHeight;
}
return layout.toDeviceIndependentPixels(owner._effectiveRowHeight);
};
UITableViewRowHeightDelegateImpl.prototype.tableViewViewForHeaderInSection = function (tableView, section) {
var _a;
var owner = (_a = this._owner) === null || _a === void 0 ? void 0 : _a.deref();
if (!owner || !owner.stickyHeader || !owner.stickyHeaderTemplate) {
if (Trace.isEnabled()) {
Trace.write("ListView: No sticky header (stickyHeader: ".concat(owner === null || owner === void 0 ? void 0 : owner.stickyHeader, ", hasTemplate: ").concat(!!(owner === null || owner === void 0 ? void 0 : owner.stickyHeaderTemplate), ")"), Trace.categories.Debug);
}
return null;
}
if (Trace.isEnabled()) {
Trace.write("ListView: Creating sticky header", Trace.categories.Debug);
}
var headerReuseIdentifier = "stickyHeader";
var headerCell = tableView.dequeueReusableHeaderFooterViewWithIdentifier(headerReuseIdentifier);
if (!headerCell) {
headerCell = ListViewHeaderCell.alloc().initWithReuseIdentifier(headerReuseIdentifier);
headerCell.backgroundColor = UIColor.clearColor;
}
owner._prepareHeader(headerCell, section);
return headerCell;
};
UITableViewRowHeightDelegateImpl.prototype.tableViewHeightForHeaderInSection = function (tableView, section) {
var _a;
var owner = (_a = this._owner) === null || _a === void 0 ? void 0 : _a.deref();
if (!owner || !owner.stickyHeader) {
return 0;
}
var height;
if (owner.stickyHeaderHeight === "auto") {
height = 44;
}
else {
height = layout.toDeviceIndependentPixels(Length.toDevicePixels(owner.stickyHeaderHeight, 44));
}
if (Trace.isEnabled()) {
Trace.write("ListView: Sticky header height: ".concat(height), Trace.categories.Debug);
}
return height;
};
UITableViewRowHeightDelegateImpl.ObjCProtocols = [UITableViewDelegate];
return UITableViewRowHeightDelegateImpl;
}(NSObject));
var UISearchResultsUpdatingImpl = (function (_super) {
__extends(UISearchResultsUpdatingImpl, _super);
function UISearchResultsUpdatingImpl() {
return _super !== null && _super.apply(this, arguments) || this;
}
UISearchResultsUpdatingImpl.initWithOwner = function (owner) {
var handler = UISearchResultsUpdatingImpl.new();
handler._owner = owner;
return handler;
};
UISearchResultsUpdatingImpl.prototype.updateSearchResultsForSearchController = function (searchController) {
var owner = this._owner ? this._owner.get() : null;
if (!owner) {
return;
}
var searchText = searchController.searchBar.text || "";
owner._isSearchActive = searchController.active;
var eventData = {
eventName: ListViewBase.searchChangeEvent,
object: owner,
text: searchText,
ios: searchController,
};
owner.notify(eventData);
};
UISearchResultsUpdatingImpl.ObjCProtocols = [UISearchResultsUpdating];
return UISearchResultsUpdatingImpl;
}(NSObject));
export class ListView extends ListViewBase {
constructor() {
super();
this._isSearchActive = false;
this.widthMeasureSpec = 0;
this._map = new Map();
this._headerMap = new Map();
this._heights = new Array();
}
createNativeView() {
return UITableView.new();
}
initNativeView() {
super.initNativeView();
const nativeView = this.nativeViewProtected;
nativeView.registerClassForCellReuseIdentifier(ListViewCell.class(), this._defaultTemplate.key);
nativeView.registerClassForHeaderFooterViewReuseIdentifier(ListViewHeaderCell.class(), 'stickyHeader');
nativeView.estimatedRowHeight = DEFAULT_HEIGHT;
nativeView.rowHeight = UITableViewAutomaticDimension;
nativeView.dataSource = this._dataSource = DataSource.initWithOwner(new WeakRef(this));
this._delegate = UITableViewDelegateImpl.initWithOwner(new WeakRef(this));
// Control section header top padding (iOS 15+)
if (nativeView.respondsToSelector('setSectionHeaderTopPadding:')) {
if (!this.stickyHeaderTopPadding) {
nativeView.sectionHeaderTopPadding = 0;
}
// When stickyHeaderTopPadding is true, don't set the property to use iOS default
}
this._setNativeClipToBounds();
}
disposeNativeView() {
this._cleanupSearchController();
this._delegate = null;
this._dataSource = null;
super.disposeNativeView();
}
_setupSearchController() {
if (!this.showSearch || this._searchController) {
return; // Already setup or not needed
}
// 1. Create UISearchController with nil (show results in this table)
this._searchController = UISearchController.alloc().initWithSearchResultsController(null);
this._searchDelegate = UISearchResultsUpdatingImpl.initWithOwner(new WeakRef(this));
// 2. Tell it who will update results
this._searchController.searchResultsUpdater = this._searchDelegate;
// 3. Critical: Don't dim or obscure the table, and prevent extra content
this._searchController.obscuresBackgroundDuringPresentation = false;
this._searchController.dimsBackgroundDuringPresentation = false;
this._searchController.hidesNavigationBarDuringPresentation = false;
// 4. Placeholder text and styling
this._searchController.searchBar.placeholder = 'Search';
this._searchController.searchBar.searchBarStyle = 2 /* UISearchBarStyle.Minimal */;
// 5. CRITICAL: Proper presentation context setup
const viewController = this._getViewController();
if (viewController) {
viewController.definesPresentationContext = true;
viewController.providesPresentationContextTransitionStyle = true;
// 6a. If we're in a UINavigationController (iOS 11+)...
if (SDK_VERSION >= 11.0 && viewController.navigationItem) {
viewController.navigationItem.searchController = this._searchController;
// Set auto-hide behavior based on searchAutoHide property
viewController.navigationItem.hidesSearchBarWhenScrolling = this.searchAutoHide;
// Optional: Enable large titles for better auto-hide effect when searchAutoHide is true
// if (this.searchAutoHide && viewController.navigationController && viewController.navigationController.navigationBar) {
// // Only set large titles if not already configured
// if (!viewController.navigationController.navigationBar.prefersLargeTitles) {
// viewController.navigationController.navigationBar.prefersLargeTitles = true;
// }
// // Set large title display mode for this specific view controller
// if (viewController.navigationItem.largeTitleDisplayMode === UINavigationItemLargeTitleDisplayMode.Automatic) {
// viewController.navigationItem.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayMode.Always;
// }
// }
}
else {
// 6b. Fallback: put it at the top of our table
this.nativeViewProtected.tableHeaderView = this._searchController.searchBar;
}
}
else {
// Fallback: no view controller found, use table header
this.nativeViewProtected.tableHeaderView = this._searchController.searchBar;
}
// 7. Ensure search bar is properly sized and prevent content inset issues
this._searchController.searchBar.sizeToFit();
// 8. Disable automatic content inset adjustment that can cause spacing issues
if (this.nativeViewProtected.respondsToSelector('setContentInsetAdjustmentBehavior:')) {
// iOS 11+ - prevent automatic content inset adjustments
this.nativeViewProtected.contentInsetAdjustmentBehavior = 2 /* UIScrollViewContentInsetAdjustmentBehavior.Never */;
}
else {
// iOS 10 and below - disable automatic content inset
this.nativeViewProtected.automaticallyAdjustsScrollIndicatorInsets = false;
}
if (Trace.isEnabled()) {
Trace.write(`ListView: UISearchController setup complete with searchAutoHide: ${this.searchAutoHide}`, Trace.categories.Debug);
}
}
_cleanupSearchController() {
if (!this._searchController) {
return;
}
// Remove search controller from navigation item or table header
const viewController = this._getViewController();
if (viewController && viewController.navigationItem && viewController.navigationItem.searchController === this._searchController) {
viewController.navigationItem.searchController = null;
}
else if (this.nativeViewProtected.tableHeaderView === this._searchController.searchBar) {
this.nativeViewProtected.tableHeaderView = null;
}
// Reset content inset adjustment behavior
if (this.nativeViewProtected.respondsToSelector('setContentInsetAdjustmentBehavior:')) {
// iOS 11+ - restore automatic content inset adjustments
this.nativeViewProtected.contentInsetAdjustmentBehavior = 0 /* UIScrollViewContentInsetAdjustmentBehavior.Automatic */;
}
else {
// iOS 10 and below - restore automatic content inset
this.nativeViewProtected.automaticallyAdjustsScrollIndicatorInsets = true;
}
// Cleanup references
this._searchController.searchResultsUpdater = null;
this._searchController = null;
this._searchDelegate = null;
}
_getViewController() {
// Helper to get the current view controller
let parent = this.parent;
while (parent) {
if (parent.viewController) {
return parent.viewController;
}
parent = parent.parent;
}
return null;
}
_setNativeClipToBounds() {
// Always set clipsToBounds for list-view
const view = this.nativeViewProtected;
if (view) {
view.clipsToBounds = true;
}
}
onLoaded() {
super.onLoaded();
if (this._isDataDirty) {
this.refresh();
}
this.nativeViewProtected.delegate = this._delegate;
// Setup search controller if enabled
if (this.showSearch) {
this._setupSearchController();
}
}
// @ts-ignore
get ios() {
return this.nativeViewProtected;
}
get _childrenCount() {
return this._map.size + this._headerMap.size;
}
eachChildView(callback) {
this._map.forEach((view, key) => {
callback(view);
});
this._headerMap.forEach((view, key) => {
callback(view);
});
}
scrollToIndex(index) {
this._scrollToIndex(index, false);
}
scrollToIndexAnimated(index) {
this._scrollToIndex(index);
}
_scrollToIndex(index, animated = true) {
if (!this.nativeViewProtected) {
return;
}
const itemsLength = this.items ? this.items.length : 0;
// mimic Android behavior that silently coerces index values within [0, itemsLength - 1] range
if (itemsLength > 0) {
if (index < 0) {
index = 0;
}
else if (index >= itemsLength) {
index = itemsLength - 1;
}
this.nativeViewProtected.scrollToRowAtIndexPathAtScrollPositionAnimated(NSIndexPath.indexPathForItemInSection(index, 0), 1 /* UITableViewScrollPosition.Top */, animated);
}
else if (Trace.isEnabled()) {
Trace.write(`Cannot scroll listview to index ${index} when listview items not set`, Trace.categories.Binding);
}
}
refresh() {
// clear bindingContext when it is not observable because otherwise bindings to items won't reevaluate
this._map.forEach((view, nativeView, map) => {
if (!(view.bindingContext instanceof Observable)) {
view.bindingContext = null;
}
});
this._headerMap.forEach((view, nativeView, map) => {
if (!(view.bindingContext instanceof Observable)) {
view.bindingContext = null;
}
});
if (this.isLoaded) {
this.nativeViewProtected.reloadData();
this.requestLayout();
this._isDataDirty = false;
}
else {
this._isDataDirty = true;
}
}
isItemAtIndexVisible(itemIndex) {
const indexes = Array.from(this.nativeViewProtected.indexPathsForVisibleRows);
return indexes.some((visIndex) => visIndex.row === itemIndex);
}
getHeight(index) {
return this._heights[index];
}
setHeight(index, value) {
this._heights[index] = value;
}
_onRowHeightPropertyChanged(oldValue, newValue) {
const value = layout.toDeviceIndependentPixels(this._effectiveRowHeight);
const nativeView = this.nativeViewProtected;
if (value < 0) {
nativeView.rowHeight = UITableViewAutomaticDimension;
nativeView.estimatedRowHeight = DEFAULT_HEIGHT;
this._delegate = UITableViewDelegateImpl.initWithOwner(new WeakRef(this));
}
else {
nativeView.rowHeight = value;
nativeView.estimatedRowHeight = value;
this._delegate = UITableViewRowHeightDelegateImpl.initWithOwner(new WeakRef(this));
}
if (this.isLoaded) {
nativeView.delegate = this._delegate;
}
super._onRowHeightPropertyChanged(oldValue, newValue);
}
requestLayout() {
// When preparing cell or header don't call super - no need to invalidate our measure when cell/header desiredSize is changed.
if (!this._preparingCell && !this._preparingHeader) {
super.requestLayout();
}
}
measure(widthMeasureSpec, heightMeasureSpec) {
const changed = this._currentWidthMeasureSpec !== widthMeasureSpec || this._currentHeightMeasureSpec !== heightMeasureSpec;
this.widthMeasureSpec = widthMeasureSpec;
super.measure(widthMeasureSpec, heightMeasureSpec);
// Reload native view cells only in the case of size change
if (changed) {
this.nativeViewProtected.reloadData();
}
}
onMeasure(widthMeasureSpec, heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
this._map.forEach((childView, listViewCell) => {
View.measureChild(this, childView, childView._currentWidthMeasureSpec, childView._currentHeightMeasureSpec);
});
this._headerMap.forEach((childView, listViewHeaderCell) => {
View.measureChild(this, childView, childView._currentWidthMeasureSpec, childView._currentHeightMeasureSpec);
});
}
onLayout(left, top, right, bottom) {
super.onLayout(left, top, right, bottom);
this._map.forEach((childView, listViewCell) => {
const rowHeight = this._effectiveRowHeight;
const cellHeight = rowHeight > 0 ? rowHeight : this.getHeight(childView._listViewItemIndex);
if (cellHeight) {
const width = layout.getMeasureSpecSize(this.widthMeasureSpec);
childView.iosOverflowSafeAreaEnabled = false;
View.layoutChild(this, childView, 0, 0, width, cellHeight);
}
});
this._headerMap.forEach((childView, listViewHeaderCell) => {
const headerHeight = this.stickyHeaderHeight === 'auto' ? 44 : Length.toDevicePixels(this.stickyHeaderHeight, 44);
const width = layout.getMeasureSpecSize(this.widthMeasureSpec);
childView.iosOverflowSafeAreaEnabled = false;
View.layoutChild(this, childView, 0, 0, width, headerHeight);
});
}
_layoutCell(cellView, indexPath) {
if (cellView) {
const rowHeight = this._effectiveRowHeight;
const heightMeasureSpec = rowHeight >= 0 ? layout.makeMeasureSpec(rowHeight, layout.EXACTLY) : infinity;
const measuredSize = View.measureChild(this, cellView, this.widthMeasureSpec, heightMeasureSpec);
const height = measuredSize.measuredHeight;
this.setHeight(indexPath.row, height);
return height;
}
return this.nativeViewProtected.estimatedRowHeight;
}
_prepareCell(cell, indexPath) {
let cellHeight;
try {
this._preparingCell = true;
let view = cell.view;
if (!view) {
if (this.sectioned) {
// For sectioned data, we need to calculate the absolute index for template selection
let absoluteIndex = 0;
for (let i = 0; i < indexPath.section; i++) {
absoluteIndex += this._getItemsInSection(i).length;
}
absoluteIndex += indexPath.row;
view = this._getItemTemplate(absoluteIndex).createView();
}
else {
view = this._getItemTemplate(indexPath.row).createView();
}
}
const args = notifyForItemAtIndex(this, cell, view, ITEMLOADING, indexPath);
view = args.view || this._getDefaultItemContent(this.sectioned ? indexPath.row : indexPath.row);
// Proxy containers should not get treated as layouts.
// Wrap them in a real layout as well.
if (view instanceof ProxyViewContainer) {
const sp = new StackLayout();
sp.addChild(view);
view = sp;
}
// If cell is reused it have old content - remove it first.
if (!cell.view) {
cell.owner = new WeakRef(view);
}
else if (cell.view !== view) {
// Remove view from super view now as nativeViewProtected will be null afterwards
cell.view.nativeViewProtected?.removeFromSuperview();
this._removeContainer(cell);
cell.owner = new WeakRef(view);
}
if (this.sectioned) {
this._prepareItemInSection(view, indexPath.section, indexPath.row);
view._listViewItemIndex = indexPath.row; // Keep row index for compatibility
view._listViewSectionIndex = indexPath.section;
}
else {
this._prepareItem(view, indexPath.row);
view._listViewItemIndex = indexPath.row;
}
this._map.set(cell, view);
// We expect that views returned from itemLoading are new (e.g. not reused).
if (view && !view.parent) {
this._addView(view);
cell.contentView.addSubview(view.nativeViewProtected);
}
cellHeight = this._layoutCell(view, indexPath);
}
finally {
this._preparingCell = false;
}
return cellHeight;
}
_removeContainer(cell) {
const view = cell.view;
// This is to clear the StackLayout that is used to wrap ProxyViewContainer instances.
if (!(view.parent instanceof ListView)) {
this._removeView(view.parent);
}
// No need to request layout when we are removing cells.
const preparing = this._preparingCell;
this._preparingCell = true;
view.parent._removeView(view);
view._listViewItemIndex = undefined;
this._preparingCell = preparing;
this._map.delete(cell);
}
_prepareHeader(headerCell, section) {
let headerHeight;
try {
this._preparingHeader = true;
let view = headerCell.view;
if (!view) {
view = this._getHeaderTemplate();
if (!view) {
if (Trace.isEnabled()) {
Trace.write(`ListView: Failed to create header view for section ${section}`, Trace.categories.Debug);
}
// Create a fallback view
const lbl = new Label();
lbl.text = `Section ${section}`;
view = lbl;
}
}
// Handle header cell reuse
if (!headerCell.view) {
headerCell.owner = new WeakRef(view);
}
else if (headerCell.view !== view) {
// Remove old view and set new one
headerCell.view.nativeViewProtected?.removeFromSuperview();
this._removeHeaderContainer(headerCell);
headerCell.owner = new WeakRef(view);
}
// Clear existing binding context and set new one
if (view.bindingContext) {
view.bindingContext = null;
}
if (this.sectioned) {
const sectionData = this._getSectionData(section);
if (sectionData) {
view.bindingContext = sectionData;
}
else {
// Fallback if section data is missing
view.bindingContext = { title: `Section ${section}`, section: section };
}
}
else {
view.bindingContext = this.bindingContext;
}
// Force immediate binding context evaluation
if (view && typeof view._onBindingContextChanged === 'function') {
view._onBindingContextChanged(null, view.bindingContext);
// Also trigger for child views
// @ts-ignore
if (view._childrenCount) {
view.eachChildView((child) => {
if (typeof child._onBindingContextChanged === 'function') {
child._onBindingContextChanged(null, view.bindingContext);
}
return true;
});
}
}
this._headerMap.set(headerCell, view);
// Add new header view to the cell
if (view && !view.parent) {
this._addView(view);
headerCell.contentView.addSubview(view.nativeViewProtected);
}
// Request layout and measure/layout the header
if (view && view.bindingContext) {
view.requestLayout();
}
headerHeight = this._layoutHeader(view);
}
finally {
this._preparingHeader = false;
}
return headerHeight;
}
_layoutHeader(headerView) {
if (headerView) {
const headerHeight = this.stickyHeaderHeight === 'auto' ? 44 : Length.toDevicePixels(this.stickyHeaderHeight, 44);
const heightMeasureSpec = layout.makeMeasureSpec(headerHeight, layout.EXACTLY);
const measuredSize = View.measureChild(this, headerView, this.widthMeasureSpec, heightMeasureSpec);
// Layout the header with the measured size
View.layoutChild(this, headerView, 0, 0, measuredSize.measuredWidth, measuredSize.measuredHeight);
return measuredSize.measuredHeight;
}
return 44;
}
_getHeaderTemplate() {
if (this.stickyHeaderTemplate) {
if (__UI_USE_EXTERNAL_RENDERER__) {
if (isFunction(this.stickyHeaderTemplate)) {
return this.stickyHeaderTemplate();
}
}
else {
if (typeof this.stickyHeaderTemplate === 'string') {
try {
const parsed = Builder.parse(this.stickyHeaderTemplate, this);
if (!parsed) {
// Create a simple fallback
const fallbackLabel = new Label();
fallbackLabel.text = 'Parse Failed';
return fallbackLabel;
}
return parsed;
}
catch (error) {
if (Trace.isEnabled()) {
Trace.write(`ListView: Template parsing error: ${error}`, Trace.categories.Debug);
}
// Create a simple fallback
const errorLabel = new Label();
errorLabel.text = 'Template Error';
return errorLabel;
}
}
else {
const view = this.stickyHeaderTemplate();
if (Trace.isEnabled()) {
Trace.write(`ListView: Created header view from template function: ${!!view} (type: ${view?.constructor?.name})`, Trace.categories.Debug);
}
return view;
}
}
}
if (Trace.isEnabled()) {
Trace.write(`ListView: No sticky header template, creating default`, Trace.categories.Debug);
}
// Return a default header if no template is provided
const lbl = new Label();
lbl.text = 'Default Header';
return lbl;
}
_removeHeaderContainer(headerCell) {
const view = headerCell.view;
// This is to clear the StackLayout that is used to wrap ProxyViewContainer instances.
if (!(view.parent instanceof ListView)) {
this._removeView(view.parent);
}
// No need to request layout when we are removing headers.
const preparing = this._preparingHeader;
this._preparingHeader = true;
view.parent._removeView(view);
this._preparingHeader = preparing;
this._headerMap.delete(headerCell);
}
[separatorColorProperty.getDefault]() {
return this.nativeViewProtected.separatorColor;
}
[separatorColorProperty.setNative](value) {
this.nativeViewProtected.separatorColor = value instanceof Color ? value.ios : value;
}
[itemTemplatesProperty.getDefault]() {
return null;
}
[itemTemplatesProperty.setNative](value) {
this._itemTemplatesInternal = new Array(this._defaultTemplate);
if (value) {
for (let i = 0, length = value.length; i < length; i++) {
this.nativeViewProtected.registerClassForCellReuseIdentifier(ListViewCell.class(), value[i].key);
}
this._itemTemplatesInternal = this._itemTemplatesInternal.concat(value);
}
this.refresh();
}
[iosEstimatedRowHeightProperty.getDefault]() {
return DEFAULT_HEIGHT;
}
[iosEstimatedRowHeightProperty.setNative](value) {
const nativeView = this.nativeViewProtected;
const estimatedHeight = layout.toDeviceIndependentPixels(Length.toDevicePixels(value, 0));
nativeView.estimatedRowHeight = estimatedHeight < 0 ? DEFAULT_HEIGHT : estimatedHeight;
}
[stickyHeaderProperty.getDefault]() {
return false;
}
[stickyHeaderProperty.setNative](value) {
if (Trace.isEnabled()) {
Trace.write(`ListView: stickyHeader set to ${value}`, Trace.categories.Debug);
}
// Immediately refresh to apply changes
if (this.isLoaded) {
this.refresh();
}
}
[stickyHeaderTemplateProperty.getDefault]() {
return null;
}
[stickyHeaderTemplateProperty.setNative](value) {
if (Trace.isEnabled()) {
Trace.write(`ListView: stickyHeaderTemplate set: ${typeof value} ${value ? '(has value)' : '(null)'}`, Trace.categories.Debug);
}
// Clear any cached template
this._headerTemplateCache = null;
// Immediately refresh to apply changes
if (this.isLoaded) {
this.refresh();
}
}
[stickyHeaderHeightProperty.getDefault]() {
return 'auto';
}
[stickyHeaderHeightProperty.setNative](value) {
if (Trace.isEnabled()) {
Trace.write(`ListView: stickyHeaderHeight set to ${value}`, Trace.categories.Debug);
}
// Immediately refresh to apply changes
if (this.isLoaded) {
this.refresh();
}
}
[sectionedProperty.getDefault]() {
return false;
}
[sectionedProperty.setNative](value) {
if (Trace.isEnabled()) {
Trace.write(`ListView: sectioned set to ${value}`, Trace.categories.Debug);
}
// Immediately refresh to apply changes
if (this.isLoaded) {
this.refresh();
}
}
[showSearchProperty.getDefault]() {
return false;
}
[showSearchProperty.setNative](value) {
if (Trace.isEnabled()) {
Trace.write(`ListView: showSearch set to ${value}`, Trace.categories.Debug);
}
if (value) {
this._setupSearchController();
}
else {
this._cleanupSearchController();
}
}
[searchAutoHideProperty.getDefault]() {
return false;
}
[searchAutoHideProperty.setNative](value) {
if (Trace.isEnabled()) {
Trace.write(`ListView: searchAutoHide set to ${value}`, Trace.categories.Debug);
}
// If search is already enabled, update the existing search controller
if (this.showSearch && this._searchController) {
const viewController = this._getViewController();
if (viewController && viewController.navigationItem && SDK_VERSION >= 11.0) {
viewController.navigationItem.hidesSearchBarWhenScrolling = value;
// Enable large titles for better auto-hide effect when searchAutoHide is true
// if (value && viewController.navigationController && viewController.navigationController.navigationBar) {
// if (!viewController.navigationController.navigationBar.prefersLargeTitles) {
// viewController.navigationController.navigationBar.prefersLargeTitles = true;
// }
// if (viewController.navigationItem.largeTitleDisplayMode === UINavigationItemLargeTitleDisplayMode.Automatic) {
// viewController.navigationItem.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayMode.Always;
// }
// }
}
}
// If search is not enabled yet, the property will be used when _setupSearchController is called
}
}
__decorate([
profile,
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", void 0)
], ListView.prototype, "onLoaded", null);
//# sourceMappingURL=index.ios.js.map