rename dir

This commit is contained in:
pengfei.zhou
2019-12-21 23:01:45 +08:00
parent f3c427ee92
commit 98b3d419ff
149 changed files with 0 additions and 124 deletions

View File

View File

@@ -0,0 +1,33 @@
/*
* 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.
*/
//
// WSClient.h
// Doric
//
// Created by pengfei.zhou on 2019/8/14.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface DoricWSClient : NSObject
- (instancetype)initWithUrl:(NSString *)url;
- (void)close;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,81 @@
/*
* 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.
*/
//
// WSClient.m
// Doric
//
// Created by pengfei.zhou on 2019/8/14.
//
#import "DoricWSClient.h"
#import <SocketRocket/SRWebSocket.h>
#import "DoricUtil.h"
#import "DoricContextManager.h"
@interface DoricWSClient () <SRWebSocketDelegate>
@property(nonatomic, strong) SRWebSocket *websocket;
@end
@implementation DoricWSClient
- (instancetype)initWithUrl:(NSString *)url {
if (self = [super init]) {
_websocket = [[SRWebSocket alloc] initWithURL:[NSURL URLWithString:url]];
_websocket.delegate = self;
[_websocket open];
}
return self;
}
- (void)webSocketDidOpen:(SRWebSocket *)webSocket {
DoricLog(@"webSocketDidOpen");
}
- (void)webSocket:(SRWebSocket *)webSocket didReceivePong:(NSData *)pongPayload {
DoricLog(@"webSocketdidReceivePong");
}
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message {
NSData *jsonData = [message dataUsingEncoding:NSUTF8StringEncoding];
NSError *err;
NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:jsonData
options:NSJSONReadingMutableContainers
error:&err];
if (err) {
DoricLog(@"webSocketdidReceiveMessage parse error%@", err);
return;
}
NSString *source = [[dic valueForKey:@"source"] mutableCopy];
NSString *script = [dic valueForKey:@"script"];
for (NSValue *value in [[DoricContextManager instance] aliveContexts]) {
DoricContext *context = value.nonretainedObjectValue;
if ([source containsString:context.source]) {
[context reload:script];
}
}
}
- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error {
DoricLog(@"webSocketdidFailWithError");
}
- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean {
DoricLog(@"webSocketdidCloseWithCode");
}
- (void)close {
[self.websocket close];
}
@end

View File

@@ -0,0 +1,30 @@
/*
* Copyright [2019] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "DoricContext.h"
#import "DoricLayouts.h"
#import "DoricExtensions.h"
#import "DoricViewNode.h"
#import "DoricRootNode.h"
#import "UIView+Doric.h"
#import "DoricUtil.h"
#import "DoricPanel.h"
#import "DoricJSLoaderManager.h"
#import "DoricNavigatorDelegate.h"
#import "DoricNavBarDelegate.h"
#import "DoricViewController.h"
#import "DoricPromise.h"
#import "DoricLibrary.h"
#import "DoricNativePlugin.h"

View File

@@ -0,0 +1,65 @@
/*
* 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.
*/
//
// DoricContext.h
// Doric
//
// Created by pengfei.zhou on 2019/7/25.
//
#import <Foundation/Foundation.h>
#import "DoricDriver.h"
#import "DoricNavigatorDelegate.h"
#import "DoricNavBarDelegate.h"
NS_ASSUME_NONNULL_BEGIN
@class DoricViewNode;
@class DoricRootNode;
@interface DoricContext : NSObject
@property(nonatomic, weak) id <DoricNavigatorDelegate> navigator;
@property(nonatomic, weak) id <DoricNavBarDelegate> navBar;
@property(nonatomic, strong) NSString *contextId;
@property(nonatomic, strong) DoricDriver *driver;
@property(nonatomic, strong) NSMutableDictionary *pluginInstanceMap;
@property(nonatomic, strong) NSString *source;
@property(nonatomic, strong) NSString *script;;
@property(nonatomic, strong) NSMutableDictionary *initialParams;
@property(nonatomic, strong) DoricRootNode *rootNode;
@property(nonatomic, strong) NSMutableDictionary <NSString *, DoricViewNode *> *headNodes;
@property(nonatomic, copy) NSString *extra;
- (instancetype)initWithScript:(NSString *)script source:(NSString *)source extra:(NSString *)extra;
- (DoricAsyncResult *)callEntity:(NSString *)method, ...;
- (DoricAsyncResult *)callEntity:(NSString *)method withArguments:(va_list)args;
- (DoricAsyncResult *)callEntity:(NSString *)method withArgumentsArray:(NSArray *)args;
- (void)initContextWithWidth:(CGFloat)width height:(CGFloat)height;
- (void)reload:(NSString *)script;
- (void)onShow;
- (void)onHidden;
- (DoricViewNode *)targetViewNode:(NSString *)viewId;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,101 @@
/*
* 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.
*/
//
// DoricContext.m
// Doric
//
// Created by pengfei.zhou on 2019/7/25.
//
#import "DoricContext.h"
#import "DoricContextManager.h"
#import "DoricRootNode.h"
#import "DoricConstant.h"
#import "DoricExtensions.h"
@implementation DoricContext
- (instancetype)initWithScript:(NSString *)script source:(NSString *)source extra:(NSString *)extra {
if (self = [super init]) {
_driver = [DoricDriver instance];
_pluginInstanceMap = [NSMutableDictionary new];
[[DoricContextManager instance] createContext:self script:script source:source];
_headNodes = [NSMutableDictionary new];
DoricRootNode *rootNode = [[DoricRootNode alloc] initWithContext:self];
_rootNode = rootNode;
_script = script;
_source = source;
_initialParams = [@{@"width": @(0), @"height": @(0)} mutableCopy];
_extra = extra;
[self callEntity:DORIC_ENTITY_CREATE, nil];
}
return self;
}
- (DoricViewNode *)targetViewNode:(NSString *)viewId {
if ([self.rootNode.viewId isEqualToString:viewId]) {
return self.rootNode;
} else {
return self.headNodes[viewId];
}
}
- (void)dealloc {
[self callEntity:DORIC_ENTITY_DESTROY, nil];
[[DoricContextManager instance] destroyContext:self];
}
- (DoricAsyncResult *)callEntity:(NSString *)method, ... {
va_list args;
va_start(args, method);
DoricAsyncResult *ret = [self.driver invokeContextEntity:self.contextId method:method arguments:args];
va_end(args);
return ret;
}
- (DoricAsyncResult *)callEntity:(NSString *)method withArguments:(va_list)args {
return [self.driver invokeContextEntity:self.contextId method:method arguments:args];
}
- (DoricAsyncResult *)callEntity:(NSString *)method withArgumentsArray:(NSArray *)args {
return [self.driver invokeContextEntity:self.contextId method:method argumentsArray:args];
}
- (void)initContextWithWidth:(CGFloat)width height:(CGFloat)height {
[self.initialParams also:^(NSMutableDictionary *it) {
it[@"width"] = @(width);
it[@"height"] = @(height);
}];
[self callEntity:DORIC_ENTITY_INIT, self.initialParams, self.extra, nil];
}
- (void)reload:(NSString *)script {
self.rootNode.viewId = nil;
self.script = script;
[self.driver createContext:self.contextId script:script source:self.source];
[self callEntity:DORIC_ENTITY_INIT, self.initialParams, nil];
[self onShow];
}
- (void)onShow {
[self callEntity:DORIC_ENTITY_SHOW, nil];
}
- (void)onHidden {
[self callEntity:DORIC_ENTITY_HIDDEN, nil];
}
@end

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.
*/
//
// DoricComponent.h
// Doric
//
// Created by pengfei.zhou on 2019/7/29.
//
#import <Foundation/Foundation.h>
#import "DoricContext.h"
NS_ASSUME_NONNULL_BEGIN
@interface DoricContextHolder : NSObject
@property(nonatomic, weak) DoricContext *doricContext;
- (instancetype)initWithContext:(DoricContext *)doricContext;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,34 @@
/*
* 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.
*/
//
// DoricComponent.m
// Doric
//
// Created by pengfei.zhou on 2019/7/29.
//
#import "DoricContextHolder.h"
@implementation DoricContextHolder
- (instancetype)initWithContext:(DoricContext *)doricContext {
if (self = [self init]) {
_doricContext = doricContext;
}
return self;
}
@end

View File

@@ -0,0 +1,40 @@
/*
* 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.
*/
//
// DoricContextManager.h
// Doric
//
// Created by pengfei.zhou on 2019/7/29.
//
#import <Foundation/Foundation.h>
#import "DoricContext.h"
NS_ASSUME_NONNULL_BEGIN
@interface DoricContextManager : NSObject
+ (instancetype)instance;
- (void)createContext:(DoricContext *)context script:(NSString *)script source:(NSString *)source;
- (void)destroyContext:(DoricContext *)context;
- (DoricContext *)getContext:(NSString *)contextId;
- (NSArray *)aliveContexts;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,84 @@
/*
* 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.
*/
//
// DoricContextManager.m
// Doric
//
// Created by pengfei.zhou on 2019/7/29.
//
#import "DoricContextManager.h"
#import "DoricContext.h"
@interface DoricContextManager ()
@property(nonatomic) NSInteger counter;
@property(nonatomic, strong) NSMutableDictionary *doricContextMap;
@property(nonatomic, strong) dispatch_queue_t mapQueue;
@end
@implementation DoricContextManager
- (instancetype)init {
if (self = [super init]) {
_doricContextMap = [[NSMutableDictionary alloc] init];
_counter = 0;
_mapQueue = dispatch_queue_create("doric.contextmap", DISPATCH_QUEUE_SERIAL);
}
return self;
}
+ (instancetype)instance {
static DoricContextManager *_instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[DoricContextManager alloc] init];
});
return _instance;
}
- (void)createContext:(DoricContext *)context script:(NSString *)script source:(NSString *)source {
context.contextId = [NSString stringWithFormat:@"%ld", (long) ++self.counter];
[context.driver createContext:context.contextId script:script source:source];
dispatch_sync(self.mapQueue, ^() {
NSValue *value = [NSValue valueWithNonretainedObject:context];
[self.doricContextMap setValue:value forKey:context.contextId];
});
}
- (DoricContext *)getContext:(NSString *)contextId {
__block DoricContext *context;
dispatch_sync(self.mapQueue, ^{
NSValue *value = [self.doricContextMap valueForKey:contextId];
context = value.nonretainedObjectValue;
});
return context;
}
- (void)destroyContext:(DoricContext *)context {
NSString *contextId = context.contextId;
[context.driver destroyContext:contextId].finishCallback = ^{
dispatch_sync(self.mapQueue, ^() {
[self.doricContextMap removeObjectForKey:contextId];
});
};
}
- (NSArray *)aliveContexts {
return [self.doricContextMap allValues];
}
@end

View File

@@ -0,0 +1,59 @@
/*
* 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.
*/
//
// DoricDriver.h
// Doric
//
// Created by pengfei.zhou on 2019/7/26.
//
#import <Foundation/Foundation.h>
#import "DoricAsyncResult.h"
#import "DoricRegistry.h"
typedef NS_ENUM(NSInteger, QueueMode) {
JS = 0,
UI,
INDEPENDENT
};
NS_ASSUME_NONNULL_BEGIN
@interface DoricDriver : NSObject
+ (instancetype)instance;
@property(nonatomic, strong) DoricRegistry *registry;
- (DoricAsyncResult *)createContext:(NSString *)contextId script:(NSString *)script source:(NSString *)source;
- (DoricAsyncResult *)destroyContext:(NSString *)contextId;
- (DoricAsyncResult *)invokeDoricMethod:(NSString *)method, ...;
- (DoricAsyncResult *)invokeContextEntity:(NSString *)contextId method:(NSString *)method, ...;
- (DoricAsyncResult *)invokeContextEntity:(NSString *)contextId method:(NSString *)method arguments:(va_list)args;
- (DoricAsyncResult *)invokeContextEntity:(NSString *)contextId method:(NSString *)method argumentsArray:(NSArray *)args;
- (void)connectDevKit:(NSString *)url;
- (void)disconnectDevKit;
- (void)ensureSyncInMainQueue:(dispatch_block_t)block;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,193 @@
/*
* 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.
*/
//
// DoricDriver.m
// Doric
//
// Created by pengfei.zhou on 2019/7/26.
//
#import "DoricDriver.h"
#import "DoricJSEngine.h"
#import "DoricConstant.h"
#import "DoricWSClient.h"
@interface DoricDriver ()
@property(nonatomic, strong) DoricJSEngine *jsExecutor;
@property(nonatomic, strong) DoricWSClient *wsclient;
@end
@implementation DoricDriver
@dynamic registry;
- (instancetype)init {
if (self = [super init]) {
_jsExecutor = [[DoricJSEngine alloc] init];
}
return self;
}
- (DoricRegistry *)registry {
return self.jsExecutor.registry;
}
+ (instancetype)instance {
static DoricDriver *_instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[DoricDriver alloc] init];
});
return _instance;
}
- (DoricAsyncResult<JSValue *> *)invokeDoricMethod:(NSString *)method, ... {
va_list args;
va_start(args, method);
DoricAsyncResult *ret = [self invokeDoricMethod:method arguments:args];
va_end(args);
return ret;
}
- (DoricAsyncResult<JSValue *> *)invokeDoricMethod:(NSString *)method arguments:(va_list)args {
DoricAsyncResult *ret = [[DoricAsyncResult alloc] init];
NSMutableArray *array = [[NSMutableArray alloc] init];
id arg;
while ((arg = va_arg(args, id)) != nil) {
[array addObject:arg];
}
__weak typeof(self) _self = self;
dispatch_async(self.jsExecutor.jsQueue, ^() {
__strong typeof(_self) self = _self;
if (!self) return;
@try {
JSValue *jsValue = [self.jsExecutor invokeDoricMethod:method argumentsArray:array];
[ret setupResult:jsValue];
} @catch (NSException *exception) {
[ret setupError:exception];
}
});
return ret;
}
- (DoricAsyncResult<JSValue *> *)invokeContextEntity:(NSString *)contextId method:(NSString *)method, ... {
va_list args;
va_start(args, method);
DoricAsyncResult *ret = [self invokeContextEntity:contextId method:method arguments:args];
va_end(args);
return ret;
}
- (DoricAsyncResult *)invokeContextEntity:(NSString *)contextId method:(NSString *)method arguments:(va_list)args {
DoricAsyncResult *ret = [[DoricAsyncResult alloc] init];
NSMutableArray *array = [[NSMutableArray alloc] init];
[array addObject:contextId];
[array addObject:method];
id arg = va_arg(args, id);
while (arg != nil) {
[array addObject:arg];
arg = va_arg(args, JSValue *);
}
__weak typeof(self) _self = self;
dispatch_async(self.jsExecutor.jsQueue, ^() {
__strong typeof(_self) self = _self;
if (!self) return;
@try {
JSValue *jsValue = [self.jsExecutor invokeDoricMethod:DORIC_CONTEXT_INVOKE argumentsArray:array];
[ret setupResult:jsValue];
} @catch (NSException *exception) {
[ret setupError:exception];
}
});
return ret;
}
- (DoricAsyncResult *)invokeContextEntity:(NSString *)contextId method:(NSString *)method argumentsArray:(NSArray *)args {
DoricAsyncResult *ret = [[DoricAsyncResult alloc] init];
NSMutableArray *array = [[NSMutableArray alloc] init];
[array addObject:contextId];
[array addObject:method];
for (id arg in args) {
[array addObject:arg];
}
__weak typeof(self) _self = self;
dispatch_async(self.jsExecutor.jsQueue, ^() {
__strong typeof(_self) self = _self;
if (!self) return;
@try {
JSValue *jsValue = [self.jsExecutor invokeDoricMethod:DORIC_CONTEXT_INVOKE argumentsArray:array];
[ret setupResult:jsValue];
} @catch (NSException *exception) {
[ret setupError:exception];
}
});
return ret;
}
- (DoricAsyncResult *)createContext:(NSString *)contextId script:(NSString *)script source:(NSString *)source {
DoricAsyncResult *ret = [[DoricAsyncResult alloc] init];
__weak typeof(self) _self = self;
dispatch_async(self.jsExecutor.jsQueue, ^() {
__strong typeof(_self) self = _self;
if (!self) return;
@try {
[self.jsExecutor prepareContext:contextId script:script source:source];
[ret setupResult:[NSNumber numberWithBool:YES]];
} @catch (NSException *exception) {
[ret setupError:exception];
}
});
return ret;
}
- (DoricAsyncResult *)destroyContext:(NSString *)contextId {
DoricAsyncResult *ret = [[DoricAsyncResult alloc] init];
__weak typeof(self) _self = self;
dispatch_async(self.jsExecutor.jsQueue, ^() {
__strong typeof(_self) self = _self;
if (!self) return;
@try {
[self.jsExecutor destroyContext:contextId];
[ret setupResult:[NSNumber numberWithBool:YES]];
} @catch (NSException *exception) {
[ret setupError:exception];
}
});
return ret;
}
- (void)connectDevKit:(NSString *)url {
if (self.wsclient) {
[self.wsclient close];
}
self.wsclient = [[DoricWSClient alloc] initWithUrl:url];
}
- (void)disconnectDevKit {
if (self.wsclient) {
[self.wsclient close];
self.wsclient = nil;
}
}
- (void)ensureSyncInMainQueue:(dispatch_block_t)block {
if (strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(dispatch_get_main_queue())) == 0) {
block();
} else {
dispatch_async(dispatch_get_main_queue(), block);
}
}
@end

View File

