import { EventData , Observable } from "data/observable" ; import { KeyedTemplate , Length , View } from "ui/core/view" ; import * as utils from "utils/utils" ; import { GridViewBase , itemTemplatesProperty , orientationProperty , paddingBottomProperty , paddingLeftProperty , paddingRightProperty , paddingTopProperty , } from "./grid-view-common" ; import { GridItemEventData , Orientation , ScrollEventData } from "." ; export * from "./grid-view-common" ; export class GridView extends GridViewBase { private _layout : UICollectionViewFlowLayout ; private _dataSource : GridViewDataSource ; private _delegate : UICollectionViewDelegateImpl ; private _preparingCell : boolean = false ; private _map : Map < GridViewCell , View > ; constructor ( ) { super ( ) ; this . _map = new Map < GridViewCell , View > ( ) ; } public createNativeView ( ) { this . _layout = UICollectionViewFlowLayout . alloc ( ) . init ( ) ; this . _layout . minimumLineSpacing = 0 ; this . _layout . minimumInteritemSpacing = 0 ; return UICollectionView . alloc ( ) . initWithFrameCollectionViewLayout ( CGRectMake ( 0 , 0 , 0 , 0 ) , this . _layout ) ; } public initNativeView ( ) { super . initNativeView ( ) ; const nativeView = this . nativeViewProtected ; nativeView . backgroundColor = utils . ios . getter ( UIColor , UIColor . clearColor ) ; nativeView . registerClassForCellWithReuseIdentifier ( GridViewCell . class ( ) , this . _defaultTemplate . key ) ; nativeView . autoresizesSubviews = false ; nativeView . autoresizingMask = UIViewAutoresizing . None ; this . _dataSource = GridViewDataSource . initWithOwner ( new WeakRef ( this ) ) ; nativeView . dataSource = this . _dataSource ; this . _delegate = UICollectionViewDelegateImpl . initWithOwner ( new WeakRef ( this ) ) ; this . _setNativeClipToBounds ( ) ; this . _updateColWidthProperty ( ) ; this . _updateRowHeightProperty ( ) ; } public disposeNativeView ( ) { this . _layout = null ; this . _delegate = null ; this . _dataSource = null ; super . disposeNativeView ( ) ; } public onLoaded ( ) { super . onLoaded ( ) ; this . ios . delegate = this . _delegate ; } public onUnloaded ( ) { this . ios . delegate = null ; super . onUnloaded ( ) ; } get ios ( ) : UICollectionView { return this . nativeViewProtected ; } get _childrenCount ( ) : number { return this . _map . size ; } get horizontalOffset ( ) : number { return this . nativeViewProtected . contentOffset . x ; } get verticalOffset ( ) : number { return this . nativeViewProtected . contentOffset . y ; } public [ paddingTopProperty . getDefault ] ( ) { return this . _layout . sectionInset . top ; } public [ paddingTopProperty . setNative ] ( value ) { this . _setPadding ( { top : utils . layout . toDeviceIndependentPixels ( this . effectivePaddingTop ) } ) ; } public [ paddingRightProperty . getDefault ] ( ) { return this . _layout . sectionInset . right ; } public [ paddingRightProperty . setNative ] ( value ) { this . _setPadding ( { right : utils . layout . toDeviceIndependentPixels ( this . effectivePaddingRight ) } ) ; } public [ paddingBottomProperty . getDefault ] ( ) { return this . _layout . sectionInset . bottom ; } public [ paddingBottomProperty . setNative ] ( value ) { this . _setPadding ( { bottom : utils . layout . toDeviceIndependentPixels ( this . effectivePaddingBottom ) } ) ; } public [ paddingLeftProperty . getDefault ] ( ) { return this . _layout . sectionInset . left ; } public [ paddingLeftProperty . setNative ] ( value ) { this . _setPadding ( { left : utils . layout . toDeviceIndependentPixels ( this . effectivePaddingLeft ) } ) ; } public [ orientationProperty . getDefault ] ( ) { if ( this . _layout . scrollDirection === UICollectionViewScrollDirection . Horizontal ) { return "horizontal" ; } return "vertical" ; } public [ orientationProperty . setNative ] ( value ) { if ( value === "horizontal" ) { this . _layout . scrollDirection = UICollectionViewScrollDirection . Horizontal ; } else { this . _layout . scrollDirection = UICollectionViewScrollDirection . Vertical ; } } public [ itemTemplatesProperty . getDefault ] ( ) { return null ; } public [ itemTemplatesProperty . setNative ] ( value ) { this . _itemTemplatesInternal = new Array < KeyedTemplate > ( this . _defaultTemplate ) ; if ( value ) { for ( const template of value ) { this . ios . registerClassForCellWithReuseIdentifier ( GridViewCell . class ( ) , template . key ) ; } this . _itemTemplatesInternal = this . _itemTemplatesInternal . concat ( value ) ; } this . refresh ( ) ; } public eachChildView ( callback ) { this . _map . forEach ( ( view , key ) => { callback ( view ) ; } ) ; } public onLayout ( left , top , right , bottom ) { super . onLayout ( left , top , right , bottom ) ; const layout = this . ios . collectionViewLayout as UICollectionViewFlowLayout ; layout . itemSize = CGSizeMake ( utils . layout . toDeviceIndependentPixels ( this . _effectiveColWidth ) , utils . layout . toDeviceIndependentPixels ( this . _effectiveRowHeight ) ) ; this . _map . forEach ( ( childView , listViewCell ) => { childView . iosOverflowSafeAreaEnabled = false ; View . layoutChild ( this , childView , 0 , 0 , this . _effectiveColWidth , this . _effectiveRowHeight ) ; } ) ; } public refresh ( ) { this . eachChildView ( ( view ) => { if ( ! ( view . bindingContext instanceof Observable ) ) { view . bindingContext = null ; } return true ; } ) ; if ( this . isLoaded ) { this . ios . reloadData ( ) ; this . requestLayout ( ) ; } } public scrollToIndex ( index , animated = true ) { this . ios . scrollToItemAtIndexPathAtScrollPositionAnimated ( NSIndexPath . indexPathForItemInSection ( index , 0 ) , this . orientation === "vertical" ? UICollectionViewScrollPosition . Top : UICollectionViewScrollPosition . Left , animated ) ; } public requestLayout ( ) { if ( ! this . _preparingCell ) { super . requestLayout ( ) ; } } public measure ( widthMeasureSpec , heightMeasureSpec ) { const changed = ( this as any ) . _setCurrentMeasureSpecs ( widthMeasureSpec , heightMeasureSpec ) ; super . measure ( widthMeasureSpec , heightMeasureSpec ) ; if ( changed ) { this . ios . reloadData ( ) ; } } public onMeasure ( widthMeasureSpec , heightMeasureSpec ) { super . onMeasure ( widthMeasureSpec , heightMeasureSpec ) ; this . _map . forEach ( ( childView , gridViewCell ) => { View . measureChild ( this , childView , childView . _currentWidthMeasureSpec , childView . _currentHeightMeasureSpec ) ; } ) ; } public _setNativeClipToBounds ( ) { this . ios . clipsToBounds = true ; } public _removeContainer ( cell ) { const view = cell . view ; view . parent . _removeView ( view ) ; this . _map . delete ( cell ) ; } public _prepareCell ( cell , indexPath ) { try { this . _preparingCell = true ; let view = cell . view ; if ( ! view ) { view = this . _getItemTemplate ( indexPath . row ) . createView ( ) ; } const args = { eventName : GridViewBase . itemLoadingEvent , object : this , index : indexPath . row , view , ios : cell , android : undefined , } ; this . notify ( args ) ; view = args . view ; if ( ! cell . view ) { cell . owner = new WeakRef ( view ) ; } else if ( cell . view !== view ) { this . _removeContainer ( cell ) ; ( cell . view . nativeView as UIView ) . removeFromSuperview ( ) ; cell . owner = new WeakRef ( view ) ; } this . _prepareItem ( view , indexPath . row ) ; this . _map . set ( cell , view ) ; if ( view && ! view . parent ) { this . _addView ( view ) ; cell . contentView . addSubview ( view . ios ) ; } this . _layoutCell ( view , indexPath ) ; } finally { this . _preparingCell = false ; } } private _layoutCell ( cellView , index ) { if ( cellView ) { const widthMeasureSpec = utils . layout . makeMeasureSpec ( this . _effectiveColWidth , utils . layout . EXACTLY ) ; const heightMeasureSpec = utils . layout . makeMeasureSpec ( this . _effectiveRowHeight , utils . layout . EXACTLY ) ; View . measureChild ( this , cellView , widthMeasureSpec , heightMeasureSpec ) ; } } private _setPadding ( newPadding ) { const padding = { top : this . _layout . sectionInset . top , right : this . _layout . sectionInset . right , bottom : this . _layout . sectionInset . bottom , left : this . _layout . sectionInset . left } ; const newValue = Object . assign ( padding , newPadding ) ; this . _layout . sectionInset = UIEdgeInsetsFromString ( `{${ newValue . top },${ newValue . left },${ newValue . bottom },${ newValue . right }}` ) ; } } class GridViewCell extends UICollectionViewCell { public static new ( ) { return super . new ( ) as GridViewCell ; } public static class ( ) { return GridViewCell ; } public owner : WeakRef < View > ; get view ( ) : View { return this . owner ? this . owner . get ( ) : null ; } public willMoveToSuperview ( newSuperview ) { const parent = ( this . view ? this . view . parent : null ) as GridView ; if ( parent && ! newSuperview ) { parent . _removeContainer ( this ) ; } } } @ ObjCClass ( UICollectionViewDataSource ) class GridViewDataSource extends NSObject implements UICollectionViewDataSource { public static initWithOwner ( owner ) { const dataSource = GridViewDataSource . new ( ) as GridViewDataSource ; dataSource . _owner = owner ; return dataSource ; } private _owner : WeakRef < GridView > ; public numberOfSectionsInCollectionView ( collectionView ) { return 1 ; } public collectionViewNumberOfItemsInSection ( collectionView , section ) { const owner = this . _owner . get ( ) ; return owner . items ? owner . items . length : 0 ; } public collectionViewCellForItemAtIndexPath ( collectionView , indexPath ) { const owner = this . _owner . get ( ) ; const template = owner . _getItemTemplate ( indexPath . row ) ; const cell = collectionView . dequeueReusableCellWithReuseIdentifierForIndexPath ( template . key , indexPath ) || GridViewCell . new ( ) ; owner . _prepareCell ( cell , indexPath ) ; const cellView = cell . view ; if ( cellView && ( cellView as any ) . isLayoutRequired ) { cellView . iosOverflowSafeAreaEnabled = false ; View . layoutChild ( owner , cellView , 0 , 0 , owner . _effectiveColWidth , owner . _effectiveRowHeight ) ; } return cell ; } } @ ObjCClass ( UICollectionViewDelegate , UICollectionViewDelegateFlowLayout ) class UICollectionViewDelegateImpl extends NSObject implements UICollectionViewDelegate , UICollectionViewDelegateFlowLayout { public static initWithOwner ( owner ) { const delegate = UICollectionViewDelegateImpl . new ( ) as UICollectionViewDelegateImpl ; delegate . _owner = owner ; return delegate ; } private _owner : WeakRef < GridView > ; public collectionViewWillDisplayCellForItemAtIndexPath ( collectionView , cell , indexPath ) { const owner = this . _owner . get ( ) ; if ( indexPath . row === owner . items . length - 1 ) { owner . notify < EventData > ( { eventName : GridViewBase . loadMoreItemsEvent , object : owner } ) ; } if ( cell . preservesSuperviewLayoutMargins ) { cell . preservesSuperviewLayoutMargins = false ; } if ( cell . layoutMargins ) { cell . layoutMargins = UIEdgeInsetsZero ; } } public collectionViewDidSelectItemAtIndexPath ( collectionView , indexPath ) { const cell = collectionView . cellForItemAtIndexPath ( indexPath ) ; const owner = this . _owner . get ( ) ; owner . notify < GridItemEventData > ( { eventName : GridViewBase . itemTapEvent , object : owner , index : indexPath . row , view : ( cell as GridViewCell ) . view , ios : cell , android : undefined , } ) ; cell . highlighted = false ; return indexPath ; } public scrollViewDidScroll ( collectionView ) { const owner = this . _owner . get ( ) ; owner . notify < ScrollEventData > ( { object : owner , eventName : GridViewBase . scrollEvent , scrollX : owner . horizontalOffset , scrollY : owner . verticalOffset } ) ; } }