diff --git a/iOS/Pod/Classes/DoricRegistry.m b/iOS/Pod/Classes/DoricRegistry.m index 0d7f9ea6..adae7587 100644 --- a/iOS/Pod/Classes/DoricRegistry.m +++ b/iOS/Pod/Classes/DoricRegistry.m @@ -38,6 +38,8 @@ #import "DoricNavigatorPlugin.h" #import "DoricNavBarPlugin.h" #import "DoricRefreshableNode.h" +#import "DoricCollectionItemNode.h" +#import "DoricCollectionNode.h" @interface DoricRegistry () @@ -78,6 +80,8 @@ - (void)innerRegister { [self registerViewNode:DoricSliderNode.class withName:@"Slider"]; [self registerViewNode:DoricSlideItemNode.class withName:@"SlideItem"]; [self registerViewNode:DoricRefreshableNode.class withName:@"Refreshable"]; + [self registerViewNode:DoricCollectionItemNode.class withName:@"CollectionItem"]; + [self registerViewNode:DoricCollectionNode.class withName:@"Collection"]; } - (void)registerJSBundle:(NSString *)bundle withName:(NSString *)name { diff --git a/iOS/Pod/Classes/Shader/DoricCollectionItemNode.h b/iOS/Pod/Classes/Shader/DoricCollectionItemNode.h new file mode 100644 index 00000000..74965d32 --- /dev/null +++ b/iOS/Pod/Classes/Shader/DoricCollectionItemNode.h @@ -0,0 +1,10 @@ +// +// Created by pengfei.zhou on 2019/11/28. +// + +#import +#import "DoricStackNode.h" + + +@interface DoricCollectionItemNode : DoricStackNode +@end \ No newline at end of file diff --git a/iOS/Pod/Classes/Shader/DoricCollectionItemNode.m b/iOS/Pod/Classes/Shader/DoricCollectionItemNode.m new file mode 100644 index 00000000..da58fc8a --- /dev/null +++ b/iOS/Pod/Classes/Shader/DoricCollectionItemNode.m @@ -0,0 +1,33 @@ +// +// Created by pengfei.zhou on 2019/11/28. +// + +#import "DoricCollectionItemNode.h" + +@interface DoricCollectionItemView : DoricStackView +@end + +@implementation DoricCollectionItemView +@end + +@interface DoricCollectionItemNode () +@end + + +@implementation DoricCollectionItemNode +- (instancetype)initWithContext:(DoricContext *)doricContext { + if (self = [super initWithContext:doricContext]) { + self.reusable = YES; + } + return self; +} + +- (void)initWithSuperNode:(DoricSuperNode *)superNode { + [super initWithSuperNode:superNode]; + self.reusable = YES; +} + +- (DoricStackView *)build { + return [DoricCollectionItemView new]; +} +@end diff --git a/iOS/Pod/Classes/Shader/DoricCollectionNode.h b/iOS/Pod/Classes/Shader/DoricCollectionNode.h new file mode 100644 index 00000000..90dbf200 --- /dev/null +++ b/iOS/Pod/Classes/Shader/DoricCollectionNode.h @@ -0,0 +1,9 @@ +// +// Created by pengfei.zhou on 2019/11/28. +// + +#import +#import "DoricSuperNode.h" + +@interface DoricCollectionNode : DoricSuperNode +@end \ No newline at end of file diff --git a/iOS/Pod/Classes/Shader/DoricCollectionNode.m b/iOS/Pod/Classes/Shader/DoricCollectionNode.m new file mode 100644 index 00000000..5678cee6 --- /dev/null +++ b/iOS/Pod/Classes/Shader/DoricCollectionNode.m @@ -0,0 +1,71 @@ +// +// Created by pengfei.zhou on 2019/11/28. +// + +#import "DoricCollectionNode.h" +#import "DoricCollectionItemNode.h" +#import "DoricExtensions.h" + +@interface DoricCollectionViewCell : UICollectionViewCell +@property(nonatomic, strong) DoricCollectionItemNode *viewNode; +@end + +@implementation DoricCollectionViewCell +@end + +@interface DoricCollectionView : UICollectionView +@end + +@implementation DoricCollectionView +- (CGSize)sizeThatFits:(CGSize)size { + if (self.subviews.count > 0) { + CGFloat width = size.width; + CGFloat height = size.height; + for (UIView *child in self.subviews) { + CGSize childSize = [child measureSize:size]; + width = MAX(childSize.width, width); + height = MAX(childSize.height, height); + } + return CGSizeMake(width, height); + } + return size; +} + +- (void)layoutSelf:(CGSize)targetSize { + [super layoutSelf:targetSize]; + [self reloadData]; +} +@end + +@interface DoricCollectionNode () +@property(nonatomic, strong) NSMutableDictionary *itemViewIds; +@property(nonatomic, assign) NSUInteger itemCount; +@property(nonatomic, assign) NSUInteger batchCount; +@end + +@implementation DoricCollectionNode +- (instancetype)initWithContext:(DoricContext *)doricContext { + if (self = [super initWithContext:doricContext]) { + _itemViewIds = [NSMutableDictionary new]; + _batchCount = 15; + } + return self; +} + +- (UICollectionView *)build { + UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init]; + [flowLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal]; + + return [[[DoricCollectionView alloc] initWithFrame:CGRectZero + collectionViewLayout:flowLayout] + also:^(UICollectionView *it) { + it.backgroundColor = [UIColor whiteColor]; + it.pagingEnabled = YES; + it.delegate = self; + it.dataSource = self; + [it registerClass:[DoricCollectionViewCell class] forCellWithReuseIdentifier:@"doricCell"]; + }]; +} + + +@end \ No newline at end of file diff --git a/iOS/Pod/Classes/Shader/DoricSliderNode.m b/iOS/Pod/Classes/Shader/DoricSliderNode.m index 9e495f8c..899b88f8 100644 --- a/iOS/Pod/Classes/Shader/DoricSliderNode.m +++ b/iOS/Pod/Classes/Shader/DoricSliderNode.m @@ -24,11 +24,11 @@ #import "Doric.h" #import "DoricSlideItemNode.h" -@interface DoricCollectionViewCell : UICollectionViewCell +@interface DoricSliderViewCell : UICollectionViewCell @property(nonatomic, strong) DoricSlideItemNode *doricSlideItemNode; @end -@implementation DoricCollectionViewCell +@implementation DoricSliderViewCell @end @interface DoricSliderNode () @@ -37,18 +37,20 @@ @interface DoricSliderNode () 0) { + CGFloat width = size.width; CGFloat height = size.height; for (UIView *child in self.subviews) { CGSize childSize = [child measureSize:size]; + width = MAX(childSize.width, width); height = MAX(childSize.height, height); } - return CGSizeMake(size.width, size.height); + return CGSizeMake(width, height); } return size; } @@ -72,14 +74,14 @@ - (UICollectionView *)build { UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init]; [flowLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal]; - return [[[DoricCollectionView alloc] initWithFrame:CGRectZero - collectionViewLayout:flowLayout] + return [[[DoricSliderView alloc] initWithFrame:CGRectZero + collectionViewLayout:flowLayout] also:^(UICollectionView *it) { it.backgroundColor = [UIColor whiteColor]; it.pagingEnabled = YES; it.delegate = self; it.dataSource = self; - [it registerClass:[DoricCollectionViewCell class] forCellWithReuseIdentifier:@"doricCell"]; + [it registerClass:[DoricSliderViewCell class] forCellWithReuseIdentifier:@"doricCell"]; }]; } @@ -118,7 +120,7 @@ - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collection NSUInteger position = (NSUInteger) indexPath.row; NSDictionary *model = [self itemModelAt:position]; NSDictionary *props = model[@"props"]; - DoricCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"doricCell" forIndexPath:indexPath]; + DoricSliderViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"doricCell" forIndexPath:indexPath]; if (!cell.doricSlideItemNode) { DoricSlideItemNode *slideItemNode = [[DoricSlideItemNode alloc] initWithContext:self.doricContext]; [slideItemNode initWithSuperNode:self]; @@ -159,8 +161,8 @@ - (DoricViewNode *)subNodeWithViewId:(NSString *)viewId { __block DoricViewNode *ret = nil; [self.doricContext.driver ensureSyncInMainQueue:^{ for (UICollectionViewCell *collectionViewCell in self.view.visibleCells) { - if ([collectionViewCell isKindOfClass:[DoricCollectionViewCell class]]) { - DoricSlideItemNode *node = ((DoricCollectionViewCell *) collectionViewCell).doricSlideItemNode; + if ([collectionViewCell isKindOfClass:[DoricSliderViewCell class]]) { + DoricSlideItemNode *node = ((DoricSliderViewCell *) collectionViewCell).doricSlideItemNode; if ([viewId isEqualToString:node.viewId]) { ret = node; break; diff --git a/js-framework/src/widget/collection.ts b/js-framework/src/widget/collection.ts new file mode 100644 index 00000000..280bf13f --- /dev/null +++ b/js-framework/src/widget/collection.ts @@ -0,0 +1,79 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Stack } from './layouts' +import { Property, IView, Superview } from '../ui/view' + +export class CollectionItem extends Stack { + /** + * Set to reuse native view + */ + @Property + identifier?: string +} +export interface ICollection extends IView { + renderItem: (index: number) => CollectionItem + itemCount: number + batchCount?: number +} + +export class Collection extends Superview implements ICollection { + private cachedViews: Map = new Map + private ignoreDirtyCallOnce = false + allSubviews() { + return this.cachedViews.values() + } + + @Property + itemCount = 0 + + @Property + renderItem!: (index: number) => CollectionItem + + @Property + batchCount = 15 + + reset() { + this.cachedViews.clear() + this.itemCount = 0 + } + private getItem(itemIdx: number) { + let view = this.cachedViews.get(`${itemIdx}`) + if (view === undefined) { + view = this.renderItem(itemIdx) + view.superview = this + this.cachedViews.set(`${itemIdx}`, view) + } + return view + } + + isDirty() { + if (this.ignoreDirtyCallOnce) { + this.ignoreDirtyCallOnce = false + //Ignore the dirty call once. + return false + } + return super.isDirty() + } + + private renderBunchedItems(start: number, length: number) { + this.ignoreDirtyCallOnce = true; + return new Array(Math.min(length, this.itemCount - start)).fill(0).map((_, idx) => { + const listItem = this.getItem(start + idx) + return listItem.toModel() + }) + } +} + diff --git a/js-framework/src/widget/index.widget.ts b/js-framework/src/widget/index.widget.ts index 12a1efc1..9e8dde39 100644 --- a/js-framework/src/widget/index.widget.ts +++ b/js-framework/src/widget/index.widget.ts @@ -19,4 +19,5 @@ export * from './image' export * from './list' export * from './slider' export * from './scroller' -export * from './refreshable' \ No newline at end of file +export * from './refreshable' +export * from './collection' \ No newline at end of file