@@ -0,0 +1,25 @@
/*
* 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/12/11.
//
#import <Foundation/Foundation.h>
#import "DoricRegistry.h"
@interface DoricLibrary : NSObject
- (void)load:(DoricRegistry *)registry;
@end

View File

@@ -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/12/11.
//
#import "DoricLibrary.h"
@implementation DoricLibrary
- (void)load:(DoricRegistry *)registry {
}
@end

View File

@@ -0,0 +1,29 @@
/*
* 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/23.
//
#import <Foundation/Foundation.h>
#import "DoricContext.h"
#import "DoricNavigatorDelegate.h"
@interface DoricPanel : UIViewController
@property(nonatomic, strong) DoricContext *doricContext;
- (void)config:(NSString *)script alias:(NSString *)alias extra:(NSString *)extra;
@end

View File

@@ -0,0 +1,54 @@
/*
* 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/23.
//
#import "DoricPanel.h"
#import "Doric.h"
@implementation DoricPanel
- (void)config:(NSString *)script alias:(NSString *)alias extra:(NSString *)extra {
self.doricContext = [[[DoricContext alloc] initWithScript:script source:alias extra:extra] also:^(DoricContext *it) {
[it.rootNode setupRootView:[[DoricStackView new] also:^(DoricStackView *it) {
[self.view addSubview:it];
}]];
}];
}
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
[self.doricContext.rootNode.view also:^(DoricStackView *it) {
if (it.width != self.view.width || it.height != self.view.height) {
it.width = self.view.width;
it.height = self.view.height;
[self.doricContext initContextWithWidth:it.width height:it.height];
}
}];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self.doricContext onShow];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[self.doricContext onHidden];
}
@end

View File

@@ -0,0 +1,46 @@
/*
* 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.
*/
//
// DoricRegistry.h
// Doric
//
// Created by pengfei.zhou on 2019/7/27.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@class DoricLibrary;
@interface DoricRegistry : NSObject
- (NSString *)acquireJSBundle:(NSString *)name;
- (void)registerJSBundle:(NSString *)bundle withName:(NSString *)name;
- (void)registerNativePlugin:(Class)pluginClass withName:(NSString *)name;
- (Class)acquireNativePlugin:(NSString *)name;
- (void)registerViewNode:(Class)nodeClass withName:(NSString *)name;
- (Class)acquireViewNode:(NSString *)name;
+ (void)register:(DoricLibrary *)library;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,153 @@
/*
* 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.
*/
//
// DoricRegistry.m
// Doric
//
// Created by pengfei.zhou on 2019/7/27.
//
#import "DoricRegistry.h"
#import "DoricModalPlugin.h"
#import "DoricNetworkPlugin.h"
#import "DoricShaderPlugin.h"
#import "DoricStackNode.h"
#import "DoricVLayoutNode.h"
#import "DoricHLayoutNode.h"
#import "DoricTextNode.h"
#import "DoricImageNode.h"
#import "DoricListNode.h"
#import "DoricListItemNode.h"
#import "DoricScrollerNode.h"
#import "DoricSliderNode.h"
#import "DoricSlideItemNode.h"
#import "DoricStoragePlugin.h"
#import "DoricNavigatorPlugin.h"
#import "DoricNavBarPlugin.h"
#import "DoricRefreshableNode.h"
#import "DoricFlowLayoutItemNode.h"
#import "DoricFlowLayoutNode.h"
#import "DoricPopoverPlugin.h"
#import "DoricAnimatePlugin.h"
#import "DoricNestedSliderNode.h"
#import "DoricInputNode.h"
#import "DoricLibrary.h"
@interface DoricLibraries : NSObject
@property(nonatomic, strong) NSMutableSet <DoricLibrary *> *libraries;
+ (instancetype)instance;
@end
@implementation DoricLibraries
- (instancetype)init {
if (self = [super init]) {
_libraries = [NSMutableSet new];
}
return self;
}
+ (instancetype)instance {
static DoricLibraries *_instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[DoricLibraries alloc] init];
});
return _instance;
}
@end
@interface DoricRegistry ()
@property(nonatomic, strong) NSMutableDictionary *bundles;
@property(nonatomic, strong) NSMutableDictionary *plugins;
@property(nonatomic, strong) NSMutableDictionary *nodes;
@end
@implementation DoricRegistry
+ (void)register:(DoricLibrary *)library {
[DoricLibraries.instance.libraries addObject:library];
}
- (instancetype)init {
if (self = [super init]) {
_bundles = [[NSMutableDictionary alloc] init];
_plugins = [[NSMutableDictionary alloc] init];
_nodes = [[NSMutableDictionary alloc] init];
[self innerRegister];
[DoricLibraries.instance.libraries enumerateObjectsUsingBlock:^(DoricLibrary *obj, BOOL *stop) {
[obj load:self];
}];
}
return self;
}
- (void)innerRegister {
[self registerNativePlugin:DoricShaderPlugin.class withName:@"shader"];
[self registerNativePlugin:DoricModalPlugin.class withName:@"modal"];
[self registerNativePlugin:DoricNetworkPlugin.class withName:@"network"];
[self registerNativePlugin:DoricStoragePlugin.class withName:@"storage"];
[self registerNativePlugin:DoricNavigatorPlugin.class withName:@"navigator"];
[self registerNativePlugin:DoricNavBarPlugin.class withName:@"navbar"];
[self registerNativePlugin:DoricPopoverPlugin.class withName:@"popover"];
[self registerNativePlugin:DoricAnimatePlugin.class withName:@"animate"];
[self registerViewNode:DoricStackNode.class withName:@"Stack"];
[self registerViewNode:DoricVLayoutNode.class withName:@"VLayout"];
[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"];
[self registerViewNode:DoricScrollerNode.class withName:@"Scroller"];
[self registerViewNode:DoricSliderNode.class withName:@"Slider"];
[self registerViewNode:DoricSlideItemNode.class withName:@"SlideItem"];
[self registerViewNode:DoricRefreshableNode.class withName:@"Refreshable"];
[self registerViewNode:DoricFlowLayoutItemNode.class withName:@"FlowLayoutItem"];
[self registerViewNode:DoricFlowLayoutNode.class withName:@"FlowLayout"];
[self registerViewNode:DoricNestedSliderNode.class withName:@"NestedSlider"];
[self registerViewNode:DoricInputNode.class withName:@"Input"];
}
- (void)registerJSBundle:(NSString *)bundle withName:(NSString *)name {
self.bundles[name] = bundle;
}
- (NSString *)acquireJSBundle:(NSString *)name {
return self.bundles[name];
}
- (void)registerNativePlugin:(Class)pluginClass withName:(NSString *)name {
self.plugins[name] = pluginClass;
}
- (Class)acquireNativePlugin:(NSString *)name {
return self.plugins[name];
}
- (void)registerViewNode:(Class)nodeClass withName:(NSString *)name {
self.nodes[name] = nodeClass;
}
- (Class)acquireViewNode:(NSString *)name {
return self.nodes[name];
}
@end

View File

@@ -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 onO 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/23.
//
#import <Foundation/Foundation.h>
#import "DoricNavigatorDelegate.h"
#import "DoricNavBarDelegate.h"
@interface DoricViewController : UIViewController <DoricNavigatorDelegate, DoricNavBarDelegate>
- (instancetype)initWithScheme:(NSString *)scheme alias:(NSString *)alias extra:(NSString *)extra;
@end

View File

@@ -0,0 +1,107 @@
/*
* 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/23.
//
#import "DoricViewController.h"
#import "DoricAsyncResult.h"
#import "DoricJSLoaderManager.h"
#import "DoricPanel.h"
#import "UIView+Doric.h"
#import "DoricExtensions.h"
#import "DoricUtil.h"
@interface DoricViewController ()
@property(nonatomic, strong) DoricPanel *doricPanel;
@property(nonatomic) BOOL navBarHidden;
@property(nonatomic, strong) UIImage *navBarImage;
@end
@implementation DoricViewController
- (instancetype)initWithScheme:(NSString *)scheme alias:(NSString *)alias extra:(NSString *)extra {
if (self = [super init]) {
self.edgesForExtendedLayout = UIRectEdgeNone;
DoricAsyncResult <NSString *> *result = [DoricJSLoaderManager.instance request:scheme];
result.resultCallback = ^(NSString *result) {
dispatch_async(dispatch_get_main_queue(), ^{
DoricPanel *panel = [DoricPanel new];
[panel.view also:^(UIView *it) {
it.backgroundColor = [UIColor whiteColor];
it.width = self.view.width;
it.height = self.view.height;
}];
[self.view addSubview:panel.view];
[self addChildViewController:panel];
[panel config:result alias:alias extra:extra];
panel.doricContext.navigator = self;
panel.doricContext.navBar = self;
self.doricPanel = panel;
});
};
}
return self;
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.navBarHidden = self.navigationController.navigationBarHidden;
self.navBarImage = [self.navigationController.navigationBar backgroundImageForBarMetrics:UIBarMetricsDefault];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (self.navigationController.navigationBarHidden != self.navBarHidden) {
[self.navigationController setNavigationBarHidden:self.navBarHidden];
}
if (self.navBarImage != [self.navigationController.navigationBar backgroundImageForBarMetrics:UIBarMetricsDefault]) {
[self.navigationController.navigationBar setBackgroundImage:self.navBarImage forBarMetrics:UIBarMetricsDefault];
}
}
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
self.doricPanel.view.width = self.view.width;
self.doricPanel.view.height = self.view.height;
}
- (void)doric_navigator_push:(NSString *)scheme alias:(NSString *)alias animated:(BOOL)animated extra:(NSString *)extra {
DoricViewController *viewController = [[DoricViewController alloc] initWithScheme:scheme alias:alias extra:extra];
[self.navigationController pushViewController:viewController animated:animated];
}
- (void)doric_navigator_pop:(BOOL)animated {
[self.navigationController popViewControllerAnimated:animated];
}
- (BOOL)doric_navBar_isHidden {
return self.navigationController.navigationBarHidden;
}
- (void)doric_navBar_setHidden:(BOOL)hidden {
[self.navigationController setNavigationBarHidden:hidden];
}
- (void)doric_navBar_setTitle:(NSString *)title {
self.title = title;
}
- (void)doric_navBar_setBackgroundColor:(UIColor *)color {
[self.navigationController.navigationBar setBackgroundImage:UIImageWithColor(color) forBarMetrics:UIBarMetricsDefault];
}
@end

View File

@@ -0,0 +1,32 @@
/*
* 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.
*/
//
// DoricJSCoreExecutor.h
// Doric
//
// Created by pengfei.zhou on 2019/7/25.
//
#import <Foundation/Foundation.h>
#import "DoricJSExecutorProtocol.h"
NS_ASSUME_NONNULL_BEGIN
@interface DoricJSCoreExecutor : NSObject <DoricJSExecutorProtocol>
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,64 @@
/*
* 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.
*/
//
// DoricJSCoreExecutor.m
// Doric
//
// Created by pengfei.zhou on 2019/7/25.
//
#import "DoricJSCoreExecutor.h"
@interface DoricJSCoreExecutor ()
@property(nonatomic, strong) JSContext *jsContext;
@end
@implementation DoricJSCoreExecutor
- (instancetype)init {
if (self = [super init]) {
_jsContext = [[JSContext alloc] init];
}
return self;
}
- (void)checkJSException {
if (self.jsContext.exception) {
NSString *errMsg = [NSString stringWithFormat:@"%@ (line %@ in the generated bundle)\n/***StackTrace***/\n%@/***StackTrace***/", self.jsContext.exception, self.jsContext.exception[@"line"], self.jsContext.exception[@"stack"]];
@throw [[NSException alloc] initWithName:@"DoricJS" reason:errMsg userInfo:nil];
}
}
- (NSString *)loadJSScript:(NSString *)script source:(NSString *)source {
NSString *ret = [[self.jsContext evaluateScript:script withSourceURL:[NSURL URLWithString:source]] toString];
[self checkJSException];
return ret;
}
- (void)injectGlobalJSObject:(NSString *)name obj:(id)obj {
self.jsContext[name] = obj;
[self checkJSException];
}
- (JSValue *)invokeObject:(NSString *)objName method:(NSString *)funcName args:(NSArray *)args {
JSValue *obj = [self.jsContext objectForKeyedSubscript:objName];
JSValue *ret = [obj invokeMethod:funcName withArguments:args];
[self checkJSException];
return ret;
}
@end

View File

@@ -0,0 +1,46 @@
/*
* 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.
*/
//
// DoricJSEngine.h
// Doric
//
// Created by pengfei.zhou on 2019/7/26.
//
#import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>
#import "DoricRegistry.h"
NS_ASSUME_NONNULL_BEGIN
@interface DoricJSEngine : NSObject
@property(nonatomic, strong) dispatch_queue_t jsQueue;
@property(nonatomic, strong) DoricRegistry *registry;
- (void)prepareContext:(NSString *)contextId script:(NSString *)script source:(NSString *)source;
- (void)destroyContext:(NSString *)contextId;
- (JSValue *)invokeDoricMethod:(NSString *)method, ...;
- (JSValue *)invokeDoricMethod:(NSString *)method arguments:(va_list)args;
- (JSValue *)invokeDoricMethod:(NSString *)method argumentsArray:(NSArray *)args;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,189 @@
/*
* 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.
*/
//
// DoricJSEngine.m
// Doric
//
// Created by pengfei.zhou on 2019/7/26.
//
#import "DoricJSEngine.h"
#import "DoricJSExecutorProtocol.h"
#import "DoricJSCoreExecutor.h"
#import "DoricJSRemoteExecutor.h"
#import "DoricConstant.h"
#import "DoricUtil.h"
#import "DoricBridgeExtension.h"
@interface DoricJSEngine ()
@property(nonatomic, strong) id <DoricJSExecutorProtocol> jsExecutor;
@property(nonatomic, strong) NSMutableDictionary *timers;
@property(nonatomic, strong) DoricBridgeExtension *bridgeExtension;
@end
@implementation DoricJSEngine
- (instancetype)init {
if (self = [super init]) {
_jsQueue = dispatch_queue_create("doric.jsengine", DISPATCH_QUEUE_SERIAL);
_bridgeExtension = [[DoricBridgeExtension alloc] init];
dispatch_async(_jsQueue, ^() {
self.timers = [[NSMutableDictionary alloc] init];
// Debug:
// self.jsExecutor = [[DoricJSRemoteExecutor alloc] init];
self.jsExecutor = [DoricJSCoreExecutor new];
self.registry = [[DoricRegistry alloc] init];
[self initJSExecutor];
[self initDoricEnvironment];
});
}
return self;
}
- (void)initJSExecutor {
__weak typeof(self) _self = self;
NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
[self.jsExecutor injectGlobalJSObject:INJECT_ENVIRONMENT obj:@{
@"platform": @"iOS",
@"platformVersion": [[UIDevice currentDevice] systemVersion],
@"appName": infoDictionary[@"CFBundleName"],
@"appVersion": infoDictionary[@"CFBundleShortVersionString"],
@"screenWidth": @([[UIScreen mainScreen] bounds].size.width),
@"screenHeight": @([[UIScreen mainScreen] bounds].size.height),
}];
[self.jsExecutor injectGlobalJSObject:INJECT_LOG obj:^(NSString *type, NSString *message) {
DoricLog(@"JS:%@", message);
}];
[self.jsExecutor injectGlobalJSObject:INJECT_EMPTY obj:^() {
}];
[self.jsExecutor injectGlobalJSObject:INJECT_REQUIRE obj:^(NSString *name) {
__strong typeof(_self) self = _self;
if (!self) return NO;
NSString *content = [self.registry acquireJSBundle:name];
if (!content) {
DoricLog(@"require js bundle:%@ is empty", name);
return NO;
}
@try {
[self.jsExecutor loadJSScript:[self packageModuleScript:name content:content]
source:[@"Module://" stringByAppendingString:name]];
} @catch (NSException *e) {
DoricLog(@"require js bundle:%@ error,for %@", name, e.reason);
}
return YES;
}];
[self.jsExecutor injectGlobalJSObject:INJECT_TIMER_SET
obj:^(NSNumber *timerId, NSNumber *interval, NSNumber *isInterval) {
__strong typeof(_self) self = _self;
NSString *timerId_str = [timerId stringValue];
BOOL repeat = [isInterval boolValue];
NSTimer *timer = [NSTimer timerWithTimeInterval:[interval doubleValue] / 1000 target:self selector:@selector(callbackTimer:) userInfo:@{@"timerId": timerId, @"repeat": isInterval} repeats:repeat];
[self.timers setValue:timer forKey:timerId_str];
dispatch_async(dispatch_get_main_queue(), ^() {
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
});
}];
[self.jsExecutor injectGlobalJSObject:INJECT_TIMER_CLEAR
obj:^(NSString *timerId) {
__strong typeof(_self) self = _self;
NSTimer *timer = [self.timers valueForKey:timerId];
if (timer) {
[timer invalidate];
[self.timers removeObjectForKey:timerId];
}
}];
[self.jsExecutor injectGlobalJSObject:INJECT_BRIDGE obj:^(NSString *contextId, NSString *module, NSString *method, NSString *callbackId, id argument) {
return [self.bridgeExtension callNativeWithContextId:contextId module:module method:method callbackId:callbackId argument:argument];
}];
}
- (void)initDoricEnvironment {
[self loadBuiltinJS:DORIC_BUNDLE_SANDBOX];
NSString *path = [DoricBundle() pathForResource:DORIC_BUNDLE_LIB ofType:@"js" inDirectory:@"bundle"];
NSString *jsContent = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
[self.jsExecutor loadJSScript:[self packageModuleScript:DORIC_MODULE_LIB content:jsContent]
source:[@"Module://" stringByAppendingString:DORIC_MODULE_LIB]];
}
- (void)loadBuiltinJS:(NSString *)fileName {
NSString *path = [DoricBundle() pathForResource:DORIC_BUNDLE_SANDBOX ofType:@"js" inDirectory:@"bundle"];
NSString *jsContent = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
[self.jsExecutor loadJSScript:jsContent source:[@"Assets://" stringByAppendingString:fileName]];
}
- (JSValue *)invokeDoricMethod:(NSString *)method, ... {
va_list args;
va_start(args, method);
JSValue *ret = [self invokeDoricMethod:method arguments:args];
va_end(args);
return ret;
}
- (JSValue *)invokeDoricMethod:(NSString *)method arguments:(va_list)args {
NSMutableArray *array = [[NSMutableArray alloc] init];
id arg = va_arg(args, id);
while (arg != nil) {
[array addObject:arg];
arg = va_arg(args, JSValue *);
}
return [self.jsExecutor invokeObject:GLOBAL_DORIC method:method args:array];
}
- (JSValue *)invokeDoricMethod:(NSString *)method argumentsArray:(NSArray *)args {
return [self.jsExecutor invokeObject:GLOBAL_DORIC method:method args:args];
}
- (NSString *)packageContextScript:(NSString *)contextId content:(NSString *)content {
NSString *ret = [NSString stringWithFormat:TEMPLATE_CONTEXT_CREATE, content, contextId, contextId, contextId];
return ret;
}
- (NSString *)packageModuleScript:(NSString *)moduleName content:(NSString *)content {
NSString *ret = [NSString stringWithFormat:TEMPLATE_MODULE, moduleName, content];
return ret;
}
- (void)prepareContext:(NSString *)contextId script:(NSString *)script source:(NSString *)source {
[self.jsExecutor loadJSScript:[self packageContextScript:contextId content:script]
source:[@"Context://" stringByAppendingString:source]];
}
- (void)destroyContext:(NSString *)contextId {
[self.jsExecutor loadJSScript:[NSString stringWithFormat:TEMPLATE_CONTEXT_DESTROY, contextId]
source:[@"_Context://" stringByAppendingString:contextId]];
}
- (void)callbackTimer:(NSTimer *)timer {
NSDictionary *userInfo = timer.userInfo;
NSNumber *timerId = [userInfo valueForKey:@"timerId"];
NSNumber *repeat = [userInfo valueForKey:@"repeat"];
__weak typeof(self) _self = self;
dispatch_async(self.jsQueue, ^() {
__strong typeof(_self) self = _self;
@try {
[self invokeDoricMethod:DORIC_TIMER_CALLBACK, timerId, nil];
} @catch (NSException *exception) {
DoricLog(@"Timer Callback error:%@", exception.reason);
}
if (![repeat boolValue]) {
[self.timers removeObjectForKey:[timerId stringValue]];
}
});
}
@end

View File

@@ -0,0 +1,38 @@
/*
* 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.
*/
//
// DoricJSEngineProtocal.h
// Doric
//
// Created by pengfei.zhou on 2019/7/25.
//
#import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>
NS_ASSUME_NONNULL_BEGIN
@protocol DoricJSExecutorProtocol <NSObject>
- (NSString *)loadJSScript:(NSString *)script source:(NSString *)source;
- (void)injectGlobalJSObject:(NSString *)name obj:(id)obj;
- (JSValue *)invokeObject:(NSString *)objName method:(NSString *)funcName args:(NSArray *)args;
@end
NS_ASSUME_NONNULL_END

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.
*/
//
// DoricJSRemoteExecutor.h
// Pods
//
// Created by 王劲鹏 on 2019/10/31.
//
#import <Foundation/Foundation.h>
#import "DoricJSExecutorProtocol.h"
NS_ASSUME_NONNULL_BEGIN
@interface DoricJSRemoteExecutor : NSObject <DoricJSExecutorProtocol>
@property(nonatomic, strong) dispatch_semaphore_t semaphore;
- (void)close;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,207 @@
/*
* 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.
*/
//
// DoricJSRemoteExecutor.m
// Doric
//
// Created by on 2019/10/31.
//
#import "DoricJSRemoteExecutor.h"
#import <SocketRocket/SRWebSocket.h>
#import "DoricUtil.h"
#import "DoricJSRemoteArgType.h"
#import "NSString+JsonString.h"
static NSString * const kUrlStr = @"ws://192.168.24.240:2080";
typedef id (^Block0)(void);
typedef id (^Block1)(id arg0);
typedef id (^Block2)(id arg0, id arg1);
typedef id (^Block3)(id arg0, id arg1, id arg2);
typedef id (^Block4)(id arg0, id arg1, id arg2, id arg3);
typedef id (^Block5)(id arg0, id arg1, id arg2, id arg3, id arg4);
@interface DoricJSRemoteExecutor () <SRWebSocketDelegate>
@property(nonatomic, strong) SRWebSocket *srWebSocket;
@property(nonatomic, strong) NSMutableDictionary <NSString *, id> *blockMDic;
@property(nonatomic, strong) JSValue *temp;
@end
@implementation DoricJSRemoteExecutor
- (instancetype)init {
if (self = [super init]) {
[self srWebSocket];
_semaphore = dispatch_semaphore_create(0);
DC_LOCK(self.semaphore);
}
return self;
}
#pragma mark - SRWebSocketDelegate
- (void)webSocketDidOpen:(SRWebSocket *)webSocket {
DoricLog(@"debugger webSocketDidOpen");
DC_UNLOCK(self.semaphore);
}
- (void)webSocket:(SRWebSocket *)webSocket didReceivePong:(NSData *)pongPayload {
DoricLog(@"debugger webSocketdidReceivePong");
}
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message {
NSData *jsonData = [message dataUsingEncoding:NSUTF8StringEncoding];
NSError *err;
NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:jsonData
options:NSJSONReadingMutableContainers
error:&err];
if (err) {
DoricLog(@"debugger webSocketdidReceiveMessage parse error%@", err);
return;
}
NSString *cmd = [[dic valueForKey:@"cmd"] copy];
if ([cmd isEqualToString:@"injectGlobalJSFunction"]) {
NSString *name = [dic valueForKey:@"name"];
NSArray *argsArr = [dic valueForKey:@"arguments"];
NSMutableArray *argsMarr = [NSMutableArray new];
for (NSUInteger i = 0; i < argsArr.count; i++) {
[argsMarr addObject:argsArr[i]];
}
id result;
id tmpBlk = self.blockMDic[name];
if (argsArr.count == 0) {
result = ((Block0) tmpBlk)();
} else if (argsArr.count == 1) {
result = ((Block1) tmpBlk)(argsArr[0]);
} else if (argsArr.count == 2) {
result = ((Block2)tmpBlk)(argsArr[0], argsArr[1]);
} else if (argsArr.count == 3) {
result = ((Block3)tmpBlk)(argsArr[0], argsArr[1], argsArr[2]);
} else if (argsArr.count == 4) {
result = ((Block4)tmpBlk)(argsArr[0], argsArr[1], argsArr[2], argsArr[3]);
} else if (argsArr.count == 5) {
result = ((Block5)tmpBlk)(argsArr[0], argsArr[1], argsArr[2], argsArr[3], argsArr[4]);
}else {
DoricLog(@"error:args to more than 5. args:%@",argsArr);
}
} else if ([cmd isEqualToString:@"invokeMethod"]) {
@try {
self.temp = [JSValue valueWithObject:[dic valueForKey:@"result"] inContext:nil];
} @catch (NSException *exception) {
DoricLog(@"debugger ", NSStringFromSelector(_cmd), exception.reason);
} @finally {
DC_UNLOCK(self.semaphore);
}
}
}
- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error {
DoricLog(@"debugger webSocketdidFailWithError");
DC_UNLOCK(self.semaphore);
}
- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean {
DoricLog(@"debugger webSocketdidCloseWithCode");
}
#pragma mark - DoricJSExecutorProtocol
- (NSString *)loadJSScript:(NSString *)script source:(NSString *)source {
return nil;
}
- (void)injectGlobalJSObject:(NSString *)name obj:(id)obj {
if ([obj isKindOfClass:NSClassFromString(@"NSBlock")]) {
self.blockMDic[name] = obj;
}
NSDictionary *jsonDic = @{
@"cmd": @"injectGlobalJSFunction",
@"name": name
};
NSString *jsonStr = [NSString dc_convertToJsonWithDic:jsonDic];
if (!jsonStr) {
return;
}
[self.srWebSocket send:jsonStr];
}
- (JSValue *)invokeObject:(NSString *)objName method:(NSString *)funcName args:(NSArray *)args {
NSMutableArray *argsMArr = [NSMutableArray new];
for (id arg in args) {
NSDictionary *dic = [self dicForArg:arg];
[argsMArr addObject:dic];
}
NSArray *argsArr = [argsMArr copy];
NSDictionary *jsonDic = @{
@"cmd": @"invokeMethod",
@"objectName": objName,
@"functionName": funcName,
@"javaValues": argsArr
};
NSString *jsonStr = [NSString dc_convertToJsonWithDic:jsonDic];
if (!jsonStr) {
return nil;
}
[self.srWebSocket send:jsonStr];
DC_LOCK(self.semaphore);
return self.temp;
}
- (NSDictionary *)dicForArg:(id)arg {
DoricJSRemoteArgType type = DoricargTypeWithArg(arg);
if (type == DoricJSRemoteArgTypeObject || type == DoricJSRemoteArgTypeArray) {
NSString *jsonStr = [NSString dc_convertToJsonWithDic:(NSDictionary *)arg];
arg = jsonStr;
}
NSDictionary *dic = @{
@"type": @(type),
@"value": arg
};
return dic;
}
- (void)close {
[self.srWebSocket close];
}
#pragma mark - Properties
- (SRWebSocket *)srWebSocket {
if (!_srWebSocket) {
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:kUrlStr] cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:10];
_srWebSocket = [[SRWebSocket alloc] initWithURLRequest:request];
_srWebSocket.delegate = self;
[_srWebSocket open];
}
return _srWebSocket;
}
- (NSMutableDictionary *)blockMDic {
if (!_blockMDic) {
_blockMDic = [NSMutableDictionary new];
}
return _blockMDic;
}
@end

