diff --git a/renderer/native/ios/renderer/HippyComponent.h b/renderer/native/ios/renderer/HippyComponent.h index 671789f0b40..4ca9063dd46 100644 --- a/renderer/native/ios/renderer/HippyComponent.h +++ b/renderer/native/ios/renderer/HippyComponent.h @@ -30,9 +30,9 @@ typedef void (^HippyDirectEventBlock)(NSDictionary *body); /** - * Logical node in a tree of application components. Both `NativeRenderObject` and - * `UIView` conforms to this. Allows us to write utilities that reason about - * trees generally. + * Logical node in a tree of application components. + * Both `HippyShadowView` and `UIView` conforms to this. + * Allows us to write utilities that reason about trees generally. */ @protocol HippyComponent @@ -43,35 +43,37 @@ typedef void (^HippyDirectEventBlock)(NSDictionary *body); @property (nonatomic, copy) NSDictionary *props; @property (nonatomic, assign) CGRect frame; -@property(nonatomic, readwrite)__kindof id parentComponent; +/// The parent of current component +@property (nonatomic, weak) id parent; -- (NSArray<__kindof id> *)subcomponents; +/// Subviews of current component +- (NSArray> *)subcomponents; -/// <#Description#> +/// Inset /// - Parameters: -/// - subview: <#subview description#> -/// - atIndex: <#atIndex description#> +/// - subview: id +/// - atIndex: NSInteger - (void)insertHippySubview:(id)subview atIndex:(NSInteger)atIndex; -/// <#Description#> -/// - Parameter subview: <#subview description#> +/// Remove +/// - Parameter subview: id - (void)removeHippySubview:(id)subview; -/// <#Description#> +/// Move /// - Parameters: -/// - subview: <#subview description#> -/// - atIndex: <#atIndex description#> +/// - subview: id +/// - atIndex: NSInteger - (void)moveHippySubview:(id)subview toIndex:(NSInteger)atIndex; -/// <#Description#> +/// Remove from superview - (void)removeFromHippySuperview; -/// <#Description#> -/// - Parameter frame: <#frame description#> +/// Set Frame +/// - Parameter frame: CGRect - (void)hippySetFrame:(CGRect)frame; -/// <#Description#> -/// - Parameter point: <#point description#> +/// Get accurate tag in special cases such as subviews +/// - Parameter point: CGPoint - (NSNumber *)hippyTagAtPoint:(CGPoint)point; /// View/ShadowView is a root view diff --git a/renderer/native/ios/renderer/HippyComponentMap.h b/renderer/native/ios/renderer/HippyComponentMap.h index 9db9b21c5bd..2b8ac6224ee 100644 --- a/renderer/native/ios/renderer/HippyComponentMap.h +++ b/renderer/native/ios/renderer/HippyComponentMap.h @@ -34,10 +34,28 @@ class RootNode; NS_ASSUME_NONNULL_BEGIN +typedef NS_ENUM(NSUInteger, HippyComponentReferenceType) { + HippyComponentReferenceTypeStrong, + HippyComponentReferenceTypeWeak, +}; + @interface HippyComponentMap : NSObject +/// Whether all recorded elements are strongly referenced, +/// +/// Attention, Attention, Attention: +/// All UI views are weakly referenced! +/// All Shadowviews are strongly referenced! +@property (nonatomic, assign, readonly) BOOL isStrongHoldAllComponents; + +/// Whether access is required from the main thread @property(nonatomic, assign) BOOL requireInMainThread; +/// Init Method +- (instancetype)initWithComponentsReferencedType:(HippyComponentReferenceType)type; +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + - (void)addRootComponent:(id)component rootNode:(std::weak_ptr)rootNode forTag:(NSNumber *)tag; @@ -60,7 +78,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)removeComponentByComponentTag:(NSNumber *)componentTag onRootTag:(NSNumber *)rootTag; -- (NSMutableDictionary> *)componentsForRootTag:(NSNumber *)tag; +- (NSDictionary> *)componentsForRootTag:(NSNumber *)tag; - (__kindof id)componentForTag:(NSNumber *)componentTag onRootTag:(NSNumber *)tag; diff --git a/renderer/native/ios/renderer/HippyComponentMap.mm b/renderer/native/ios/renderer/HippyComponentMap.mm index c3b70e2c6d6..6ef58deb851 100644 --- a/renderer/native/ios/renderer/HippyComponentMap.mm +++ b/renderer/native/ios/renderer/HippyComponentMap.mm @@ -21,14 +21,14 @@ */ #import "HippyComponentMap.h" - +#import "HippyLog.h" #include "dom/root_node.h" using RootNode = hippy::RootNode; @interface HippyComponentMap () { NSMapTable> *_rootComponentsMap; - NSMutableDictionary> *> *_componentsMap; + NSMutableDictionary *_componentsMap; std::unordered_map> _rootNodesMap; } @@ -36,9 +36,10 @@ @interface HippyComponentMap () { @implementation HippyComponentMap -- (instancetype)init { +- (instancetype)initWithComponentsReferencedType:(HippyComponentReferenceType)type { self = [super init]; if (self) { + _isStrongHoldAllComponents = (HippyComponentReferenceTypeStrong == type); _rootComponentsMap = [NSMapTable strongToWeakObjectsMapTable]; _componentsMap = [NSMutableDictionary dictionary]; _rootNodesMap.reserve(8); @@ -56,7 +57,12 @@ - (void)addRootComponent:(id)component NSAssert(component && tag, @"component &&tag must not be null in method %@", NSStringFromSelector(_cmd)); NSAssert([self threadCheck], @"%@ method needs run in main thread", NSStringFromSelector(_cmd)); if (component && tag && ![_componentsMap objectForKey:tag]) { - NSMutableDictionary *dic = [NSMutableDictionary dictionary]; + id dic = nil; + if (_isStrongHoldAllComponents) { + dic = [NSMutableDictionary dictionary]; + } else { + dic = [NSMapTable strongToWeakObjectsMapTable]; + } [dic setObject:component forKey:tag]; [_componentsMap setObject:dic forKey:tag]; [_rootComponentsMap setObject:component forKey:tag]; @@ -122,23 +128,28 @@ - (void)removeComponentByComponentTag:(NSNumber *)componentTag onRootTag:(NSNumb } } -- (NSMutableDictionary> *)componentsForRootTag:(NSNumber *)tag { +- (NSDictionary> *)componentsForRootTag:(NSNumber *)tag { NSAssert(tag, @"tag must not be null in method %@", NSStringFromSelector(_cmd)); NSAssert([self threadCheck], @"%@ method needs run in main thread", NSStringFromSelector(_cmd)); if (tag) { id map = [_componentsMap objectForKey:tag]; - return map; + if (_isStrongHoldAllComponents) { + return map; + } else { + return ((NSMapTable *)map).dictionaryRepresentation; + } } return nil; } - (__kindof id)componentForTag:(NSNumber *)componentTag - onRootTag:(NSNumber *)tag { - NSAssert(componentTag && tag, @"componentTag && tag must not be null in method %@", NSStringFromSelector(_cmd)); + onRootTag:(NSNumber *)tag { NSAssert([self threadCheck], @"%@ method needs run in main thread", NSStringFromSelector(_cmd)); if (componentTag && tag) { id map = [_componentsMap objectForKey:tag]; return [map objectForKey:componentTag]; + } else { + HippyLogError(@"componentTag && tag must not be null"); } return nil; } diff --git a/renderer/native/ios/renderer/HippyUIManager.h b/renderer/native/ios/renderer/HippyUIManager.h index 00094a05887..c95ad3d214c 100644 --- a/renderer/native/ios/renderer/HippyUIManager.h +++ b/renderer/native/ios/renderer/HippyUIManager.h @@ -96,9 +96,6 @@ /// Get all rootView - (NSArray<__kindof UIView *> *)rootViews; -/// Purge view from superView -- (void)purgeViewsFromComponentTags:(NSArray *)componentTag onRootTag:(NSNumber *)rootTag; - /// Update view with props - (void)updateView:(NSNumber *)componentTag onRootTag:(NSNumber *)rootTag props:(NSDictionary *)pros; @@ -112,7 +109,7 @@ * @param renderObject HippyShadowView corresponding to UIView * @return view created by HippyShadowView */ -- (UIView *)createViewRecursivelyFromRenderObject:(HippyShadowView *)renderObject; +- (UIView *)createViewForShadowListItem:(HippyShadowView *)renderObject; /// Register extra components /// @param extraComponents extra components classes diff --git a/renderer/native/ios/renderer/HippyUIManager.mm b/renderer/native/ios/renderer/HippyUIManager.mm index f1792c3b8b5..1934390a705 100644 --- a/renderer/native/ios/renderer/HippyUIManager.mm +++ b/renderer/native/ios/renderer/HippyUIManager.mm @@ -45,6 +45,7 @@ #import "HippyBridgeModule.h" #import "HippyModulesSetup.h" #import "NativeRenderManager.h" +#import "HippyShadowListView.h" #include "dom/root_node.h" #include "objc/runtime.h" #include @@ -148,7 +149,7 @@ - (void)removeAllObjects { @end static void NativeRenderTraverseViewNodes(id view, void (^block)(id)) { - if (view.hippyTag) { + if (view.hippyTag != nil) { block(view); for (id subview in view.subcomponents) { NativeRenderTraverseViewNodes(subview, block); @@ -213,8 +214,8 @@ - (void)dealloc { } - (void)initContext { - _shadowViewRegistry = [[HippyComponentMap alloc] init]; - _viewRegistry = [[HippyComponentMap alloc] init]; + _shadowViewRegistry = [[HippyComponentMap alloc] initWithComponentsReferencedType:HippyComponentReferenceTypeStrong]; + _viewRegistry = [[HippyComponentMap alloc] initWithComponentsReferencedType:HippyComponentReferenceTypeWeak]; _viewRegistry.requireInMainThread = YES; _pendingUIBlocks = [NSMutableArray new]; _componentTransactionListeners = [NSHashTable weakObjectsHashTable]; @@ -262,20 +263,6 @@ - (void)domNodeForComponentTag:(int32_t)componentTag } } -- (HippyComponentMap *)renderObjectRegistry { - if (!_shadowViewRegistry) { - _shadowViewRegistry = [[HippyComponentMap alloc] init]; - } - return _shadowViewRegistry; - } - -- (HippyComponentMap *)viewRegistry { - if (!_viewRegistry) { - _viewRegistry = [[HippyComponentMap alloc] init]; - } - return _viewRegistry; -} - - (UIView *)viewForHippyTag:(NSNumber *)hippyTag onRootTag:(NSNumber *)rootTag { AssertMainQueue(); return [_viewRegistry componentForTag:hippyTag onRootTag:rootTag]; @@ -461,25 +448,15 @@ - (void)setFrame:(CGRect)frame forRootView:(UIView *)view { */ - (void)purgeChildren:(NSArray> *)children onRootTag:(NSNumber *)rootTag - fromRegistry:(NSMutableDictionary> *)registry { + fromRegistry:(HippyComponentMap *)registryMap { + NSDictionary *currentRegistry = [registryMap componentsForRootTag:rootTag]; for (id child in children) { - NativeRenderTraverseViewNodes(registry[child.hippyTag], ^(id subview) { + NativeRenderTraverseViewNodes(currentRegistry[child.hippyTag], ^(id subview) { NSAssert(![subview isHippyRootView], @"Root views should not be unregistered"); if ([subview respondsToSelector:@selector(invalidate)]) { [subview performSelector:@selector(invalidate)]; } - [registry removeObjectForKey:subview.hippyTag]; - }); - } -} - -- (void)purgeViewsFromComponentTags:(NSArray *)componentTags onRootTag:(NSNumber *)rootTag { - for (NSNumber *componentTag in componentTags) { - UIView *view = [self viewForHippyTag:componentTag onRootTag:rootTag]; - HippyComponentMap *componentMap = _viewRegistry; - NativeRenderTraverseViewNodes(view, ^(id subview) { - NSAssert(![subview isHippyRootView], @"Root views should not be unregistered"); - [componentMap removeComponent:subview forRootTag:rootTag]; + [registryMap removeComponent:subview forRootTag:rootTag]; }); } } @@ -490,61 +467,133 @@ - (void)removeChildren:(NSArray> *)children fromContainer:(id } } -- (UIView *)createViewFromRenderObject:(HippyShadowView *)renderObject { + +#pragma mark - View Releted + +- (UIView *)createViewFromShadowView:(HippyShadowView *)shadowView { AssertMainQueue(); - HippyAssert(renderObject.viewName, @"view name is needed for creating a view"); - HippyComponentData *componentData = [self componentDataForViewName:renderObject.viewName]; - UIView *view = [self createViewByComponentData:componentData - componentTag:renderObject.hippyTag - rootTag:renderObject.rootTag - properties:renderObject.props - viewName:renderObject.viewName]; - view.renderManager = [self renderManager]; - [view hippySetFrame:renderObject.frame]; - const std::vector &eventNames = [renderObject allEventNames]; - for (auto &event : eventNames) { - [self addEventNameInMainThread:event - forDomNodeId:[renderObject.hippyTag intValue] - onRootNode:renderObject.rootNode]; + HippyAssert(shadowView.viewName, @"view name is needed for creating a view"); + HippyComponentData *componentData = [self componentDataForViewName:shadowView.viewName]; + + // create view from componentData + UIView *view = nil; + { + NSNumber *hippyTag = shadowView.hippyTag; + NSNumber *rootTag = shadowView.rootTag; + NSString *viewName = shadowView.viewName; + NSDictionary *props = shadowView.props; + + // 1. first, check if already has one in view cache and whether it can be reused, otherwise create one. + view = [self viewForHippyTag:hippyTag onRootTag:rootTag]; + BOOL canBeRetrievedFromCache = YES; + if (view && [view respondsToSelector:@selector(canBeRetrievedFromViewCache)]) { + canBeRetrievedFromCache = [view canBeRetrievedFromViewCache]; + } + /** + * subviews & hippySubviews should be removed from the view which we get from cache(viewRegistry). + * otherwise hippySubviews will be inserted multiple times. + */ + if (view && canBeRetrievedFromCache) { + [view resetHippySubviews]; + } else { + view = [componentData createViewWithTag:hippyTag initProps:props]; + } + + if (view) { + // 2. then, set necessary properties for this view. + view.viewName = viewName; + view.rootTag = rootTag; + view.renderManager = [self renderManager]; + [componentData setProps:props forView:view]; // Must be done before bgColor to prevent wrong default + } } return view; } -- (UIView *)createViewRecursivelyFromRenderObject:(HippyShadowView *)renderObject { +- (UIView *)createViewForShadowListItem:(HippyShadowView *)renderObject { AssertMainQueue(); std::lock_guard lock([self renderQueueLock]); + // There was a timing problem here: + // If a batch of subviews of the cell has already been `created` before + // update to CreationTypeInstantly, then this batch of views will not be created + // until the next `cellForItemAtIndexPath` call. + // we currently resolve this issue by setting the CreationType synchronously. + // TODO: CreationType's further optimization is needed in the future + [renderObject synchronousRecusivelySetCreationTypeToInstant]; return [self createViewRecursiveFromRenderObjectWithNOLock:renderObject]; } -- (UIView *)createViewRecursiveFromRenderObjectWithNOLock:(HippyShadowView *)renderObject { - UIView *view = [self createViewFromRenderObject:renderObject]; - NSUInteger index = 0; - for (HippyShadowView *subRenderObject in renderObject.subcomponents) { - UIView *subview = [self createViewRecursiveFromRenderObjectWithNOLock:subRenderObject]; - [view insertHippySubview:subview atIndex:index]; - index++; - } - view.hippyShadowView = renderObject; - view.renderManager = [self renderManager]; - [view clearSortedSubviews]; - [view didUpdateHippySubviews]; - NSMutableSet *applierBlocks = [NSMutableSet setWithCapacity:256]; - [renderObject amendLayoutBeforeMount:applierBlocks]; - if (applierBlocks.count) { - NSDictionary *viewRegistry = - [_viewRegistry componentsForRootTag:renderObject.rootTag]; - for (NativeRenderApplierBlock block in applierBlocks) { - block(viewRegistry); +- (UIView *)createViewRecursiveFromRenderObjectWithNOLock:(HippyShadowView *)shadowView { + UIView *view = [self createViewFromShadowView:shadowView]; + if (view) { + // Special handling of lazy list, which is a cellView + // because lazy loading list needs to be re-layout + if ([shadowView isKindOfClass:HippyShadowListView.class]) { + auto domManager = _domManager.lock(); + if (domManager) { + __weak HippyUIManager *weakSelf = self; + NSNumber *rootTag = shadowView.rootTag; + std::function func = [weakSelf, rootTag](){ + __strong HippyUIManager *strongSelf = weakSelf; + if (strongSelf) { + [strongSelf setNeedsLayoutForRootNodeTag:rootTag]; + } + }; + domManager->PostTask(hippy::Scene({func})); + } + } + + // after creation, add view to _viewRegistry and _componentTransactionListeners. + if ([view respondsToSelector:@selector(hippyBridgeDidFinishTransaction)]) { + [self->_componentTransactionListeners addObject:view]; + } + [self.viewRegistry addComponent:view forRootTag:shadowView.rootTag]; + + // TODO: hippy3 events binding handling, performance needs to be improved here. + const std::vector &eventNames = [shadowView allEventNames]; + for (auto &event : eventNames) { + [self addEventNameInMainThread:event + forView:view + onRootNode:shadowView.rootNode]; + } + + NSUInteger index = 0; + for (HippyShadowView *subRenderObject in shadowView.subcomponents) { + UIView *subview = [self createViewRecursiveFromRenderObjectWithNOLock:subRenderObject]; + [view insertHippySubview:subview atIndex:index]; + index++; + } + + // set necessary properties and update frame + view.hippyShadowView = shadowView; + view.renderManager = [self renderManager]; + [view hippySetFrame:shadowView.frame]; + + [view clearSortedSubviews]; + [view didUpdateHippySubviews]; + + NSMutableSet *applierBlocks = [NSMutableSet set]; + [shadowView amendLayoutBeforeMount:applierBlocks]; + if (applierBlocks.count) { + for (NativeRenderApplierBlock block in applierBlocks) { + // Note: viewRegistry may be modified in the block, and it may be stored internally as NSMapTable + // so to ensure that it is up-to-date, it can only be retrieved each time. + NSDictionary *viewRegistry = [self.viewRegistry componentsForRootTag:shadowView.rootTag]; + block(viewRegistry, view); + } } } return view; } -- (NSDictionary *)createRenderObjectFromNode:(const std::shared_ptr &)domNode - onRootNode:(std::weak_ptr)rootNode { + +#pragma mark - + +- (HippyShadowView *)createRenderObjectFromNode:(const std::shared_ptr &)domNode + onRootNode:(std::weak_ptr)rootNode { auto strongRootNode = rootNode.lock(); if (!strongRootNode || !domNode) { - return @{}; + return nil; } int32_t root_id = strongRootNode->GetId(); NSNumber *rootTag = @(root_id); @@ -570,44 +619,8 @@ - (NSDictionary *)createRenderObjectFromNode:(const std::shared_ptrGetLayoutResult(); renderObject.frame = CGRectMakeFromLayoutResult(domNode->GetLayoutResult()); [componentData setProps:props forShadowView:renderObject]; - [_shadowViewRegistry addComponent:renderObject forRootTag:rootTag]; } - return props; -} - -- (UIView *)createViewByComponentData:(HippyComponentData *)componentData - componentTag:(NSNumber *)componentTag - rootTag:(NSNumber *)rootTag - properties:(NSDictionary *)props - viewName:(NSString *)viewName { - UIView *view = [self viewForHippyTag:componentTag onRootTag:rootTag]; - BOOL canBeRetrievedFromCache = YES; - if (view && [view respondsToSelector:@selector(canBeRetrievedFromViewCache)]) { - canBeRetrievedFromCache = [view canBeRetrievedFromViewCache]; - } - - /** - * subviews & hippySubviews should be removed from the view which we get from cache(_viewRegistry). - * otherwise hippySubviews will be inserted multiple times. - */ - if (view && canBeRetrievedFromCache) { - [view resetHippySubviews]; - } - else { - view = [componentData createViewWithTag:componentTag initProps:props]; - } - if (view) { - view.viewName = viewName; - view.rootTag = rootTag; - view.renderManager = [self renderManager]; - [componentData setProps:props forView:view]; // Must be done before bgColor to prevent wrong default - - if ([view respondsToSelector:@selector(hippyBridgeDidFinishTransaction)]) { - [self->_componentTransactionListeners addObject:view]; - } - [_viewRegistry addComponent:view forRootTag:rootTag]; - } - return view; + return renderObject; } - (void)updateView:(nonnull NSNumber *)componentTag @@ -688,7 +701,7 @@ - (void)amendPendingUIBlocksWithStylePropagationUpdateForRenderObject:(HippyShad if (applierBlocks.count) { [self addUIBlock:^(__unused HippyUIManager *uiManager, NSDictionary *viewRegistry) { for (NativeRenderApplierBlock block in applierBlocks) { - block(viewRegistry); + block(viewRegistry, nil); } }]; } @@ -715,23 +728,25 @@ - (void)flushUIBlocksOnRootNode:(std::weak_ptr)rootNode { __strong __typeof(weakSelf)strongSelf = weakSelf; if (strongSelf) { TDF_PERF_LOG("flushUIBlocksOnRootNode on main thread(random id:%u)",rand); - NSDictionary *viewReg = [strongSelf.viewRegistry componentsForRootTag:@(rootTag)]; - @try { - for (HippyViewManagerUIBlock block in previousPendingUIBlocks) { + for (HippyViewManagerUIBlock block in previousPendingUIBlocks) { + @try { + // Note: viewRegistry may be modified in the block, and it may be stored internally as NSMapTable + // so to ensure that it is up-to-date, it can only be retrieved each time. + NSDictionary* viewReg = [strongSelf.viewRegistry componentsForRootTag:@(rootTag)]; block(strongSelf, viewReg); + } @catch (NSException *exception) { + HippyLogError(@"Exception thrown while executing UI block: %@", exception); } - } @catch (NSException *exception) { - HippyLogError(@"Exception thrown while executing UI block: %@", exception); } - TDF_PERF_LOG("flushUIBlocksOnRootNode on main thread done, block count:%d(random id:%u)", previousPendingUIBlocks.count, rand); + TDF_PERF_LOG("flushUIBlocksOnRootNode done, block count:%d(random id:%u)", previousPendingUIBlocks.count, rand); } }); } TDF_PERF_LOG("flushUIBlocksOnRootNode End(random id:%u)",rand); } -#pragma mark - -#pragma mark View Render Manager + +#pragma mark - NativeRenderManager implementation /** * When NativeRenderUIManager received command to create view by node, NativeRenderUIManager must get all new created view ordered by index, set frames, @@ -757,12 +772,11 @@ - (void)createRenderNodes:(std::vector> &&)nodes NSNumber *rootNodeTag = @(strongRootNode->GetId()); std::lock_guard lock([self renderQueueLock]); NativeRenderViewsRelation *manager = [[NativeRenderViewsRelation alloc] init]; -// NSMutableDictionary *dicProps = [NSMutableDictionary dictionaryWithCapacity:nodes.size()]; for (const std::shared_ptr &node : nodes) { const auto& render_info = node->GetRenderInfo(); [manager addViewTag:render_info.id forSuperViewTag:render_info.pid atIndex:render_info.index]; - NSDictionary *nodeProps = [self createRenderObjectFromNode:node onRootNode:rootNode]; -// [dicProps setObject:nodeProps forKey:@(node->GetId())]; + HippyShadowView *shadowView = [self createRenderObjectFromNode:node onRootNode:rootNode]; + [_shadowViewRegistry addComponent:shadowView forRootTag:shadowView.rootTag]; } [manager enumerateViewsHierarchy:^(int32_t tag, const std::vector &subviewTags, const std::vector &subviewIndices) { NSAssert(subviewTags.size() == subviewIndices.size(), @"subviewTags count must be equal to subviewIndices count"); @@ -773,14 +787,32 @@ - (void)createRenderNodes:(std::vector> &&)nodes } [superRenderObject didUpdateHippySubviews]; }]; + __block NSMutableArray *tempCreatedViews = [NSMutableArray arrayWithCapacity:nodes.size()]; // Used to temporarily hold views objects. for (const std::shared_ptr &node : nodes) { NSNumber *componentTag = @(node->GetId()); - HippyShadowView *renderObject = [_shadowViewRegistry componentForTag:componentTag onRootTag:rootNodeTag]; - if (NativeRenderCreationTypeInstantly == [renderObject creationType] && !_uiCreationLazilyEnabled) { + HippyShadowView *shadowView = [_shadowViewRegistry componentForTag:componentTag onRootTag:rootNodeTag]; + if (NativeRenderCreationTypeInstantly == [shadowView creationType] && !_uiCreationLazilyEnabled) { [self addUIBlock:^(HippyUIManager *uiManager, __unused NSDictionary *viewRegistry) { - UIView *view = [uiManager createViewFromRenderObject:renderObject]; - view.hippyShadowView = renderObject; - view.renderManager = [uiManager renderManager]; + UIView *view = [uiManager createViewFromShadowView:shadowView]; + view.hippyShadowView = shadowView; + [view hippySetFrame:shadowView.frame]; + + if (uiManager && view) { + // after creation, add view to _viewRegistry, and _componentTransactionListeners. + if ([view respondsToSelector:@selector(hippyBridgeDidFinishTransaction)]) { + [uiManager->_componentTransactionListeners addObject:view]; + } + [uiManager.viewRegistry addComponent:view forRootTag:shadowView.rootTag]; + [tempCreatedViews addObject:view]; + + // TODO: hippy3 events binding handling, performance needs to be improved here. + const std::vector &eventNames = [shadowView allEventNames]; + for (auto &event : eventNames) { + [uiManager addEventNameInMainThread:event + forView:view + onRootNode:shadowView.rootNode]; + } + } }]; } } @@ -802,6 +834,9 @@ - (void)createRenderNodes:(std::vector> &&)nodes }]; } }]; + [self addUIBlock:^(HippyUIManager *uiManager, NSDictionary *viewRegistry) { + HippyLogInfo(@"Created views: %lu, full registry: %lu", (unsigned long)tempCreatedViews.count, viewRegistry.count); + }]; } - (void)updateRenderNodes:(std::vector>&&)nodes @@ -850,7 +885,7 @@ - (void)deleteRenderNodesIds:(std::vector> &&)no #endif std::lock_guard lock([self renderQueueLock]); NSNumber *rootTag = @(strongRootNode->GetId()); - NSMutableDictionary *currentRegistry = [_shadowViewRegistry componentsForRootTag:rootTag]; + NSDictionary *currentRegistry = [_shadowViewRegistry componentsForRootTag:rootTag]; for (auto dom_node : nodes) { int32_t tag = dom_node->GetRenderInfo().id; @@ -858,7 +893,7 @@ - (void)deleteRenderNodesIds:(std::vector> &&)no [renderObject dirtyPropagation:NativeRenderUpdateLifecycleLayoutDirtied]; if (renderObject) { [renderObject removeFromHippySuperview]; - [self purgeChildren:@[renderObject] onRootTag:rootTag fromRegistry:currentRegistry]; + [self purgeChildren:@[renderObject] onRootTag:rootTag fromRegistry:_shadowViewRegistry]; } } __weak HippyUIManager *weakSelf = self; @@ -875,7 +910,7 @@ - (void)deleteRenderNodesIds:(std::vector> &&)no if (!view) { continue; } - UIView *parentView = [view parentComponent]; + UIView *parentView = (UIView *)[view parent]; if (!parentView) { continue; } @@ -883,8 +918,7 @@ - (void)deleteRenderNodesIds:(std::vector> &&)no [view removeFromHippySuperview]; [views addObject:view]; } - NSMutableDictionary *currentViewRegistry = [strongSelf->_viewRegistry componentsForRootTag:rootTag]; - [strongSelf purgeChildren:views onRootTag:rootTag fromRegistry:currentViewRegistry]; + [strongSelf purgeChildren:views onRootTag:rootTag fromRegistry:strongSelf.viewRegistry]; for (UIView *view in parentViews) { [view clearSortedSubviews]; [view didUpdateHippySubviews]; @@ -909,7 +943,7 @@ - (void)renderMoveViews:(const std::vector &&)ids onRootTag:@(rootTag)]; for (int32_t componentTag : ids) { HippyShadowView *view = [_shadowViewRegistry componentForTag:@(componentTag) onRootTag:@(rootTag)]; - HippyAssert(fromObjectView == [view parentComponent], @"parent of object view with tag %d is not object view with tag %d", componentTag, fromContainer); + HippyAssert(fromObjectView == [view parent], @"parent of object view with tag %d is not object view with tag %d", componentTag, fromContainer); [view removeFromHippySuperview]; [toObjectView insertHippySubview:view atIndex:index]; } @@ -926,7 +960,7 @@ - (void)renderMoveViews:(const std::vector &&)ids if (!view) { continue; } - HippyAssert(fromView == [view parentComponent], @"parent of object view with tag %d is not object view with tag %d", tag, fromContainer); + HippyAssert(fromView == [view parent], @"parent of object view with tag %d is not object view with tag %d", tag, fromContainer); [view removeFromHippySuperview]; [toView insertHippySubview:view atIndex:index]; } @@ -951,9 +985,9 @@ - (void)renderMoveNodes:(std::vector> &&)nodes int32_t componentTag = node->GetId(); HippyShadowView *objectView = [_shadowViewRegistry componentForTag:@(componentTag) onRootTag:@(rootTag)]; [objectView dirtyPropagation:NativeRenderUpdateLifecycleLayoutDirtied]; - HippyAssert(!parentObjectView || parentObjectView == [objectView parentComponent], @"try to move object view on different parent object view"); + HippyAssert(!parentObjectView || parentObjectView == [objectView parent], @"try to move object view on different parent object view"); if (!parentObjectView) { - parentObjectView = [objectView parentComponent]; + parentObjectView = (HippyShadowView *)[objectView parent]; } [parentObjectView moveHippySubview:objectView toIndex:index]; } @@ -968,9 +1002,9 @@ - (void)renderMoveNodes:(std::vector> &&)nodes if (!view) { continue; } - HippyAssert(!superView || superView == [view parentComponent], @"try to move views on different parent views"); + HippyAssert(!superView || superView == [view parent], @"try to move views on different parent views"); if (!superView) { - superView = [view parentComponent]; + superView = (UIView *)[view parent]; } [superView moveHippySubview:view toIndex:index]; } @@ -1096,7 +1130,8 @@ - (void)registerExtraComponent:(NSArray *)extraComponents { #pragma mark - Event Handler -- (void)addEventName:(const std::string &)name forDomNodeId:(int32_t)node_id +- (void)addEventName:(const std::string &)name + forDomNodeId:(int32_t)node_id onRootNode:(std::weak_ptr)rootNode { auto strongRootNode = rootNode.lock(); if (!strongRootNode) { @@ -1107,27 +1142,32 @@ - (void)addEventName:(const std::string &)name forDomNodeId:(int32_t)node_id [renderObject addEventName:name]; if (name == hippy::kClickEvent) { [self addUIBlock:^(HippyUIManager *uiManager, NSDictionary *viewRegistry) { - [uiManager addClickEventListenerForView:node_id onRootNode:rootNode]; + UIView *view = viewRegistry[@(node_id)]; + [uiManager addClickEventListenerForView:view onRootNode:rootNode]; }]; } else if (name == hippy::kLongClickEvent) { [self addUIBlock:^(HippyUIManager *uiManager, NSDictionary *viewRegistry) { - [uiManager addLongClickEventListenerForView:node_id onRootNode:rootNode]; + UIView *view = viewRegistry[@(node_id)]; + [uiManager addLongClickEventListenerForView:view onRootNode:rootNode]; }]; } else if (name == hippy::kTouchStartEvent || name == hippy::kTouchMoveEvent || name == hippy::kTouchEndEvent || name == hippy::kTouchCancelEvent) { std::string name_ = name; [self addUIBlock:^(HippyUIManager *uiManager, NSDictionary *viewRegistry) { - [uiManager addTouchEventListenerForType:name_ forView:node_id onRootNode:rootNode]; + UIView *view = viewRegistry[@(node_id)]; + [uiManager addTouchEventListenerForType:name_ forView:view onRootNode:rootNode]; }]; } else if (name == hippy::kShowEvent || name == hippy::kDismissEvent) { std::string name_ = name; [self addUIBlock:^(HippyUIManager *uiManager, NSDictionary *viewRegistry) { - [uiManager addShowEventListenerForType:name_ forView:node_id onRootNode:rootNode]; + UIView *view = viewRegistry[@(node_id)]; + [uiManager addShowEventListenerForType:name_ forView:view onRootNode:rootNode]; }]; } else if (name == hippy::kPressIn || name == hippy::kPressOut) { std::string name_ = name; [self addUIBlock:^(HippyUIManager *uiManager, NSDictionary *viewRegistry) { - [uiManager addPressEventListenerForType:name_ forView:node_id onRootNode:rootNode]; + UIView *view = viewRegistry[@(node_id)]; + [uiManager addPressEventListenerForType:name_ forView:view onRootNode:rootNode]; }]; } else if (name == kVSyncKey) { std::string name_ = name; @@ -1156,40 +1196,41 @@ - (void)addEventName:(const std::string &)name forDomNodeId:(int32_t)node_id else { std::string name_ = name; [self addUIBlock:^(HippyUIManager *uiManager, NSDictionary *viewRegistry) { - [uiManager addPropertyEvent:name_ forDomNode:node_id onRootNode:rootNode]; + UIView *view = viewRegistry[@(node_id)]; + [uiManager addPropertyEvent:name_ forView:view onRootNode:rootNode]; }]; } } /// Called when creating view from shadowView - (void)addEventNameInMainThread:(const std::string &)name - forDomNodeId:(int32_t)node_id + forView:(UIView *)view onRootNode:(std::weak_ptr)rootNode { AssertMainQueue(); if (name == hippy::kClickEvent) { - [self addClickEventListenerForView:node_id onRootNode:rootNode]; + [self addClickEventListenerForView:view onRootNode:rootNode]; } else if (name == hippy::kLongClickEvent) { - [self addLongClickEventListenerForView:node_id onRootNode:rootNode]; + [self addLongClickEventListenerForView:view onRootNode:rootNode]; } else if (name == hippy::kTouchStartEvent || name == hippy::kTouchMoveEvent || name == hippy::kTouchEndEvent || name == hippy::kTouchCancelEvent) { - [self addTouchEventListenerForType:name forView:node_id onRootNode:rootNode]; + [self addTouchEventListenerForType:name forView:view onRootNode:rootNode]; } else if (name == hippy::kShowEvent || name == hippy::kDismissEvent) { - [self addShowEventListenerForType:name forView:node_id onRootNode:rootNode]; + [self addShowEventListenerForType:name forView:view onRootNode:rootNode]; } else if (name == hippy::kPressIn || name == hippy::kPressOut) { - [self addPressEventListenerForType:name forView:node_id onRootNode:rootNode]; + [self addPressEventListenerForType:name forView:view onRootNode:rootNode]; } else { - [self addPropertyEvent:name forDomNode:node_id onRootNode:rootNode]; + [self addPropertyEvent:name forView:view onRootNode:rootNode]; } } -- (void)addClickEventListenerForView:(int32_t)componentTag onRootNode:(std::weak_ptr)rootNode { +- (void)addClickEventListenerForView:(UIView *)view onRootNode:(std::weak_ptr)rootNode { AssertMainQueue(); auto strongRootNode = rootNode.lock(); if (!strongRootNode) { return; } int32_t root_id = strongRootNode->GetId(); - UIView *view = [self viewForHippyTag:@(componentTag) onRootTag:@(root_id)]; + int32_t componentTag = view.hippyTag.intValue; if (view) { __weak id weakSelf = self; OnTouchEventHandler eventListener = ^(CGPoint point, @@ -1210,21 +1251,20 @@ - (void)addClickEventListenerForView:(int32_t)componentTag onRootNode:(std::weak }]; } }; -// [view addViewEvent:NativeRenderViewEventTypeClick eventListener:eventListener]; [view setOnClick:eventListener]; } else { } } -- (void)addLongClickEventListenerForView:(int32_t)componentTag onRootNode:(std::weak_ptr)rootNode { +- (void)addLongClickEventListenerForView:(UIView *)view onRootNode:(std::weak_ptr)rootNode { AssertMainQueue(); auto strongRootNode = rootNode.lock(); if (!strongRootNode) { return; } int32_t root_id = strongRootNode->GetId(); - UIView *view = [self viewForHippyTag:@(componentTag) onRootTag:@(root_id)]; + int32_t componentTag = view.hippyTag.intValue; if (view) { __weak id weakSelf = self; OnTouchEventHandler eventListener = ^(CGPoint point, @@ -1245,8 +1285,6 @@ - (void)addLongClickEventListenerForView:(int32_t)componentTag onRootNode:(std:: }]; } }; - -// [view addViewEvent:NativeRenderViewEventTypeLongClick eventListener:]; [view setOnLongClick:eventListener]; } else { @@ -1254,7 +1292,7 @@ - (void)addLongClickEventListenerForView:(int32_t)componentTag onRootNode:(std:: } - (void)addPressEventListenerForType:(const std::string &)type - forView:(int32_t)componentTag + forView:(UIView *)view onRootNode:(std::weak_ptr)rootNode { auto strongRootNode = rootNode.lock(); if (!strongRootNode) { @@ -1262,7 +1300,7 @@ - (void)addPressEventListenerForType:(const std::string &)type } int32_t root_id = strongRootNode->GetId(); AssertMainQueue(); - UIView *view = [self viewForHippyTag:@(componentTag) onRootTag:@(root_id)]; + int32_t componentTag = view.hippyTag.intValue; if (view) { std::string block_type = type; __weak id weakSelf = self; @@ -1293,7 +1331,7 @@ - (void)addPressEventListenerForType:(const std::string &)type } - (void)addTouchEventListenerForType:(const std::string &)type - forView:(int32_t)componentTag + forView:(UIView *)view onRootNode:(std::weak_ptr)rootNode { AssertMainQueue(); auto strongRootNode = rootNode.lock(); @@ -1301,7 +1339,7 @@ - (void)addTouchEventListenerForType:(const std::string &)type return; } int32_t root_id = strongRootNode->GetId(); - UIView *view = [self viewForHippyTag:@(componentTag) onRootTag:@(root_id)]; + int32_t componentTag = view.hippyTag.intValue; if (view) { const std::string type_ = type; __weak id weakSelf = self; @@ -1339,7 +1377,7 @@ - (void)addTouchEventListenerForType:(const std::string &)type } - (void)addShowEventListenerForType:(const std::string &)type - forView:(int32_t)componentTag + forView:(UIView *)view onRootNode:(std::weak_ptr)rootNode { // Note: not implemented // iOS do not have these event. @@ -1377,7 +1415,8 @@ - (void)removeVSyncEventOnRootNode:(std::weak_ptr)rootNode { [[RenderVsyncManager sharedInstance] unregisterVsyncObserverForKey:vsyncKey]; } -- (void)addPropertyEvent:(const std::string &)name forDomNode:(int32_t)node_id +- (void)addPropertyEvent:(const std::string &)name + forView:(UIView *)view onRootNode:(std::weak_ptr)rootNode { AssertMainQueue(); auto strongRootNode = rootNode.lock(); @@ -1385,7 +1424,7 @@ - (void)addPropertyEvent:(const std::string &)name forDomNode:(int32_t)node_id return; } int32_t root_id = strongRootNode->GetId(); - UIView *view = [self viewForHippyTag:@(node_id) onRootTag:@(root_id)]; + int32_t node_id = view.hippyTag.intValue; if (view) { std::string name_ = name; NSDictionary *componentDataByName = [_componentDataByName copy]; @@ -1438,7 +1477,7 @@ - (void)layoutAndMountOnRootNode:(std::weak_ptr)rootNode { if (uiBlocks.count) { [self addUIBlock:^(__unused HippyUIManager *uiManager, NSDictionary *viewRegistry) { for (NativeRenderApplierBlock block in uiBlocks) { - block(viewRegistry); + block(viewRegistry, nil); } }]; } diff --git a/renderer/native/ios/renderer/component/listview/NativeRenderBaseListView.mm b/renderer/native/ios/renderer/component/listview/NativeRenderBaseListView.mm index df537e784c8..46ba0c37ae1 100644 --- a/renderer/native/ios/renderer/component/listview/NativeRenderBaseListView.mm +++ b/renderer/native/ios/renderer/component/listview/NativeRenderBaseListView.mm @@ -32,6 +32,7 @@ #import "UIView+DirectionalLayout.h" #import "UIView+Hippy.h" #import "UIView+Render.h" +#import "HippyShadowListView.h" static NSString *const kCellIdentifier = @"HippyListCellIdentifier"; static NSString *const kSupplementaryIdentifier = @"HippySupplementaryIdentifier"; @@ -128,21 +129,18 @@ - (void)setScrollEnabled:(BOOL)value { } -#pragma mark Data Load - +#pragma mark - Data Load + +// BaseListview's super is WaterfallView +// here we use super's hippyBridgeDidFinishTransaction imp to trigger reload, +// and override reloadData to handle special logic - (void)reloadData { - [self refreshItemNodes]; - [_dataSource applyDiff:_previousDataSource - changedConext:self.changeContext - forWaterfallView:self.collectionView - completion:^(BOOL success) { - if (success) { - self->_previousDataSource = [self->_dataSource copy]; - } - else { - self->_previousDataSource = nil; - } - }]; + NSArray *datasource = [self.hippyShadowView.subcomponents copy]; + self->_dataSource = [[NativeRenderBaseListViewDataSource alloc] initWithDataSource:datasource + itemViewName:[self compoentItemName] + containBannerView:NO]; + [self.collectionView reloadData]; + if (self.initialContentOffset) { CGFloat initialContentOffset = self.initialContentOffset; dispatch_async(dispatch_get_main_queue(), ^{ @@ -166,7 +164,6 @@ - (void)insertHippySubview:(UIView *)subview atIndex:(NSInteger)atIndex { _headerRefreshView = (NativeRenderHeaderRefresh *)subview; [_headerRefreshView setScrollView:self.collectionView]; _headerRefreshView.delegate = self; - [_weakItemMap setObject:subview forKey:[subview hippyTag]]; } else if ([subview isKindOfClass:[NativeRenderFooterRefresh class]]) { if (_footerRefreshView) { [_footerRefreshView unsetFromScrollView]; @@ -174,29 +171,9 @@ - (void)insertHippySubview:(UIView *)subview atIndex:(NSInteger)atIndex { _footerRefreshView = (NativeRenderFooterRefresh *)subview; [_footerRefreshView setScrollView:self.collectionView]; _footerRefreshView.delegate = self; - [_weakItemMap setObject:subview forKey:[subview hippyTag]]; } } -- (void)didUpdateHippySubviews { - self.dirtyContent = YES; -} - -- (void)hippyBridgeDidFinishTransaction { - if (self.dirtyContent) { - [self reloadData]; - self.dirtyContent = NO; - } -} - -- (void)refreshItemNodes { - NSArray *datasource = [self popDataSource]; - self->_dataSource = [[NativeRenderBaseListViewDataSource alloc] initWithDataSource:datasource - itemViewName:[self compoentItemName] - containBannerView:NO]; -} - - #pragma mark - Delegate & Datasource - (BOOL)collectionView:(UICollectionView *)collectionView canEditItemAtIndexPath:(NSIndexPath *)indexPath { @@ -237,10 +214,11 @@ - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView forIndexPath:indexPath]; HippyShadowView *headerRenderObject = [self.dataSource headerForSection:section]; if (headerRenderObject && [headerRenderObject isKindOfClass:[HippyShadowView class]]) { - UIView *headerView = [self.renderImpl createViewRecursivelyFromRenderObject:headerRenderObject]; + UIView *headerView = [self.renderImpl createViewForShadowListItem:headerRenderObject]; CGRect frame = headerView.frame; frame.origin = CGPointZero; headerView.frame = frame; + [view addSubview:headerView]; } return view; @@ -279,46 +257,32 @@ - (void)collectionView:(UICollectionView *)collectionView } } -- (void)collectionView:(UICollectionView *)collectionView - didEndDisplayingCell:(UICollectionViewCell *)cell - forItemAtIndexPath:(NSIndexPath *)indexPath { - if ([cell isKindOfClass:[NativeRenderBaseListViewCell class]]) { - NativeRenderBaseListViewCell *hpCell = (NativeRenderBaseListViewCell *)cell; - if (hpCell.cellView) { - [_cachedItems setObject:[hpCell.cellView hippyTag] forKey:indexPath]; - } - } -} - - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { - UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kCellIdentifier forIndexPath:indexPath]; + NativeRenderBaseListViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kCellIdentifier forIndexPath:indexPath]; + HippyShadowView *shadowView = [self.dataSource cellForIndexPath:indexPath]; - // Create and Add real Hippy cell content - [self addCellViewToCollectionViewCell:cell atIndexPath:indexPath]; - return cell; -} - -- (void)addCellViewToCollectionViewCell:(UICollectionViewCell *)cell atIndexPath:(NSIndexPath *)indexPath { - HippyAssert(self.renderImpl, @"no rendercontext detected"); - if (!self.renderImpl) { - return; + UIView *cellView = nil; + UIView *cachedVisibleCellView = [_cachedWeakCellViews objectForKey:shadowView.hippyTag]; + if (cachedVisibleCellView && + [shadowView isKindOfClass:NativeRenderObjectWaterfallItem.class] && + !((NativeRenderObjectWaterfallItem *)shadowView).layoutDirty) { + cellView = cachedVisibleCellView; + HippyLogTrace(@"🟢 use cached visible cellView at %@ for %@", indexPath, shadowView.hippyTag); + } else { + cellView = [self.renderImpl createViewForShadowListItem:shadowView]; + [_cachedWeakCellViews setObject:cellView forKey:shadowView.hippyTag]; + HippyLogTrace(@"🟡 create cellView at %@ for %@", indexPath, shadowView.hippyTag); } - HippyShadowView *cellRenderObject = [self.dataSource cellForIndexPath:indexPath]; - [cellRenderObject recusivelySetCreationTypeToInstant]; - NativeRenderBaseListViewCell *hpCell = (NativeRenderBaseListViewCell *)cell; - UIView *cellView = [self.renderImpl createViewRecursivelyFromRenderObject:cellRenderObject]; - if (cellView) { - [_cachedItems removeObjectForKey:indexPath]; - } HippyAssert([cellView conformsToProtocol:@protocol(ViewAppearStateProtocol)], @"subviews of NativeRenderBaseListViewCell must conform to protocol ViewAppearStateProtocol"); - hpCell.cellView = cellView; - cellView.parentComponent = self; - [_weakItemMap setObject:cellView forKey:[cellView hippyTag]]; + cell.cellView = cellView; + cellView.parent = self; + return cell; } - (void)tableViewDidLayoutSubviews:(NativeRenderListTableView *)tableView { + [super tableViewDidLayoutSubviews:tableView]; NSArray *visibleCells = [self.collectionView visibleCells]; for (NativeRenderBaseListViewCell *cell in visibleCells) { CGRect cellRectInTableView = [self.collectionView convertRect:[cell bounds] fromView:cell]; diff --git a/renderer/native/ios/renderer/component/listview/NativeRenderBaseListViewDataSource.mm b/renderer/native/ios/renderer/component/listview/NativeRenderBaseListViewDataSource.mm index 23ad0c1c2d0..d9de8955b57 100644 --- a/renderer/native/ios/renderer/component/listview/NativeRenderBaseListViewDataSource.mm +++ b/renderer/native/ios/renderer/component/listview/NativeRenderBaseListViewDataSource.mm @@ -23,7 +23,7 @@ #import #import "NativeRenderBaseListViewDataSource.h" #import "HippyShadowView.h" -#import "NativeRenderObjectWaterfall.h" +#import "HippyShadowListView.h" @interface NativeRenderBaseListViewDataSource () { NSMutableArray *_headerRenderObjects; @@ -155,15 +155,5 @@ - (UIView *)bannerView { return nil; } -- (void)applyDiff:(NativeRenderBaseListViewDataSource *)another - changedConext:(WaterfallItemChangeContext *)context - forWaterfallView:(UICollectionView *)view - completion:(void(^)(BOOL success))completion { - [view reloadData]; - completion(YES); - return; - // Note:原增量刷新代码存在大量问题,已删除 -} - @end diff --git a/renderer/native/ios/renderer/component/listview/NativeRenderBaseListViewManager.mm b/renderer/native/ios/renderer/component/listview/NativeRenderBaseListViewManager.mm index 33ad2d26c07..c688755f11e 100644 --- a/renderer/native/ios/renderer/component/listview/NativeRenderBaseListViewManager.mm +++ b/renderer/native/ios/renderer/component/listview/NativeRenderBaseListViewManager.mm @@ -22,7 +22,7 @@ #import "NativeRenderBaseListViewManager.h" #import "NativeRenderBaseListView.h" -#import "NativeRenderObjectWaterfall.h" +#import "HippyShadowListView.h" #import "HippyUIManager.h" @implementation NativeRenderBaseListViewManager @@ -52,7 +52,7 @@ - (UIView *)view { } - (HippyShadowView *)hippyShadowView { - return [[NativeRenderObjectWaterfall alloc] init]; + return [[HippyShadowListView alloc] init]; } HIPPY_EXPORT_METHOD(scrollToIndex:(nonnull NSNumber *)componentTag diff --git a/renderer/native/ios/renderer/component/scrollview/HippyScrollView.mm b/renderer/native/ios/renderer/component/scrollview/HippyScrollView.mm index 8c1a6b450ff..68fb850269c 100644 --- a/renderer/native/ios/renderer/component/scrollview/HippyScrollView.mm +++ b/renderer/native/ios/renderer/component/scrollview/HippyScrollView.mm @@ -240,7 +240,7 @@ - (void)insertHippySubview:(UIView *)view atIndex:(NSInteger)atIndex { _scrollView.contentSize = _contentView.frame.size; [view onAttachedToWindow]; [_scrollView addSubview:view]; - view.parentComponent = self; + view.parent = self; if (_didSetContentOffset) { _didSetContentOffset = NO; return; @@ -279,7 +279,7 @@ - (void)removeHippySubview:(UIView *)subview { [super removeHippySubview:subview]; NSAssert(_contentView == subview, @"Attempted to remove non-existent subview"); [_contentView removeObserver:self forKeyPath:@"frame"]; - _contentView.parentComponent = nil; + _contentView.parent = nil; _contentView = nil; } diff --git a/renderer/native/ios/renderer/component/smartViewPager/NativeRenderSmartViewPagerView.mm b/renderer/native/ios/renderer/component/smartViewPager/NativeRenderSmartViewPagerView.mm index 6717e22a83e..87e94c83394 100644 --- a/renderer/native/ios/renderer/component/smartViewPager/NativeRenderSmartViewPagerView.mm +++ b/renderer/native/ios/renderer/component/smartViewPager/NativeRenderSmartViewPagerView.mm @@ -356,13 +356,13 @@ - (void)collectionView:(UICollectionView *)collectionView NSIndexPath *adjustIndexPath = [NSIndexPath indexPathForRow:cellIndex inSection:indexPath.section]; NativeRenderWaterfallViewCell *hpCell = (NativeRenderWaterfallViewCell *)cell; HippyShadowView *renderObject = [_dataSource cellForIndexPath:adjustIndexPath]; - [renderObject recusivelySetCreationTypeToInstant]; - UIView *cellView = [self.renderImpl createViewRecursivelyFromRenderObject:renderObject]; + UIView *cellView = [self.renderImpl createViewForShadowListItem:renderObject]; hpCell.cellView = cellView; - cellView.parentComponent = self; + cellView.parent = self; } - (void)tableViewDidLayoutSubviews:(NativeRenderListTableView *)tableView { + [super tableViewDidLayoutSubviews:tableView]; NSArray *visibleCells = [self.collectionView visibleCells]; for (NativeRenderBaseListViewCell *cell in visibleCells) { CGRect cellRectInTableView = [self.collectionView convertRect:[cell bounds] fromView:cell]; diff --git a/renderer/native/ios/renderer/component/text/NativeRenderObjectText.mm b/renderer/native/ios/renderer/component/text/NativeRenderObjectText.mm index b6941f7e85f..2beb9e88431 100644 --- a/renderer/native/ios/renderer/component/text/NativeRenderObjectText.mm +++ b/renderer/native/ios/renderer/component/text/NativeRenderObjectText.mm @@ -189,7 +189,7 @@ - (void)contentSizeMultiplierDidChange:(__unused NSNotification *)note { - (NSDictionary *)processUpdatedProperties:(NSMutableSet *)applierBlocks parentProperties:(NSDictionary *)parentProperties { - if ([[self parentComponent] isKindOfClass:[NativeRenderObjectText class]]) { + if ([[self parent] isKindOfClass:[NativeRenderObjectText class]]) { return parentProperties; } @@ -198,13 +198,16 @@ - (void)contentSizeMultiplierDidChange:(__unused NSNotification *)note { UIEdgeInsets padding = self.paddingAsInsets; CGFloat width = self.frame.size.width - (padding.left + padding.right); - NSNumber *parentTag = [[self parentComponent] hippyTag]; + NSNumber *parentTag = [[self parent] hippyTag]; // MTTlayout NSTextStorage *textStorage = [self buildTextStorageForWidth:width widthMode:hippy::LayoutMeasureMode::Exactly]; CGRect textFrame = [self calculateTextFrame:textStorage]; UIColor *color = self.color ?: [UIColor blackColor]; - [applierBlocks addObject:^(NSDictionary *viewRegistry) { - NativeRenderText *view = (NativeRenderText *)viewRegistry[self.hippyTag]; + [applierBlocks addObject:^(NSDictionary *viewRegistry, UIView * _Nullable lazyCreatedView) { + NativeRenderText *view = (NativeRenderText *)(lazyCreatedView ?: viewRegistry[self.hippyTag]); + if (![view isKindOfClass:NativeRenderText.class]) { + return; + } view.textFrame = textFrame; view.textStorage = textStorage; view.textColor = color; diff --git a/renderer/native/ios/renderer/component/view/HippyShadowView.h b/renderer/native/ios/renderer/component/view/HippyShadowView.h index 33c0372da0d..cee3312b6f2 100644 --- a/renderer/native/ios/renderer/component/view/HippyShadowView.h +++ b/renderer/native/ios/renderer/component/view/HippyShadowView.h @@ -24,6 +24,8 @@ #import "HippyComponent.h" #import "HippyConvert+NativeRender.h" +#ifdef __cplusplus + #include namespace hippy { @@ -35,6 +37,7 @@ struct LayoutResult; enum class Direction; } } +#endif /* __cplusplus */ typedef NS_ENUM(NSUInteger, NativeRenderUpdateLifecycle) { NativeRenderUpdateLifecycleUninitialized = 0, @@ -52,7 +55,7 @@ typedef NS_ENUM(NSUInteger, NativeRenderCreationType) { @class HippyShadowView; -typedef void (^NativeRenderApplierBlock)(NSDictionary *viewRegistry); +typedef void (^NativeRenderApplierBlock)(NSDictionary *viewRegistry, UIView * _Nullable lazyCreatedView); typedef UIView *(^NativeRenderViewCreationBlock)(HippyShadowView *renderObject); typedef void (^NativeRenderViewInsertionBlock)(UIView *container, NSArray *children); @@ -76,38 +79,7 @@ extern NSString *const NativeRenderShadowViewDiffTag; NativeRenderUpdateLifecycle _propagationLifecycle; } -/** - * NativeRenderComponent interface. - */ - -/** - * Get all native render object - */ -- (NSArray *)subcomponents; -/** - * Get super render object - */ -- (HippyShadowView *)parentComponent; - -/** - * Insert native render object at index. - * - * @param subview A render object subview to insert - * @param atIndex position for hippy subview to insert - * @discussion atIndex must not exceed range of current index - */ -- (void)insertHippySubview:(HippyShadowView *)subview atIndex:(NSInteger)atIndex; - -/** - * Remove render object - * - * @param subview A render object to delete - */ -- (void)removeHippySubview:(HippyShadowView *)subview; - -@property(nonatomic, weak, readonly) HippyShadowView *superview; -@property(nonatomic, copy) NSString *viewName; @property(nonatomic, strong) UIColor *backgroundColor; // Used to propagate to children @property(nonatomic, copy) HippyDirectEventBlock onLayout; @property(nonatomic, readonly) BOOL confirmedLayoutDirectionDidUpdated; @@ -176,16 +148,20 @@ extern NSString *const NativeRenderShadowViewDiffTag; */ @property (nonatomic, assign) NativeRenderCreationType creationType; +#ifdef __cplusplus @property (nonatomic, assign) std::weak_ptr domManager; @property (nonatomic, assign) std::weak_ptr domNode; @property (nonatomic, assign) std::weak_ptr rootNode; +#endif + /** * set create type of itself and its all descendants to NativeRenderCreationTypeInstantly */ -- (void)recusivelySetCreationTypeToInstant; +- (void)synchronousRecusivelySetCreationTypeToInstant; + - (UIView *)createView:(NativeRenderViewCreationBlock)creationBlock insertChildren:(NativeRenderViewInsertionBlock)insertionBlock; @@ -234,6 +210,7 @@ extern NSString *const NativeRenderShadowViewDiffTag; - (NSDictionary *)mergeProps:(NSDictionary *)props; +#ifdef __cplusplus /** * Add event to NativeRenderObject * @param name event name @@ -254,6 +231,7 @@ extern NSString *const NativeRenderShadowViewDiffTag; */ - (void)clearEventNames; + @property(nonatomic, assign) hippy::LayoutResult nodeLayoutResult; @property(nonatomic, assign) hippy::Direction layoutDirection; @@ -261,4 +239,6 @@ extern NSString *const NativeRenderShadowViewDiffTag; - (void)applyConfirmedLayoutDirectionToSubviews:(hippy::Direction)confirmedLayoutDirection; - (BOOL)isLayoutSubviewsRTL; +#endif + @end diff --git a/renderer/native/ios/renderer/component/view/HippyShadowView.mm b/renderer/native/ios/renderer/component/view/HippyShadowView.mm index 502ceaa9f7c..0a98ae07853 100644 --- a/renderer/native/ios/renderer/component/view/HippyShadowView.mm +++ b/renderer/native/ios/renderer/component/view/HippyShadowView.mm @@ -39,6 +39,7 @@ @interface HippyShadowView () { + __weak HippyShadowView *_superview; NSMutableArray *_objectSubviews; BOOL _recomputePadding; BOOL _recomputeMargin; @@ -56,7 +57,8 @@ @implementation HippyShadowView @synthesize hippyTag = _hippyTag; @synthesize props = _props; @synthesize rootTag = _rootTag; -@synthesize tagName =_tagName; +@synthesize tagName = _tagName; +@synthesize viewName = _viewName; - (void)amendLayoutBeforeMount:(NSMutableSet *)blocks { if (NativeRenderUpdateLifecycleComputed == _propagationLifecycle) { @@ -73,16 +75,16 @@ - (void)amendLayoutBeforeMount:(NSMutableSet *)blocks if (_didUpdateSubviews) { _didUpdateSubviews = NO; [self didUpdateHippySubviews]; - [applierBlocks addObject:^(NSDictionary *viewRegistry) { - UIView *view = viewRegistry[self->_hippyTag]; + [applierBlocks addObject:^(NSDictionary *viewRegistry, UIView * _Nullable lazyCreatedView) { + UIView *view = lazyCreatedView ?: viewRegistry[self->_hippyTag]; [view clearSortedSubviews]; [view didUpdateHippySubviews]; }]; } if (_confirmedLayoutDirectionDidUpdated) { hippy::Direction direction = [self confirmedLayoutDirection]; - [applierBlocks addObject:^(NSDictionary *viewRegistry) { - UIView *view = viewRegistry[self->_hippyTag]; + [applierBlocks addObject:^(NSDictionary *viewRegistry, UIView * _Nullable lazyCreatedView) { + UIView *view = lazyCreatedView ?: viewRegistry[self->_hippyTag]; [view applyLayoutDirectionFromParent:direction]; }]; _confirmedLayoutDirectionDidUpdated = NO; @@ -90,8 +92,8 @@ - (void)amendLayoutBeforeMount:(NSMutableSet *)blocks if (!_backgroundColor) { UIColor *parentBackgroundColor = parentProperties[NativeRenderBackgroundColorProp]; if (parentBackgroundColor) { - [applierBlocks addObject:^(NSDictionary *viewRegistry) { - UIView *view = viewRegistry[self->_hippyTag]; + [applierBlocks addObject:^(NSDictionary *viewRegistry, UIView * _Nullable lazyCreatedView) { + UIView *view = lazyCreatedView ?: viewRegistry[self->_hippyTag]; [view hippySetInheritedBackgroundColor:parentBackgroundColor]; }]; } @@ -154,8 +156,8 @@ - (BOOL)isPropagationDirty:(NativeRenderUpdateLifecycle)dirtyType { } - (void)dirtyText:(BOOL)needToDoLayout { - if ([self parentComponent]) { - [[self parentComponent] dirtyText:needToDoLayout]; + if ([self parent]) { + [(HippyShadowView *)[self parent] dirtyText:needToDoLayout]; } } @@ -165,7 +167,7 @@ - (BOOL)isTextDirty { - (NativeRenderCreationType)creationType { if (NativeRenderCreationTypeUndetermined == _creationType) { - HippyShadowView *superRenderObject = [self parentComponent]; + HippyShadowView *superRenderObject = [self parent]; if (superRenderObject && ![superRenderObject isHippyRootView]) { _creationType = [superRenderObject creationType]; } @@ -180,24 +182,6 @@ - (void)setTextComputed { // _textLifecycle = NativeRenderUpdateLifecycleComputed; } -- (void)recusivelySetCreationTypeToInstant { - auto domManager = self.domManager.lock(); - if (domManager) { - __weak HippyShadowView *weakSelf = self; - - std::vector> ops = {[weakSelf](){ - if (weakSelf) { - HippyShadowView *strongSelf = weakSelf; - strongSelf.creationType = NativeRenderCreationTypeInstantly; - for (HippyShadowView *subRenderObject in strongSelf.subcomponents) { - [subRenderObject synchronousRecusivelySetCreationTypeToInstant]; - } - } - }}; - domManager->PostTask(hippy::dom::Scene(std::move(ops))); - } -} - - (void)synchronousRecusivelySetCreationTypeToInstant { self.creationType = NativeRenderCreationTypeInstantly; for (HippyShadowView *subShadowView in self.subcomponents) { @@ -247,7 +231,7 @@ - (void)removeHippySubview:(HippyShadowView *)subview { } - (void)removeFromHippySuperview { - id superview = [self parentComponent]; + id superview = [self parent]; [superview removeHippySubview:self]; } @@ -255,12 +239,12 @@ - (void)removeFromHippySuperview { return _objectSubviews; } -- (HippyShadowView *)parentComponent { +- (HippyShadowView *)parent { return _superview; } -- (void)setParentComponent:(__kindof id)parentComponent { - _superview = parentComponent; +- (void)setParent:(id)parent { + _superview = parent; } - (NSNumber *)hippyTagAtPoint:(CGPoint)point { @@ -462,7 +446,7 @@ - (BOOL)isLayoutSubviewsRTL { - (void)checkLayoutDirection:(NSMutableSet *)viewsSet direction:(hippy::Direction *)direction{ if (hippy::Direction::Inherit == self.confirmedLayoutDirection) { [viewsSet addObject:self]; - HippyShadowView *shadowSuperview = [self parentComponent]; + HippyShadowView *shadowSuperview = [self parent]; if (!shadowSuperview) { if (direction) { NSWritingDirection writingDirection = @@ -481,7 +465,7 @@ - (void)checkLayoutDirection:(NSMutableSet *)viewsSet directi - (void)superviewLayoutDirectionChangedTo:(hippy::Direction)direction { if (hippy::Direction::Inherit == self.layoutDirection) { - self.confirmedLayoutDirection = [self superview].confirmedLayoutDirection; + self.confirmedLayoutDirection = ((HippyShadowView *)[self parent]).confirmedLayoutDirection; for (HippyShadowView *subview in self.subcomponents) { [subview superviewLayoutDirectionChangedTo:self.confirmedLayoutDirection]; } diff --git a/renderer/native/ios/renderer/component/view/UIView+DirectionalLayout.mm b/renderer/native/ios/renderer/component/view/UIView+DirectionalLayout.mm index 642a5b23574..933ced8e60c 100644 --- a/renderer/native/ios/renderer/component/view/UIView+DirectionalLayout.mm +++ b/renderer/native/ios/renderer/component/view/UIView+DirectionalLayout.mm @@ -57,7 +57,7 @@ - (BOOL)isLayoutSubviewsRTL { - (void)checkLayoutDirection:(NSMutableSet *)viewsSet direction:(hippy::Direction *)direction{ if (hippy::Direction::Inherit == self.confirmedLayoutDirection) { [viewsSet addObject:self]; - [(UIView *)[self parentComponent] checkLayoutDirection:viewsSet direction:direction]; + [(UIView *)[self parent] checkLayoutDirection:viewsSet direction:direction]; } else if (direction) { *direction = self.confirmedLayoutDirection; diff --git a/renderer/native/ios/renderer/component/view/UIView+Hippy.mm b/renderer/native/ios/renderer/component/view/UIView+Hippy.mm index 9c7d1e7111c..6770a2881c9 100644 --- a/renderer/native/ios/renderer/component/view/UIView+Hippy.mm +++ b/renderer/native/ios/renderer/component/view/UIView+Hippy.mm @@ -112,16 +112,20 @@ - (void)setTagName:(NSString *)tagName { } - (__kindof HippyShadowView *)hippyShadowView { - NSHashTable *hashTable = objc_getAssociatedObject(self, _cmd); - return [hashTable anyObject]; + @synchronized (self) { + NSHashTable *hashTable = objc_getAssociatedObject(self, _cmd); + return [hashTable anyObject]; + } } - (void)setHippyShadowView:(__kindof HippyShadowView *)renderObject { - NSHashTable *hashTable = [NSHashTable weakObjectsHashTable]; - if (renderObject) { - [hashTable addObject:renderObject]; + @synchronized (self) { + NSHashTable *hashTable = [NSHashTable weakObjectsHashTable]; + if (renderObject) { + [hashTable addObject:renderObject]; + } + objc_setAssociatedObject(self, @selector(hippyShadowView), hashTable, OBJC_ASSOCIATION_RETAIN); } - objc_setAssociatedObject(self, @selector(hippyShadowView), hashTable, OBJC_ASSOCIATION_RETAIN); } - (BOOL)isHippyRootView { @@ -140,18 +144,17 @@ - (NSNumber *)hippyTagAtPoint:(CGPoint)point { return objc_getAssociatedObject(self, _cmd); } -- (UIView *)parentComponent { +- (UIView *)parent { return [objc_getAssociatedObject(self, _cmd) anyObject]; } -- (void)setParentComponent:(__kindof id)parentComponent { - if (parentComponent) { +- (void)setParent:(id)parent { + if (parent) { NSHashTable *hashTable = [NSHashTable weakObjectsHashTable]; - [hashTable addObject:parentComponent]; - objc_setAssociatedObject(self, @selector(parentComponent), hashTable, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - } - else { - objc_setAssociatedObject(self, @selector(parentComponent), nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + [hashTable addObject:parent]; + objc_setAssociatedObject(self, @selector(parent), hashTable, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } else { + objc_setAssociatedObject(self, @selector(parent), nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } } @@ -173,7 +176,7 @@ - (void)insertHippySubview:(UIView *)subview atIndex:(NSInteger)atIndex { else { [subviews addObject:subview]; } - subview.parentComponent = self; + subview.parent = self; } - (void)moveHippySubview:(UIView *)subview toIndex:(NSInteger)atIndex { @@ -197,11 +200,11 @@ - (void)removeHippySubview:(UIView *)subview { [subviews removeObject:subview]; [subview sendDetachedFromWindowEvent]; [subview removeFromSuperview]; - subview.parentComponent = nil; + subview.parent = nil; } - (void)removeFromHippySuperview { - [(UIView *)self.parentComponent removeHippySubview:self]; + [(UIView *)self.parent removeHippySubview:self]; } - (void)resetHippySubviews { @@ -218,7 +221,7 @@ - (UIView *)NativeRenderRootView { UIView *candidateRootView = self; BOOL isRootView = [candidateRootView isHippyRootView]; while (!isRootView && candidateRootView) { - candidateRootView = [candidateRootView parentComponent]; + candidateRootView = [candidateRootView parent]; isRootView = [candidateRootView isHippyRootView]; } return candidateRootView; diff --git a/renderer/native/ios/renderer/component/waterfalllist/NativeRenderObjectWaterfall.h b/renderer/native/ios/renderer/component/waterfalllist/HippyShadowListView.h similarity index 88% rename from renderer/native/ios/renderer/component/waterfalllist/NativeRenderObjectWaterfall.h rename to renderer/native/ios/renderer/component/waterfalllist/HippyShadowListView.h index 08b1eb9014c..c36233db9ef 100644 --- a/renderer/native/ios/renderer/component/waterfalllist/NativeRenderObjectWaterfall.h +++ b/renderer/native/ios/renderer/component/waterfalllist/HippyShadowListView.h @@ -43,7 +43,10 @@ NS_ASSUME_NONNULL_BEGIN @end -@interface NativeRenderObjectWaterfall : HippyShadowView +@interface HippyShadowListView : HippyShadowView + +///// Whether current ShadowList is dirty. +//@property (nonatomic, assign) BOOL isDirty; @property(nonatomic, readonly, strong)WaterfallItemChangeContext *itemChangeContext; diff --git a/renderer/native/ios/renderer/component/waterfalllist/NativeRenderObjectWaterfall.mm b/renderer/native/ios/renderer/component/waterfalllist/HippyShadowListView.mm similarity index 79% rename from renderer/native/ios/renderer/component/waterfalllist/NativeRenderObjectWaterfall.mm rename to renderer/native/ios/renderer/component/waterfalllist/HippyShadowListView.mm index 727ca5c3ebc..c3cb331333c 100644 --- a/renderer/native/ios/renderer/component/waterfalllist/NativeRenderObjectWaterfall.mm +++ b/renderer/native/ios/renderer/component/waterfalllist/HippyShadowListView.mm @@ -20,7 +20,7 @@ * limitations under the License. */ -#import "NativeRenderObjectWaterfall.h" +#import "HippyShadowListView.h" #import "NativeRenderWaterfallView.h" #import "HippyAssert.h" @@ -43,7 +43,7 @@ @implementation WaterfallItemChangeContext - (instancetype)init { self = [super init]; if (self) { - _deletedItems = [NSMutableSet setWithCapacity:8]; + _deletedItems = [NSMutableSet set]; _addedItems = [NSHashTable weakObjectsHashTable]; _movedItems = [NSHashTable weakObjectsHashTable]; _frameChangedItems = [NSHashTable weakObjectsHashTable]; @@ -127,13 +127,16 @@ - (NSString *)description { @end -@interface NativeRenderObjectWaterfall () { + +#pragma mark - + +@interface HippyShadowListView () { WaterfallItemChangeContext *_itemChangeContext; } @end -@implementation NativeRenderObjectWaterfall +@implementation HippyShadowListView - (instancetype)init{ self = [super init]; @@ -174,29 +177,5 @@ - (void)itemFrameChanged:(__kindof NativeRenderObjectWaterfallItem *)item { [_itemChangeContext appendFrameChangedItem:item]; } -- (void)amendLayoutBeforeMount:(NSMutableSet *)blocks { - if ([self isPropagationDirty:NativeRenderUpdateLifecycleLayoutDirtied] && - _itemChangeContext.hasChanges) { - WaterfallItemChangeContext *context = [_itemChangeContext copy]; - NSArray *dataSource = [self.subcomponents copy]; - __weak __typeof(self)weakSelf = self; - NativeRenderApplierBlock block = ^void(NSDictionary *viewRegistry) { - __strong __typeof(weakSelf)strongSelf = weakSelf; - if (!strongSelf) { - return; - } - NativeRenderWaterfallView *view = (NativeRenderWaterfallView *)[viewRegistry objectForKey:[strongSelf hippyTag]]; - HippyAssert([view isKindOfClass:[NativeRenderWaterfallView class]], @"view must be kind of NativeRenderWaterfallView"); - if ([view isKindOfClass:[NativeRenderWaterfallView class]]) { - view.dirtyContent = YES; - view.changeContext = context; - [view pushDataSource:dataSource]; - } - }; - [blocks addObject:block]; - [_itemChangeContext clear]; - } - [super amendLayoutBeforeMount:blocks]; -} @end diff --git a/renderer/native/ios/renderer/component/waterfalllist/NativeRenderWaterfallView.h b/renderer/native/ios/renderer/component/waterfalllist/NativeRenderWaterfallView.h index 515b20999eb..06bfc5629ed 100644 --- a/renderer/native/ios/renderer/component/waterfalllist/NativeRenderWaterfallView.h +++ b/renderer/native/ios/renderer/component/waterfalllist/NativeRenderWaterfallView.h @@ -27,6 +27,7 @@ #import "HippyScrollableProtocol.h" #import "HippyScrollProtocol.h" #import "NativeRenderTouchesView.h" +#import "NativeRenderListTableView.h" NS_ASSUME_NONNULL_BEGIN @@ -42,12 +43,12 @@ typedef NS_ENUM(NSInteger, NativeRenderScrollState) { * NativeRenderWaterfallView is a waterfall component, internal implementation is UICollectionView */ @interface NativeRenderWaterfallView : NativeRenderTouchesView { + NativeRenderCollectionViewDelegateWaterfallLayout, HippyScrollableProtocol, + HippyListTableViewLayoutProtocol, HippyScrollProtocol> { @protected NativeRenderWaterfallViewDataSource *_dataSource; - NativeRenderWaterfallViewDataSource *_previousDataSource; - NSMapTable *_weakItemMap; - NSMutableDictionary *_cachedItems; + + NSMapTable *_cachedWeakCellViews; NativeRenderHeaderRefresh *_headerRefreshView; NativeRenderFooterRefresh *_footerRefreshView; @@ -55,10 +56,6 @@ typedef NS_ENUM(NSInteger, NativeRenderScrollState) { BOOL _allowNextScrollNoMatterWhat; } -@property(nonatomic, assign) BOOL dirtyContent; - -@property(nonatomic, strong) WaterfallItemChangeContext *changeContext; - /** * Content inset for NativeRenderWaterfallView */ @@ -119,10 +116,6 @@ typedef NS_ENUM(NSInteger, NativeRenderScrollState) { @property (nonatomic, copy) HippyDirectEventBlock onRefresh; @property (nonatomic, copy) HippyDirectEventBlock onExposureReport; -- (NSUInteger)maxCachedItemCount; - -- (NSArray *)findFurthestIndexPathsFromScreen; - /** * Initial collection view */ @@ -166,9 +159,6 @@ typedef NS_ENUM(NSInteger, NativeRenderScrollState) { */ - (void)reloadData; -- (void)pushDataSource:(NSArray *)dataSource; -- (NSArray *)popDataSource; - /** * Reserved, not implemented */ diff --git a/renderer/native/ios/renderer/component/waterfalllist/NativeRenderWaterfallView.mm b/renderer/native/ios/renderer/component/waterfalllist/NativeRenderWaterfallView.mm index 1d8a7f19f87..dc15d726d7b 100644 --- a/renderer/native/ios/renderer/component/waterfalllist/NativeRenderWaterfallView.mm +++ b/renderer/native/ios/renderer/component/waterfalllist/NativeRenderWaterfallView.mm @@ -30,22 +30,20 @@ #import "HippyShadowView.h" #import "HippyUIManager.h" #import "UIView+Render.h" -#import "NativeRenderListTableView.h" #import "NativeRenderWaterfallViewCell.h" #import "HippyRootView.h" +#import "HippyShadowListView.h" static NSString *kCellIdentifier = @"HippyWaterfallCellIdentifier"; static NSString *kWaterfallItemName = @"WaterfallItem"; static const NSTimeInterval delayForPurgeView = 1.f; -@interface NativeRenderWaterfallView () { +@interface NativeRenderWaterfallView () { NSHashTable> *_scrollListeners; BOOL _isInitialListReady; UIColor *_backgroundColor; BOOL _manualScroll; - NSMutableArray *> *_dataSourcePool; - dispatch_semaphore_t _dataSourceSem; } @property (nonatomic, strong) NativeRenderCollectionViewWaterfallLayout *layout; @@ -60,6 +58,7 @@ @interface NativeRenderWaterfallView () *datasource = [self popDataSource]; - _dataSource = [[NativeRenderWaterfallViewDataSource alloc] initWithDataSource:datasource - itemViewName:[self compoentItemName] - containBannerView:_containBannerView]; -} - #pragma mark Setter & Getter -- (NSUInteger)maxCachedItemCount { - return NSUIntegerMax; -} - -- (NSUInteger)differenceFromIndexPath:(NSIndexPath *)indexPath1 againstAnother:(NSIndexPath *)indexPath2 { - NSAssert([NSThread mainThread], @"must be in main thread"); - long diffCount = 0; - for (NSUInteger index = MIN([indexPath1 section], [indexPath2 section]); index < MAX([indexPath1 section], [indexPath2 section]); index++) { - diffCount += [_collectionView numberOfItemsInSection:index]; - } - diffCount = diffCount + [indexPath1 row] - [indexPath2 row]; - return abs(diffCount); -} - -- (NSInteger)differenceFromIndexPath:(NSIndexPath *)indexPath againstVisibleIndexPaths:(NSArray *)visibleIndexPaths { - NSIndexPath *firstIndexPath = [visibleIndexPaths firstObject]; - NSIndexPath *lastIndexPath = [visibleIndexPaths lastObject]; - NSUInteger diffFirst = [self differenceFromIndexPath:indexPath againstAnother:firstIndexPath]; - NSUInteger diffLast = [self differenceFromIndexPath:indexPath againstAnother:lastIndexPath]; - return MIN(diffFirst, diffLast); -} - -- (NSArray *)findFurthestIndexPathsFromScreen { - NSUInteger visibleItemsCount = [[self.collectionView visibleCells] count]; - NSUInteger maxCachedItemCount = [self maxCachedItemCount] == NSUIntegerMax ? visibleItemsCount * 3 : [self maxCachedItemCount]; - NSUInteger cachedCount = [_cachedItems count]; - NSInteger cachedCountToRemove = cachedCount > maxCachedItemCount ? cachedCount - maxCachedItemCount : 0; - if (0 != cachedCountToRemove) { - NSArray *visibleIndexPaths = [_collectionView indexPathsForVisibleItems]; - NSArray *sortedCachedItemKey = [[_cachedItems allKeys] sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) { - NSIndexPath *ip1 = obj1; - NSIndexPath *ip2 = obj2; - NSUInteger ip1Diff = [self differenceFromIndexPath:ip1 againstVisibleIndexPaths:visibleIndexPaths]; - NSUInteger ip2Diff = [self differenceFromIndexPath:ip2 againstVisibleIndexPaths:visibleIndexPaths]; - if (ip1Diff > ip2Diff) { - return NSOrderedAscending; - } - else if (ip1Diff < ip2Diff) { - return NSOrderedDescending; - } - else { - return NSOrderedSame; - } - }]; - NSArray *result = [sortedCachedItemKey subarrayWithRange:NSMakeRange(0, cachedCountToRemove)]; - return result; - } - return nil; -} - -- (void)purgeFurthestIndexPathsFromScreen { - NSArray *furthestIndexPaths = [self findFurthestIndexPathsFromScreen]; - //purge view - NSArray *objects = [_cachedItems objectsForKeys:furthestIndexPaths notFoundMarker:@(-1)]; - [self.renderImpl purgeViewsFromComponentTags:objects onRootTag:self.rootTag]; - //purge cache - [_cachedItems removeObjectsForKeys:furthestIndexPaths]; -} - - (void)setContentInset:(UIEdgeInsets)contentInset { _contentInset = contentInset; @@ -282,19 +194,27 @@ - (BOOL)isManualScrolling { return _manualScroll; } + +#pragma mark - Data Reload + +- (void)hippyBridgeDidFinishTransaction { + HippyShadowListView *listNode = self.hippyShadowView; + if (!_dataSource || (listNode && listNode.itemChangeContext.hasChanges)) { + HippyLogTrace(@"🔥 %@ Reload %@", self.hippyTag, [[listNode itemChangeContext] description]); + [self cacheVisibleCellViewsForReuse]; + [self reloadData]; + [listNode.itemChangeContext clear]; + } +} + - (void)reloadData { - [self refreshItemNodes]; - [_dataSource applyDiff:_previousDataSource - changedConext:self.changeContext - forWaterfallView:self.collectionView - completion:^(BOOL success) { - if (success) { - self->_previousDataSource = [self->_dataSource copy]; - } - else { - self->_previousDataSource = nil; - } - }]; + NSArray *datasource = [self.hippyShadowView.subcomponents copy]; + _dataSource = [[NativeRenderWaterfallViewDataSource alloc] initWithDataSource:datasource + itemViewName:[self compoentItemName] + containBannerView:_containBannerView]; + + [self.collectionView reloadData]; + if (!_isInitialListReady) { _isInitialListReady = YES; if (self.onInitialListReady) { @@ -303,20 +223,6 @@ - (void)reloadData { } } -- (void)pushDataSource:(NSArray *)dataSource { - dispatch_semaphore_wait(_dataSourceSem, DISPATCH_TIME_FOREVER); - [_dataSourcePool addObject:dataSource]; - dispatch_semaphore_signal(_dataSourceSem); -} - -- (NSArray *)popDataSource { - dispatch_semaphore_wait(_dataSourceSem, DISPATCH_TIME_FOREVER); - NSArray *datasource = [_dataSourcePool lastObject]; - [_dataSourcePool removeLastObject]; - dispatch_semaphore_signal(_dataSourceSem); - return datasource; -} - - (void)insertHippySubview:(UIView *)subview atIndex:(NSInteger)atIndex { if ([subview isKindOfClass:[NativeRenderHeaderRefresh class]]) { if (_headerRefreshView) { @@ -326,7 +232,6 @@ - (void)insertHippySubview:(UIView *)subview atIndex:(NSInteger)atIndex { [_headerRefreshView setScrollView:self.collectionView]; _headerRefreshView.delegate = self; _headerRefreshView.frame = subview.hippyShadowView.frame; - [_weakItemMap setObject:subview forKey:[subview hippyTag]]; } else if ([subview isKindOfClass:[NativeRenderFooterRefresh class]]) { if (_footerRefreshView) { [_footerRefreshView removeFromSuperview]; @@ -337,25 +242,11 @@ - (void)insertHippySubview:(UIView *)subview atIndex:(NSInteger)atIndex { _footerRefreshView.frame = subview.hippyShadowView.frame; UIEdgeInsets insets = self.collectionView.contentInset; self.collectionView.contentInset = UIEdgeInsetsMake(insets.top, insets.left, _footerRefreshView.frame.size.height, insets.right); - [_weakItemMap setObject:subview forKey:[subview hippyTag]]; } } -- (NSArray *)subcomponents { - return [[_weakItemMap dictionaryRepresentation] allValues]; -} - -- (void)removeFromHippySuperview { - [super removeFromHippySuperview]; - [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(purgeFurthestIndexPathsFromScreen) object:nil]; - [self purgeFurthestIndexPathsFromScreen]; -} - -- (void)didMoveToWindow { - if (!self.window) { - [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(purgeFurthestIndexPathsFromScreen) object:nil]; - [self purgeFurthestIndexPathsFromScreen]; - } +- (void)didUpdateHippySubviews { + // Do nothing, as subviews is managed by `insertHippySubview:atIndex:` or lazy-created } #pragma mark - UICollectionViewDataSource @@ -369,7 +260,21 @@ - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSe - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { NativeRenderWaterfallViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kCellIdentifier forIndexPath:indexPath]; - [self addCellViewToCollectionViewCell:cell atIndexPath:indexPath]; + HippyShadowView *shadowView = [_dataSource cellForIndexPath:indexPath]; + + UIView *cellView = nil; + UIView *cachedCellView = [_cachedWeakCellViews objectForKey:shadowView.hippyTag]; + if (cachedCellView && + [shadowView isKindOfClass:NativeRenderObjectWaterfallItem.class] && + !((NativeRenderObjectWaterfallItem *)shadowView).layoutDirty) { + cellView = cachedCellView; + } else { + cellView = [self.renderImpl createViewForShadowListItem:shadowView]; + [_cachedWeakCellViews setObject:cellView forKey:shadowView.hippyTag]; + } + + cell.cellView = cellView; + cellView.parent = self; return cell; } @@ -395,30 +300,6 @@ - (void)collectionView:(UICollectionView *)collectionView } } -- (void)collectionView:(UICollectionView *)collectionView - didEndDisplayingCell:(UICollectionViewCell *)cell - forItemAtIndexPath:(NSIndexPath *)indexPath { - if ([cell isKindOfClass:[NativeRenderWaterfallViewCell class]]) { - NativeRenderWaterfallViewCell *hpCell = (NativeRenderWaterfallViewCell *)cell; - if (hpCell.cellView) { - [_cachedItems setObject:[hpCell.cellView hippyTag] forKey:indexPath]; - } - } -} - -- (void)addCellViewToCollectionViewCell:(UICollectionViewCell *)cell atIndexPath:(NSIndexPath *)indexPath { - NativeRenderWaterfallViewCell *hpCell = (NativeRenderWaterfallViewCell *)cell; - HippyShadowView *renderObjectView = [_dataSource cellForIndexPath:indexPath]; - [renderObjectView recusivelySetCreationTypeToInstant]; - UIView *cellView = [self.renderImpl createViewRecursivelyFromRenderObject:renderObjectView]; - if (cellView) { - [_cachedItems removeObjectForKey:indexPath]; - } - hpCell.cellView = cellView; - cellView.parentComponent = self; - [_weakItemMap setObject:cellView forKey:[cellView hippyTag]]; -} - #pragma mark - NativeRenderCollectionViewDelegateWaterfallLayout - (CGSize)collectionView:(UICollectionView *)collectionView @@ -487,8 +368,6 @@ - (void)scrollViewDidScroll:(UIScrollView *)scrollView { [scrollViewListener scrollViewDidScroll:scrollView]; } } - [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(purgeFurthestIndexPathsFromScreen) object:nil]; - [self performSelector:@selector(purgeFurthestIndexPathsFromScreen) withObject:nil afterDelay:delayForPurgeView]; [_headerRefreshView scrollViewDidScroll]; [_footerRefreshView scrollViewDidScroll]; } @@ -679,7 +558,7 @@ - (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView { } - (void)tableViewDidLayoutSubviews:(NativeRenderListTableView *)tableView { - + [self clearVisibleCellViewsCacheBeforeReload]; } - (void)refreshCompleted:(NSInteger)status text:(NSString *)text { @@ -761,18 +640,32 @@ - (void)didMoveToSuperview { } -#pragma mark - +#pragma mark - Memory optimization - (void)didReceiveMemoryWarning { [self cleanUpCachedItems]; } - (void)cleanUpCachedItems { - //purge view - NSArray *objects = [_cachedItems allValues]; - [self.renderImpl purgeViewsFromComponentTags:objects onRootTag:self.rootTag]; - //purge cache - [_cachedItems removeAllObjects]; + // nop +} + +- (void)cacheVisibleCellViewsForReuse { + // Before reload, cache the current visible cellViews temporarily, + // because cells can potentially be reused. + // And remove them when the reload is complete in `tableViewDidLayoutSubviews` method. + NSArray *visibleCells = [self.collectionView visibleCells]; + NSMutableArray *visibleCellViews = [NSMutableArray arrayWithCapacity:visibleCells.count]; + for (UICollectionViewCell *cell in visibleCells) { + if ([cell isKindOfClass:NativeRenderWaterfallViewCell.class]) { + [visibleCellViews addObject:((NativeRenderWaterfallViewCell *)cell).cellView]; + } + } + _visibleCellViewsBeforeReload = visibleCellViews; +} + +- (void)clearVisibleCellViewsCacheBeforeReload { + _visibleCellViewsBeforeReload = nil; } @end diff --git a/renderer/native/ios/renderer/component/waterfalllist/NativeRenderWaterfallViewDataSource.h b/renderer/native/ios/renderer/component/waterfalllist/NativeRenderWaterfallViewDataSource.h index 7309b6d4b4c..8b9330da8e2 100644 --- a/renderer/native/ios/renderer/component/waterfalllist/NativeRenderWaterfallViewDataSource.h +++ b/renderer/native/ios/renderer/component/waterfalllist/NativeRenderWaterfallViewDataSource.h @@ -25,7 +25,7 @@ NS_ASSUME_NONNULL_BEGIN -@class HippyShadowView, WaterfallItemChangeContext; +@class HippyShadowView; @interface NativeRenderWaterfallViewDataSource : NSObject @@ -47,10 +47,6 @@ NS_ASSUME_NONNULL_BEGIN - (NSIndexPath *)indexPathForFlatIndex:(NSInteger)index; - (NSInteger)flatIndexForIndexPath:(NSIndexPath *)indexPath; -- (void)applyDiff:(NativeRenderWaterfallViewDataSource *)another - changedConext:(WaterfallItemChangeContext *)context - forWaterfallView:(UICollectionView *)view - completion:(void(^)(BOOL success))completion; @end diff --git a/renderer/native/ios/renderer/component/waterfalllist/NativeRenderWaterfallViewDataSource.mm b/renderer/native/ios/renderer/component/waterfalllist/NativeRenderWaterfallViewDataSource.mm index 86c1f7c946a..02bc1755cd1 100644 --- a/renderer/native/ios/renderer/component/waterfalllist/NativeRenderWaterfallViewDataSource.mm +++ b/renderer/native/ios/renderer/component/waterfalllist/NativeRenderWaterfallViewDataSource.mm @@ -25,7 +25,7 @@ #import "HippyAssert.h" #import "NativeRenderWaterfallViewDataSource.h" #import "HippyShadowView.h" -#import "NativeRenderObjectWaterfall.h" +#import "HippyShadowListView.h" @interface NativeRenderWaterfallViewDataSource () { BOOL _containBannerView; @@ -178,14 +178,5 @@ - (NSInteger)flatIndexForIndexPath:(NSIndexPath *)indexPath { return index; } -- (void)applyDiff:(NativeRenderWaterfallViewDataSource *)another - changedConext:(WaterfallItemChangeContext *)context - forWaterfallView:(UICollectionView *)view - completion:(void(^)(BOOL success))completion{ - [view reloadData]; - completion(YES); - return; - // Note:原增量刷新代码存在大量问题,已删除 -} @end diff --git a/renderer/native/ios/renderer/component/waterfalllist/NativeRenderWaterfallViewManager.mm b/renderer/native/ios/renderer/component/waterfalllist/NativeRenderWaterfallViewManager.mm index 15c82d7905c..4421ece300f 100644 --- a/renderer/native/ios/renderer/component/waterfalllist/NativeRenderWaterfallViewManager.mm +++ b/renderer/native/ios/renderer/component/waterfalllist/NativeRenderWaterfallViewManager.mm @@ -22,7 +22,7 @@ #import "NativeRenderWaterfallViewManager.h" #import "NativeRenderWaterfallView.h" -#import "NativeRenderObjectWaterfall.h" +#import "HippyShadowListView.h" #import "HippyUIManager.h" @implementation NativeRenderWaterfallViewManager @@ -50,7 +50,7 @@ - (UIView *)view { } - (HippyShadowView *)hippyShadowView { - return [[NativeRenderObjectWaterfall alloc] init]; + return [[HippyShadowListView alloc] init]; } HIPPY_EXPORT_METHOD(refreshCompleted:(nonnull NSNumber *)reactTag