/* * 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. */ // // Created by pengfei.zhou on 2019/11/15. // #import #import "DoricListNode.h" #import "DoricExtensions.h" #import "DoricListItemNode.h" #import "DoricLayouts.h" @interface DoricTableViewCell : UITableViewCell @property(nonatomic, strong) DoricListItemNode *doricListItemNode; @end @implementation DoricTableViewCell @end @interface DoricTableView : UITableView @end @implementation DoricTableView - (CGSize)sizeThatFits:(CGSize)size { if (self.subviews.count > 0) { CGFloat width = size.width; CGFloat height = 0; for (UIView *child in self.subviews) { CGSize childSize = [child measureSize:size]; width = MAX(childSize.width, width); height += childSize.height; } return CGSizeMake(width, MAX(height, size.height)); } return size; } - (void)layoutSelf:(CGSize)targetSize { [super layoutSelf:targetSize]; [self reloadData]; } @end @interface DoricListNode () @property(nonatomic, strong) NSMutableDictionary *itemViewIds; @property(nonatomic, strong) NSMutableDictionary *itemHeights; @property(nonatomic, assign) NSUInteger itemCount; @property(nonatomic, assign) NSUInteger batchCount; @end @implementation DoricListNode - (instancetype)initWithContext:(DoricContext *)doricContext { if (self = [super initWithContext:doricContext]) { _itemViewIds = [NSMutableDictionary new]; _itemHeights = [NSMutableDictionary new]; _batchCount = 15; } return self; } - (UITableView *)build { return [[DoricTableView new] also:^(UITableView *it) { it.dataSource = self; it.delegate = self; it.separatorStyle = UITableViewCellSeparatorStyleNone; }]; } - (void)blendView:(UITableView *)view forPropName:(NSString *)name propValue:(id)prop { if ([@"itemCount" isEqualToString:name]) { self.itemCount = [prop unsignedIntegerValue]; [self.view reloadData]; } else if ([@"renderItem" isEqualToString:name]) { [self.itemViewIds removeAllObjects]; [self clearSubModel]; [self.view reloadData]; } else if ([@"batchCount" isEqualToString:name]) { self.batchCount = [prop unsignedIntegerValue]; } else { [super blendView:view forPropName:name propValue:prop]; } } - (void)blend:(NSDictionary *)props { [super blend:props]; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.itemCount; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { NSUInteger position = (NSUInteger) indexPath.row; NSDictionary *model = [self itemModelAt:position]; NSDictionary *props = model[@"props"]; NSString *reuseId = props[@"identifier"]; DoricTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseId ?: @"doriccell"]; if (!cell) { cell = [[DoricTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseId ?: @"doriccell"]; DoricListItemNode *listItemNode = [[DoricListItemNode alloc] initWithContext:self.doricContext]; [listItemNode initWithSuperNode:self]; cell.doricListItemNode = listItemNode; [cell.contentView addSubview:listItemNode.view]; } DoricListItemNode *node = cell.doricListItemNode; node.viewId = model[@"id"]; [node blend:props]; [node.view setNeedsLayout]; CGSize size = [node.view sizeThatFits:CGSizeMake(cell.width, cell.height)]; [self callItem:position height:size.height]; return cell; } - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { NSUInteger position = (NSUInteger) indexPath.row; NSNumber *heightNumber = self.itemHeights[@(position)]; if (heightNumber) { return [heightNumber floatValue]; } else { return 44.f; } } - (NSDictionary *)itemModelAt:(NSUInteger)position { NSString *viewId = self.itemViewIds[@(position)]; if (viewId && viewId.length > 0) { return [self subModelOf:viewId]; } else { DoricAsyncResult *result = [self callJSResponse:@"renderBunchedItems", @(position), @(self.batchCount), nil]; JSValue *models = [result waitUntilResult]; NSArray *array = [models toArray]; [array enumerateObjectsUsingBlock:^(NSDictionary *obj, NSUInteger idx, BOOL *stop) { NSString *thisViewId = obj[@"id"]; [self setSubModel:obj in:thisViewId]; NSUInteger pos = position + idx; self.itemViewIds[@(pos)] = thisViewId; }]; return array[0]; } } - (void)blendSubNode:(NSDictionary *)subModel { NSString *viewId = subModel[@"id"]; DoricViewNode *viewNode = [self subNodeWithViewId:viewId]; if (viewNode) { [viewNode blend:subModel[@"props"]]; } else { NSMutableDictionary *model = [[self subModelOf:viewId] mutableCopy]; [self recursiveMixin:subModel to:model]; [self setSubModel:model in:viewId]; } [self.itemViewIds enumerateKeysAndObjectsUsingBlock:^(NSNumber *_Nonnull key, NSString *_Nonnull obj, BOOL *_Nonnull stop) { if ([viewId isEqualToString:obj]) { *stop = YES; NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[key integerValue] inSection:0]; [UIView performWithoutAnimation:^{ [self.view reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone]; }]; } }]; } - (void)callItem:(NSUInteger)position height:(CGFloat)height { NSNumber *old = self.itemHeights[@(position)]; if (old && old.floatValue == height) { return; } self.itemHeights[@(position)] = @(height); NSIndexPath *indexPath = [NSIndexPath indexPathForRow:position inSection:0]; [UIView performWithoutAnimation:^{ [self.view reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone]; }]; } - (DoricViewNode *)subNodeWithViewId:(NSString *)viewId { __block DoricViewNode *ret = nil; [self.doricContext.driver ensureSyncInMainQueue:^{ for (UITableViewCell *tableViewCell in self.view.visibleCells) { if ([tableViewCell isKindOfClass:[DoricTableViewCell class]]) { DoricListItemNode *node = ((DoricTableViewCell *) tableViewCell).doricListItemNode; if ([viewId isEqualToString:node.viewId]) { ret = node; break; } } } }]; return ret; } @end