View File

@@ -0,0 +1,31 @@
/*
* 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.
*/
//
// DoricBridgeExtension.h
// Doric
//
// Created by pengfei.zhou on 2019/7/29.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface DoricBridgeExtension : NSObject
- (id)callNativeWithContextId:(NSString *)contextId module:(NSString *)module method:(NSString *)method callbackId:(NSString *)callbackId argument:(id)argument;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,124 @@
/*
* 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.
*/
//
// DoricBridgeExtension.m
// Doric
//
// Created by pengfei.zhou on 2019/7/29.
//
#import "DoricBridgeExtension.h"
#import "DoricRegistry.h"
#import "DoricContextManager.h"
#import "DoricNativePlugin.h"
#import "DoricUtil.h"
#import <JavaScriptCore/JavaScriptCore.h>
#import <objc/runtime.h>
@implementation DoricBridgeExtension
- (id)callNativeWithContextId:(NSString *)contextId module:(NSString *)module method:(NSString *)method callbackId:(NSString *)callbackId argument:(id)argument {
DoricContext *context = [[DoricContextManager instance] getContext:contextId];
DoricRegistry *registry = context.driver.registry;
Class pluginClass = [registry acquireNativePlugin:module];
DoricNativePlugin *nativePlugin = context.pluginInstanceMap[module];
if (nativePlugin == nil) {
nativePlugin = [(DoricNativePlugin *) [pluginClass alloc] initWithContext:context];
context.pluginInstanceMap[module] = nativePlugin;
}
return [self findClass:pluginClass target:nativePlugin context:context method:method callbackId:callbackId argument:argument];
}
- (id)createParamWithMethodName:(NSString *)method context:(DoricContext *)context callbackId:(NSString *)callbackId argument:(id)argument {
if ([method isEqualToString:@"withPromise"]) {
return [[DoricPromise alloc] initWithContext:context callbackId:callbackId];
}
return argument;
}
- (id)findClass:(Class)clz target:(id)target context:(DoricContext *)context method:(NSString *)name callbackId:(NSString *)callbackId argument:(id)argument {
unsigned int count;
id ret = nil;
Method *methods = class_copyMethodList(clz, &count);
BOOL isFound = NO;
for (int i = 0; i < count; i++) {
NSString *methodName = [NSString stringWithCString:sel_getName(method_getName(methods[i])) encoding:NSUTF8StringEncoding];
NSArray *array = [methodName componentsSeparatedByString:@":"];
if (array && [array count] > 0) {
if ([array[0] isEqualToString:name]) {
isFound = YES;
SEL selector = NSSelectorFromString(methodName);
NSMethodSignature *methodSignature = [target methodSignatureForSelector:selector];
if (methodSignature) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
invocation.selector = selector;
invocation.target = target;
__weak __typeof__(self) _self = self;
dispatch_block_t block = ^() {
__strong __typeof__(_self) self = _self;
@try {
NSMutableArray *tempArray = [NSMutableArray new];
for (NSUInteger idx = 2; idx < methodSignature.numberOfArguments; idx++) {
if (idx - 2 > [array count]) {
break;
}
id args = [self createParamWithMethodName:array[idx - 2] context:context callbackId:callbackId argument:argument];
if (args) {
[tempArray addObject:args];
}
[invocation setArgument:&args atIndex:idx];
}
[invocation invoke];
} @catch (NSException *exception) {
DoricLog(@"CallNative Error:%@", exception.reason);
}
};
const char *retType = methodSignature.methodReturnType;
if (!strcmp(retType, @encode(void))) {
ret = nil;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), block);
} else if (!strcmp(retType, @encode(id))) {
void *retValue;
block();
[invocation getReturnValue:&retValue];
id returnValue = (__bridge id) retValue;
ret = [JSValue valueWithObject:[returnValue copy] inContext:[JSContext currentContext]];
} else {
DoricLog(@"CallNative Error:%@", @"Must return object type");
ret = nil;
}
}
break;
}
}
}
if (methods) {
free(methods);
}
if (!isFound) {
Class superclass = class_getSuperclass(clz);
if (superclass && superclass != [NSObject class]) {
return [self findClass:superclass target:target context:context method:name callbackId:callbackId argument:argument];
}
}
return ret;
}
@end

View File

@@ -0,0 +1,25 @@
/*
* 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/23.
//
#import <Foundation/Foundation.h>
#import "DoricLoaderProtocol.h"
@interface DoricHttpJSLoader : NSObject <DoricLoaderProtocol>
@end

View File

@@ -0,0 +1,45 @@
/*
* 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/23.
//
#import "DoricHttpJSLoader.h"
@implementation DoricHttpJSLoader
- (BOOL)filter:(NSString *)scheme {
return [scheme hasPrefix:@"http"];
}
- (DoricAsyncResult <NSString *> *)request:(NSString *)scheme {
DoricAsyncResult *ret = [DoricAsyncResult new];
NSURL *URL = [NSURL URLWithString:scheme];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
[[[NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]
dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (!error) {
NSString *dataStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
[ret setupResult:dataStr];
} else {
[ret setupError:[[NSException alloc] initWithName:@"DoricJSLoaderManager Exception" reason:error.description userInfo:nil]];
}
}] resume];
return ret;
}
@end

View File

@@ -0,0 +1,33 @@
/*
* 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.
*/
//
// DoricJSLoaderManager.h
// Doric
//
// Created by pengfei.zhou on 2019/11/23.
//
#import <Foundation/Foundation.h>
#import "DoricLoaderProtocol.h"
#import "DoricAsyncResult.h"
@interface DoricJSLoaderManager : NSObject
+ (instancetype)instance;
- (void)addJSLoader:(id <DoricLoaderProtocol>)loader;
- (DoricAsyncResult <NSString *> *)request:(NSString *)scheme;
@end

View File

@@ -0,0 +1,68 @@
/*
* 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.
*/
//
// DoricJSLoaderManager.m
// Doric
//
// Created by pengfei.zhou on 2019/11/23.
//
#import "DoricJSLoaderManager.h"
#import "DoricMainBundleJSLoader.h"
#import "DoricHttpJSLoader.h"
#import "Doric.h"
@interface DoricJSLoaderManager ()
@property(nonatomic, copy) NSSet <id <DoricLoaderProtocol>> *loaders;
@end
@implementation DoricJSLoaderManager
+ (instancetype)instance {
static DoricJSLoaderManager *_instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [DoricJSLoaderManager new];
});
return _instance;
}
- (instancetype)init {
if (self = [super init]) {
_loaders = [[NSSet alloc] initWithArray:@[
[DoricMainBundleJSLoader new],
[DoricHttpJSLoader new],
]];
}
return self;
}
- (void)addJSLoader:(id <DoricLoaderProtocol>)loader {
self.loaders = [[self.loaders mutableCopy] also:^(NSMutableSet *it) {
[it addObject:loader];
}];
}
- (DoricAsyncResult <NSString *> *)request:(NSString *)scheme {
__block DoricAsyncResult *ret;
[self.loaders enumerateObjectsUsingBlock:^(id <DoricLoaderProtocol> obj, BOOL *stop) {
if ([obj filter:scheme]) {
ret = [obj request:scheme];
*stop = YES;
}
}];
return ret;
}
@end

View File

@@ -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/23.
//
#import <Foundation/Foundation.h>
#import "DoricAsyncResult.h"
@protocol DoricLoaderProtocol <NSObject>
- (BOOL)filter:(NSString *)scheme;
- (DoricAsyncResult <NSString *> *)request:(NSString *)scheme;
@end

View File

@@ -0,0 +1,25 @@
/*
* 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/23.
//
#import <Foundation/Foundation.h>
#import "DoricLoaderProtocol.h"
@interface DoricMainBundleJSLoader : NSObject <DoricLoaderProtocol>
@end

View File

@@ -0,0 +1,42 @@
/*
* 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/23.
//
#import "DoricMainBundleJSLoader.h"
@implementation DoricMainBundleJSLoader
- (BOOL)filter:(NSString *)scheme {
return [scheme hasPrefix:@"assets"];
}
- (DoricAsyncResult <NSString *> *)request:(NSString *)scheme {
DoricAsyncResult <NSString *> *ret = [DoricAsyncResult new];
NSString *path = [[NSBundle mainBundle] bundlePath];
NSString *fullPath = [path stringByAppendingPathComponent:[scheme substringFromIndex:@"assets://".length]];
NSError *error;
NSString *jsContent = [NSString stringWithContentsOfFile:fullPath encoding:NSUTF8StringEncoding error:&error];
if (error) {
[ret setupError:[NSException new]];
} else {
[ret setupResult:jsContent];
}
return ret;
}
@end

View File

@@ -0,0 +1,31 @@
/*
* 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/25.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@protocol DoricNavBarDelegate <NSObject>
- (BOOL)doric_navBar_isHidden;
- (void)doric_navBar_setHidden:(BOOL)hidden;
- (void)doric_navBar_setTitle:(NSString *)title;
- (void)doric_navBar_setBackgroundColor:(UIColor *)color;
@end

View File

@@ -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/23.
//
#import <Foundation/Foundation.h>
@protocol DoricNavigatorDelegate <NSObject>
- (void)doric_navigator_push:(NSString *)scheme alias:(NSString *)alias animated:(BOOL)animated extra:(NSString *)extra;
- (void)doric_navigator_pop:(BOOL)animated;
@end

View File

@@ -0,0 +1,10 @@
//
// Created by pengfei.zhou on 2019/11/29.
//
#import <Foundation/Foundation.h>
#import "DoricNativePlugin.h"
@interface DoricAnimatePlugin : DoricNativePlugin
@end

View File

@@ -0,0 +1,33 @@
//
// Created by pengfei.zhou on 2019/11/29.
//
#import "DoricAnimatePlugin.h"
#import "DoricRootNode.h"
@implementation DoricAnimatePlugin
- (void)submit:(NSDictionary *)args withPromise:(DoricPromise *)promise {
[promise resolve:nil];
}
- (void)animateRender:(NSDictionary *)args withPromise:(DoricPromise *)promise {
NSNumber *duration = args[@"duration"];
dispatch_async(dispatch_get_main_queue(), ^{
NSString *viewId = args[@"id"];
[UIView animateWithDuration:[duration floatValue] / 1000
animations:^{
if (self.doricContext.rootNode.viewId == nil) {
self.doricContext.rootNode.viewId = viewId;
[self.doricContext.rootNode blend:args[@"props"]];
} else {
DoricViewNode *viewNode = [self.doricContext targetViewNode:viewId];
[viewNode blend:args[@"props"]];
}
}
completion:^(BOOL finished) {
[promise resolve:nil];
}];
});
}
@end

View File

@@ -0,0 +1,32 @@
/*
* 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.
*/
//
// DoricModalPlugin.h
// Doric
//
// Created by pengfei.zhou on 2019/7/29.
//
#import <Foundation/Foundation.h>
#import "DoricNativePlugin.h"
NS_ASSUME_NONNULL_BEGIN
@interface DoricModalPlugin : DoricNativePlugin
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,115 @@
/*
* 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.
*/
//
// DoricModalPlugin.m
// Doric
//
// Created by pengfei.zhou on 2019/7/29.
//
#import "DoricModalPlugin.h"
#import "DoricUtil.h"
#import "DoricExtensions.h"
@implementation DoricModalPlugin
- (void)toast:(NSDictionary *)dic withPromise:(DoricPromise *)promise {
dispatch_async(dispatch_get_main_queue(), ^{
__block DoricGravity gravity = BOTTOM;
[dic[@"gravity"] also:^(NSNumber *it) {
gravity = (DoricGravity) [it integerValue];
}];
ShowToast(dic[@"msg"], gravity);
});
}
- (void)alert:(NSDictionary *)dic withPromise:(DoricPromise *)promise {
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:dic[@"title"]
message:dic[@"msg"]
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *action = [UIAlertAction actionWithTitle:dic[@"okLabel"] ?: NSLocalizedString(@"OK", nil)
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
[promise resolve:nil];
}];
[alert addAction:action];
UIViewController *vc = [UIApplication sharedApplication].keyWindow.rootViewController;
[vc presentViewController:alert animated:YES completion:nil];
});
}
- (void)confirm:(NSDictionary *)dic withPromise:(DoricPromise *)promise {
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:dic[@"title"]
message:dic[@"msg"]
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:dic[@"okLabel"] ?: NSLocalizedString(@"Ok", nil)
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
[promise resolve:nil];
}];
[alert addAction:okAction];
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:dic[@"cancelLabel"] ?: NSLocalizedString(@"Cancel", nil)
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
[promise reject:nil];
}];
[alert addAction:cancelAction];
UIViewController *vc = [UIApplication sharedApplication].keyWindow.rootViewController;
[vc presentViewController:alert animated:YES completion:nil];
});
}
- (void)prompt:(NSDictionary *)dic withPromise:(DoricPromise *)promise {
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:dic[@"title"]
message:dic[@"msg"]
preferredStyle:UIAlertControllerStyleAlert];
NSString *placeholder = dic[@"defaultText"];
NSString *preText = dic[@"text"];
[alert addTextFieldWithConfigurationHandler:^(UITextField *_Nonnull textField) {
if (placeholder.length > 0) {
textField.placeholder = placeholder;
}
if (preText.length > 0) {
textField.text = preText;
}
}];
__weak typeof(alert) _alert = alert;
UIAlertAction *okAction = [UIAlertAction actionWithTitle:dic[@"okLabel"] ?: NSLocalizedString(@"Ok", nil)
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
__strong typeof(_alert) alert = _alert;
[promise resolve:alert.textFields.lastObject.text];
}];
[alert addAction:okAction];
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:dic[@"cancelLabel"] ?: NSLocalizedString(@"Cancel", nil)
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
__strong typeof(_alert) alert = _alert;
[promise reject:alert.textFields.lastObject.text];
}];
[alert addAction:cancelAction];
UIViewController *vc = [UIApplication sharedApplication].keyWindow.rootViewController;
[vc presentViewController:alert animated:YES completion:nil];
});
}
@end

View File

@@ -0,0 +1,34 @@
/*
* 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.
*/
//
// DoricNativePlugin.h
// Doric
//
// Created by pengfei.zhou on 2019/7/29.
//
#import <Foundation/Foundation.h>
#import "DoricContextHolder.h"
#import "DoricPromise.h"
#import "DoricRegistry.h"
NS_ASSUME_NONNULL_BEGIN
@interface DoricNativePlugin : DoricContextHolder
@end
NS_ASSUME_NONNULL_END

View File

@@ -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.
*/
//
// DoricNativePlugin.m
// Doric
//
// Created by pengfei.zhou on 2019/7/29.
//
#import "DoricNativePlugin.h"
@implementation DoricNativePlugin
@end

View File

@@ -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/25.
//
#import <Foundation/Foundation.h>
#import "DoricNativePlugin.h"
@interface DoricNavBarPlugin : DoricNativePlugin
@end

View File

@@ -0,0 +1,68 @@
/*
* 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/25.
//
#import "DoricNavBarPlugin.h"
#import "DoricUtil.h"
@implementation DoricNavBarPlugin
- (void)isHidden:(NSDictionary *)param withPromise:(DoricPromise *)promise {
if (self.doricContext.navBar) {
dispatch_async(dispatch_get_main_queue(), ^{
[promise resolve:@([self.doricContext.navBar doric_navBar_isHidden])];
});
} else {
[promise reject:@"Not implement NavBar"];
}
}
- (void)setHidden:(NSDictionary *)param withPromise:(DoricPromise *)promise {
if (self.doricContext.navBar) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.doricContext.navBar doric_navBar_setHidden:[param[@"hidden"] boolValue]];
[promise resolve:nil];
});
} else {
[promise reject:@"Not implement NavBar"];
}
}
- (void)setTitle:(NSDictionary *)param withPromise:(DoricPromise *)promise {
if (self.doricContext.navBar) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.doricContext.navBar doric_navBar_setTitle:param[@"title"]];
[promise resolve:nil];
});
} else {
[promise reject:@"Not implement NavBar"];
}
}
- (void)setBgColor:(NSDictionary *)param withPromise:(DoricPromise *)promise {
if (self.doricContext.navBar) {
dispatch_async(dispatch_get_main_queue(), ^{
UIColor *color = DoricColor(param[@"color"]);
[self.doricContext.navBar doric_navBar_setBackgroundColor:color];
[promise resolve:nil];
});
} else {
[promise reject:@"Not implement NavBar"];
}
}
@end

View File

@@ -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/23.
//
#import <Foundation/Foundation.h>
#import "DoricNativePlugin.h"
@interface DoricNavigatorPlugin : DoricNativePlugin
@end

View File

@@ -0,0 +1,52 @@
/*
* 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/23.
//
#import "DoricNavigatorPlugin.h"
@implementation DoricNavigatorPlugin
- (void)push:(NSDictionary *)params {
dispatch_async(dispatch_get_main_queue(), ^{
BOOL animated = YES;
NSString *scheme = params[@"scheme"];
NSString *alias = scheme;
NSDictionary *config = params[@"config"];
if (config) {
if (config[@"animated"]) {
animated = [config[@"animated"] boolValue];
}
if (config[@"alias"]) {
alias = config[@"alias"];
}
}
[self.doricContext.navigator doric_navigator_push:scheme alias:alias animated:animated extra:config[@"extra"]];
});
}
- (void)pop:(NSDictionary *)params {
dispatch_async(dispatch_get_main_queue(), ^{
BOOL animated = YES;
if (params[@"animated"]) {
animated = [params[@"animated"] boolValue];
}
[self.doricContext.navigator doric_navigator_pop:animated];
});
}
@end

View File

@@ -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/21.
//
#import <Foundation/Foundation.h>
#import "DoricNativePlugin.h"
@interface DoricNetworkPlugin : DoricNativePlugin
@end

View File

@@ -0,0 +1,59 @@
/*
* 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/21.
//
#import "DoricNetworkPlugin.h"
@implementation DoricNetworkPlugin
- (void)request:(NSDictionary *)dic withPromise:(DoricPromise *)promise {
NSString *url = dic[@"url"];
NSString *method = dic[@"method"];
NSDictionary <NSString *, NSString *> *headers = dic[@"headers"];
NSNumber *timeout = dic[@"timeout"];
NSString *data = dic[@"data"];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:url]];
request.HTTPMethod = method.uppercaseString;
if (timeout) {
request.timeoutInterval = [timeout floatValue] / 1000;
}
if (headers) {
[headers enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop) {
[request setValue:obj forHTTPHeaderField:key];
}];
}
if (data) {
[request setHTTPBody:[data dataUsingEncoding:NSUTF8StringEncoding]];
}
[[[NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]
dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (!error) {
NSString *dataStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSDictionary *resDic = @{
@"status": @(((NSHTTPURLResponse *) response).statusCode),
@"headers": ((NSHTTPURLResponse *) response).allHeaderFields,
@"data": dataStr,
};
[promise resolve:resDic];
} else {
[promise reject:error.description];
}
}] resume];
}
@end

View File

@@ -0,0 +1,9 @@
//
// Created by pengfei.zhou on 2019/11/28.
//
#import <Foundation/Foundation.h>
#import "DoricNativePlugin.h"
@interface DoricPopoverPlugin : DoricNativePlugin
@end

View File

@@ -0,0 +1,70 @@
//
// Created by pengfei.zhou on 2019/11/28.
//
#import "DoricPopoverPlugin.h"
#import "DoricRootNode.h"
#import "Doric.h"
@interface DoricPopoverPlugin ()
@property(nonatomic, strong) UIView *fullScreenView;
@end
@implementation DoricPopoverPlugin
- (void)show:(NSDictionary *)params withPromise:(DoricPromise *)promise {
dispatch_async(dispatch_get_main_queue(), ^{
UIView *superView = [UIApplication sharedApplication].windows.lastObject;
if (!self.fullScreenView) {
self.fullScreenView = [[DoricStackView new] also:^(UIView *it) {
it.width = superView.width;
it.height = superView.height;
it.top = it.left = 0;
[superView addSubview:it];
}];
}
[superView bringSubviewToFront:self.fullScreenView];
self.fullScreenView.hidden = NO;
NSString *viewId = params[@"id"];
NSString *type = params[@"type"];
DoricViewNode *viewNode = [self.doricContext targetViewNode:viewId];
if (!viewNode) {
viewNode = [[DoricViewNode create:self.doricContext withType:type] also:^(DoricViewNode *it) {
it.viewId = viewId;
[it initWithSuperNode:nil];
it.view.layoutConfig = [DoricLayoutConfig new];
[self.fullScreenView addSubview:it.view];
self.doricContext.headNodes[viewId] = it;
}];
}
[viewNode blend:params[@"props"]];
[promise resolve:nil];
});
}
- (void)dismiss:(NSDictionary *)params withPromise:(DoricPromise *)promise {
NSString *viewId = params[@"id"];
dispatch_async(dispatch_get_main_queue(), ^{
if (viewId) {
DoricViewNode *viewNode = [self.doricContext targetViewNode:viewId];
[self dismissViewNode:viewNode];
} else {
[self dismissPopover];
}
[promise resolve:nil];
});
}
- (void)dismissViewNode:(DoricViewNode *)node {
[self.doricContext.headNodes removeObjectForKey:node.viewId];
[node.view removeFromSuperview];
if (self.doricContext.headNodes.count == 0) {
self.fullScreenView.hidden = YES;
}
}
- (void)dismissPopover {
for (DoricViewNode *node in self.doricContext.headNodes.allValues) {
[self dismissViewNode:node];
}
}
@end

View File

@@ -0,0 +1,32 @@
/*
* 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.
*/
//
// DoricPromise.h
// Doric
//
// Created by pengfei.zhou on 2019/7/29.
//
#import <Foundation/Foundation.h>
#import "DoricContext.h"
@interface DoricPromise : NSObject
- (instancetype)initWithContext:(DoricContext *)context callbackId:(NSString *)callbackId;
- (void)resolve:(id)result;
- (void)reject:(id)result;
@end

