diff --git a/iOS/Example/Example.xcodeproj/project.pbxproj b/iOS/Example/Example.xcodeproj/project.pbxproj index 43cd63ad..0a47f19e 100644 --- a/iOS/Example/Example.xcodeproj/project.pbxproj +++ b/iOS/Example/Example.xcodeproj/project.pbxproj @@ -20,6 +20,7 @@ E2334AFE22E9D2070098A085 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = E2334AFD22E9D2070098A085 /* main.m */; }; E2334B0822E9D2070098A085 /* ExampleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E2334B0722E9D2070098A085 /* ExampleTests.m */; }; E2334B1322E9D2070098A085 /* ExampleUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = E2334B1222E9D2070098A085 /* ExampleUITests.m */; }; + E2BF7BF7237E8E9F001B0EDC /* ListDemo.js in Resources */ = {isa = PBXBuildFile; fileRef = E2BF7BF6237E8E9F001B0EDC /* ListDemo.js */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -67,6 +68,7 @@ E2334B0E22E9D2070098A085 /* ExampleUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ExampleUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; E2334B1222E9D2070098A085 /* ExampleUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ExampleUITests.m; sourceTree = ""; }; E2334B1422E9D2070098A085 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + E2BF7BF6237E8E9F001B0EDC /* ListDemo.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = ListDemo.js; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -123,6 +125,7 @@ E21DC9D12302865E00660C5C /* src */ = { isa = PBXGroup; children = ( + E2BF7BF6237E8E9F001B0EDC /* ListDemo.js */, E21DC9D22302865E00660C5C /* Snake.js */, E21DC9D32302865E00660C5C /* Counter.js */, ); @@ -295,6 +298,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + E2BF7BF7237E8E9F001B0EDC /* ListDemo.js in Resources */, E21DC9D42302870000660C5C /* Snake.js in Resources */, E21DC9D52302870000660C5C /* Counter.js in Resources */, E2334AFB22E9D2070098A085 /* LaunchScreen.storyboard in Resources */, diff --git a/iOS/Example/Example/ViewController.m b/iOS/Example/Example/ViewController.m index bb96000b..cd3b433d 100644 --- a/iOS/Example/Example/ViewController.m +++ b/iOS/Example/Example/ViewController.m @@ -26,7 +26,7 @@ @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; - NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"Snake" ofType:@"js"]; + NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"ListDemo" ofType:@"js"]; NSString *jsContent = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil]; self.doricContext = [[DoricContext alloc] initWithScript:jsContent source:@"test.js"]; [self.doricContext.rootNode setupRootView:[[DoricStackView new] also:^(DoricStackView *it) { diff --git a/iOS/Pod/Classes/DoricRegistry.m b/iOS/Pod/Classes/DoricRegistry.m index 93204eec..81b3b0c2 100644 --- a/iOS/Pod/Classes/DoricRegistry.m +++ b/iOS/Pod/Classes/DoricRegistry.m @@ -28,6 +28,8 @@ #import "DoricHLayoutNode.h" #import "DoricTextNode.h" #import "DoricImageNode.h" +#import "DoricListNode.h" +#import "DoricListItemNode.h" @interface DoricRegistry () @@ -58,6 +60,8 @@ - (void)innerRegister { [self registerViewNode:DoricHLayoutNode.class withName:@"HLayout"]; [self registerViewNode:DoricTextNode.class withName:@"Text"]; [self registerViewNode:DoricImageNode.class withName:@"Image"]; + [self registerViewNode:DoricListNode.class withName:@"List"]; + [self registerViewNode:DoricListItemNode.class withName:@"ListItem"]; } - (void)registerJSBundle:(NSString *)bundle withName:(NSString *)name { diff --git a/iOS/Pod/Classes/Shader/DoricListItemNode.h b/iOS/Pod/Classes/Shader/DoricListItemNode.h new file mode 100644 index 00000000..ded76f48 --- /dev/null +++ b/iOS/Pod/Classes/Shader/DoricListItemNode.h @@ -0,0 +1,26 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Created by pengfei.zhou on 2019/11/15. +// + +#import + +#import "DoricStackNode.h" + +@interface DoricListItemNode : DoricGroupNode + +@end \ No newline at end of file diff --git a/iOS/Pod/Classes/Shader/DoricListItemNode.m b/iOS/Pod/Classes/Shader/DoricListItemNode.m new file mode 100644 index 00000000..bba08457 --- /dev/null +++ b/iOS/Pod/Classes/Shader/DoricListItemNode.m @@ -0,0 +1,27 @@ +/* + * 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 "DoricListItemNode.h" + + +@implementation DoricListItemNode +- (UITableViewCell *)build { + return nil; +} +@end \ No newline at end of file diff --git a/iOS/Pod/Classes/Shader/DoricListNode.h b/iOS/Pod/Classes/Shader/DoricListNode.h new file mode 100644 index 00000000..55750e4b --- /dev/null +++ b/iOS/Pod/Classes/Shader/DoricListNode.h @@ -0,0 +1,24 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Created by pengfei.zhou on 2019/11/15. +// + +#import +#import "DoricSuperNode.h" +@interface DoricListNode : DoricSuperNode + +@end \ No newline at end of file diff --git a/iOS/Pod/Classes/Shader/DoricListNode.m b/iOS/Pod/Classes/Shader/DoricListNode.m new file mode 100644 index 00000000..f0b6cb29 --- /dev/null +++ b/iOS/Pod/Classes/Shader/DoricListNode.m @@ -0,0 +1,113 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Created by pengfei.zhou on 2019/11/15. +// + +#import +#import "DoricListNode.h" +#import "DoricExtensions.h" +#import "DoricListItemNode.h" + +@interface DoricListNode () +@property(nonatomic, strong) NSMutableDictionary *tempNodes; +@property(nonatomic, assign) NSUInteger itemCount; +@property(nonatomic, assign) NSUInteger batchCount; +@end + +@implementation DoricListNode +- (instancetype)initWithContext:(DoricContext *)doricContext { + if (self = [super initWithContext:doricContext]) { + _tempNodes = [NSMutableDictionary new]; + _batchCount = 15; + } + return self; +} + +- (UITableView *)build { + return [[UITableView new] also:^(UITableView *it) { + it.dataSource = self; + it.delegate = self; + }]; +} + +- (void)blendView:(UITableView *)view forPropName:(NSString *)name propValue:(id)prop { + if ([@"itemCount" isEqualToString:name]) { + self.itemCount = [prop unsignedIntegerValue]; + } else if ([@"renderItem" isEqualToString:name]) { + [self.tempNodes removeAllObjects]; + [self clearSubModel]; + } else if ([@"batchCount" isEqualToString:name]) { + self.batchCount = [prop unsignedIntegerValue]; + } else { + [super blendView:view forPropName:name propValue:prop]; + } +} + +- (void)blend:(NSDictionary *)props { + [super blend:props]; + [self.view reloadData]; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return self.itemCount; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + NSUInteger position = (NSUInteger) indexPath.row; + NSDictionary *model = [self itemModelAt:position]; + NSDictionary *props = model[@"props"]; + NSString *reuseId = props[@"identifier"]; + + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseId]; + if (!cell) { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseId]; + } + DoricListItemNode *node = self.tempNodes[@(position)]; + node.view = cell; + [node blend:props]; + return cell; +} + +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { + NSUInteger position = (NSUInteger) indexPath.row; + NSDictionary *model = [self itemModelAt:position]; + + return 60; +} + +- (NSDictionary *)itemModelAt:(NSUInteger)position { + NSString *viewId = self.tempNodes[@(position)].viewId; + if (viewId && viewId.length > 0) { + return [self subModelOf:viewId]; + } else { + DoricAsyncResult *result = [self callJSResponse:@"renderBunchedItems", @(position), @(self.batchCount), nil]; + JSValue *models = [result waitUntilResult]; + NSArray *array = [models toArray]; + [array enumerateObjectsUsingBlock:^(NSDictionary *obj, NSUInteger idx, BOOL *stop) { + NSString *thisViewId = obj[@"id"]; + [self setSubModel:obj in:thisViewId]; + NSUInteger pos = position + idx; + DoricListItemNode *node = [[DoricListItemNode alloc] initWithContext:self.doricContext]; + node.viewId = thisViewId; + [node initWithSuperNode:self]; + self.tempNodes[@(pos)] = node; + }]; + return array[0]; + } +} + +@end diff --git a/iOS/Pod/Classes/Shader/DoricViewNode.h b/iOS/Pod/Classes/Shader/DoricViewNode.h index 07ccbd43..54cf533e 100644 --- a/iOS/Pod/Classes/Shader/DoricViewNode.h +++ b/iOS/Pod/Classes/Shader/DoricViewNode.h @@ -48,9 +48,9 @@ NS_ASSUME_NONNULL_BEGIN - (void)blendView:(V)view forPropName:(NSString *)name propValue:(id)prop; -- (void)callJSResponse:(NSString *)funcId, ...; +- (DoricAsyncResult *)callJSResponse:(NSString *)funcId, ...; -+ (DoricViewNode *)create:(DoricContext *)context withType:(NSString *)type; ++ (__kindof DoricViewNode *)create:(DoricContext *)context withType:(NSString *)type; - (void)requestLayout; @end diff --git a/iOS/Pod/Classes/Shader/DoricViewNode.m b/iOS/Pod/Classes/Shader/DoricViewNode.m index 29a5f812..9c3ded24 100644 --- a/iOS/Pod/Classes/Shader/DoricViewNode.m +++ b/iOS/Pod/Classes/Shader/DoricViewNode.m @@ -193,7 +193,7 @@ - (void)onClick:(UIView *)view { return [[ret reverseObjectEnumerator] allObjects]; } -- (void)callJSResponse:(NSString *)funcId, ... { +- (DoricAsyncResult *)callJSResponse:(NSString *)funcId, ... { NSMutableArray *array = [[NSMutableArray alloc] init]; [array addObject:self.idList]; [array addObject:funcId]; @@ -203,11 +203,12 @@ - (void)callJSResponse:(NSString *)funcId, ... { while ((arg = va_arg(args, id)) != nil) { [array addObject:arg]; } - [self.doricContext callEntity:DORIC_ENTITY_RESPONSE withArgumentsArray:array]; + DoricAsyncResult *ret = [self.doricContext callEntity:DORIC_ENTITY_RESPONSE withArgumentsArray:array]; va_end(args); + return ret; } -+ (DoricViewNode *)create:(DoricContext *)context withType:(NSString *)type { ++ (__kindof DoricViewNode *)create:(DoricContext *)context withType:(NSString *)type { DoricRegistry *registry = context.driver.registry; Class clz = [registry acquireViewNode:type]; return [(DoricViewNode *) [clz alloc] initWithContext:context]; diff --git a/iOS/Pod/Classes/Util/DoricAsyncResult.h b/iOS/Pod/Classes/Util/DoricAsyncResult.h index 59a60c96..9eb813ff 100644 --- a/iOS/Pod/Classes/Util/DoricAsyncResult.h +++ b/iOS/Pod/Classes/Util/DoricAsyncResult.h @@ -43,6 +43,8 @@ typedef void(^DoricFinishCallback)(void); - (BOOL)hasResult; - (R)getResult; + +- (R)waitUntilResult; @end NS_ASSUME_NONNULL_END diff --git a/iOS/Pod/Classes/Util/DoricAsyncResult.m b/iOS/Pod/Classes/Util/DoricAsyncResult.m index 9317659d..786f2eff 100644 --- a/iOS/Pod/Classes/Util/DoricAsyncResult.m +++ b/iOS/Pod/Classes/Util/DoricAsyncResult.m @@ -22,31 +22,32 @@ #import "DoricAsyncResult.h" -@interface DoricAsyncResult() -@property(nonatomic,strong) id result; +@interface DoricAsyncResult () +@property(nonatomic, strong) id result; @end @implementation DoricAsyncResult - (void)setupResult:(id)result { self.result = result; - if(self.resultCallback){ + if (self.resultCallback) { self.resultCallback(result); } - if(self.finishCallback){ + if (self.finishCallback) { self.finishCallback(); } } - (void)setupError:(NSException *)exception { self.result = exception; - if(self.exceptionCallback){ + if (self.exceptionCallback) { self.exceptionCallback(exception); } - if(self.finishCallback){ + if (self.finishCallback) { self.finishCallback(); } } + - (BOOL)hasResult { return self.result; } @@ -57,22 +58,35 @@ - (id)getResult { - (void)setResultCallback:(DoricResultCallback)callback { _resultCallback = callback; - if(self.result && ![self.result isKindOfClass: [NSException class]]){ + if (self.result && ![self.result isKindOfClass:[NSException class]]) { callback(self.result); } } - (void)setExceptionCallback:(DoricExceptionCallback)exceptionCallback { _exceptionCallback = exceptionCallback; - if([self.result isKindOfClass: [NSException class]]){ + if ([self.result isKindOfClass:[NSException class]]) { exceptionCallback(self.result); } } - (void)setFinishCallback:(DoricFinishCallback)callback { _finishCallback = callback; - if(self.result){ + if (self.result) { callback(); } } + +- (id)waitUntilResult { + if (self.result) { + return self.result; + } + + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + self.resultCallback = ^(id r) { + dispatch_semaphore_signal(semaphore); + }; + dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); + return self.result; +} @end