feat:add subview in iOS

This commit is contained in:
pengfei.zhou 2019-11-15 14:57:41 +08:00
parent bdabde2f56
commit 52fcafeb92
15 changed files with 282 additions and 133 deletions

View File

@ -67,7 +67,7 @@ public abstract class GroupNode<F extends ViewGroup> extends SuperNode<F> {
if (id.equals(oldNode.getId())) {
//The same,skip
} else {
//Find in remaining nodes
//Find in remain nodes
int position = -1;
for (int start = idx + 1; start < mChildNodes.size(); start++) {
ViewNode node = mChildNodes.get(start);

View File

@ -25,7 +25,7 @@
@implementation DoricContextHolder
- (instancetype)initWithContext:(DoricContext *)doricContext {
if (self = [super init]) {
if (self = [self init]) {
_doricContext = doricContext;
}
return self;

View File

@ -20,19 +20,11 @@
// Created by pengfei.zhou on 2019/7/30.
//
#import "DoricViewNode.h"
#import "DoricSuperNode.h"
NS_ASSUME_NONNULL_BEGIN
@interface DoricGroupNode <V:UIView *, P:DoricLayoutConfig *> : DoricViewNode<V>
@property(nonatomic, strong) NSMutableDictionary *children;
@property(nonatomic, strong) NSMutableArray *indexedChildren;
- (void)blendChild:(DoricViewNode *)child layoutConfig:(NSDictionary *)layoutConfig;
- (P)generateDefaultLayoutParams;
@interface DoricGroupNode <V:UIView *, P:DoricLayoutConfig *> : DoricSuperNode<V, P>
@end
NS_ASSUME_NONNULL_END

View File

@ -23,124 +23,117 @@
#import <Doric/DoricExtensions.h>
#import "DoricGroupNode.h"
@interface DoricGroupNode ()
@property(nonatomic, copy) NSArray<DoricViewNode *> *childNodes;
@property(nonatomic, copy) NSArray <NSString *> *childViewIds;
@end
@implementation DoricGroupNode
- (instancetype)initWithContext:(DoricContext *)doricContext {
if (self = [super initWithContext:doricContext]) {
_children = [[NSMutableDictionary alloc] init];
_indexedChildren = [[NSMutableArray alloc] init];
_childNodes = @[];
_childViewIds = @[];
}
return self;
}
- (UIView *)build:(NSDictionary *)props {
- (UIView *)build {
UIView *ret = [[UIView alloc] init];
ret.clipsToBounds = YES;
return ret;
}
- (void)blendView:(UIView *)view forPropName:(NSString *)name propValue:(id)prop {
if ([name isEqualToString:@"children"]) {
NSArray *array = prop;
NSInteger i;
NSMutableArray *tobeRemoved = [[NSMutableArray alloc] init];
for (i = 0; i < array.count; i++) {
NSDictionary *val = array[i];
if (!val || (NSNull *) val == [NSNull null]) {
continue;
}
NSString *type = val[@"type"];
NSString *viewId = val[@"id"];
DoricViewNode *node = self.children[viewId];
if (node == nil) {
node = [DoricViewNode create:self.doricContext withType:type];
node.index = i;
node.parent = self;
node.viewId = viewId;
self.children[viewId] = node;
} else {
if (i != node.index) {
[self.indexedChildren removeObjectAtIndex:i];
node.index = i;
[node.view removeFromSuperview];
}
[tobeRemoved removeObject:node];
}
DoricViewNode *old = i >= self.indexedChildren.count ? nil : self.indexedChildren[i];
if (old && old != node) {
[old.view removeFromSuperview];
self.indexedChildren[i] = [NSNull null];
[tobeRemoved addObject:old];
}
DoricLayoutConfig *params = node.layoutConfig;
if (params == nil) {
params = [self generateDefaultLayoutParams];
node.layoutConfig = params;
}
[node blend:val[@"props"]];
if (self.indexedChildren.count <= i) {
[self.view addSubview:node.view];
[self.indexedChildren addObject:node];
} else if (self.indexedChildren[i] == [NSNull null]) {
self.indexedChildren[i] = node;
[self.view insertSubview:node.view atIndex:i];
}
}
NSInteger start = i;
while (start < self.indexedChildren.count) {
DoricViewNode *node = self.indexedChildren[(NSUInteger) start];
if (node) {
[self.children removeObjectForKey:node.viewId];
[node.view removeFromSuperview];
[tobeRemoved removeObject:node];
}
start++;
}
if (i < self.indexedChildren.count) {
[self.indexedChildren removeObjectsInRange:NSMakeRange((NSUInteger) i, self.indexedChildren.count - i)];
}
for (DoricViewNode *node in tobeRemoved) {
[self.children removeObjectForKey:node.viewId];
}
if ([@"children" isEqualToString:name]) {
self.childViewIds = prop;
} else {
[super blendView:view forPropName:name propValue:prop];
}
}
- (void)blend:(NSDictionary *)props {
[super blend:props];
[self configChildNodes];
}
- (DoricLayoutConfig *)generateDefaultLayoutParams {
DoricLayoutConfig *params = [[DoricLayoutConfig alloc] init];
return params;
}
- (void)blendChild:(DoricViewNode *)child layoutConfig:(NSDictionary *)layoutConfig {
DoricLayoutConfig *params = child.layoutConfig;
- (void)configChildNodes {
NSMutableArray *childNodes = [self.childNodes mutableCopy];
for (NSUInteger idx = 0; idx < self.childViewIds.count; idx++) {
NSString *viewId = self.childViewIds[idx];
NSDictionary *model = [self subModelOf:viewId];
NSString *type = model[@"type"];
if (idx < self.childNodes.count) {
DoricViewNode *oldNode = childNodes[idx];
if ([viewId isEqualToString:oldNode.viewId]) {
///Same,skip
} else {
///Find in remain nodes
NSInteger position = -1;
for (NSUInteger start = idx + 1; start < childNodes.count; start++) {
DoricViewNode *node = childNodes[start];
if ([viewId isEqualToString:node.viewId]) {
position = start;
break;
}
}
if (position >= 0) {
///Found ,swap idx,position
DoricViewNode *reused = childNodes[(NSUInteger) position];
[childNodes removeObjectAtIndex:(NSUInteger) position];
[childNodes removeObjectAtIndex:idx];
[childNodes insertObject:reused atIndex:idx];
[childNodes insertObject:oldNode atIndex:(NSUInteger) position];
[layoutConfig[@"widthSpec"] also:^(NSNumber *it) {
if (it) {
params.widthSpec = (DoricLayoutSpec) [it integerValue];
}
}];
///View swap index
[reused.view removeFromSuperview];
[oldNode.view removeFromSuperview];
[self.view insertSubview:reused.view atIndex:idx];
[self.view insertSubview:oldNode.view atIndex:position];
} else {
///Not found,insert
DoricViewNode *viewNode = [DoricViewNode create:self.doricContext withType:type];
viewNode.viewId = viewId;
[viewNode initWithSuperNode:self];
[viewNode blend:model[@"props"]];
[childNodes insertObject:viewNode atIndex:idx];
[self.view insertSubview:viewNode.view atIndex:idx];
}
}
[layoutConfig[@"heightSpec"] also:^(NSNumber *it) {
if (it) {
params.heightSpec = (DoricLayoutSpec) [it integerValue];
}
}];
if ([params isKindOfClass:DoricMarginConfig.class]) {
DoricMarginConfig *marginParams = (DoricMarginConfig *) params;
NSDictionary *margin = layoutConfig[@"margin"];
if (margin) {
marginParams.margin = DoricMarginMake(
[(NSNumber *) margin[@"left"] floatValue],
[(NSNumber *) margin[@"top"] floatValue],
[(NSNumber *) margin[@"right"] floatValue],
[(NSNumber *) margin[@"bottom"] floatValue]);
} else {
/// Insert
DoricViewNode *viewNode = [DoricViewNode create:self.doricContext withType:type];
viewNode.viewId = viewId;
[viewNode initWithSuperNode:self];
[viewNode blend:model[@"props"]];
[childNodes addObject:viewNode];
[self.view addSubview:viewNode.view];
}
}
NSUInteger count = childNodes.count;
for (NSUInteger idx = self.childViewIds.count; idx < count; idx++) {
DoricViewNode *viewNode = childNodes.lastObject;
[childNodes removeLastObject];
[viewNode.view removeFromSuperview];
}
self.childNodes = [childNodes copy];
}
- (void)blendSubNode:(NSDictionary *)subModel {
NSString *viewId = subModel[@"id"];
[self.childNodes enumerateObjectsUsingBlock:^(DoricViewNode *obj, NSUInteger idx, BOOL *stop) {
if ([viewId isEqualToString:obj.viewId]) {
[obj blend:subModel[@"props"]];
*stop = YES;
}
}];
}
@end

View File

@ -24,7 +24,7 @@
#import "DoricUtil.h"
@implementation DoricHLayoutNode
- (DoricHLayoutView *)build:(NSDictionary *)props {
- (DoricHLayoutView *)build {
return [DoricHLayoutView new];
}
@ -38,13 +38,13 @@ - (void)blendView:(DoricHLayoutView *)view forPropName:(NSString *)name propValu
}
}
- (void)blendChild:(DoricViewNode *)child layoutConfig:(NSDictionary *)layoutConfig {
[super blendChild:child layoutConfig:layoutConfig];
if (![child.layoutConfig isKindOfClass:DoricLinearConfig.class]) {
- (void)blendSubNode:(DoricViewNode *)subNode layoutConfig:(NSDictionary *)layoutConfig {
[super blendSubNode:subNode layoutConfig:layoutConfig];
if (![subNode.layoutConfig isKindOfClass:DoricLinearConfig.class]) {
DoricLog(@"blend DoricHLayoutView child error,layout params not match");
return;
}
DoricLinearConfig *params = (DoricLinearConfig *) child.layoutConfig;
DoricLinearConfig *params = (DoricLinearConfig *) subNode.layoutConfig;
NSDictionary *margin = layoutConfig[@"margin"];
if (margin) {
params.margin = DoricMarginMake(

View File

@ -25,7 +25,7 @@
@implementation DoricImageNode
- (UIImageView *)build:(NSDictionary *)props {
- (UIImageView *)build {
return [[UIImageView alloc] init];
}

View File

@ -25,7 +25,6 @@
@implementation DoricRootNode
- (void)setupRootView:(DoricStackView *)view {
self.view = view;
self.layoutConfig = view.layoutConfig;
}
- (void)render:(NSDictionary *)props {

View File

@ -25,7 +25,7 @@
@implementation DoricStackNode
- (DoricStackView *)build:(NSDictionary *)props {
- (DoricStackView *)build {
return [DoricStackView new];
}
@ -41,13 +41,13 @@ - (DoricStackConfig *)generateDefaultLayoutParams {
return [[DoricStackConfig alloc] init];
}
- (void)blendChild:(DoricViewNode *)child layoutConfig:(NSDictionary *)layoutConfig {
[super blendChild:child layoutConfig:layoutConfig];
if (![child.layoutConfig isKindOfClass:DoricStackConfig.class]) {
- (void)blendSubNode:(DoricViewNode *)subNode layoutConfig:(NSDictionary *)layoutConfig {
[super blendSubNode:subNode layoutConfig:layoutConfig];
if (![subNode.layoutConfig isKindOfClass:DoricStackConfig.class]) {
DoricLog(@"blend DoricHLayoutView child error,layout params not match");
return;
}
DoricStackConfig *params = (DoricStackConfig *) child.layoutConfig;
DoricStackConfig *params = (DoricStackConfig *) subNode.layoutConfig;
NSNumber *alignment = layoutConfig[@"alignment"];
if (alignment) {
params.alignment = (DoricGravity) [alignment integerValue];

View File

@ -0,0 +1,35 @@
/*
* 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 <Foundation/Foundation.h>
#import "DoricViewNode.h"
@interface DoricSuperNode<V:UIView *, P:DoricLayoutConfig *> : DoricViewNode<V>
- (P)generateDefaultLayoutParams;
- (void)blendSubNode:(DoricViewNode *)subNode layoutConfig:(NSDictionary *)layoutConfig;
- (void)blendSubNode:(NSDictionary *)subModel;
- (NSDictionary *)subModelOf:(NSString *)viewId;
- (void)setSubModel:(NSDictionary *)model in:(NSString *)viewId;
- (void)clearSubModel;
@end

View File

@ -0,0 +1,117 @@
/*
* 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 "DoricSuperNode.h"
#import "DoricExtensions.h"
@interface DoricSuperNode ()
@property(nonatomic, strong) NSMutableDictionary <NSString *, NSMutableDictionary *> *subNodes;
@end
@implementation DoricSuperNode
- (instancetype)initWithContext:(DoricContext *)doricContext {
if (self = [super initWithContext:doricContext]) {
_subNodes = [NSMutableDictionary new];
}
return self;
}
- (void)blendView:(UIView *)view forPropName:(NSString *)name propValue:(id)prop {
if ([@"subviews" isEqualToString:name]) {
NSArray *subviews = prop;
for (NSDictionary *subModel in subviews) {
[self mixinSubNode:subModel];
[self blendSubNode:subModel];
}
}
}
- (void)mixinSubNode:(NSDictionary *)dictionary {
NSString *viewId = dictionary[@"id"];
NSMutableDictionary *oldModel = self.subNodes[viewId];
if (oldModel) {
[self mixin:dictionary to:oldModel];
} else {
self.subNodes[viewId] = [dictionary mutableCopy];
}
}
- (void)mixin:(NSDictionary *)srcModel to:(NSMutableDictionary *)targetModel {
NSDictionary *srcProp = srcModel[@"props"];
NSMutableDictionary *targetProp = [targetModel[@"props"] mutableCopy];
[srcProp enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) {
if (![@"subviews" isEqualToString:key]) {
targetProp[key] = obj;
}
}];
targetModel[@"props"] = [targetProp copy];
}
- (void)blendSubNode:(DoricViewNode *)subNode layoutConfig:(NSDictionary *)layoutConfig {
DoricLayoutConfig *params = subNode.layoutConfig;
[layoutConfig[@"widthSpec"] also:^(NSNumber *it) {
if (it) {
params.widthSpec = (DoricLayoutSpec) [it integerValue];
}
}];
[layoutConfig[@"heightSpec"] also:^(NSNumber *it) {
if (it) {
params.heightSpec = (DoricLayoutSpec) [it integerValue];
}
}];
if ([params isKindOfClass:DoricMarginConfig.class]) {
DoricMarginConfig *marginParams = (DoricMarginConfig *) params;
NSDictionary *margin = layoutConfig[@"margin"];
if (margin) {
marginParams.margin = DoricMarginMake(
[(NSNumber *) margin[@"left"] floatValue],
[(NSNumber *) margin[@"top"] floatValue],
[(NSNumber *) margin[@"right"] floatValue],
[(NSNumber *) margin[@"bottom"] floatValue]);
}
}
}
- (void)blendSubNode:(NSDictionary *)subModel {
NSAssert(NO, @"Should override class:%@ ,method:%@.", NSStringFromClass([self class]),
NSStringFromSelector(_cmd));
}
- (DoricLayoutConfig *)generateDefaultLayoutParams {
DoricLayoutConfig *params = [[DoricLayoutConfig alloc] init];
return params;
}
- (NSDictionary *)subModelOf:(NSString *)viewId {
return self.subNodes[viewId];
}
- (void)setSubModel:(NSDictionary *)model in:(NSString *)viewId {
self.subNodes[viewId] = [model mutableCopy];
}
- (void)clearSubModel {
[self.subNodes removeAllObjects];
}
@end

View File

@ -25,7 +25,7 @@
#import "DoricGroupNode.h"
@implementation DoricTextNode
- (id)build:(NSDictionary *)props {
- (UILabel *)build {
return [[UILabel alloc] init];
}

View File

@ -25,7 +25,7 @@
@implementation DoricVLayoutNode
- (DoricVLayoutView *)build:(NSDictionary *)props {
- (DoricVLayoutView *)build {
return [DoricVLayoutView new];
}
@ -39,13 +39,13 @@ - (void)blendView:(DoricVLayoutView *)view forPropName:(NSString *)name propValu
}
}
- (void)blendChild:(DoricViewNode *)child layoutConfig:(NSDictionary *)layoutconfig {
[super blendChild:child layoutConfig:layoutconfig];
if (![child.layoutConfig isKindOfClass:DoricLinearConfig.class]) {
- (void)blendSubNode:(DoricViewNode *)subNode layoutConfig:(NSDictionary *)layoutconfig {
[super blendSubNode:subNode layoutConfig:layoutconfig];
if (![subNode.layoutConfig isKindOfClass:DoricLinearConfig.class]) {
DoricLog(@"blend DoricVLayoutView child error,layout params not match");
return;
}
DoricLinearConfig *params = (DoricLinearConfig *) child.layoutConfig;
DoricLinearConfig *params = (DoricLinearConfig *) subNode.layoutConfig;
NSDictionary *margin = layoutconfig[@"margin"];
if (margin) {
params.margin = DoricMarginMake(

View File

@ -25,22 +25,24 @@
#import "UIView+Doric.h"
NS_ASSUME_NONNULL_BEGIN
@class DoricGroupNode;
@class DoricSuperNode;
@interface DoricViewNode <V:UIView *> : DoricContextHolder
@property(nonatomic, strong) V view;
@property(nonatomic, weak) DoricGroupNode *parent;
@property(nonatomic, weak) DoricSuperNode *superNode;
@property(nonatomic) NSInteger index;
@property(nonatomic, strong) NSString *viewId;
@property(nonatomic, copy) NSString *viewId;
@property(nonatomic, strong) DoricLayoutConfig *layoutConfig;
@property(nonatomic, readonly) DoricLayoutConfig *layoutConfig;
@property(nonatomic, strong, readonly) NSArray<NSString *> *idList;
@property(nonatomic, readonly) NSArray<NSString *> *idList;
- (V)build:(NSDictionary *)props;
- (void)initWithSuperNode:(DoricSuperNode *)superNode;
- (V)build;
- (void)blend:(NSDictionary *)props;

View File

@ -25,6 +25,8 @@
#import "DoricGroupNode.h"
#import "DoricRootNode.h"
#import "DoricConstant.h"
#import "DoricSuperNode.h"
#import "DoricExtensions.h"
void DoricAddEllipticArcPath(CGMutablePathRef path,
CGPoint origin,
@ -77,14 +79,23 @@ - (instancetype)initWithContext:(DoricContext *)doricContext {
return self;
}
- (UIView *)build:(NSDictionary *)props {
- (void)initWithSuperNode:(DoricSuperNode *)superNode {
self.superNode = superNode;
self.view = [[self build] also:^(UIView *it) {
it.layoutConfig = [superNode generateDefaultLayoutParams];
}];
}
- (DoricLayoutConfig *)layoutConfig {
return self.view.layoutConfig;
}
- (UIView *)build {
return [[UIView alloc] init];
}
- (void)blend:(NSDictionary *)props {
if (self.view == nil) {
self.view = [self build:props];
}
self.view.layoutConfig = self.layoutConfig;
for (NSString *key in props) {
id value = props[key];
@ -110,8 +121,8 @@ - (void)blendView:(UIView *)view forPropName:(NSString *)name propValue:(id)prop
} else if ([name isEqualToString:@"bgColor"]) {
view.backgroundColor = DoricColor(prop);
} else if ([name isEqualToString:@"layoutConfig"]) {
if (self.parent && [prop isKindOfClass:[NSDictionary class]]) {
[self.parent blendChild:self layoutConfig:prop];
if (self.superNode && [prop isKindOfClass:[NSDictionary class]]) {
[self.superNode blendSubNode:self layoutConfig:prop];
}
} else if ([name isEqualToString:@"onClick"]) {
self.callbackIds[@"onClick"] = prop;
@ -176,7 +187,7 @@ - (void)onClick:(UIView *)view {
DoricViewNode *node = self;
do {
[ret addObject:node.viewId];
node = node.parent;
node = node.superNode;
} while (node && ![node isKindOfClass:[DoricRootNode class]]);
return [[ret reverseObjectEnumerator] allObjects];
@ -203,7 +214,7 @@ + (DoricViewNode *)create:(DoricContext *)context withType:(NSString *)type {
}
- (void)requestLayout {
[self.parent requestLayout];
[self.superNode requestLayout];
}
@end

View File

@ -26,7 +26,7 @@ void DoricLog(NSString *_Nonnull format, ...);
UIColor *_Nonnull DoricColor(NSNumber *_Nonnull number);
NSBundle *DoricBundle();
NSBundle *_Nonnull DoricBundle(void);
#ifndef DC_LOCK
#define DC_LOCK(lock) dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);