View File

@@ -0,0 +1,49 @@
/*
* 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.
*/
//
// DoricPromise.m
// Doric
//
// Created by pengfei.zhou on 2019/7/29.
//
#import "DoricPromise.h"
#import "DoricConstant.h"
@interface DoricPromise ()
@property(nonatomic, strong) DoricContext *context;
@property(nonatomic, strong) NSString *callbackId;
@end
@implementation DoricPromise
- (instancetype)initWithContext:(DoricContext *)context callbackId:(NSString *)callbackId {
if (self = [super init]) {
_context = context;
_callbackId = callbackId;
}
return self;
}
- (void)resolve:(id)result {
[self.context.driver invokeDoricMethod:DORIC_BRIDGE_RESOLVE, self.context.contextId, self.callbackId, result, nil];
}
- (void)reject:(id)result {
[self.context.driver invokeDoricMethod:DORIC_BRIDGE_REJECT, self.context.contextId, self.callbackId, result, nil];
}
@end

View File

@@ -0,0 +1,32 @@
/*
* 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.
*/
//
// DoricShaderPlugin.h
// Doric
//
// Created by pengfei.zhou on 2019/7/29.
//
#import <Foundation/Foundation.h>
#import "DoricNativePlugin.h"
NS_ASSUME_NONNULL_BEGIN
@interface DoricShaderPlugin : DoricNativePlugin
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,143 @@
/*
* 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.
*/
//
// DoricShaderPlugin.m
// Doric
//
// Created by pengfei.zhou on 2019/7/29.
//
#import "DoricShaderPlugin.h"
#import "DoricRootNode.h"
#import "DoricUtil.h"
#import <objc/runtime.h>
@implementation DoricShaderPlugin
- (void)render:(NSDictionary *)argument {
if (!argument) {
return;
}
__weak typeof(self) _self = self;
dispatch_async(dispatch_get_main_queue(), ^{
__strong typeof(_self) self = _self;
NSString *viewId = argument[@"id"];
if (self.doricContext.rootNode.viewId == nil) {
self.doricContext.rootNode.viewId = viewId;
[self.doricContext.rootNode blend:argument[@"props"]];
} else {
DoricViewNode *viewNode = [self.doricContext targetViewNode:viewId];
[viewNode blend:argument[@"props"]];
}
});
}
- (id)command:(NSDictionary *)argument withPromise:(DoricPromise *)promise {
NSArray *viewIds = argument[@"viewIds"];
id args = argument[@"args"];
NSString *name = argument[@"name"];
DoricViewNode *viewNode = nil;
for (NSString *viewId in viewIds) {
if (!viewNode) {
viewNode = [self.doricContext targetViewNode:viewId];
} else {
if ([viewNode isKindOfClass:[DoricSuperNode class]]) {
viewNode = [((DoricSuperNode *) viewNode) subNodeWithViewId:viewId];
}
}
}
if (!viewNode) {
[promise reject:@"Cannot find opposite view"];
return nil;
} else {
return [self findClass:[viewNode class] target:viewNode method:name promise:promise argument:args];
}
}
- (id)createParamWithMethodName:(NSString *)method promise:(DoricPromise *)promise argument:(id)argument {
if ([method isEqualToString:@"withPromise"]) {
return promise;
}
return argument;
}
- (id)findClass:(Class)clz target:(id)target method:(NSString *)name promise:(DoricPromise *)promise argument:(id)argument {
unsigned int count;
id ret = nil;
Method *methods = class_copyMethodList(clz, &count);
BOOL isFound = NO;
for (int i = 0; i < count; i++) {
NSString *methodName = [NSString stringWithCString:sel_getName(method_getName(methods[i])) encoding:NSUTF8StringEncoding];
NSArray *array = [methodName componentsSeparatedByString:@":"];
if (array && [array count] > 0) {
if ([array[0] isEqualToString:name]) {
isFound = YES;
SEL selector = NSSelectorFromString(methodName);
NSMethodSignature *methodSignature = [target methodSignatureForSelector:selector];
if (methodSignature) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
invocation.selector = selector;
invocation.target = target;
__weak __typeof__(self) _self = self;
dispatch_block_t block = ^() {
__strong __typeof__(_self) self = _self;
@try {
for (NSUInteger idx = 2; idx < methodSignature.numberOfArguments; idx++) {
if (idx - 2 > [array count]) {
break;
}
id param = [self createParamWithMethodName:array[idx - 2] promise:promise argument:argument];
[invocation setArgument:&param atIndex:idx];
}
[invocation invoke];
} @catch (NSException *exception) {
DoricLog(@"CallNative Error:%@", exception.reason);
}
};
dispatch_async(dispatch_get_main_queue(), ^{
void *retValue;
block();
const char *retType = methodSignature.methodReturnType;
if (!strcmp(retType, @encode(void))) {
} else {
[invocation getReturnValue:&retValue];
id returnValue = (__bridge id) retValue;
[promise resolve:returnValue];
}
});
return ret;
}
break;
}
}
}
if (methods) {
free(methods);
}
if (!isFound) {
Class superclass = class_getSuperclass(clz);
if (superclass && superclass != [NSObject class]) {
return [self findClass:superclass target:target method:name promise:promise argument:argument];
}
}
return ret;
}
@end

View File

@@ -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/22.
//
#import <Foundation/Foundation.h>
#import "DoricNativePlugin.h"
@interface DoricStoragePlugin : DoricNativePlugin
@end

View File

@@ -0,0 +1,98 @@
/*
* 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/22.
//
#import "DoricStoragePlugin.h"
#import "YYDiskCache.h"
static NSString *doric_prefix = @"pref";
@interface DoricStoragePlugin ()
@property(atomic, strong) NSMutableDictionary <NSString *, YYDiskCache *> *cachedMap;
@property(nonatomic, strong) YYDiskCache *defaultCache;
@property(nonatomic, copy) NSString *basePath;
@end
@implementation DoricStoragePlugin
- (instancetype)initWithContext:(DoricContext *)doricContext {
if (self = [super initWithContext:doricContext]) {
_basePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]
stringByAppendingPathComponent:@"doric"];
_cachedMap = [NSMutableDictionary new];
}
return self;
}
- (YYDiskCache *)defaultCache {
if (!_defaultCache) {
_defaultCache = [[YYDiskCache alloc] initWithPath:[self.basePath stringByAppendingPathComponent:doric_prefix]];
}
return _defaultCache;
}
- (YYDiskCache *)getDiskCache:(NSString *)zone {
YYDiskCache *diskCache;
if (zone) {
diskCache = self.cachedMap[zone];
if (!diskCache) {
diskCache = [[YYDiskCache alloc] initWithPath:[self.basePath
stringByAppendingPathComponent:[NSString stringWithFormat:@"%@_%@", doric_prefix, zone]]];
self.cachedMap[zone] = diskCache;
}
} else {
diskCache = self.defaultCache;
}
return diskCache;
}
- (void)setItem:(NSDictionary *)argument withPromise:(DoricPromise *)promise {
NSString *zone = argument[@"zone"];
NSString *key = argument[@"key"];
NSString *value = argument[@"value"];
YYDiskCache *diskCache = [self getDiskCache:zone];
[diskCache setObject:value forKey:key withBlock:^{
[promise resolve:nil];
}];
}
- (void)getItem:(NSDictionary *)argument withPromise:(DoricPromise *)promise {
NSString *zone = argument[@"zone"];
NSString *key = argument[@"key"];
YYDiskCache *diskCache = [self getDiskCache:zone];
[diskCache objectForKey:key withBlock:^(NSString *key, NSString *value) {
[promise resolve:value];
}];
}
- (void)remove:(NSDictionary *)argument withPromise:(DoricPromise *)promise {
NSString *zone = argument[@"zone"];
NSString *key = argument[@"key"];
YYDiskCache *diskCache = [self getDiskCache:zone];
[diskCache removeObjectForKey:key withBlock:^(NSString *key) {
[promise resolve:nil];
}];
}
- (void)clear:(NSDictionary *)argument withPromise:(DoricPromise *)promise {
NSString *zone = argument[@"zone"];
YYDiskCache *diskCache = [self getDiskCache:zone];
[diskCache removeAllObjectsWithBlock:^{
[promise resolve:nil];
}];
}
@end

View File

@@ -0,0 +1,25 @@
/*
* 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/26.
//
#import <Foundation/Foundation.h>
#import "DoricSuperNode.h"
#import "DoricSwipeRefreshLayout.h"
@interface DoricRefreshableNode : DoricSuperNode<DoricSwipeRefreshLayout *>
@end

View File

@@ -0,0 +1,176 @@
/*
* 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/26.
//
#import "DoricRefreshableNode.h"
#import "Doric.h"
@interface DoricRefreshableNode () <DoricSwipePullingDelegate>
@property(nonatomic, strong) DoricViewNode *contentNode;
@property(nonatomic, copy) NSString *contentViewId;
@property(nonatomic, strong) DoricViewNode *headerNode;
@property(nonatomic, copy) NSString *headerViewId;
@end
@implementation DoricRefreshableNode
- (DoricSwipeRefreshLayout *)build {
return [[DoricSwipeRefreshLayout new] also:^(DoricSwipeRefreshLayout *it) {
it.swipePullingDelegate = self;
}];
}
- (void)blendView:(DoricSwipeRefreshLayout *)view forPropName:(NSString *)name propValue:(id)prop {
if ([@"content" isEqualToString:name]) {
self.contentViewId = prop;
} else if ([@"header" isEqualToString:name]) {
self.headerViewId = prop;
} else if ([@"onRefresh" isEqualToString:name]) {
__weak typeof(self) _self = self;
NSString *funcId = prop;
self.view.onRefreshBlock = ^{
__strong typeof(_self) self = _self;
[self callJSResponse:funcId, nil];
};
} else {
[super blendView:view forPropName:name propValue:prop];
}
}
- (DoricViewNode *)subNodeWithViewId:(NSString *)viewId {
if ([viewId isEqualToString:self.contentViewId]) {
return self.contentNode;
} else if ([viewId isEqualToString:self.headerViewId]) {
return self.headerNode;
} else {
return nil;
}
}
- (void)blend:(NSDictionary *)props {
[super blend:props];
[self blendContent];
[self blendHeader];
}
- (void)blendContent {
NSDictionary *contentModel = [self subModelOf:self.contentViewId];
if (!contentModel) {
return;
}
NSString *viewId = contentModel[@"id"];
NSString *type = contentModel[@"type"];
NSDictionary *childProps = contentModel[@"props"];
if (self.contentNode) {
if ([self.contentNode.viewId isEqualToString:viewId]) {
//skip
} else {
if (self.reusable && [type isEqualToString:self.contentNode.type]) {
[self.contentNode also:^(DoricViewNode *it) {
it.viewId = viewId;
[it blend:childProps];
}];
} else {
self.contentNode = [[DoricViewNode create:self.doricContext withType:type] also:^(DoricViewNode *it) {
it.viewId = viewId;
[it initWithSuperNode:self];
[it blend:childProps];
self.view.contentView = it.view;
}];
}
}
} else {
self.contentNode = [[DoricViewNode create:self.doricContext withType:type] also:^(DoricViewNode *it) {
it.viewId = viewId;
[it initWithSuperNode:self];
[it blend:childProps];
self.view.contentView = it.view;
}];
}
}
- (void)blendHeader {
NSDictionary *headerModel = [self subModelOf:self.headerViewId];
if (!headerModel) {
return;
}
NSString *viewId = headerModel[@"id"];
NSString *type = headerModel[@"type"];
NSDictionary *childProps = headerModel[@"props"];
if (self.headerNode) {
if ([self.headerNode.viewId isEqualToString:viewId]) {
//skip
} else {
if (self.reusable && [type isEqualToString:self.headerNode.type]) {
[self.headerNode also:^(DoricViewNode *it) {
it.viewId = viewId;
[it blend:childProps];
}];
} else {
self.headerNode = [[DoricViewNode create:self.doricContext withType:type] also:^(DoricViewNode *it) {
it.viewId = viewId;
[it initWithSuperNode:self];
[it blend:childProps];
self.view.headerView = it.view;
}];
}
}
} else {
self.headerNode = [[DoricViewNode create:self.doricContext withType:type] also:^(DoricViewNode *it) {
it.viewId = viewId;
[it initWithSuperNode:self];
[it blend:childProps];
self.view.headerView = it.view;
}];
}
}
- (void)blendSubNode:(NSDictionary *)subModel {
[[self subNodeWithViewId:subModel[@"id"]] blend:subModel[@"props"]];
}
- (void)startAnimation {
[self.headerNode callJSResponse:@"startAnimation", nil];
}
- (void)stopAnimation {
[self.headerNode callJSResponse:@"stopAnimation", nil];
}
- (void)setPullingDistance:(CGFloat)distance {
[self.headerNode callJSResponse:@"setPullingDistance", @(distance), nil];
}
- (void)setRefreshing:(NSNumber *)refreshable withPromise:(DoricPromise *)promise {
self.view.refreshing = [refreshable boolValue];
[promise resolve:nil];
}
- (void)setRefreshable:(NSNumber *)refreshing withPromise:(DoricPromise *)promise {
self.view.refreshable = [refreshing boolValue];
[promise resolve:nil];
}
- (NSNumber *)isRefreshing {
return @(self.view.refreshing);
}
- (NSNumber *)isRefreshable {
return @(self.view.refreshable);
}
@end

View File

@@ -0,0 +1,22 @@
//
// Created by pengfei.zhou on 2019/11/26.
//
#import <Foundation/Foundation.h>
@protocol DoricSwipePullingDelegate <NSObject>
- (void)startAnimation;
- (void)stopAnimation;
- (void)setPullingDistance:(CGFloat)rotation;
@end
@interface DoricSwipeRefreshLayout : UIScrollView
@property(nonatomic, strong) UIView *contentView;
@property(nonatomic, strong) UIView *headerView;
@property(nonatomic, assign) BOOL refreshable;
@property(nonatomic, assign) BOOL refreshing;
@property(nonatomic, strong) void (^onRefreshBlock)(void);
@property(nonatomic, weak) id <DoricSwipePullingDelegate> swipePullingDelegate;
@end

View File

@@ -0,0 +1,159 @@
/*
* 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/26.
//
#import "DoricSwipeRefreshLayout.h"
#import "UIView+Doric.h"
#import "DoricLayouts.h"
#import "Doric.h"
@interface DoricSwipeRefreshLayout () <UIScrollViewDelegate>
@end
@implementation DoricSwipeRefreshLayout
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
self.showsHorizontalScrollIndicator = NO;
self.showsVerticalScrollIndicator = NO;
self.alwaysBounceVertical = YES;
self.delegate = self;
if (@available(iOS 11, *)) {
self.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
}
return self;
}
- (instancetype)init {
if (self = [super init]) {
self.showsHorizontalScrollIndicator = NO;
self.showsVerticalScrollIndicator = NO;
self.alwaysBounceVertical = YES;
self.delegate = self;
if (@available(iOS 11, *)) {
self.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
}
return self;
}
- (CGSize)sizeThatFits:(CGSize)size {
if (self.contentView) {
return [self.contentView measureSize:size];
}
return CGSizeZero;
}
- (BOOL)requestFromSubview:(UIView *)subview {
if (subview == self.headerView) {
return NO;
}
return [super requestFromSubview:subview];
}
- (void)layoutSubviews {
[super layoutSubviews];
}
- (void)layoutSelf:(CGSize)targetSize {
if (self.contentOffset.y != 0) {
return;
}
self.width = targetSize.width;
self.height = targetSize.height;
[self.headerView also:^(UIView *it) {
[it layoutSelf:[it measureSize:targetSize]];
it.bottom = 0;
it.centerX = self.centerX;
}];
[self.contentView also:^(UIView *it) {
[it layoutSelf:targetSize];
}];
self.contentSize = self.frame.size;
}
- (void)setContentView:(UIView *)contentView {
if (_contentView) {
[_contentView removeFromSuperview];
}
_contentView = contentView;
[self addSubview:contentView];
}
- (void)setHeaderView:(UIView *)headerView {
if (_headerView) {
[_headerView removeFromSuperview];
}
_headerView = headerView;
[self addSubview:headerView];
self.refreshable = YES;
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
if (-scrollView.contentOffset.y >= self.headerView.height) {
dispatch_async(dispatch_get_main_queue(), ^{
self.refreshing = YES;
});
}
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView.contentOffset.y <= 0) {
[self.swipePullingDelegate setPullingDistance:-scrollView.contentOffset.y];
}
}
- (void)setContentOffset:(CGPoint)contentOffset {
[super setContentOffset:contentOffset];
}
- (void)setRefreshing:(BOOL)refreshing {
if (_refreshing == refreshing) {
return;
}
if (refreshing) {
[self setContentOffset:CGPointMake(0, -self.headerView.height) animated:YES];
self.scrollEnabled = NO;
if (self.onRefreshBlock) {
self.onRefreshBlock();
}
} else {
[self setContentOffset:(CGPoint) {0, 0} animated:YES];
self.scrollEnabled = YES;
}
_refreshing = refreshing;
}
- (void)setRefreshable:(BOOL)refreshable {
self.scrollEnabled = refreshable;
if (refreshable) {
self.contentOffset = (CGPoint) {0, 0};
self.contentInset = UIEdgeInsetsMake(0, 0, 0, 0);
}
}
- (BOOL)refreshable {
return self.scrollEnabled;
}
- (void)setContentSize:(CGSize)contentSize {
[super setContentSize:contentSize];
}
@end

View File

@@ -0,0 +1,25 @@
/*
* 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/28.
//
#import <Foundation/Foundation.h>
#import "DoricStackNode.h"
@interface DoricFlowLayoutItemNode : DoricStackNode
@end

View File

@@ -0,0 +1,48 @@
/*
* 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/28.
//
#import "DoricFlowLayoutItemNode.h"
@interface DoricFlowLayoutItemView : DoricStackView
@end
@implementation DoricFlowLayoutItemView
@end
@interface DoricFlowLayoutItemNode ()
@end
@implementation DoricFlowLayoutItemNode
- (instancetype)initWithContext:(DoricContext *)doricContext {
if (self = [super initWithContext:doricContext]) {
self.reusable = YES;
}
return self;
}
- (void)initWithSuperNode:(DoricSuperNode *)superNode {
[super initWithSuperNode:superNode];
self.reusable = YES;
}
- (DoricStackView *)build {
return [DoricFlowLayoutItemView new];
}
@end

View File

@@ -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/28.
//
#import <Foundation/Foundation.h>
#import "DoricSuperNode.h"
@interface DoricFlowLayoutNode : DoricSuperNode<UICollectionView *>
@end

View File

@@ -0,0 +1,372 @@
/*
* 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/28.
//
#import "DoricFlowLayoutNode.h"
#import "DoricFlowLayoutItemNode.h"
#import "DoricExtensions.h"
#import <JavaScriptCore/JavaScriptCore.h>
@protocol DoricFlowLayoutDelegate
- (CGFloat)doricFlowLayoutItemHeightAtIndexPath:(NSIndexPath *)indexPath;
- (CGFloat)doricFlowLayoutItemWidthAtIndexPath:(NSIndexPath *)indexPath;
- (CGFloat)doricFlowLayoutColumnSpace;
- (CGFloat)doricFlowLayoutRowSpace;
- (NSInteger)doricFlowLayoutColumnCount;
@end
@interface DoricFlowLayout : UICollectionViewLayout
@property(nonatomic, readonly) NSInteger columnCount;
@property(nonatomic, readonly) CGFloat columnSpace;
@property(nonatomic, readonly) CGFloat rowSpace;
@property(nonatomic, strong) NSMutableDictionary <NSNumber *, NSNumber *> *columnHeightInfo;
@property(nonatomic, weak) id <DoricFlowLayoutDelegate> delegate;
@end
@implementation DoricFlowLayout
- (instancetype)init {
if (self = [super init]) {
_columnHeightInfo = [NSMutableDictionary new];
}
return self;
}
- (NSInteger)columnCount {
return self.delegate.doricFlowLayoutColumnCount ?: 2;
}
- (CGFloat)columnSpace {
return self.delegate.doricFlowLayoutColumnSpace ?: 0;
}
- (CGFloat)rowSpace {
return self.delegate.doricFlowLayoutRowSpace ?: 0;
}
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
return YES;
}
- (void)prepareLayout {
[super prepareLayout];
for (int i = 0; i < self.columnCount; i++) {
self.columnHeightInfo[@(i)] = @(0);
}
}
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
for (int i = 0; i < self.columnCount; i++) {
self.columnHeightInfo[@(i)] = @(0);
}
NSMutableArray *array = [NSMutableArray array];
NSInteger count = [self.collectionView numberOfItemsInSection:0];
for (int i = 0; i < count; i++) {
UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
[array addObject:attrs];
}
return array;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
NSNumber *minYOfColumn = @(0);
for (NSNumber *key in self.columnHeightInfo.allKeys) {
if ([self.columnHeightInfo[key] floatValue] < [self.columnHeightInfo[minYOfColumn] floatValue]) {
minYOfColumn = key;
}
}
CGFloat width = [self.delegate doricFlowLayoutItemWidthAtIndexPath:indexPath];
CGFloat height = [self.delegate doricFlowLayoutItemHeightAtIndexPath:indexPath];
CGFloat x = (width + self.columnSpace) * [minYOfColumn integerValue];
CGFloat y = [self.columnHeightInfo[minYOfColumn] floatValue];
if (y > 0) {
y += self.rowSpace;
}
if (width == self.collectionView.width) {
CGFloat maxY = 0;
for (NSNumber *column in self.columnHeightInfo.allValues) {
maxY = MAX(maxY, [column floatValue]);
}
y = maxY + self.rowSpace;
} else {
self.columnHeightInfo[minYOfColumn] = @(y + height);
}
UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
attrs.frame = CGRectMake(x, y, width, height);
return attrs;
}
- (CGSize)collectionViewContentSize {
CGFloat width = self.collectionView.width;
CGFloat height = 0;
for (NSNumber *column in self.columnHeightInfo.allValues) {
height = MAX(height, [column floatValue]);
}
return CGSizeMake(width, height);
}
@end
@interface DoricFlowLayoutViewCell : UICollectionViewCell
@property(nonatomic, strong) DoricFlowLayoutItemNode *viewNode;
@end
@implementation DoricFlowLayoutViewCell
@end
@interface DoricFlowLayoutView : UICollectionView
@end
@implementation DoricFlowLayoutView
- (CGSize)sizeThatFits:(CGSize)size {
if (self.subviews.count > 0) {
CGFloat width = size.width;
CGFloat height = size.height;
for (UIView *child in self.subviews) {
CGSize childSize = [child measureSize:size];
width = MAX(childSize.width, width);
height = MAX(childSize.height, height);
}
return CGSizeMake(width, height);
}
return size;
}
- (void)layoutSelf:(CGSize)targetSize {
[super layoutSelf:targetSize];
[self reloadData];
}
@end
@interface DoricFlowLayoutNode () <UICollectionViewDataSource, UICollectionViewDelegate, DoricFlowLayoutDelegate>
@property(nonatomic, strong) NSMutableDictionary <NSNumber *, NSString *> *itemViewIds;
@property(nonatomic, strong) NSMutableDictionary <NSNumber *, NSValue *> *itemSizeInfo;
@property(nonatomic, assign) NSUInteger itemCount;
@property(nonatomic, assign) NSUInteger batchCount;
@property(nonatomic, assign) NSUInteger columnCount;
@property(nonatomic, assign) CGFloat columnSpace;
@property(nonatomic, assign) CGFloat rowSpace;
@property(nonatomic, copy) NSString *renderItemFuncId;
@property(nonatomic, copy) NSString *onLoadMoreFuncId;
@property(nonatomic, copy) NSString *loadMoreViewId;
@property(nonatomic, assign) BOOL loadMore;
@end
@implementation DoricFlowLayoutNode
- (instancetype)initWithContext:(DoricContext *)doricContext {
if (self = [super initWithContext:doricContext]) {
_itemViewIds = [NSMutableDictionary new];
_itemSizeInfo = [NSMutableDictionary new];
_batchCount = 15;
_columnCount = 2;
}
return self;
}
- (UICollectionView *)build {
DoricFlowLayout *flowLayout = [[DoricFlowLayout alloc] init];
flowLayout.delegate = self;
return [[[DoricFlowLayoutView alloc] initWithFrame:CGRectZero
collectionViewLayout:flowLayout]
also:^(UICollectionView *it) {
it.backgroundColor = [UIColor whiteColor];
it.pagingEnabled = YES;
it.delegate = self;
it.dataSource = self;
[it registerClass:[DoricFlowLayoutViewCell class] forCellWithReuseIdentifier:@"doricCell"];
[it registerClass:[DoricFlowLayoutViewCell class] forCellWithReuseIdentifier:@"doricLoadMoreCell"];
}];
}
- (void)blendView:(UICollectionView *)view forPropName:(NSString *)name propValue:(id)prop {
if ([@"columnSpace" isEqualToString:name]) {
self.columnSpace = [prop floatValue];
[self.view.collectionViewLayout invalidateLayout];
} else if ([@"rowSpace" isEqualToString:name]) {
self.rowSpace = [prop floatValue];
[self.view.collectionViewLayout invalidateLayout];
} else if ([@"columnCount" isEqualToString:name]) {
self.columnCount = [prop unsignedIntegerValue];
[self.view reloadData];
[self.view.collectionViewLayout invalidateLayout];
} else if ([@"itemCount" isEqualToString:name]) {
self.itemCount = [prop unsignedIntegerValue];
[self.view reloadData];
} else if ([@"renderItem" isEqualToString:name]) {
if ([self.renderItemFuncId isEqualToString:prop]) {
} else {
[self.itemViewIds removeAllObjects];
[self clearSubModel];
[self.view reloadData];
self.renderItemFuncId = prop;
}
} else if ([@"batchCount" isEqualToString:name]) {
self.batchCount = [prop unsignedIntegerValue];
} else if ([@"onLoadMore" isEqualToString:name]) {
self.onLoadMoreFuncId = prop;
} else if ([@"loadMoreView" isEqualToString:name]) {
self.loadMoreViewId = prop;
} else if ([@"loadMore" isEqualToString:name]) {
self.loadMore = [prop boolValue];
} else {
[super blendView:view forPropName:name propValue:prop];
}
}
- (NSDictionary *)itemModelAt:(NSUInteger)position {
if (position >= self.itemCount) {
return [self subModelOf:self.loadMoreViewId];
}
NSString *viewId = self.itemViewIds[@(position)];
if (viewId && viewId.length > 0) {
return [self subModelOf:viewId];
} else {
DoricAsyncResult *result = [self callJSResponse:@"renderBunchedItems", @(position), @(self.batchCount), nil];
JSValue *models = [result waitUntilResult];
NSArray *array = [models toArray];
[array enumerateObjectsUsingBlock:^(NSDictionary *obj, NSUInteger idx, BOOL *stop) {
NSString *thisViewId = obj[@"id"];
[self setSubModel:obj in:thisViewId];
NSUInteger pos = position + idx;
self.itemViewIds[@(pos)] = thisViewId;
}];
return array[0];
}
}
- (DoricViewNode *)subNodeWithViewId:(NSString *)viewId {
__block DoricViewNode *ret = nil;
[self.doricContext.driver ensureSyncInMainQueue:^{
for (UICollectionViewCell *collectionViewCell in self.view.visibleCells) {
if ([collectionViewCell isKindOfClass:[DoricFlowLayoutViewCell class]]) {
DoricFlowLayoutItemNode *node = ((DoricFlowLayoutViewCell *) collectionViewCell).viewNode;
if ([viewId isEqualToString:node.viewId]) {
ret = node;
break;
}
}
}
}];
return ret;
}
- (void)blendSubNode:(NSDictionary *)subModel {
dispatch_async(dispatch_get_main_queue(), ^{
NSString *viewId = subModel[@"id"];
DoricViewNode *viewNode = [self subNodeWithViewId:viewId];
if (viewNode) {
[viewNode blend:subModel[@"props"]];
} else {
NSMutableDictionary *model = [[self subModelOf:viewId] mutableCopy];
[self recursiveMixin:subModel to:model];
[self setSubModel:model in:viewId];
}
[self.itemViewIds enumerateKeysAndObjectsUsingBlock:^(NSNumber *_Nonnull key, NSString *_Nonnull obj, BOOL *_Nonnull stop) {
if ([viewId isEqualToString:obj]) {
*stop = YES;
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[key integerValue] inSection:0];
[UIView performWithoutAnimation:^{
[self.view reloadItemsAtIndexPaths:@[indexPath]];
}];
}
}];
});
}
- (void)callItem:(NSUInteger)position size:(CGSize)size {
NSValue *old = self.itemSizeInfo[@(position)];
if (old && CGSizeEqualToSize([old CGSizeValue], size)) {
return;
}
self.itemSizeInfo[@(position)] = [NSValue valueWithCGSize:size];
[self.view.collectionViewLayout invalidateLayout];
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return self.itemCount + (self.loadMore ? 1 : 0);
}
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
NSUInteger position = (NSUInteger) indexPath.row;
NSDictionary *model = [self itemModelAt:position];
NSDictionary *props = model[@"props"];
DoricFlowLayoutViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"doricCell" forIndexPath:indexPath];
if (!cell.viewNode) {
DoricFlowLayoutItemNode *itemNode = [[DoricFlowLayoutItemNode alloc] initWithContext:self.doricContext];
[itemNode initWithSuperNode:self];
cell.viewNode = itemNode;
[cell.contentView addSubview:itemNode.view];
}
DoricFlowLayoutItemNode *node = cell.viewNode;
node.viewId = model[@"id"];
[node blend:props];
CGFloat width = (collectionView.width - (self.columnCount - 1) * self.columnSpace) / self.columnCount;
CGSize size = [node.view measureSize:CGSizeMake(width, collectionView.height)];
if (position > 0 && position >= self.itemCount && self.onLoadMoreFuncId) {
size = CGSizeMake(collectionView.width, size.height);
[self callJSResponse:self.onLoadMoreFuncId, nil];
}
[node.view layoutSelf:size];
[self callItem:position size:size];
return cell;
}
- (CGFloat)doricFlowLayoutItemHeightAtIndexPath:(NSIndexPath *)indexPath {
NSUInteger position = (NSUInteger) indexPath.row;
NSValue *value = self.itemSizeInfo[@(position)];
if (value) {
return [value CGSizeValue].height;
} else {
return 100;
}
}
- (CGFloat)doricFlowLayoutItemWidthAtIndexPath:(NSIndexPath *)indexPath {
NSUInteger position = (NSUInteger) indexPath.row;
NSValue *value = self.itemSizeInfo[@(position)];
if (value) {
return [value CGSizeValue].width;
} else {
return 100;
}
}
- (CGFloat)doricFlowLayoutColumnSpace {
return self.columnSpace;
}
- (CGFloat)doricFlowLayoutRowSpace {
return self.rowSpace;
}
- (NSInteger)doricFlowLayoutColumnCount {
return self.columnCount;
}
@end

View File

@@ -0,0 +1,30 @@
/*
* 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.
*/
//
// DoricGroupNode.h
// Doric
//
// Created by pengfei.zhou on 2019/7/30.
//
#import "DoricSuperNode.h"
NS_ASSUME_NONNULL_BEGIN
@interface DoricGroupNode <V:UIView *> : DoricSuperNode<V>
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,170 @@
/*
* 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.
*/
//
// DoricGroupNode.m
// Doric
//
// Created by pengfei.zhou on 2019/7/30.
//
#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]) {
_childNodes = @[];
_childViewIds = @[];
}
return self;
}
- (UIView *)build {
UIView *ret = [[UIView alloc] init];
ret.clipsToBounds = YES;
return ret;
}
- (void)blendView:(UIView *)view forPropName:(NSString *)name propValue:(id)prop {
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)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 {
if (self.reusable) {
if ([oldNode.type isEqualToString:type]) {
///Same type,can be reused
oldNode.viewId = viewId;
[oldNode blend:model[@"props"]];
} else {
///Replace this view
[childNodes removeObjectAtIndex:idx];
[oldNode.view removeFromSuperview];
DoricViewNode *viewNode = [DoricViewNode create:self.doricContext withType:type];
if ([viewNode isKindOfClass:[DoricGroupNode class]]) {
((DoricGroupNode *) viewNode).reusable = self.reusable;
}
viewNode.viewId = viewId;
[viewNode initWithSuperNode:self];
[viewNode blend:model[@"props"]];
[childNodes insertObject:viewNode atIndex:idx];
[self.view insertSubview:viewNode.view atIndex:idx];
}
} 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];
///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];
}
}
}
} else {
/// Insert
DoricViewNode *viewNode = [DoricViewNode create:self.doricContext withType:type];
if ([viewNode isKindOfClass:[DoricGroupNode class]]) {
((DoricGroupNode *) viewNode).reusable = self.reusable;
}
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;
}
}];
}
- (DoricViewNode *)subNodeWithViewId:(NSString *)viewId {
for (DoricViewNode *viewNode in self.childNodes) {
if ([viewId isEqualToString:viewNode.viewId]) {
return viewNode;
}
}
return nil;
}
@end

View File

@@ -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.
*/
//
// DoricHLayoutNode.h
// Doric
//
// Created by pengfei.zhou on 2019/7/30.
//
#import "DoricGroupNode.h"
@interface DoricHLayoutNode : DoricGroupNode<DoricHLayoutView *>
@end

View File

@@ -0,0 +1,40 @@
/*
* 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.
*/
//
// DoricHLayoutNode.m
// Doric
//
// Created by pengfei.zhou on 2019/7/30.
//
#import "DoricHLayoutNode.h"
#import "DoricUtil.h"
@implementation DoricHLayoutNode
- (DoricHLayoutView *)build {
return [DoricHLayoutView new];
}
- (void)blendView:(DoricHLayoutView *)view forPropName:(NSString *)name propValue:(id)prop {
if ([name isEqualToString:@"gravity"]) {
view.gravity = (DoricGravity) [(NSNumber *) prop integerValue];
} else if ([name isEqualToString:@"space"]) {
view.space = [(NSNumber *) prop floatValue];
} else {
[super blendView:view forPropName:name propValue:prop];
}
}
@end

View File

@@ -0,0 +1,31 @@
/*
* 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.
*/
//
// DoricImageNode.h
// Doric
//
// Created by pengfei.zhou on 2019/8/6.
//
#import "DoricViewNode.h"
NS_ASSUME_NONNULL_BEGIN
@interface DoricImageNode : DoricViewNode<UIImageView *>
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,104 @@
/*
* 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.
*/
//
// DoricImageNode.m
// Doric
//
// Created by pengfei.zhou on 2019/8/6.
//
#import "DoricImageNode.h"
#import "Doric.h"
#import "YYWebImage.h"
@interface DoricImageNode ()
@property(nonatomic, copy) NSString *loadCallbackId;
@property(nonatomic, assign) BOOL isBlur;
@end
@implementation DoricImageNode
- (UIImageView *)build {
return [[YYAnimatedImageView new] also:^(UIImageView *it) {
it.clipsToBounds = YES;
}];
}
- (void)blend:(NSDictionary *)props {
NSInteger value = [props[@"isBlur"] intValue];
if(value == 1) {
self.isBlur = YES;
}
[super blend:props];
}
- (void)blendView:(UIImageView *)view forPropName:(NSString *)name propValue:(id)prop {
if ([@"imageUrl" isEqualToString:name]) {
__weak typeof(self) _self = self;
[view yy_setImageWithURL:[NSURL URLWithString:prop] placeholder:nil options:0 completion:^(UIImage *image, NSURL *url, YYWebImageFromType from, YYWebImageStage stage, NSError *error) {
__strong typeof(_self) self = _self;
if (error) {
if (self.loadCallbackId.length > 0) {
[self callJSResponse:self.loadCallbackId, nil];
}
} else {
if (self.loadCallbackId.length > 0) {
[self callJSResponse:self.loadCallbackId,
@{@"width": @(image.size.width), @"height": @(image.size.height)},
nil];
}
[self requestLayout];
}
if(self.isBlur) {
UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
UIVisualEffectView *effectView = [[UIVisualEffectView alloc]initWithEffect:blurEffect];
effectView.frame = CGRectMake(0, 0, image.size.width, image.size.height);
[view addSubview:effectView];
}
}];
} else if ([@"scaleType" isEqualToString:name]) {
switch ([prop integerValue]) {
case 1: {
self.view.contentMode = UIViewContentModeScaleAspectFit;
break;
}
case 2: {
self.view.contentMode = UIViewContentModeScaleAspectFill;
break;
}
default: {
self.view.contentMode = UIViewContentModeScaleToFill;
break;
}
}
} else if ([@"loadCallback" isEqualToString:name]) {
self.loadCallbackId = prop;
} else if ([@"imageBase64" isEqualToString:name]) {
NSString *base64 = prop;
if (YES == [base64 hasPrefix:@"data:image"]) {
base64 = [base64 componentsSeparatedByString:@","].lastObject;
}
NSData *imageData = [[NSData alloc] initWithBase64EncodedString:base64
options:NSDataBase64DecodingIgnoreUnknownCharacters];
UIImage *image = [UIImage imageWithData:imageData];
self.view.image = image;
} else {
[super blendView:view forPropName:name propValue:prop];
}
}
@end

View File

@@ -0,0 +1,31 @@
/*
* 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.
*/
//
// DoricInputNode.h
// Doric
//
// Created by 姜腾 on 2019/12/11.
//
#import "DoricViewNode.h"
NS_ASSUME_NONNULL_BEGIN
@interface DoricInputNode : DoricViewNode<UITextView *>
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,173 @@
/*
* 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.
*/
//
// DoricInputNode.m
// Doric
//
// Created by on 2019/12/11.
//
#import "DoricInputNode.h"
#import "DoricUtil.h"
#import "DoricPromise.h"
typedef void (^onTextChangeBlock)(NSString *text,DoricInputNode *node);
typedef void (^onFocusChangeBlock)(BOOL focused,DoricInputNode *node);
@interface DoricInputNode()<UITextViewDelegate>
@property(nonatomic, copy) onTextChangeBlock onTextChange;
@property(nonatomic, copy) onFocusChangeBlock onFocusShange;
@property(nonatomic, strong) UILabel *placeholderLabel;
@end
@implementation DoricInputNode
- (UITextView *)build {
UITextView *v = [[UITextView alloc] init];
v.delegate = self;
return v;
}
- (void)blendView:(UITextView *)view forPropName:(NSString *)name propValue:(id)prop {
if ([name isEqualToString:@"text"]) {
view.text = prop;
} else if ([name isEqualToString:@"textSize"]) {
view.font = [UIFont systemFontOfSize:[(NSNumber *) prop floatValue]];
} else if ([name isEqualToString:@"textColor"]) {
view.textColor = DoricColor(prop);
} else if ([name isEqualToString:@"textAlignment"]) {
DoricGravity gravity = (DoricGravity) [(NSNumber *) prop integerValue];
NSTextAlignment alignment = NSTextAlignmentCenter;
if ((gravity & LEFT) == LEFT) {
alignment = NSTextAlignmentLeft;
} else if ((gravity & RIGHT) == RIGHT) {
alignment = NSTextAlignmentRight;
}
view.textAlignment = alignment;
} else if ([name isEqualToString:@"multiline"]) {
BOOL mutilin = [(NSNumber *) prop boolValue];
if (!mutilin) {
view.textContainer.maximumNumberOfLines = 1;
}else {
view.textContainer.maximumNumberOfLines = 0;
}
} else if ([name isEqualToString:@"hintText"]) {
self.placeholderLabel.text = (NSString *)prop;
} else if ([name isEqualToString:@"hintTextColor"]) {
self.placeholderLabel.textColor = DoricColor(prop);
} else if ([name isEqualToString:@"onTextChange"]) {
if ([prop isKindOfClass:[NSString class]]) {
self.onTextChange = ^(NSString *text, DoricInputNode *node) {
[node callJSResponse:prop,text,nil];
};
}else {
self.onTextChange = nil;
}
} else if ([name isEqualToString:@"onFocusChange"]) {
if ([prop isKindOfClass:[NSString class]]) {
self.onFocusShange = ^(BOOL focused, DoricInputNode *node) {
[node callJSResponse:prop,@(focused),nil];
};
}else {
self.onFocusShange = nil;
}
} else{
[super blendView:view forPropName:name propValue:prop];
}
}
- (void)blend:(NSDictionary *)props {
[super blend:props];
[self updatePlaceholderLabel];
[self.view.superview setNeedsLayout];
}
#pragma mark - Doric-JS api
- (NSString *)getText {
return self.view.text;
}
- (void)setSelection:(NSDictionary *)params withPromise:(DoricPromise *)promise {
NSString *start = params[@"start"];
NSString *end = params[@"end"];
if (([start isKindOfClass:[NSString class]] || [start isKindOfClass:[NSNumber class]]) &&
([start isKindOfClass:[NSString class]] || [start isKindOfClass:[NSNumber class]])) {
self.view.selectedRange = NSMakeRange(start.intValue, end.intValue - start.intValue);
}
[promise resolve:nil];
}
- (void)requestFocus {
[self.view becomeFirstResponder];
}
- (void)releaseFocus {
[self.view resignFirstResponder];
}
#pragma mark - UITextViewDelegate
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView {
if (self.onFocusShange) {
self.onFocusShange(YES, self);
}
return YES;
}
- (BOOL)textViewShouldEndEditing:(UITextView *)textView {
if (self.onFocusShange) {
self.onFocusShange(NO, self);
}
return YES;
}
- (void)textViewDidChange:(UITextView *)textView {
if (self.onTextChange) {
self.onTextChange(textView.text, self);
}
[self updatePlaceholderLabel];
}
#pragma mark - placeholderLabel
- (UILabel *)placeholderLabel {
if (!_placeholderLabel) {
_placeholderLabel = [[UILabel alloc] init];
_placeholderLabel.numberOfLines = 0;
_placeholderLabel.userInteractionEnabled = NO;
}
return _placeholderLabel;
}
- (void)updatePlaceholderLabel {
if (self.view.text.length) {
[self.placeholderLabel removeFromSuperview];
return;
} else {
[self.view insertSubview:self.placeholderLabel atIndex:0];
}
self.placeholderLabel.textAlignment = self.view.textAlignment;
CGFloat lineFragmentPadding = self.view.textContainer.lineFragmentPadding;
UIEdgeInsets textContainerInset = self.view.textContainerInset;
CGFloat x = lineFragmentPadding + textContainerInset.left;
CGFloat y = textContainerInset.top;
CGFloat width = CGRectGetWidth(self.view.bounds) - x - lineFragmentPadding - textContainerInset.right;
CGFloat height = [self.placeholderLabel sizeThatFits:CGSizeMake(width, 0)].height;
self.placeholderLabel.frame = CGRectMake(x, y, width, height);
}
@end

View File

@@ -0,0 +1,106 @@
/*
* 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/10/23.
//
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>
#import <UIKit/UIKit.h>
typedef UIEdgeInsets DoricMargin;
typedef UIEdgeInsets DoricPadding;
DoricMargin DoricMarginMake(CGFloat left, CGFloat top, CGFloat right, CGFloat bottom);
typedef NS_ENUM(NSInteger, DoricLayoutSpec) {
DoricLayoutExact = 0,
DoricLayoutWrapContent = 1,
DoricLayoutAtMost = 2,
};
typedef NS_ENUM(NSInteger, DoricGravity) {
SPECIFIED = 1,
START = 1 << 1,
END = 1 << 2,
SHIFT_X = 0,
SHIFT_Y = 4,
LEFT = (START | SPECIFIED) << SHIFT_X,
RIGHT = (END | SPECIFIED) << SHIFT_X,
TOP = (START | SPECIFIED) << SHIFT_Y,
BOTTOM = (END | SPECIFIED) << SHIFT_Y,
CENTER_X = SPECIFIED << SHIFT_X,
CENTER_Y = SPECIFIED << SHIFT_Y,
CENTER = CENTER_X | CENTER_Y,
};
@interface DoricLayoutConfig : NSObject
@property(nonatomic, assign) DoricLayoutSpec widthSpec;
@property(nonatomic, assign) DoricLayoutSpec heightSpec;
@property(nonatomic) DoricMargin margin;
@property(nonatomic, assign) DoricGravity alignment;
@property(nonatomic, assign) NSUInteger weight;
- (instancetype)init;
- (instancetype)initWithWidth:(DoricLayoutSpec)width height:(DoricLayoutSpec)height;
- (instancetype)initWithWidth:(DoricLayoutSpec)width height:(DoricLayoutSpec)height margin:(DoricMargin)margin;
@end
@interface DoricLayoutContainer : UIView
@end
@interface DoricStackView : DoricLayoutContainer
@end
@interface DoricLinearView : DoricLayoutContainer
@property(nonatomic, assign) DoricGravity gravity;
@property(nonatomic, assign) CGFloat space;
@end
@interface DoricVLayoutView : DoricLinearView
@end
@interface DoricHLayoutView : DoricLinearView
@end
@interface UIView (DoricLayoutConfig)
@property(nonatomic, strong) DoricLayoutConfig *layoutConfig;
@end
@interface UIView (DoricPadding)
@property(nonatomic, assign) DoricPadding padding;
@end
@interface UIView (DoricTag)
@property(nonatomic, copy) NSString *tagString;
- (UIView *)viewWithTagString:(NSString *)tagString;
@end
@interface UIView (DoricLayouts)
- (void)layoutSelf:(CGSize)targetSize;
- (CGSize)measureSize:(CGSize)targetSize;
- (void)doricLayoutSubviews;
- (BOOL)requestFromSubview:(UIView *)subview;
@end

View File

@@ -0,0 +1,484 @@
/*
* 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/10/23.
//
#import "DoricLayouts.h"
#import <objc/runtime.h>
#import "UIView+Doric.h"
static const void *kLayoutConfig = &kLayoutConfig;
@implementation UIView (DoricLayoutConfig)
@dynamic layoutConfig;
- (void)setLayoutConfig:(DoricLayoutConfig *)layoutConfig {
objc_setAssociatedObject(self, kLayoutConfig, layoutConfig, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (DoricLayoutConfig *)layoutConfig {
return objc_getAssociatedObject(self, kLayoutConfig);
}
@end
static const void *kLayoutPadding = &kLayoutPadding;
@implementation UIView (DoricPadding)
@dynamic padding;
- (void)setPadding:(DoricPadding)padding {
objc_setAssociatedObject(self, kLayoutPadding, [NSValue value:&padding withObjCType:@encode(DoricPadding)], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (DoricPadding)padding {
DoricPadding value;
value.left = value.right = value.top = value.bottom = 0;
[objc_getAssociatedObject(self, kLayoutPadding) getValue:&value];
return value;
}
@end
static const void *kTagString = &kTagString;
@implementation UIView (DoricTag)
- (void)setTagString:(NSString *)tagString {
objc_setAssociatedObject(self, kTagString, tagString, OBJC_ASSOCIATION_COPY_NONATOMIC);
self.tag = [tagString hash];
}
- (NSString *)tagString {
return objc_getAssociatedObject(self, kTagString);
}
- (UIView *)viewWithTagString:(NSString *)tagString {
// notice the potential hash collision
return [self viewWithTag:[tagString hash]];
}
@end
@implementation UIView (DoricLayouts)
/**
* Measure self's size
* */
- (CGSize)measureSize:(CGSize)targetSize {
CGFloat width = self.width;
CGFloat height = self.height;
DoricLayoutConfig *config = self.layoutConfig;
if (!config) {
config = [DoricLayoutConfig new];
}
if (config.widthSpec == DoricLayoutAtMost
|| config.widthSpec == DoricLayoutWrapContent) {
width = targetSize.width - config.margin.left - config.margin.right;
}
if (config.heightSpec == DoricLayoutAtMost
|| config.heightSpec == DoricLayoutWrapContent) {
height = targetSize.height - config.margin.top - config.margin.bottom;
}
DoricPadding padding = self.padding;
CGSize contentSize = [self sizeThatFits:CGSizeMake(
width - padding.left - padding.right,
height - padding.top - padding.bottom)];
if (config.widthSpec == DoricLayoutWrapContent) {
width = contentSize.width + padding.left + padding.right;
}
if (config.heightSpec == DoricLayoutWrapContent) {
height = contentSize.height + padding.left + padding.top + padding.bottom;
}
return CGSizeMake(width, height);
}
/**
* layout self and subviews
* */
- (void)layoutSelf:(CGSize)targetSize {
self.width = targetSize.width;
self.height = targetSize.height;
for (UIView *view in self.subviews) {
[view layoutSelf:[view measureSize:targetSize]];
}
}
- (void)doricLayoutSubviews {
if ([self.superview requestFromSubview:self]) {
[self.superview doricLayoutSubviews];
} else {
[self layoutSelf:CGSizeMake(self.width, self.height)];
}
}
- (BOOL)requestFromSubview:(UIView *)subview {
if (self.layoutConfig
&& self.layoutConfig.widthSpec != DoricLayoutExact
&& self.layoutConfig.heightSpec != DoricLayoutExact) {
return YES;
}
return NO;
}
@end
DoricMargin DoricMarginMake(CGFloat left, CGFloat top, CGFloat right, CGFloat bottom) {
DoricMargin margin;
margin.left = left;
margin.top = top;
margin.right = right;
margin.bottom = bottom;
return margin;
}
@implementation DoricLayoutConfig
- (instancetype)init {
if (self = [super init]) {
_widthSpec = DoricLayoutExact;
_heightSpec = DoricLayoutExact;
}
return self;
}
- (instancetype)initWithWidth:(DoricLayoutSpec)width height:(DoricLayoutSpec)height {
if (self = [super init]) {
_widthSpec = width;
_heightSpec = height;
}
return self;
}
- (instancetype)initWithWidth:(DoricLayoutSpec)width height:(DoricLayoutSpec)height margin:(DoricMargin)margin {
if (self = [super init]) {
_widthSpec = width;
_heightSpec = height;
_margin = margin;
}
return self;
}
@end
@interface DoricLayoutContainer ()
@property(nonatomic, assign) CGFloat contentWidth;
@property(nonatomic, assign) CGFloat contentHeight;
@property(nonatomic, assign) NSUInteger contentWeight;
@end
@implementation DoricLayoutContainer
- (void)setNeedsLayout {
[super setNeedsLayout];
}
- (void)layoutSubviews {
[super layoutSubviews];
[self doricLayoutSubviews];
}
@end
@interface DoricStackView ()
@end
@implementation DoricStackView
- (CGSize)sizeThatFits:(CGSize)size {
CGFloat contentWidth = 0;
CGFloat contentHeight = 0;
for (UIView *child in self.subviews) {
if (child.isHidden) {
continue;
}
DoricLayoutConfig *childConfig = child.layoutConfig;
if (!childConfig) {
childConfig = [DoricLayoutConfig new];
}
CGSize childSize;
if (CGAffineTransformEqualToTransform(child.transform, CGAffineTransformIdentity)) {
childSize = [child measureSize:CGSizeMake(size.width, size.height)];
} else {
childSize = child.bounds.size;
}
contentWidth = MAX(contentWidth, childSize.width + childConfig.margin.left + childConfig.margin.right);
contentHeight = MAX(contentHeight, childSize.height + childConfig.margin.top + childConfig.margin.bottom);
}
self.contentWidth = contentWidth;
self.contentHeight = contentHeight;
return CGSizeMake(contentWidth, contentHeight);
}
- (void)layoutSelf:(CGSize)targetSize {
self.width = targetSize.width;
self.height = targetSize.height;
DoricPadding padding = self.padding;
for (UIView *child in self.subviews) {
if (child.isHidden) {
continue;
}
if (!CGAffineTransformEqualToTransform(child.transform, CGAffineTransformIdentity)) {
continue;
}
DoricLayoutConfig *childConfig = child.layoutConfig;
if (!childConfig) {
childConfig = [DoricLayoutConfig new];
}
CGSize size = [child measureSize:CGSizeMake(
targetSize.width - padding.left - padding.right,
targetSize.height - padding.top - padding.bottom)];
[child layoutSelf:size];
DoricGravity gravity = childConfig.alignment;
if ((gravity & LEFT) == LEFT) {
child.left = padding.left;
} else if ((gravity & RIGHT) == RIGHT) {
child.right = targetSize.width - padding.right;
} else if ((gravity & CENTER_X) == CENTER_X) {
child.centerX = targetSize.width / 2;
} else {
if (childConfig.margin.left) {
child.left = childConfig.margin.left + padding.left;
} else if (childConfig.margin.right) {
child.right = targetSize.width - childConfig.margin.right - padding.right;
}
}
if ((gravity & TOP) == TOP) {
child.top = padding.top;
} else if ((gravity & BOTTOM) == BOTTOM) {
child.bottom = targetSize.height - padding.bottom;
} else if ((gravity & CENTER_Y) == CENTER_Y) {
child.centerY = targetSize.height / 2;
} else {
if (childConfig.margin.top) {
child.top = childConfig.margin.top + padding.top;
} else if (childConfig.margin.bottom) {
child.bottom = targetSize.height - childConfig.margin.bottom - padding.bottom;
}
}
}
}
@end
@implementation DoricLinearView
@end
@implementation DoricVLayoutView
- (CGSize)sizeThatFits:(CGSize)size {
CGFloat contentWidth = 0;
CGFloat contentHeight = 0;
NSUInteger contentWeight = 0;
for (UIView *child in self.subviews) {
if (child.isHidden) {
continue;
}
DoricLayoutConfig *childConfig = child.layoutConfig;
if (!childConfig) {
childConfig = [DoricLayoutConfig new];
}
CGSize childSize;
if (CGAffineTransformEqualToTransform(child.transform, CGAffineTransformIdentity)) {
childSize = [child measureSize:CGSizeMake(size.width, size.height - contentHeight)];
} else {
childSize = child.bounds.size;
}
contentWidth = MAX(contentWidth, childSize.width + childConfig.margin.left + childConfig.margin.right);
contentHeight += childSize.height + self.space + childConfig.margin.top + childConfig.margin.bottom;
contentWeight += childConfig.weight;
}
contentHeight -= self.space;
self.contentWidth = contentWidth;
self.contentHeight = contentHeight;
self.contentWeight = contentWeight;
if (contentWeight) {
contentHeight = size.height;
}
return CGSizeMake(contentWidth, contentHeight);
}
- (void)layoutSelf:(CGSize)targetSize {
self.width = targetSize.width;
self.height = targetSize.height;
DoricPadding padding = self.padding;
CGFloat yStart = padding.top;
if ((self.gravity & TOP) == TOP) {
yStart = padding.top;
} else if ((self.gravity & BOTTOM) == BOTTOM) {
yStart = targetSize.height - self.contentHeight - padding.bottom;
} else if ((self.gravity & CENTER_Y) == CENTER_Y) {
yStart = (targetSize.height - self.contentHeight - padding.top - padding.bottom) / 2 + padding.top;
}
CGFloat remain = targetSize.height - self.contentHeight - padding.top - padding.bottom;
for (UIView *child in self.subviews) {
if (child.isHidden) {
continue;
}
if (!CGAffineTransformEqualToTransform(child.transform, CGAffineTransformIdentity)) {
continue;
}
DoricLayoutConfig *childConfig = child.layoutConfig;
if (!childConfig) {
childConfig = [DoricLayoutConfig new];
}
CGSize size = [child measureSize:CGSizeMake(
targetSize.width - padding.left - padding.right,
targetSize.height - yStart - padding.bottom)];
if (childConfig.weight) {
size.height += remain / self.contentWeight * childConfig.weight;
}
[child layoutSelf:size];
DoricGravity gravity = childConfig.alignment | self.gravity;
if ((gravity & LEFT) == LEFT) {
child.left = padding.left;
} else if ((gravity & RIGHT) == RIGHT) {
child.right = self.width - padding.right;
} else if ((gravity & CENTER_X) == CENTER_X) {
child.centerX = targetSize.width / 2;
} else if (childConfig.margin.left) {
child.left = childConfig.margin.left + padding.left;
} else if (childConfig.margin.right) {
child.right = targetSize.width - childConfig.margin.right - padding.right;
} else {
child.left = padding.left;
}
if (childConfig.margin.top) {
yStart += childConfig.margin.top;
}
child.top = yStart;
yStart = child.bottom + self.space;
if (childConfig.margin.bottom) {
yStart += childConfig.margin.bottom;
}
}
}
@end
@implementation DoricHLayoutView
- (CGSize)sizeThatFits:(CGSize)size {
CGFloat contentWidth = 0;
CGFloat contentHeight = 0;
NSUInteger contentWeight = 0;
for (UIView *child in self.subviews) {
if (child.isHidden) {
continue;
}
DoricLayoutConfig *childConfig = child.layoutConfig;
if (!childConfig) {
childConfig = [DoricLayoutConfig new];
}
CGSize childSize;
if (CGAffineTransformEqualToTransform(child.transform, CGAffineTransformIdentity)) {
childSize = [child measureSize:CGSizeMake(size.width - contentWidth, size.height)];
} else {
childSize = child.bounds.size;
}
contentWidth += childSize.width + self.space + childConfig.margin.left + childConfig.margin.right;
contentHeight = MAX(contentHeight, childSize.height + childConfig.margin.top + childConfig.margin.bottom);
contentWeight += childConfig.weight;
}
contentWidth -= self.space;
self.contentWidth = contentWidth;
self.contentHeight = contentHeight;
self.contentWeight = contentWeight;
if (contentWeight) {
contentWidth = size.width;
}
return CGSizeMake(contentWidth, contentHeight);
}
- (void)layoutSelf:(CGSize)targetSize {
self.width = targetSize.width;
self.height = targetSize.height;
DoricPadding padding = self.padding;
CGFloat xStart = padding.left;
if ((self.gravity & LEFT) == LEFT) {
xStart = padding.left;
} else if ((self.gravity & RIGHT) == RIGHT) {
xStart = targetSize.width - self.contentWidth - padding.right;
} else if ((self.gravity & CENTER_X) == CENTER_X) {
xStart = (targetSize.width - self.contentWidth - padding.left - padding.right) / 2 + padding.left;
}
CGFloat remain = targetSize.width - self.contentWidth - padding.left - padding.right;
for (UIView *child in self.subviews) {
if (child.isHidden) {
continue;
}
if (!CGAffineTransformEqualToTransform(child.transform, CGAffineTransformIdentity)) {
continue;
}
DoricLayoutConfig *childConfig = child.layoutConfig;
if (!childConfig) {
childConfig = [DoricLayoutConfig new];
}
CGSize size = [child measureSize:CGSizeMake(
targetSize.width - xStart - padding.right,
targetSize.height - padding.top - padding.bottom)];
if (childConfig.weight) {
size.width += remain / self.contentWeight * childConfig.weight;
}
[child layoutSelf:size];
DoricGravity gravity = childConfig.alignment | self.gravity;
if ((gravity & TOP) == TOP) {
child.top = padding.top;
} else if ((gravity & BOTTOM) == BOTTOM) {
child.bottom = targetSize.height - padding.bottom;
} else if ((gravity & CENTER_Y) == CENTER_Y) {
child.centerY = targetSize.height / 2;
} else if (childConfig.margin.top) {
child.top = childConfig.margin.top + padding.top;
} else if (childConfig.margin.bottom) {
child.bottom = targetSize.height - childConfig.margin.bottom - padding.bottom;
} else {
child.top = padding.top;
}
if (childConfig.margin.left) {
xStart += childConfig.margin.left;
}
child.left = xStart;
xStart = child.right + self.space;
if (childConfig.margin.right) {
xStart += childConfig.margin.right;
}
}
}
@end
DoricVLayoutView *vLayout(NSArray <__kindof UIView *> *views) {
DoricVLayoutView *layout = [[DoricVLayoutView alloc] initWithFrame:CGRectZero];
for (__kindof UIView *uiView in views) {
[layout addSubview:uiView];
}
layout.layoutConfig = [[DoricLayoutConfig alloc] initWithWidth:DoricLayoutWrapContent height:DoricLayoutWrapContent];
return layout;
}
DoricHLayoutView *hLayout(NSArray <__kindof UIView *> *views) {
DoricHLayoutView *layout = [[DoricHLayoutView alloc] initWithFrame:CGRectZero];
for (__kindof UIView *uiView in views) {
[layout addSubview:uiView];
}
layout.layoutConfig = [[DoricLayoutConfig alloc] initWithWidth:DoricLayoutWrapContent height:DoricLayoutWrapContent];
return layout;
}

View File

@@ -0,0 +1,25 @@
/*
* 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 "DoricStackNode.h"
@interface DoricListItemNode : DoricStackNode
@end

View File

@@ -0,0 +1,49 @@
/*
* 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"
#import "DoricExtensions.h"
@interface DoricListItemNode ()
@end
@interface DoricListItemView : DoricStackView
@end
@implementation DoricListItemView
@end
@implementation DoricListItemNode
- (instancetype)initWithContext:(DoricContext *)doricContext {
if (self = [super initWithContext:doricContext]) {
self.reusable = YES;
}
return self;
}
- (void)initWithSuperNode:(DoricSuperNode *)superNode {
[super initWithSuperNode:superNode];
self.reusable = YES;
}
- (DoricStackView *)build {
return [DoricListItemView new];
}
@end

View File

@@ -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 <Foundation/Foundation.h>
#import "DoricSuperNode.h"
@interface DoricListNode : DoricSuperNode<UITableView *>
@end

View File

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

View File

@@ -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/12/7.
//
#import <Foundation/Foundation.h>
#import "DoricGroupNode.h"
@interface DoricNestedSliderNode : DoricGroupNode<UIScrollView *>
@end

View File

@@ -0,0 +1,101 @@
/*
* 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/12/7.
//
#import "DoricNestedSliderNode.h"
#import "Doric.h"
@interface DoricNestedSliderView : UIScrollView
@end
@implementation DoricNestedSliderView
- (CGSize)sizeThatFits:(CGSize)size {
if (self.subviews.count > 0) {
CGFloat width = size.width;
CGFloat height = size.height;
for (UIView *child in self.subviews) {
CGSize childSize = [child measureSize:size];
width = MAX(childSize.width, width);
height = MAX(childSize.height, height);
}
return CGSizeMake(width, height);
}
return size;
}
- (void)layoutSelf:(CGSize)targetSize {
[super layoutSelf:targetSize];
[self.subviews forEachIndexed:^(__kindof UIView *obj, NSUInteger idx) {
obj.left = idx * self.width;
}];
[self setContentSize:CGSizeMake(self.subviews.count * self.width, self.height)];
}
@end
@interface DoricNestedSliderNode () <UIScrollViewDelegate>
@property(nonatomic, copy) NSString *onPageSelectedFuncId;
@property(nonatomic, assign) NSUInteger lastPosition;
@end
@implementation DoricNestedSliderNode
- (UIScrollView *)build {
return [[DoricNestedSliderView new] also:^(DoricNestedSliderView *it) {
it.delegate = self;
it.pagingEnabled = YES;
[it setShowsVerticalScrollIndicator:NO];
[it setShowsHorizontalScrollIndicator:NO];
}];
}
- (void)blendView:(UIScrollView *)view forPropName:(NSString *)name propValue:(id)prop {
if ([@"onPageSlided" isEqualToString:name]) {
self.onPageSelectedFuncId = prop;
} else {
[super blendView:view forPropName:name propValue:prop];
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
NSUInteger pageIndex = (NSUInteger) (scrollView.contentOffset.x / scrollView.width);
[scrollView setContentOffset:CGPointMake(pageIndex * scrollView.width, scrollView.contentOffset.y) animated:YES];
if (self.onPageSelectedFuncId && self.onPageSelectedFuncId.length > 0) {
if (pageIndex != self.lastPosition) {
[self callJSResponse:self.onPageSelectedFuncId, @(pageIndex), nil];
}
}
self.lastPosition = pageIndex;
}
- (void)slidePage:(NSDictionary *)params withPromise:(DoricPromise *)promise {
NSUInteger pageIndex = [params[@"page"] unsignedIntegerValue];
BOOL smooth = [params[@"smooth"] boolValue];
[self.view setContentOffset:CGPointMake(pageIndex * self.view.width, self.view.contentOffset.y) animated:smooth];
[promise resolve:nil];
self.lastPosition = pageIndex;
if (self.onPageSelectedFuncId && self.onPageSelectedFuncId.length > 0) {
[self callJSResponse:self.onPageSelectedFuncId, @(pageIndex), nil];
}
}
- (NSNumber *)getSlidedPage {
NSUInteger pageIndex = (NSUInteger) (self.view.contentOffset.x / self.view.width);
return @(pageIndex);
}
@end

View File

@@ -0,0 +1,33 @@
/*
* 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.
*/
//
// DoricRootNode.h
// Doric
//
// Created by pengfei.zhou on 2019/7/30.
//
#import "DoricStackNode.h"
NS_ASSUME_NONNULL_BEGIN
@interface DoricRootNode : DoricStackNode
- (void)setupRootView:(DoricStackView *)view;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,33 @@
/*
* 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.
*/
//
// DoricRootNode.m
// Doric
//
// Created by pengfei.zhou on 2019/7/30.
//
#import "DoricRootNode.h"
@implementation DoricRootNode
- (void)setupRootView:(DoricStackView *)view {
self.view = view;
}
- (void)requestLayout {
[self.view setNeedsLayout];
}
@end

View File

@@ -0,0 +1,30 @@
/*
* 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.
*/
//
// DoricScrollerNode.h
// Doric
//
// Created by pengfei.zhou on 2019/11/19.
//
#import <Foundation/Foundation.h>
#import "DoricSuperNode.h"
@interface DoricScrollView : UIScrollView
@property(nonatomic, strong) UIView *contentView;
@end
@interface DoricScrollerNode : DoricSuperNode<DoricScrollView *>
@end

View File

@@ -0,0 +1,126 @@
/*
* 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.
*/
//
// DoricScrollerNode.m
// Doric
//
// Created by pengfei.zhou on 2019/11/19.
//
#import "DoricScrollerNode.h"
#import "DoricExtensions.h"
#import "DoricRefreshableNode.h"
@implementation DoricScrollView
- (void)setContentView:(UIView *)contentView {
if (_contentView) {
[_contentView removeFromSuperview];
}
_contentView = contentView;
[self addSubview:contentView];
}
- (CGSize)sizeThatFits:(CGSize)size {
if (self.contentView) {
return [self.contentView sizeThatFits:size];
}
return CGSizeZero;
}
- (void)layoutSelf:(CGSize)targetSize {
[super layoutSelf:targetSize];
[self setContentSize:self.contentView.frame.size];
}
@end
@interface DoricScrollerNode ()
@property(nonatomic, strong) DoricViewNode *childNode;
@property(nonatomic, copy) NSString *childViewId;
@end
@implementation DoricScrollerNode
- (DoricScrollView *)build {
return [[DoricScrollView new] also:^(DoricScrollView *it) {
if (@available(iOS 11, *)) {
it.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
}];
}
- (void)initWithSuperNode:(DoricSuperNode *)superNode {
[super initWithSuperNode:superNode];
if ([superNode isKindOfClass:[DoricRefreshableNode class]]) {
self.view.bounces = NO;
}
}
- (void)blend:(NSDictionary *)props {
[super blend:props];
NSDictionary *childModel = [self subModelOf:self.childViewId];
if (!childModel) {
return;
}
NSString *viewId = childModel[@"id"];
NSString *type = childModel[@"type"];
NSDictionary *childProps = childModel[@"props"];
if (self.childNode) {
if ([self.childNode.viewId isEqualToString:viewId]) {
//skip
} else {
if (self.reusable && [type isEqualToString:self.childNode.type]) {
[self.childNode also:^(DoricViewNode *it) {
it.viewId = viewId;
[it blend:childProps];
}];
} else {
self.childNode = [[DoricViewNode create:self.doricContext withType:type] also:^(DoricViewNode *it) {
it.viewId = viewId;
[it initWithSuperNode:self];
[it blend:childProps];
self.view.contentView = it.view;
}];
}
}
} else {
self.childNode = [[DoricViewNode create:self.doricContext withType:type] also:^(DoricViewNode *it) {
it.viewId = viewId;
[it initWithSuperNode:self];
[it blend:childProps];
self.view.contentView = it.view;
}];
}
}
- (void)blendView:(DoricScrollView *)view forPropName:(NSString *)name propValue:(id)prop {
if ([@"content" isEqualToString:name]) {
self.childViewId = prop;
} else {
[super blendView:view forPropName:name propValue:prop];
}
}
- (void)blendSubNode:(NSDictionary *)subModel {
[self.childNode blend:subModel[@"props"]];
}
- (DoricViewNode *)subNodeWithViewId:(NSString *)viewId {
if ([viewId isEqualToString:self.childViewId]) {
return self.childNode;
}
return nil;
}
@end

View File

@@ -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.
*/
//
// DoricSlideItemNode.h
// Doric
//
// Created by pengfei.zhou on 2019/11/19.
//
#import <Foundation/Foundation.h>
#import "DoricStackNode.h"
@interface DoricSlideItemNode : DoricStackNode
@end

View File

@@ -0,0 +1,51 @@
/*
* 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.
*/
//
// DoricSlideItemNode.m
// Doric
//
// Created by pengfei.zhou on 2019/11/19.
//
#import "DoricSlideItemNode.h"
@interface DoricSlideItemView : DoricStackView
@end
@implementation DoricSlideItemView
- (void)layoutSubviews {
[super layoutSubviews];
}
@end
@implementation DoricSlideItemNode
- (instancetype)initWithContext:(DoricContext *)doricContext {
if (self = [super initWithContext:doricContext]) {
self.reusable = YES;
}
return self;
}
- (void)initWithSuperNode:(DoricSuperNode *)superNode {
[super initWithSuperNode:superNode];
self.reusable = YES;
self.view.clipsToBounds = YES;
}
- (DoricStackView *)build {
return [DoricSlideItemView new];
}
@end

View File

@@ -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.
*/
//
// DoricSliderNode.h
// Doric
//
// Created by pengfei.zhou on 2019/11/19.
//
#import <Foundation/Foundation.h>
#import "DoricSuperNode.h"
@interface DoricSliderNode : DoricSuperNode<UICollectionView *>
@end

View File

@@ -0,0 +1,234 @@
/*
* 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.
*/
//
// DoricSliderNode.m
// Doric
//
// Created by pengfei.zhou on 2019/11/19.
//
#import <JavaScriptCore/JavaScriptCore.h>
#import "DoricSliderNode.h"
#import "Doric.h"
#import "DoricSlideItemNode.h"
@interface DoricSliderViewCell : UICollectionViewCell
@property(nonatomic, strong) DoricSlideItemNode *doricSlideItemNode;
@end
@implementation DoricSliderViewCell
@end
@interface DoricSliderNode () <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
@property(nonatomic, strong) NSMutableDictionary <NSNumber *, NSString *> *itemViewIds;
@property(nonatomic, assign) NSUInteger itemCount;
@property(nonatomic, assign) NSUInteger batchCount;
@property(nonatomic, copy) NSString *onPageSelectedFuncId;
@property(nonatomic, assign) NSUInteger lastPosition;
@property(nonatomic, copy) NSString *renderPageFuncId;
@end
@interface DoricSliderView : UICollectionView
@end
@implementation DoricSliderView
- (CGSize)sizeThatFits:(CGSize)size {
if (self.subviews.count > 0) {
CGFloat width = size.width;
CGFloat height = size.height;
for (UIView *child in self.subviews) {
CGSize childSize = [child measureSize:size];
width = MAX(childSize.width, width);
height = MAX(childSize.height, height);
}
return CGSizeMake(width, height);
}
return size;
}
- (void)layoutSelf:(CGSize)targetSize {
[super layoutSelf:targetSize];
[self reloadData];
}
@end
@implementation DoricSliderNode
- (instancetype)initWithContext:(DoricContext *)doricContext {
if (self = [super initWithContext:doricContext]) {
_itemViewIds = [NSMutableDictionary new];
_batchCount = 15;
}
return self;
}
- (UICollectionView *)build {
UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];
[flowLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal];
return [[[DoricSliderView alloc] initWithFrame:CGRectZero
collectionViewLayout:flowLayout]
also:^(UICollectionView *it) {
it.backgroundColor = [UIColor whiteColor];
it.pagingEnabled = YES;
it.delegate = self;
it.dataSource = self;
[it registerClass:[DoricSliderViewCell class] forCellWithReuseIdentifier:@"doricCell"];
}];
}
- (void)blendView:(UICollectionView *)view forPropName:(NSString *)name propValue:(id)prop {
if ([@"itemCount" isEqualToString:name]) {
self.itemCount = [prop unsignedIntegerValue];
[self.view reloadData];
} else if ([@"renderPage" isEqualToString:name]) {
if ([self.renderPageFuncId isEqualToString:prop]) {
} else {
[self.itemViewIds removeAllObjects];
[self clearSubModel];
[self.view reloadData];
self.renderPageFuncId = prop;
}
} else if ([@"batchCount" isEqualToString:name]) {
self.batchCount = [prop unsignedIntegerValue];
} else if ([@"onPageSlided" isEqualToString:name]) {
self.onPageSelectedFuncId = prop;
} else {
[super blendView:view forPropName:name propValue:prop];
}
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return self.itemCount;
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
return CGSizeMake(self.view.width, self.view.height);
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section {
return 0;
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section {
return 0;
}
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
NSUInteger position = (NSUInteger) indexPath.row;
NSDictionary *model = [self itemModelAt:position];
NSDictionary *props = model[@"props"];
DoricSliderViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"doricCell" forIndexPath:indexPath];
if (!cell.doricSlideItemNode) {
DoricSlideItemNode *slideItemNode = [[DoricSlideItemNode alloc] initWithContext:self.doricContext];
[slideItemNode initWithSuperNode:self];
cell.doricSlideItemNode = slideItemNode;
[cell.contentView addSubview:slideItemNode.view];
}
DoricSlideItemNode *node = cell.doricSlideItemNode;
node.viewId = model[@"id"];
[node blend:props];
CGSize size = [node.view measureSize:CGSizeMake(collectionView.width, collectionView.height)];
[node.view layoutSelf:size];
return cell;
}
- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath {
return NO;
}
- (NSDictionary *)itemModelAt:(NSUInteger)position {
NSString *viewId = self.itemViewIds[@(position)];
if (viewId && viewId.length > 0) {
return [self subModelOf:viewId];
} else {
DoricAsyncResult *result = [self callJSResponse:@"renderBunchedItems", @(position), @(self.batchCount), nil];
JSValue *models = [result waitUntilResult];
NSArray *array = [models toArray];
[array enumerateObjectsUsingBlock:^(NSDictionary *obj, NSUInteger idx, BOOL *stop) {
NSString *thisViewId = obj[@"id"];
[self setSubModel:obj in:thisViewId];
NSUInteger pos = position + idx;
self.itemViewIds[@(pos)] = thisViewId;
}];
return array[0];
}
}
- (DoricViewNode *)subNodeWithViewId:(NSString *)viewId {
__block DoricViewNode *ret = nil;
[self.doricContext.driver ensureSyncInMainQueue:^{
for (UICollectionViewCell *collectionViewCell in self.view.visibleCells) {
if ([collectionViewCell isKindOfClass:[DoricSliderViewCell class]]) {
DoricSlideItemNode *node = ((DoricSliderViewCell *) collectionViewCell).doricSlideItemNode;
if ([viewId isEqualToString:node.viewId]) {
ret = node;
break;
}
}
}
}];
return ret;
}
- (void)blendSubNode:(NSDictionary *)subModel {
NSString *viewId = subModel[@"id"];
DoricViewNode *viewNode = [self subNodeWithViewId:viewId];
if (viewNode) {
[viewNode blend:subModel[@"props"]];
} else {
NSMutableDictionary *model = [[self subModelOf:viewId] mutableCopy];
[self recursiveMixin:subModel to:model];
[self setSubModel:model in:viewId];
}
[self.itemViewIds enumerateKeysAndObjectsUsingBlock:^(NSNumber *_Nonnull key, NSString *_Nonnull obj, BOOL *_Nonnull stop) {
if ([viewId isEqualToString:obj]) {
*stop = YES;
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[key integerValue] inSection:0];
[UIView performWithoutAnimation:^{
[self.view reloadItemsAtIndexPaths:@[indexPath]];
}];
}
}];
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
NSUInteger pageIndex = (NSUInteger) (scrollView.contentOffset.x / scrollView.width);
scrollView.contentOffset = CGPointMake(pageIndex * scrollView.width, scrollView.contentOffset.y);
if (self.onPageSelectedFuncId && self.onPageSelectedFuncId.length > 0) {
if (pageIndex != self.lastPosition) {
[self callJSResponse:self.onPageSelectedFuncId, @(pageIndex), nil];
}
}
self.lastPosition = pageIndex;
}
- (void)slidePage:(NSDictionary *)params withPromise:(DoricPromise *)promise {
NSUInteger pageIndex = [params[@"page"] unsignedIntegerValue];
BOOL smooth = [params[@"smooth"] boolValue];
[self.view setContentOffset:CGPointMake(pageIndex * self.view.width, self.view.contentOffset.y) animated:smooth];
[promise resolve:nil];
self.lastPosition = pageIndex;
if (self.onPageSelectedFuncId && self.onPageSelectedFuncId.length > 0) {
[self callJSResponse:self.onPageSelectedFuncId, @(pageIndex), nil];
}
}
- (NSNumber *)getSlidedPage {
NSUInteger pageIndex = (NSUInteger) (self.view.contentOffset.x / self.view.width);
return @(pageIndex);
}
@end

View File

@@ -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.
*/
//
// DoricStackNode.h
// Doric
//
// Created by pengfei.zhou on 2019/7/30.
//
#import "DoricGroupNode.h"
@interface DoricStackNode : DoricGroupNode<DoricStackView *>
@end

View File

@@ -0,0 +1,34 @@
/*
* 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.
*/
//
// DoricStackNode.m
// Doric
//
// Created by pengfei.zhou on 2019/7/30.
//
#import "DoricStackNode.h"
@implementation DoricStackNode
- (DoricStackView *)build {
return [DoricStackView new];
}
- (void)blendView:(DoricStackView *)view forPropName:(NSString *)name propValue:(id)prop {
[super blendView:view forPropName:name propValue:prop];
}
@end

View File

@@ -0,0 +1,43 @@
/*
* 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 *> : DoricViewNode<V>
@property(nonatomic, assign) BOOL reusable;
- (DoricLayoutConfig *)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;
- (void)removeSubModel:(NSString *)viewId;
- (DoricViewNode *)subNodeWithViewId:(NSString *)viewId;
- (void)recursiveMixin:(NSDictionary *)srcModel to:(NSMutableDictionary *)targetModel;
@end

View File

@@ -0,0 +1,164 @@
/*
* 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 (NSMutableDictionary *subModel in subviews) {
[self mixinSubNode:subModel];
[self blendSubNode:subModel];
}
} else {
[super blendView:view forPropName:name propValue:prop];
}
}
- (void)mixinSubNode:(NSMutableDictionary *)dictionary {
NSString *viewId = dictionary[@"id"];
NSMutableDictionary *oldModel = self.subNodes[viewId];
if (oldModel) {
[self mixin:dictionary to:oldModel];
} else {
self.subNodes[viewId] = dictionary;
}
}
- (void)mixin:(NSDictionary *)srcModel to:(NSMutableDictionary *)targetModel {
NSDictionary *srcProp = srcModel[@"props"];
NSMutableDictionary *targetProp = targetModel[@"props"];
[srcProp enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) {
if (![@"subviews" isEqualToString:key]) {
targetProp[key] = obj;
}
}];
}
- (void)recursiveMixin:(NSDictionary *)srcModel to:(NSMutableDictionary *)targetModel {
NSDictionary *srcProp = srcModel[@"props"];
NSMutableDictionary *targetProp = targetModel[@"props"];
NSMutableArray *targetOri = targetProp[@"subviews"];
[srcProp enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) {
if ([@"subviews" isEqualToString:key]) {
NSArray *subviews = obj;
if (subviews) {
for (NSDictionary *subview in subviews) {
NSString *viewId = subview[@"id"];
[targetOri enumerateObjectsUsingBlock:^(NSDictionary *obj, NSUInteger idx, BOOL *stop) {
if ([viewId isEqualToString:obj[@"id"]]) {
NSMutableDictionary *mutableDictionary = [obj mutableCopy];
[self recursiveMixin:subview to:mutableDictionary];
targetOri[idx] = [mutableDictionary copy];
*stop = YES;
}
}];
}
}
} else {
targetProp[key] = obj;
}
}];
}
- (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];
}
}];
NSDictionary *margin = layoutConfig[@"margin"];
if (margin) {
params.margin = DoricMarginMake(
[(NSNumber *) margin[@"left"] floatValue],
[(NSNumber *) margin[@"top"] floatValue],
[(NSNumber *) margin[@"right"] floatValue],
[(NSNumber *) margin[@"bottom"] floatValue]);
}
NSNumber *alignment = layoutConfig[@"alignment"];
if (alignment) {
params.alignment = (DoricGravity) [alignment integerValue];
}
NSNumber *weight = layoutConfig[@"weight"];
if (weight) {
params.weight = (DoricGravity) [weight integerValue];
}
}
- (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];
}
- (void)removeSubModel:(NSString *)viewId {
[self.subNodes removeObjectForKey:viewId];
}
- (DoricViewNode *)subNodeWithViewId:(NSString *)viewId {
NSAssert(NO, @"Should override class:%@ ,method:%@.", NSStringFromClass([self class]),
NSStringFromSelector(_cmd));
return nil;
}
- (void)requestLayout {
[self.view setNeedsLayout];
}
@end

View File

@@ -0,0 +1,31 @@
/*
* 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.
*/
//
// DoricTextNode.h
// Doric
//
// Created by pengfei.zhou on 2019/7/31.
//
#import "DoricViewNode.h"
NS_ASSUME_NONNULL_BEGIN
@interface DoricTextNode : DoricViewNode<UILabel *>
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,62 @@
/*
* 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.
*/
//
// DoricTextNode.m
// Doric
//
// Created by pengfei.zhou on 2019/7/31.
//
#import "DoricTextNode.h"
#import "DoricUtil.h"
#import "DoricGroupNode.h"
#import "Doric.h"
@implementation DoricTextNode
- (UILabel *)build {
return [[[UILabel alloc] init] also:^(UILabel *it) {
it.textAlignment = NSTextAlignmentCenter;
}];
}
- (void)blendView:(UILabel *)view forPropName:(NSString *)name propValue:(id)prop {
if ([name isEqualToString:@"text"]) {
view.text = prop;
} else if ([name isEqualToString:@"textSize"]) {
view.font = [UIFont systemFontOfSize:[(NSNumber *) prop floatValue]];
} else if ([name isEqualToString:@"textColor"]) {
view.textColor = DoricColor(prop);
} else if ([name isEqualToString:@"textAlignment"]) {
DoricGravity gravity = (DoricGravity) [(NSNumber *) prop integerValue];
NSTextAlignment alignment = NSTextAlignmentCenter;
if ((gravity & LEFT) == LEFT) {
alignment = NSTextAlignmentLeft;
} else if ((gravity & RIGHT) == RIGHT) {
alignment = NSTextAlignmentRight;
}
view.textAlignment = alignment;
} else if ([name isEqualToString:@"maxLines"]) {
view.numberOfLines = [prop integerValue];
} else {
[super blendView:view forPropName:name propValue:prop];
}
}
- (void)blend:(NSDictionary *)props {
[super blend:props];
[self.view.superview setNeedsLayout];
}
@end

View File

@@ -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.
*/
//
// DoricVLayoutNode.h
// Doric
//
// Created by pengfei.zhou on 2019/7/30.
//
#import "DoricGroupNode.h"
@interface DoricVLayoutNode : DoricGroupNode<DoricVLayoutView *>
@end

View File

@@ -0,0 +1,41 @@
/*
* 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.
*/
//
// DoricVLayoutNode.m
// Doric
//
// Created by pengfei.zhou on 2019/7/30.
//
#import "DoricVLayoutNode.h"
@implementation DoricVLayoutNode
- (DoricVLayoutView *)build {
return [DoricVLayoutView new];
}
- (void)blendView:(DoricVLayoutView *)view forPropName:(NSString *)name propValue:(id)prop {
if ([name isEqualToString:@"gravity"]) {
view.gravity = (DoricGravity) [(NSNumber *) prop integerValue];
} else if ([name isEqualToString:@"space"]) {
view.space = [(NSNumber *) prop floatValue];
} else {
[super blendView:view forPropName:name propValue:prop];
}
}
@end

View File

@@ -0,0 +1,56 @@
/*
* 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.
*/
//
// DoricViewNode.h
// Doric
//
// Created by pengfei.zhou on 2019/7/30.
//
#import "DoricContextHolder.h"
#import "DoricLayouts.h"
#import "UIView+Doric.h"
@class DoricSuperNode;
@interface DoricViewNode <V:UIView *> : DoricContextHolder
@property(nonatomic, strong) V view;
@property(nonatomic, weak) DoricSuperNode *superNode;
@property(nonatomic) NSInteger index;
@property(nonatomic, copy) NSString *viewId;
@property(nonatomic, copy) NSString *type;
@property(nonatomic, readonly) DoricLayoutConfig *layoutConfig;
@property(nonatomic, readonly) NSArray<NSString *> *idList;
- (void)initWithSuperNode:(DoricSuperNode *)superNode;
- (V)build;
- (void)blend:(NSDictionary *)props;
- (void)blendView:(V)view forPropName:(NSString *)name propValue:(id)prop;
- (DoricAsyncResult *)callJSResponse:(NSString *)funcId, ...;
+ (__kindof DoricViewNode *)create:(DoricContext *)context withType:(NSString *)type;
- (void)requestLayout;
@end

View File

@@ -0,0 +1,649 @@
/*
* 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.
*/
//
// DoricViewNode.m
// Doric
//
// Created by pengfei.zhou on 2019/7/30.
//
#import "DoricViewNode.h"
#import "DoricUtil.h"
#import "DoricGroupNode.h"
#import "DoricRootNode.h"
#import "DoricConstant.h"
#import "DoricSuperNode.h"
#import "DoricExtensions.h"
#import "DoricPromise.h"
void DoricAddEllipticArcPath(CGMutablePathRef path,
CGPoint origin,
CGFloat radius,
CGFloat startAngle,
CGFloat endAngle) {
CGAffineTransform t = CGAffineTransformMakeTranslation(origin.x, origin.y);
CGPathAddArc(path, &t, 0, 0, radius, startAngle, endAngle, NO);
}
CGPathRef DoricCreateRoundedRectPath(CGRect bounds,
CGFloat leftTop,
CGFloat rightTop,
CGFloat rightBottom,
CGFloat leftBottom) {
const CGFloat minX = CGRectGetMinX(bounds);
const CGFloat minY = CGRectGetMinY(bounds);
const CGFloat maxX = CGRectGetMaxX(bounds);
const CGFloat maxY = CGRectGetMaxY(bounds);
CGMutablePathRef path = CGPathCreateMutable();
DoricAddEllipticArcPath(path, (CGPoint) {
minX + leftTop, minY + leftTop
}, leftTop, M_PI, 3 * M_PI_2);
DoricAddEllipticArcPath(path, (CGPoint) {
maxX - rightTop, minY + rightTop
}, rightTop, 3 * M_PI_2, 0);
DoricAddEllipticArcPath(path, (CGPoint) {
maxX - rightBottom, maxY - rightBottom
}, rightBottom, 0, M_PI_2);
DoricAddEllipticArcPath(path, (CGPoint) {
minX + leftBottom, maxY - leftBottom
}, leftBottom, M_PI_2, M_PI);
CGPathCloseSubpath(path);
return path;
}
@interface AnimationCallback : NSObject <CAAnimationDelegate>
@property(nonatomic, strong) NSMutableDictionary *dictionary;
@property(nonatomic, strong) void (^startBlock)(AnimationCallback *callback);
@property(nonatomic, strong) void (^endBlock)(AnimationCallback *callback);
@end
@implementation AnimationCallback
- (instancetype)init {
if (self = [super init]) {
_dictionary = [NSMutableDictionary new];
}
return self;
}
- (void)animationDidStart:(CAAnimation *)anim {
if (self.startBlock) {
self.startBlock(self);
}
}
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
if (self.endBlock) {
self.endBlock(self);
}
}
@end
@interface DoricViewNode ()
@property(nonatomic, strong) NSMutableDictionary *callbackIds;
@property(nonatomic, copy) NSNumber *translationX;
@property(nonatomic, copy) NSNumber *translationY;
@property(nonatomic, copy) NSNumber *scaleX;
@property(nonatomic, copy) NSNumber *scaleY;
@property(nonatomic, copy) NSNumber *rotation;
@property(nonatomic, copy) NSNumber *pivotX;
@property(nonatomic, copy) NSNumber *pivotY;
@end
@implementation DoricViewNode
- (instancetype)initWithContext:(DoricContext *)doricContext {
if (self = [super initWithContext:doricContext]) {
_callbackIds = [[NSMutableDictionary alloc] init];
}
return self;
}
- (void)initWithSuperNode:(DoricSuperNode *)superNode {
if ([self isKindOfClass:[DoricSuperNode class]]) {
((DoricSuperNode *) self).reusable = superNode.reusable;
}
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 {
self.view.layoutConfig = self.layoutConfig;
for (NSString *key in props) {
id value = props[key];
[self blendView:self.view forPropName:key propValue:value];
}
[self transformProperties];
}
- (void)transformProperties {
CGAffineTransform transform = CGAffineTransformIdentity;
if (self.translationX || self.translationY) {
transform = CGAffineTransformTranslate(transform, [self.translationX floatValue] ?: 0, [self.translationY floatValue] ?: 0);
}
if (self.scaleX || self.scaleY) {
transform = CGAffineTransformScale(transform, [self.scaleX floatValue] ?: 1, [self.scaleY floatValue] ?: 1);
}
if (self.rotation) {
transform = CGAffineTransformRotate(transform, (self.rotation.floatValue ?: 0) * M_PI);
}
if (!CGAffineTransformEqualToTransform(transform, self.view.transform)) {
self.view.transform = transform;
}
if (self.pivotX || self.pivotY) {
self.view.layer.anchorPoint = CGPointMake(self.pivotX.floatValue
?: 0.5f, self.pivotY.floatValue ?: 0.5f);
}
}
- (void)blendView:(UIView *)view forPropName:(NSString *)name propValue:(id)prop {
if ([name isEqualToString:@"width"]) {
NSNumber *width = (NSNumber *) prop;
if ([width floatValue] >= 0) {
view.width = [width floatValue];
}
} else if ([name isEqualToString:@"height"]) {
NSNumber *height = (NSNumber *) prop;
if ([height floatValue] >= 0) {
view.height = [height floatValue];
}
} else if ([name isEqualToString:@"x"]) {
view.x = [(NSNumber *) prop floatValue];
} else if ([name isEqualToString:@"y"]) {
view.y = [(NSNumber *) prop floatValue];
} else if ([name isEqualToString:@"backgroundColor"]) {
view.backgroundColor = DoricColor(prop);
} else if ([name isEqualToString:@"alpha"]) {
view.alpha = [prop floatValue];
} else if ([name isEqualToString:@"layoutConfig"]) {
if (self.superNode && [prop isKindOfClass:[NSDictionary class]]) {
[self.superNode blendSubNode:self layoutConfig:prop];
} else {
[self blendLayoutConfig:prop];
}
} else if ([name isEqualToString:@"onClick"]) {
self.callbackIds[@"onClick"] = prop;
view.userInteractionEnabled = YES;
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onClick:)];
[view addGestureRecognizer:tapGestureRecognizer];
} else if ([name isEqualToString:@"border"]) {
NSDictionary *dic = prop;
CGFloat width = [(NSNumber *) dic[@"width"] floatValue];
UIColor *color = DoricColor((NSNumber *) dic[@"color"]);
view.layer.borderWidth = width;
view.layer.borderColor = color.CGColor;
} else if ([name isEqualToString:@"corners"]) {
if ([prop isKindOfClass:NSNumber.class]) {
view.layer.cornerRadius = [(NSNumber *) prop floatValue];
} else if ([prop isKindOfClass:NSDictionary.class]) {
NSDictionary *dic = prop;
CGFloat leftTop = [(NSNumber *) dic[@"leftTop"] floatValue];
CGFloat rightTop = [(NSNumber *) dic[@"rightTop"] floatValue];
CGFloat rightBottom = [(NSNumber *) dic[@"rightBottom"] floatValue];
CGFloat leftBottom = [(NSNumber *) dic[@"leftBottom"] floatValue];
if (ABS(leftTop - rightTop) > CGFLOAT_MIN
|| ABS(leftTop - rightBottom) > CGFLOAT_MIN
|| ABS(leftTop - leftBottom) > CGFLOAT_MIN) {
view.layer.cornerRadius = 0;
dispatch_async(dispatch_get_main_queue(), ^{
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
CGPathRef path = DoricCreateRoundedRectPath(self.view.bounds, leftTop, rightTop, rightBottom, leftBottom);
shapeLayer.path = path;
CGPathRelease(path);
view.layer.mask = shapeLayer;
});
} else {
view.layer.cornerRadius = leftTop;
view.layer.mask = nil;
}
}
} else if ([name isEqualToString:@"shadow"]) {
NSDictionary *dic = prop;
CGFloat opacity = [(NSNumber *) dic[@"opacity"] floatValue];
if (opacity > CGFLOAT_MIN) {
view.clipsToBounds = NO;
UIColor *color = DoricColor((NSNumber *) dic[@"color"]);
view.layer.shadowColor = color.CGColor;
view.layer.shadowRadius = [(NSNumber *) dic[@"radius"] floatValue];
view.layer.shadowOffset = CGSizeMake([(NSNumber *) dic[@"offsetX"] floatValue], [(NSNumber *) dic[@"offsetY"] floatValue]);
view.layer.shadowOpacity = (float) opacity;
} else {
view.clipsToBounds = YES;
}
} else if ([name isEqualToString:@"translationX"]) {
self.translationX = prop;
} else if ([name isEqualToString:@"translationY"]) {
self.translationY = prop;
} else if ([name isEqualToString:@"scaleX"]) {
self.scaleX = prop;
} else if ([name isEqualToString:@"scaleY"]) {
self.scaleY = prop;
} else if ([name isEqualToString:@"pivotX"]) {
self.pivotX = prop;
} else if ([name isEqualToString:@"pivotY"]) {
self.pivotY = prop;
} else if ([name isEqualToString:@"rotation"]) {
self.rotation = prop;
} else if ([name isEqualToString:@"padding"]) {
DoricPadding padding;
padding.left = padding.right = padding.top = padding.bottom = 0;
if ([prop isKindOfClass:[NSDictionary class]]) {
NSDictionary *dictionary = prop;
padding.left = [dictionary[@"left"] floatValue];
padding.right = [dictionary[@"right"] floatValue];
padding.top = [dictionary[@"top"] floatValue];
padding.bottom = [dictionary[@"bottom"] floatValue];
}
self.view.padding = padding;
} else if ([name isEqualToString:@"hidden"]) {
self.view.hidden = [prop boolValue];
} else {
DoricLog(@"Blend View error for View Type :%@, prop is %@", self.class, name);
}
}
- (void)onClick:(UIView *)view {
[self callJSResponse:self.callbackIds[@"onClick"], nil];
}
- (NSArray<NSString *> *)idList {
NSMutableArray *ret = [[NSMutableArray alloc] init];
DoricViewNode *node = self;
do {
[ret addObject:node.viewId];
node = node.superNode;
} while (node);
return [[ret reverseObjectEnumerator] allObjects];
}
- (DoricAsyncResult *)callJSResponse:(NSString *)funcId, ... {
NSMutableArray *array = [[NSMutableArray alloc] init];
[array addObject:self.idList];
[array addObject:funcId];
va_list args;
va_start(args, funcId);
id arg;
while ((arg = va_arg(args, id)) != nil) {
[array addObject:arg];
}
DoricAsyncResult *ret = [self.doricContext callEntity:DORIC_ENTITY_RESPONSE withArgumentsArray:array];
va_end(args);
return ret;
}
+ (__kindof DoricViewNode *)create:(DoricContext *)context withType:(NSString *)type {
DoricRegistry *registry = context.driver.registry;
Class clz = [registry acquireViewNode:type];
DoricViewNode *viewNode = [(DoricViewNode *) [clz alloc] initWithContext:context];
viewNode.type = type;
return viewNode;
}
- (void)requestLayout {
[self.superNode requestLayout];
}
- (NSNumber *)getWidth {
return @(self.view.width);
}
- (NSNumber *)getHeight {
return @(self.view.height);
}
- (NSDictionary *)getLocationOnScreen {
CGPoint point = [self.view convertPoint:CGPointMake(0, 0) toView:[UIApplication sharedApplication].windows.lastObject];
return @{@"x": @(point.x), @"y": @(point.y)};
}
- (void)blendLayoutConfig:(NSDictionary *)params {
[params[@"widthSpec"] also:^(NSNumber *it) {
if (it) {
self.layoutConfig.widthSpec = (DoricLayoutSpec) [it integerValue];
}
}];
[params[@"heightSpec"] also:^(NSNumber *it) {
if (it) {
self.layoutConfig.heightSpec = (DoricLayoutSpec) [it integerValue];
}
}];
NSDictionary *margin = params[@"margin"];
if (margin) {
self.layoutConfig.margin = DoricMarginMake(
[(NSNumber *) margin[@"left"] floatValue],
[(NSNumber *) margin[@"top"] floatValue],
[(NSNumber *) margin[@"right"] floatValue],
[(NSNumber *) margin[@"bottom"] floatValue]);
}
NSNumber *alignment = params[@"alignment"];
if (alignment) {
self.layoutConfig.alignment = (DoricGravity) [alignment integerValue];
}
NSNumber *weight = params[@"weight"];
if (weight) {
self.layoutConfig.weight = (DoricGravity) [weight integerValue];
}
}
- (NSDictionary *)transformation {
NSMutableDictionary *dictionary = [NSMutableDictionary new];
if (self.translationX) {
dictionary[@"translationX"] = self.translationX;
}
if (self.translationY) {
dictionary[@"translationY"] = self.translationY;
}
if (self.scaleX) {
dictionary[@"scaleX"] = self.scaleX;
}
if (self.scaleY) {
dictionary[@"scaleY"] = self.scaleY;
}
if (self.rotation) {
dictionary[@"rotation"] = self.rotation;
}
return dictionary;
}
#pragma animations
- (void)doAnimation:(id)params withPromise:(DoricPromise *)promise {
CAAnimation *animation = [self parseAnimation:params];
AnimationCallback *originDelegate = animation.delegate;
AnimationCallback *animationCallback = [[AnimationCallback new] also:^(AnimationCallback *it) {
it.startBlock = ^(AnimationCallback *callback) {
if (originDelegate) {
originDelegate.startBlock(callback);
}
[self transformProperties];
};
it.endBlock = ^(AnimationCallback *callback) {
if (originDelegate) {
originDelegate.endBlock(callback);
}
[self.view.layer removeAllAnimations];
[self transformProperties];
[promise resolve:self.transformation];
};
}];
animation.delegate = animationCallback;
if (params[@"delay"]) {
animation.beginTime = CACurrentMediaTime() + [params[@"delay"] floatValue] / 1000;
}
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
[self.view.layer addAnimation:animation forKey:nil];
}
- (CFTimeInterval)computeDurationOfAnimations:(NSArray<CAAnimation *> *)animations {
__block CFTimeInterval interval = 0;
[animations forEach:^(CAAnimation *obj) {
interval = MAX(interval, obj.beginTime + obj.duration * (1 + obj.repeatCount));
}];
return interval;
}
- (CAAnimation *)parseAnimation:(id)params {
if (params[@"animations"]) {
NSArray *anims = params[@"animations"];
CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
NSMutableArray *animations = [NSMutableArray new];
[anims forEach:^(id obj) {
[animations addObject:[self parseAnimation:obj]];
}];
animationGroup.duration = [self computeDurationOfAnimations:animations];
animationGroup.animations = animations;
animationGroup.delegate = [[AnimationCallback new] also:^(AnimationCallback *it) {
it.startBlock = ^(AnimationCallback *callback) {
[[animations map:^id(CABasicAnimation *obj) {
return obj.delegate;
}] forEach:^(AnimationCallback *obj) {
if (obj.startBlock) {
obj.startBlock(obj);
}
}];
};
it.endBlock = ^(AnimationCallback *callback) {
[[animations map:^id(CABasicAnimation *obj) {
return obj.delegate;
}] forEach:^(AnimationCallback *obj) {
if (obj.endBlock) {
obj.endBlock(obj);
}
}];
};
}];
if (params[@"delay"]) {
animationGroup.beginTime = [params[@"delay"] floatValue] / 1000;
}
return animationGroup;
} else if ([params isKindOfClass:[NSDictionary class]]) {
NSArray<NSDictionary *> *changeables = params[@"changeables"];
NSString *type = params[@"type"];
if ([@"TranslationAnimation" isEqualToString:type]) {
__block CGPoint from = self.view.layer.position;
__block CGPoint to = self.view.layer.position;
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
[changeables forEach:^(NSDictionary *obj) {
NSString *key = obj[@"key"];
if ([@"translationX" isEqualToString:key]) {
from.x += [obj[@"fromValue"] floatValue] - self.translationX.floatValue;
to.x += [obj[@"toValue"] floatValue] - self.translationX.floatValue;
[self setFillMode:animation
key:key
startValue:obj[@"fromValue"]
endValue:obj[@"toValue"]
fillMode:params[@"fillMode"]];
} else if ([@"translationY" isEqualToString:key]) {
from.y += [obj[@"fromValue"] floatValue] - self.translationY.floatValue;
to.y += [obj[@"toValue"] floatValue] - self.translationY.floatValue;
[self setFillMode:animation
key:key
startValue:obj[@"fromValue"]
endValue:obj[@"toValue"]
fillMode:params[@"fillMode"]];
}
}];
animation.fromValue = [NSValue valueWithCGPoint:from];
animation.toValue = [NSValue valueWithCGPoint:to];
if (params[@"timingFunction"]) {
animation.timingFunction = [self translateToTimingFunction:params[@"timingFunction"]];
}
[self setAnimation:animation params:params];
return animation;
} else {
CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
NSMutableArray <CABasicAnimation *> *animations = [NSMutableArray new];
[changeables forEach:^(NSDictionary *obj) {
CABasicAnimation *animation = [self parseChangeable:obj fillMode:params[@"fillMode"]];
if (params[@"timingFunction"]) {
animation.timingFunction = [self translateToTimingFunction:params[@"timingFunction"]];
}
[animations addObject:animation];
}];
animationGroup.animations = animations;
animationGroup.delegate = [[AnimationCallback new] also:^(AnimationCallback *it) {
it.startBlock = ^(AnimationCallback *callback) {
[[animations map:^id(CABasicAnimation *obj) {
return obj.delegate;
}] forEach:^(AnimationCallback *obj) {
if (obj.startBlock) {
obj.startBlock(obj);
}
}];
};
it.endBlock = ^(AnimationCallback *callback) {
[[animations map:^id(CABasicAnimation *obj) {
return obj.delegate;
}] forEach:^(AnimationCallback *obj) {
if (obj.endBlock) {
obj.endBlock(obj);
}
}];
};
}];
[self setAnimation:animationGroup params:params];
return animationGroup;
}
}
return nil;
}
- (void)setAnimation:(CAAnimation *)animation params:(NSDictionary *)params {
if (params[@"repeatCount"]) {
NSInteger repeatCount = [params[@"repeatCount"] integerValue];
if (repeatCount < 0) {
repeatCount = NSNotFound;
}
animation.repeatCount = repeatCount;
}
if (params[@"repeatMode"]) {
NSInteger repeatMode = [params[@"repeatMode"] integerValue];
animation.autoreverses = repeatMode == 2;
}
if (params[@"delay"]) {
animation.beginTime = [params[@"delay"] floatValue] / 1000;
}
animation.duration = [params[@"duration"] floatValue] / 1000;
}
- (void)setFillMode:(CAAnimation *)animation
key:(NSString *)key
startValue:(NSNumber *)startValue
endValue:(NSNumber *)endValue
fillMode:(NSNumber *)fillMode {
NSUInteger fillModeInt = fillMode.unsignedIntegerValue;
if ((fillModeInt & 2) == 2) {
[self setAnimatedValue:key value:startValue];
}
AnimationCallback *callback = [AnimationCallback new];
AnimationCallback *originCallback = animation.delegate;
__weak typeof(self) _self = self;
callback.startBlock = ^(AnimationCallback *callback) {
__strong typeof(_self) self = _self;
callback.dictionary[key] = [self getAnimatedValue:key];
if (originCallback) {
originCallback.startBlock(callback);
}
};
callback.endBlock = ^(AnimationCallback *callback) {
__strong typeof(_self) self = _self;
if ((fillModeInt & 1) == 1) {
[self setAnimatedValue:key value:endValue];
}
if (originCallback) {
originCallback.endBlock(callback);
}
};
animation.delegate = callback;
}
- (NSNumber *)getAnimatedValue:(NSString *)key {
if ([@"translationX" isEqualToString:key]) {
return self.translationX;
}
if ([@"translationY" isEqualToString:key]) {
return self.translationY;
}
if ([@"scaleX" isEqualToString:key]) {
return self.scaleX;
}
if ([@"scaleY" isEqualToString:key]) {
return self.scaleY;
}
if ([@"rotation" isEqualToString:key]) {
return self.rotation;
}
return nil;
}
- (void)setAnimatedValue:(NSString *)key value:(NSNumber *)value {
if ([@"translationX" isEqualToString:key]) {
self.translationX = value;
} else if ([@"translationY" isEqualToString:key]) {
self.translationY = value;
} else if ([@"scaleX" isEqualToString:key]) {
self.scaleX = value;
} else if ([@"scaleY" isEqualToString:key]) {
self.scaleY = value;
} else if ([@"rotation" isEqualToString:key]) {
self.rotation = value;
}
}
- (CABasicAnimation *)parseChangeable:(NSDictionary *)params fillMode:(NSNumber *)fillMode {
NSString *key = params[@"key"];
CABasicAnimation *animation = [CABasicAnimation animation];
if ([@"scaleX" isEqualToString:key]) {
animation.keyPath = @"transform.scale.x";
animation.fromValue = params[@"fromValue"];
animation.toValue = params[@"toValue"];
} else if ([@"scaleY" isEqualToString:key]) {
animation.keyPath = @"transform.scale.y";
animation.fromValue = params[@"fromValue"];
animation.toValue = params[@"toValue"];
} else if ([@"rotation" isEqualToString:key]) {
animation.keyPath = @"transform.rotation.z";
animation.fromValue = @([params[@"fromValue"] floatValue] * M_PI);
animation.toValue = @([params[@"toValue"] floatValue] * M_PI);
} else if ([@"backgroundColor" isEqualToString:key]) {
animation.keyPath = @"backgroundColor";
animation.fromValue = params[@"fromValue"];
animation.toValue = params[@"toValue"];
}
[self setFillMode:animation
key:key
startValue:params[@"fromValue"]
endValue:params[@"toValue"]
fillMode:fillMode];
return animation;
}
- (CAMediaTimingFunction *)translateToTimingFunction:(NSNumber *)timingFunction {
switch (timingFunction.integerValue) {
case 1:
return [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
case 2:
return [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
case 3:
return [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
case 4:
return [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
default:
return [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
}
}
@end

Some files were not shown because too many files have changed in this diff Show More