http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXJSASTParser.mm ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXJSASTParser.mm b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXJSASTParser.mm new file mode 100644 index 0000000..7e7a565 --- /dev/null +++ b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXJSASTParser.mm @@ -0,0 +1,919 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 "WXJSASTParser.h" +#import "WXLog.h" +#include <vector> +#include <sstream> + + +typedef enum : NSUInteger { + WXJSTokenTypeBooleanLiteral = 1, + WXJSTokenTypeEOF, + WXJSTokenTypeIdentifier, + WXJSTokenTypeKeyword, + WXJSTokenTypeNullLiteral, + WXJSTokenTypeNumericLiteral, + WXJSTokenTypePunctuator, + WXJSTokenTypeStringLiteral, + WXJSTokenTypeRegularExpression, + WXJSTokenTypeTemplate +} WXJSTokenType; + +struct WXJSToken { + WXJSTokenType type; + std::string value; + double doubleValue; + bool octal; + int start; + int end; +}; + +struct WXJSMarker { + int index; +}; + +static bool isWhiteSpace(int ch) { + return (ch == 32) || // space + (ch == 9) || // tab + (ch == 0xB) || + (ch == 0xC) || + (ch == 0xA0); +} + +static bool isIdentifierStart(int ch) +{ + return (ch == 36) || (ch == 95) || // $ or _ + (ch >= 65 && ch <= 90) || // A..Z + (ch >= 97 && ch <= 122) || // a..z + (ch == 92); // \ (backslash) +} + +static bool isIdentifierPart(int ch) { + return (ch == 36) || (ch == 95) || // $ or _ + (ch >= 65 && ch <= 90) || // A..Z + (ch >= 97 && ch <= 122) || // a..z + (ch >= 48 && ch <= 57) || // 0..9 + (ch == 92); // \ (backslash) +} + +static bool isDecimalDigit(int ch) { + return (ch >= 48 && ch <= 57); // 0..9 +} + +static bool isHexDigit(int ch) { + return std::string("0123456789abcdefABCDEF").find(ch) != std::string::npos; +} + +static bool isOctalDigit(int ch) { + return std::string("01234567").find(ch) != std::string::npos; +} + +static int binaryPrecedence(WXJSToken *token) +{ + int prec = 0; + + if (token->type != WXJSTokenTypePunctuator) { + return prec; + } + + const std::string &value = token->value; + + if (value == "||") { + prec = 1; + } + + else if (value == "&&") { + prec = 2; + } + + else if (value == "|") { + prec = 3; + } + + else if (value == "^") { + prec = 4; + } + + else if (value == "&") { + prec = 5; + } + + else if (value == "==" || value == "!=" || value == "===" || value == "!==") { + prec = 6; + } + + else if (value == "<" || value == ">" || value == "<=" || value == ">=") { + prec = 7; + } + + else if (value == "<<" || value == ">>" || value == ">>>") { + prec = 8; + } + + else if (value == "+" || value == "-") { + prec = 9; + } + + else if (value == "*" || value == "/" || value == "%") { + prec = 11; + } + + return prec; +} + +@implementation WXJSASTParser +{ + std::string _source; + + int _length; + int _index; + + WXJSToken *_lookahead; + + std::vector<WXJSToken *> _tokens; +} + ++ (instancetype)parserWithScript:(NSString *)script +{ + if (!script) { + WXLogError(@"can not init parser with nil script"); + return nil; + } + return [[self alloc] initWithScript:script]; +} + +- (instancetype)initWithScript:(NSString *)script +{ + if (self = [super init]) { + _source = std::string([script UTF8String]);; + _length = (int)(_source.length()); + _index = 0; + + _lookahead = NULL; + + [self nextToken]; + } + + return self; +} + +- (WXJSToken *)nextToken +{ + WXJSToken *token = _lookahead; + + WXJSToken *next = [self lex]; + + _lookahead = next; + + if (next->type != WXJSTokenTypeEOF) { + _tokens.push_back(token); + } + + return token; +} + +- (WXJSToken *)lex +{ + [self skipSpaces]; + + if (_index >= _length) { + WXJSToken *token = new WXJSToken(); + token->type = WXJSTokenTypeEOF; + token->value = ""; + token->start = _index; + token->end = _index; + + return token; + } + + int ch = _source[_index]; + + if (isIdentifierStart(ch)) { + return [self scanIdentifier]; + } + + // ( or ) or ; + if (ch == 40 || ch == 41 || ch == 59) { + return [self scanPunctuator]; + } + + // string starting with ' or " . + if (ch == 39 || ch == 34 ) { + return [self scanStringLiteral]; + } + + // Dot (.) U+002E can also start a floating-point number, hence the need + // to check the next + if (ch == 0x2E) { + if (isDecimalDigit(_source[_index + 1])) { + return [self scanNumericLiteral]; + } + return [self scanPunctuator]; + } + + if (isDecimalDigit(ch)) { + return [self scanNumericLiteral]; + } + + // Possible `identifier + if (ch >= 0xD800 && ch < 0xDFFF) { + if (isIdentifierStart(ch)) { + return [self scanIdentifier]; + } + } + + return [self scanPunctuator]; +} + +- (WXJSToken *)scanIdentifier +{ + int start = _index; + // Backslash (U+005C) starts an escaped + std::string identifier = [self getIdentifier]; + WXJSTokenType type; + if (identifier == "null") { + type = WXJSTokenTypeNullLiteral; + } else if (identifier == "true" || identifier == "false") { + type = WXJSTokenTypeBooleanLiteral; + } else { + type = WXJSTokenTypeIdentifier; + } + + WXJSToken *token = new WXJSToken(); + token->type = type; + token->value = identifier; + token->start = start; + token->end = _index; + + return token; +} + +- (std::string)getIdentifier +{ + int start = _index++; + while (_index < _length) { + int ch = _source[_index]; + if (isIdentifierPart(ch)) { + ++_index; + } else { + break; + } + } + + return _source.substr(start, _index - start); +} + +- (WXJSToken *)scanPunctuator +{ + int start = _index; + + // Check for most common single-character punctuators. + int ch = _source[_index]; + std::string str = ""; + switch (ch) { + // single-character punctuators + case 46: // . + case 40: // ( + case 41: // ) + case 59: // ; + case 44: // , + case 123: // { + case 125: // } + case 91: // [ + case 93: // ] + case 58: // : + case 63: // ? + case 126: // ~ + ++_index; + str = std::string(1, ch); + break; + default: + str = _source.substr(_index, 3); + if (str == "===" || str == "!==" || str == "**=") { + _index += 3; + } else { + str = str.substr(0, 2); + if (str == "&&" || str == "||" || str == "==" || str == "!=" || + str == "+=" || str == "-=" || str == "*=" || str == "/=" || + str == "++" || str == "--" || str == "&=" || str == "|=" || + str == "^=" || str == "%=" || str == "<=" || str == ">=" || + str == "=>" || str == "**") { + _index += 2; + } else { + str = _source[_index]; + // parse 2-character punctuators first + if (std::string("<>=!+-*%&|^/").find(ch) != std::string::npos) { + ++_index; + } + } + } + } + + if (_index == start) { + [self _throwUnexpectedTokenError]; + } + + WXJSToken *token = new WXJSToken(); + token->type = WXJSTokenTypePunctuator; + token->value = str; + token->start = start; + token->end = _index; + + return token; +} + +- (WXJSToken *)scanStringLiteral +{ + int start = _index; + int quote = _source[start]; + bool octal = false; + + ++_index; + std::string str = ""; + + while (_index < _length) { + int ch = _source[_index++]; + + if (ch == quote) { + quote = -1; + break; + } else if (ch == 92) { // \ (backslash) + ch = _source[_index++]; + switch (ch) { + case 'u': { + if (_source[_index] == '{') { // { + ++_index; + str += [self scanUnicodeCodePointEscape]; + } else { + int unescaped = [self scanHexEscape:ch]; + if (unescaped == -1) { + [self _throwError:@"Invalid hexadecimal escape"]; + } else { + str += unescaped; + } + } + break; + } + case 'x': { + int unescaped = [self scanHexEscape:ch]; + if (unescaped == -1) { + [self _throwError:@"Invalid hexadecimal escape"]; + } else { + str += unescaped; + } + + break; + } + case 'n': { + str += '\n'; + break; + } + case 'r': { + str += '\r'; + break; + } + case 't': { + str += '\t'; + break; + } + case 'b': { + str += '\b'; + break; + } + case 'f': { + str += '\f'; + break; + } + case 'v': { + str += '\x0B'; + break; + } + case '8': + case '9': { + str += ch; + [self _throwUnexpectedTokenError]; + break; + } + + default: + if (isOctalDigit(ch)) { + int code = ch - '0'; + + // \0 is not octal escape sequence + if (code != 0) { + octal = true; + } + + if (_index < _length && isOctalDigit(_source[_index])) { + octal = true; + code = code * 8 + _source[_index++] - '0'; + + // 3 digits are only allowed when string starts + // with 0, 1, 2, 3 + if (std::string(1, '0123').find(ch) != std::string::npos && + _index < _length && + isOctalDigit(_source[_index])) { + code = code * 8 + _source[_index++] - '0'; + } + } + str += std::string(1, code); + } else { + str += ch; + } + break; + } + } else { + str += ch; + } + } + + if (quote != -1) { + _index = start; + [self _throwUnexpectedTokenError]; + } + + WXJSToken *token = new WXJSToken(); + token->type = WXJSTokenTypeStringLiteral; + token->value = str; + token->octal = octal; + token->start = start; + token->end = _index; + + return token; +} + +- (int)scanUnicodeCodePointEscape { + int ch = _source[_index]; + int code = 0; + + if (ch == '}') { // '}' + [self _throwError:@"At least one hex digit is required in Unicode"]; + } + + while (_index < _length) { + ch = _source[_index++]; + if (!isHexDigit(ch)) { + break; + } + code = (int)(code * 16 + std::string("0123456789abcdef").find(tolower(ch))); + } + + if (code > 0x10FFFF || ch != '}') { + [self _throwUnexpectedTokenError]; + } + + return code; +} + +- (WXJSToken *)scanNumericLiteral +{ + int start = _index; + int ch = _source[start]; + std::string num; + if (ch != '.') { + num = _source[_index++]; + ch = _source[_index]; + + if (num == "0") { + if (ch == 'x' || ch == 'X') { + ++_index; + return [self scanHexLiteral:start]; + } + if (ch == 'b' || ch == 'B') { + ++_index; + return [self scanBinaryLiteral:start]; + } + if (ch == 'o' || ch == 'O') { + return [self scanOctalLiteral:ch start:start]; + } + } + + while (isDecimalDigit(_source[_index])) { + num += _source[_index++]; + } + ch = _source[_index]; + } + + if (ch == '.') { + num += _source[_index++]; + while (isDecimalDigit(_source[_index])) { + num += _source[_index++]; + } + ch = _source[_index]; + } + + if (ch == 'e' || ch == 'E') { + num += _source[_index++]; + + ch = _source[_index]; + if (ch == '+' || ch == '-') { + num += _source[_index++]; + } + if (isDecimalDigit(_source[_index])) { + while (isDecimalDigit(_source[_index])) { + num += _source[_index++]; + } + } else { + [self _throwUnexpectedTokenError]; + } + } + + if (isIdentifierStart(_source[_index])) { + [self _throwUnexpectedTokenError]; + } + + WXJSToken *token = new WXJSToken(); + token->type = WXJSTokenTypeNumericLiteral; + token->doubleValue = atof(num.c_str()); + token->start = start; + token->end = _index; + + return token; +} + +- (WXJSToken *)scanHexLiteral:(int)start +{ + std::string num = ""; + while (_index < _length) { + if (!isHexDigit(_source[_index])) { + break; + } + num += _source[_index++]; + } + + if (num.length() == 0) { + [self _throwUnexpectedTokenError]; + } + + if (isIdentifierStart(_source[_index])) { + [self _throwUnexpectedTokenError]; + } + + WXJSToken *token = new WXJSToken(); + token->type = WXJSTokenTypeNumericLiteral; + token->doubleValue = static_cast<double>(std::stoi(num, nullptr, 16)); + token->start = start; + token->end = _index; + + return token; +} + +- (WXJSToken *)scanBinaryLiteral: (int)start +{ + std::string num = ""; + int ch; + + while (_index < _length) { + ch = _source[_index]; + if (ch != '0' && ch != '1') { + break; + } + num += _source[_index++]; + } + + if (num.length() == 0) { + // only 0b or 0B + [self _throwUnexpectedTokenError]; + } + + if (_index < _length) { + ch = _source[_index]; + /* istanbul ignore else */ + if (isIdentifierStart(ch) || isDecimalDigit(ch)) { + [self _throwUnexpectedTokenError]; + } + } + + WXJSToken *token = new WXJSToken(); + token->type = WXJSTokenTypeNumericLiteral; + token->doubleValue = static_cast<double>(std::stoi(num, nullptr, 2)); + token->start = start; + token->end = _index; + + return token; +} +- (WXJSToken *)scanOctalLiteral:(int)prefix start:(int)start +{ + std::string num = ""; + bool octal = false; + + if (isOctalDigit(prefix)) { + octal = true; + num = '0' + _source[_index++]; + } else { + ++_index; + } + + while (_index < _length) { + if (!isOctalDigit(_source[_index])) { + break; + } + num += _source[_index++]; + } + + if (!octal && num.length() == 0) { + // only 0o or 0O + [self _throwUnexpectedTokenError]; + } + + if (isIdentifierStart(_source[_index]) || isDecimalDigit(_source[_index])) { + [self _throwUnexpectedTokenError]; + } + + WXJSToken *token = new WXJSToken(); + token->type = WXJSTokenTypeNumericLiteral; + token->doubleValue = static_cast<double>(std::stoi(num, nullptr, 8)); + token->octal = octal; + token->start = start; + token->end = _index; + + return token; +} + +- (int)scanHexEscape:(int)prefix +{ + int i, len, ch, code = 0; + + len = (prefix == 'u') ? 4 : 2; + for (i = 0; i < len; ++i) { + if (_index < _length && isHexDigit(_source[_index])) { + ch = _source[_index++]; + code = (int)(code * 16 + std::string("0123456789abcdef").find(tolower(ch))); + } else { + return -1; + } + } + return code; +} + +- (WXJSExpression *)parseExpression +{ + return [self parseConditionalExpression]; +} + +- (WXJSExpression *)parseConditionalExpression +{ + WXJSExpression *expr = [self parseBinaryExpression]; + + if ([self match:"?"]) { + [self nextToken]; + WXJSExpression *consequent = [self parseConditionalExpression]; + [self expect:":"]; + WXJSExpression *alternate = [self parseConditionalExpression]; + + WXJSConditionalExpression *conditionalExpr = new WXJSConditionalExpression(); + conditionalExpr->test = expr; + conditionalExpr->consequent = consequent; + conditionalExpr->alternate = alternate; + + return conditionalExpr; + } + + return expr; +} + +- (WXJSExpression *)parseBinaryExpression +{ + WXJSExpression *expr = [self parseUnaryExpression]; + + std::vector<int> precedenceStack; + std::vector<WXJSToken *> operatorStack; + std::vector<WXJSExpression *> expressionStack; + + WXJSToken *token = _lookahead; + int prec = binaryPrecedence(token); + if (prec == 0) { + return expr; + } + + [self nextToken]; + + expressionStack.push_back(expr); + precedenceStack.push_back(prec); + operatorStack.push_back(token); + expressionStack.push_back([self parseUnaryExpression]); + + WXJSExpression *right; + std::string operator_; + WXJSExpression *left; + while ((prec = binaryPrecedence(_lookahead)) > 0) { + while ((expressionStack.size() > 1) && (prec <= precedenceStack[precedenceStack.size() - 1])) { + right = expressionStack[expressionStack.size() - 1]; + expressionStack.pop_back(); + operator_ = operatorStack[operatorStack.size() - 1]->value; + operatorStack.pop_back(); precedenceStack.pop_back(); + left = expressionStack[expressionStack.size() - 1]; expressionStack.pop_back(); + expressionStack.push_back([self createBinaryExpression:operator_ left:left right:right]); + } + + // Shift. + token = [self nextToken]; + precedenceStack.push_back(prec); + operatorStack.push_back(token); + expressionStack.push_back([self parseUnaryExpression]); + } + + // Final reduce to clean-up the stack. + int i = (int)(expressionStack.size() - 1); + expr = expressionStack[i]; + while (i > 0) { + expr = [self createBinaryExpression:operatorStack[i - 1]->value left:expressionStack[i - 1] right:expr]; + i--; + } + + return expr; +} + +- (WXJSExpression *)parseUnaryExpression +{ + WXJSToken *token; + + if ([self match:"++"] || [self match:"--"] || [self match:"+"] || [self match:"-"] || [self match:"~"] || [self match:"!"]) { + token = [self nextToken]; + WXJSExpression *argument = [self parseUnaryExpression]; + WXJSUnaryExpression *expr = new WXJSUnaryExpression(); + expr->operator_ = token->value; + expr->prefix = ([self match:"++"] || [self match:"--"]) ? true : false; + expr->argument = argument; + return expr; + } + + return [self parseMemberExpression]; +} + +- (WXJSExpression *)parsePrimaryExpression +{ + int type = _lookahead->type; + + if (type == WXJSTokenTypePunctuator) { + if (_lookahead->value == "[") { + return [self parseArrayExpression]; + } else if (_lookahead->value == "(") { + return [self parseGroupExpression]; + } + } + if (type == WXJSTokenTypeIdentifier) { + WXJSIdentifier *identifier = new WXJSIdentifier(); + identifier->name = [self nextToken]->value; + return identifier; + } + + if (type == WXJSTokenTypeStringLiteral || type == WXJSTokenTypeNumericLiteral || type == WXJSTokenTypeBooleanLiteral || type == WXJSTokenTypeNullLiteral) { + return [self createLiteral:[self nextToken]]; + } else { + [self _throwUnexpectedTokenError]; + return new WXJSIdentifier(); + } +} + +- (WXJSArrayExpression *)parseArrayExpression +{ + std::vector<WXJSExpression *> expressions; + + [self expect:"["]; + + while (![self match:"]"]) { + if ([self match:","]) { + [self nextToken]; + expressions.push_back(NULL); + } else { + expressions.push_back([self parseConditionalExpression]); + + if (![self match:"]"]) { + [self expect:","]; + } + } + } + + [self expect:"]"]; + + WXJSArrayExpression *array = new WXJSArrayExpression(); + array->expressions = expressions; + + return array; +} + +- (WXJSExpression *)parseGroupExpression +{ + WXJSExpression *expr; + [self expect:"("]; + + expr = [self parseConditionalExpression]; + + [self expect:")"]; + + return expr; +} + + +- (WXJSExpression *)parseMemberExpression +{ + WXJSExpression *expr = [self parsePrimaryExpression]; + + if ([self match:"."]) { + [self expect:"."]; + WXJSExpression *property = [self parsePrimaryExpression]; + WXJSMemberExpression *memberExpr = new WXJSMemberExpression(); + memberExpr->object = expr; + memberExpr->property = property; + memberExpr->computed = false; + return memberExpr; + } else if ([self match:"["]) { + [self expect:"["]; + WXJSExpression *property = [self parseConditionalExpression]; + [self expect:"]"]; + WXJSMemberExpression *memberExpr = new WXJSMemberExpression(); + memberExpr->object = expr; + memberExpr->property = property; + memberExpr->computed = true; + return memberExpr; + } + + return expr; +} + +- (WXJSExpression *)createBinaryExpression:(const std::string &)operator_ left:(WXJSExpression *)left right:(WXJSExpression *)right +{ + WXJSBinaryExpression *node = new WXJSBinaryExpression(); + node->operator_ = operator_; + node->left = left; + node->right = right; + return node; +} + +- (WXJSLiteral *)createLiteral:(WXJSToken *)token +{ + if (token->type == WXJSTokenTypeNumericLiteral) { + WXJSNumericLiteral *node = new WXJSNumericLiteral(); + node->value = token->doubleValue; + return node; + } else if (token->type == WXJSTokenTypeNullLiteral) { + WXJSNullLiteral *node = new WXJSNullLiteral(); + return node; + } else if (token->type == WXJSTokenTypeStringLiteral) { + WXJSStringLiteral *node = new WXJSStringLiteral(); + node->value = token->value; + return node; + } else if (token->type == WXJSTokenTypeBooleanLiteral) { + WXJSBooleanLiteral *node = new WXJSBooleanLiteral(); + node->value = token->value == "true"; + return node; + } else { + assert(false); + } +} + +- (void)skipSpaces +{ + int ch; + while (_index < _length) { + ch = _source[_index]; + if (isWhiteSpace(ch)) { + ++_index; + } else { + break; + } + } +} + +- (bool)match:(std::string)value +{ + return _lookahead->type == WXJSTokenTypePunctuator && _lookahead->value == value; +} + +- (void)expect:(std::string)value +{ + WXJSToken *token = [self nextToken]; + if (token->type != WXJSTokenTypePunctuator || token->value != value) { + [self _throwUnexpectedTokenError]; + } +} + +- (void)_throwUnexpectedTokenError +{ + [self _throwError:@"Unexpected Token"]; +} + +- (void)_throwError:(NSString *)errorMessage +{ + WXLogError(@"%@, index:%d, script:%s", errorMessage, _index, _source.c_str()); +} + +@end
http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListComponent.h ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListComponent.h b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListComponent.h new file mode 100644 index 0000000..a072752 --- /dev/null +++ b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListComponent.h @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 <Foundation/Foundation.h> +#import "WXScrollerComponent.h" + +@interface WXRecycleListComponent : WXScrollerComponent + +@end http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListComponent.m ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListComponent.m b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListComponent.m new file mode 100644 index 0000000..71a52f5 --- /dev/null +++ b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListComponent.m @@ -0,0 +1,459 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 "WXLog.h" +#import "WXUtility.h" +#import "WXComponent_internal.h" +#import "WXComponentManager.h" +#import "WXSDKInstance_private.h" + +#import "WXCellSlotComponent.h" +#import "WXRecycleListLayout.h" +#import "WXRecycleListComponent.h" +#import "WXRecycleListDataManager.h" +#import "WXRecycleListTemplateManager.h" +#import "WXRecycleListUpdateManager.h" + +@interface WXRecycleListComponent () <WXRecycleListLayoutDelegate, WXRecycleListUpdateDelegate, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource> + +@end + +@implementation WXRecycleListComponent +{ + WXRecycleListDataManager *_dataManager; + WXRecycleListTemplateManager *_templateManager; + WXRecycleListUpdateManager *_updateManager; + + NSString *_templateKey; + NSString *_aliasKey; + NSString *_indexKey; + __weak UICollectionView *_collectionView; + + NSMutableDictionary *_sizeCache; + NSMutableDictionary *_stickyCache; + + NSUInteger _previousLoadMoreCellNumber; +} + +WX_EXPORT_METHOD(@selector(appendData:)) +WX_EXPORT_METHOD(@selector(insertData:atIndex:)) +WX_EXPORT_METHOD(@selector(updateData:atIndex:)) +WX_EXPORT_METHOD(@selector(removeData:)) +WX_EXPORT_METHOD(@selector(moveData:toIndex:)) +WX_EXPORT_METHOD(@selector(scrollTo:options:)) + +- (instancetype)initWithRef:(NSString *)ref + type:(NSString *)type + styles:(NSDictionary *)styles + attributes:(NSDictionary *)attributes + events:(NSArray *)events + weexInstance:(WXSDKInstance *)weexInstance +{ + if (self = [super initWithRef:ref type:type styles:styles attributes:attributes events:events weexInstance:weexInstance]) { + _dataManager = [[WXRecycleListDataManager alloc] initWithData:attributes[@"listData"]]; + _templateManager = [WXRecycleListTemplateManager new]; + _updateManager = [WXRecycleListUpdateManager new]; + _updateManager.delegate = self; + _templateKey = [WXConvert NSString:attributes[@"templateKey"]] ? : @"templateType"; + _aliasKey = [WXConvert NSString:attributes[@"alias"]]; + _indexKey = [WXConvert NSString:attributes[@"index"]]; + _sizeCache = [NSMutableDictionary dictionary]; + _stickyCache = [NSMutableDictionary dictionary]; + } + + return self; +} + +#pragma mark - WXComponent Methods + +- (UIView *)loadView +{ + WXRecycleListLayout *layout = [WXRecycleListLayout new]; + layout.delegate = self; + // to show cells that original width / height is zero, otherwise cellForItemAtIndexPath will not be called + layout.minimumLineSpacing = 0.01; + layout.minimumInteritemSpacing = 0.01; + return [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + _collectionView = (UICollectionView *)self.view; + _collectionView.allowsSelection = NO; + _collectionView.allowsMultipleSelection = NO; + _collectionView.dataSource = self; + _collectionView.delegate = self; + + _templateManager.collectionView = _collectionView; + _updateManager.collectionView = _collectionView; +} + +- (void)viewWillUnload +{ + [super viewWillUnload]; + + _collectionView.dataSource = nil; + _collectionView.delegate = nil; +} + +- (void)updateAttributes:(NSDictionary *)attributes +{ + if (attributes[@"listData"]) { + NSArray *listData = attributes[@"listData"]; + [self _updateListData:listData withCompletion:nil animation:NO]; + } +} + +- (CGPoint)absolutePositionForComponent:(WXComponent *)component +{ + CGPoint position = CGPointZero; + UIView *view = component->_view; + while (view) { + if ([view isKindOfClass:[UICollectionViewCell class]]) { + NSIndexPath *indexPath = [_collectionView indexPathForCell:(UICollectionViewCell *)view]; + if (!indexPath) { + return CGPointMake(NAN, NAN); + } + UICollectionViewLayoutAttributes *attributes = [_collectionView layoutAttributesForItemAtIndexPath:indexPath]; + CGPoint cellOrigin = attributes.frame.origin; + position = CGPointMake(position.x + cellOrigin.x, + position.y + cellOrigin.y); + break; + } + position = CGPointMake(position.x + view.frame.origin.x, + position.y + view.frame.origin.y); + view = view.superview; + } + + return position; +} + +- (void)setContentSize:(CGSize)contentSize +{ + // Do Nothing +} + +- (void)adjustSticky +{ + // Do Nothing, sticky is adjusted by layout +} + +#pragma mark - Load More Event + +- (void)loadMore +{ + [super loadMore]; + + _previousLoadMoreCellNumber = [_collectionView numberOfItemsInSection:0]; +} + +- (BOOL)isNeedLoadMore +{ + BOOL superNeedLoadMore = [super isNeedLoadMore]; + return superNeedLoadMore && _previousLoadMoreCellNumber != [_collectionView numberOfItemsInSection:0]; +} + +- (void)resetLoadmore +{ + [super resetLoadmore]; + _previousLoadMoreCellNumber = 0; +} + +#pragma mark - Exported Component Methods + +- (void)appendData:(NSArray *)appendingData +{ + if (![appendingData isKindOfClass:[NSArray class]]) { + WXLogError(@"wrong format of appending data:%@", appendingData); + return; + } + + NSArray *oldData = [_dataManager data]; + [_updateManager updateWithAppendingData:appendingData oldData:oldData completion:nil animation:NO]; +} + +- (void)insertData:(id)data atIndex:(NSUInteger)index +{ + // TODO: bring the update logic to UpdateManager + // TODO: update cell because index has changed + NSMutableArray *newListData = [[_dataManager data] mutableCopy]; + if (index <= newListData.count) { + [newListData insertObject:data atIndex:index]; + [_dataManager updateData:newListData]; + + NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:0]; + + [UIView performWithoutAnimation:^{ + [_collectionView insertItemsAtIndexPaths:@[indexPath]]; + }]; + } +} + +- (void)updateData:(id)data atIndex:(NSUInteger)index +{ + // TODO: bring the update logic to UpdateManager + NSMutableArray *newListData = [[_dataManager data] mutableCopy]; + if (index < newListData.count) { + newListData[index] = data; + [_dataManager updateData:newListData]; + + NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:0]; + UICollectionViewCell *cellView = [_collectionView cellForItemAtIndexPath:indexPath]; + WXCellSlotComponent *cellComponent = (WXCellSlotComponent *)cellView.wx_component; + if (cellComponent) { + [self _updateBindingData:data forCell:cellComponent atIndexPath:indexPath]; + } + } +} + +- (void)removeData:(NSArray *)indexes +{ + // TODO: bring the update logic to UpdateManager + NSMutableArray *newListData = [[_dataManager data] mutableCopy]; + NSMutableIndexSet *indexSet = [NSMutableIndexSet new]; + NSMutableArray *indexPaths = [NSMutableArray array]; + for (NSNumber *index in indexes) { + if ([index unsignedIntegerValue] >= newListData.count) { + WXLogError(@"invalid remove index:%@", index); + continue; + } + [indexSet addIndex:[index unsignedIntegerValue]]; + [indexPaths addObject:[NSIndexPath indexPathForItem:[index unsignedIntegerValue] inSection:0]]; + } + + [newListData removeObjectsAtIndexes:indexSet]; + [_dataManager updateData:newListData]; + [UIView performWithoutAnimation:^{ + [_collectionView deleteItemsAtIndexPaths:indexPaths]; + }]; +} + +- (void)moveData:(NSUInteger)fromIndex toIndex:(NSUInteger)toIndex +{ + // TODO: bring the update logic to UpdateManager + NSMutableArray *newListData = [[_dataManager data] mutableCopy]; + id data = newListData[fromIndex]; + [newListData removeObjectAtIndex:fromIndex]; + [newListData insertObject:data atIndex:toIndex]; + [_dataManager updateData:newListData]; + + NSIndexPath *fromIndexPath = [NSIndexPath indexPathForItem:fromIndex inSection:0]; + NSIndexPath *toIndexPath = [NSIndexPath indexPathForItem:toIndex inSection:0]; + [UIView performWithoutAnimation:^{ + [_collectionView moveItemAtIndexPath:fromIndexPath toIndexPath:toIndexPath]; + }]; +} + +- (void)scrollTo:(NSUInteger)index options:(NSDictionary *)options +{ + NSIndexPath *toIndexPath = [NSIndexPath indexPathForItem:index inSection:0]; + BOOL animated = options[@"animated"] ? [WXConvert BOOL:options[@"animated"]] : NO; + [_collectionView scrollToItemAtIndexPath:toIndexPath atScrollPosition:UICollectionViewScrollPositionTop animated:animated]; +} + +#pragma mark - WXComonent Internal Methods + +- (void)_insertSubcomponent:(WXComponent *)subcomponent atIndex:(NSInteger)index +{ + [super _insertSubcomponent:subcomponent atIndex:index]; + + if ([subcomponent isKindOfClass:[WXCellSlotComponent class]]) { + WXCellSlotComponent *cell = (WXCellSlotComponent*)subcomponent; + [self.weexInstance.componentManager _addUITask:^{ + [_templateManager addTemplate:cell]; + }]; + + //TODO: update collection view if adding template + } +} + +#pragma mark - Private + +- (void)_updateBindingData:(NSDictionary *)data forCell:(WXCellSlotComponent *)cellComponent atIndexPath:(NSIndexPath *)indexPath +{ + if (_aliasKey) { + data = @{_aliasKey:data}; + } + if (_indexKey) { + NSMutableDictionary *dataNew = [data mutableCopy]; + dataNew[_indexKey] = @(indexPath.item); + data = dataNew; + } + +#ifdef DEBUG + NSDate *startTime = [NSDate date]; +#endif + WXPerformBlockSyncOnComponentThread(^{ + [cellComponent updateCellData:data]; + }); +#ifdef DEBUG + double duration = -[startTime timeIntervalSinceNow] * 1000; + WXLogDebug(@"cell:%zi update data time:%f", indexPath.item, duration); +#endif + + NSValue *cachedSize = _sizeCache[indexPath]; + if (!cachedSize || !CGSizeEqualToSize([cachedSize CGSizeValue] , cellComponent.calculatedFrame.size)) { + _sizeCache[indexPath] = [NSValue valueWithCGSize:cellComponent.calculatedFrame.size]; + [_collectionView.collectionViewLayout invalidateLayout]; + } + NSNumber *cachedSticky = _stickyCache[indexPath]; + BOOL isSticky = cellComponent->_positionType == WXPositionTypeSticky; + if (!cachedSticky || [cachedSticky boolValue] != isSticky) { + _stickyCache[indexPath] = @(isSticky); + } +} + +- (void)_updateListData:(NSArray *)newData + withCompletion:(WXRecycleListUpdateCompletion)completion + animation:(BOOL)animation +{ + if (![newData isKindOfClass:[NSArray class]]) { + WXLogError(@"wrong format of list data:%@", newData); + completion(NO); + return; + } + + NSArray *oldData = [_dataManager data]; + [_updateManager updateWithNewData:newData oldData:oldData completion:completion animation:animation]; +} + +#pragma mark - UICollectionViewDataSource + +- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView +{ + return 1; +} + +- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section +{ + return [_dataManager numberOfItems]; +} + +- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath +{ + // 1. get the data relating to the cell + NSDictionary *data = [_dataManager dataAtIndex:indexPath.row]; + if (!data || ![data isKindOfClass:[NSDictionary class]]) { + WXLogError(@"No data or wrong data format for index:%zd, data:%@", indexPath.item, data); + return nil; + } + + // 2. get the template type specified by data + NSString *templateType = data[_templateKey]; + if (!templateType) { + WXLogError(@"Each data should have a value for %@ to indicate template type", _templateKey); + return nil; + } + + // 3. dequeue a cell component by template type + UICollectionViewCell *cellView = [_collectionView dequeueReusableCellWithReuseIdentifier:templateType forIndexPath:indexPath]; + WXCellSlotComponent *cellComponent = (WXCellSlotComponent *)cellView.wx_component; + if (!cellComponent) { + cellComponent = [_templateManager dequeueCellSlotWithType:templateType forIndexPath:indexPath]; + cellView.wx_component = cellComponent; + WXPerformBlockOnComponentThread(^{ + //TODO: How can we avoid this? + [super _insertSubcomponent:cellComponent atIndex:self.subcomponents.count]; + }); + } + + // 4. binding the data to the cell component + [self _updateBindingData:data forCell:cellComponent atIndexPath:indexPath]; + + // 5. Add cell component's view to content view. + UIView *contentView = cellComponent.view; + if (contentView.superview == cellView.contentView) { + return cellView; + } + + for (UIView *view in cellView.contentView.subviews) { + [view removeFromSuperview]; + } + [cellView.contentView addSubview:contentView]; + [cellView setAccessibilityIdentifier:contentView.accessibilityIdentifier]; + + WXLogDebug(@"Return cell view:%@, indexPath:%@", cellView, indexPath); + + dispatch_async(dispatch_get_main_queue(), ^{ + [self handleAppear]; + }); + + return cellView; +} + +- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath +{ + return nil; +} + +#pragma mark - UICollectionViewDelegate + +- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath +{ + WXLogDebug(@"will display cell:%@, at index path:%@", cell, indexPath); +} + +- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath +{ + WXLogDebug(@"Did end displaying cell:%@, at index path:%@", cell, indexPath); +} + +#pragma mark - UICollectionViewDelegateFlowLayout + +- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath +{ + NSValue *size = _sizeCache[indexPath]; + if (size) { + return [size CGSizeValue]; + } else { + NSDictionary *data = [_dataManager dataAtIndex:indexPath.row]; + WXCellSlotComponent *cell = [_templateManager templateWithType:data[_templateKey]]; + CGSize size = cell.calculatedFrame.size; + _sizeCache[indexPath] = [NSValue valueWithCGSize:size]; + return CGSizeMake(_collectionView.frame.size.width, size.height); + } +} + +#pragma mark - WXRecycleListLayoutDelegate + +- (BOOL)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout isNeedStickyForIndexPath:(NSIndexPath *)indexPath +{ + NSNumber *cachedSticky = _stickyCache[indexPath]; + if (cachedSticky) { + return [cachedSticky boolValue]; + } else { + return NO; + } +} + +#pragma mark - WXRecycleListUpdateDelegate + +- (void)updateManager:(WXRecycleListUpdateManager *)manager willUpdateData:(id)newData +{ + [_dataManager updateData:newData]; +} + +- (void)updateManager:(WXRecycleListUpdateManager *)manager didUpdateData:(id)newData withSuccess:(BOOL)finished +{ + +} + +@end http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListDataManager.h ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListDataManager.h b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListDataManager.h new file mode 100644 index 0000000..fc053c5 --- /dev/null +++ b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListDataManager.h @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 <Foundation/Foundation.h> + +@interface WXRecycleListDataManager : NSObject + +- (instancetype)initWithData:(NSArray *)data; + +- (void)updateData:(NSArray *)data; + +- (NSArray *)data; + +- (NSDictionary *)dataAtIndex:(NSInteger)index; + +- (NSInteger)numberOfItems; + +@end http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListDataManager.m ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListDataManager.m b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListDataManager.m new file mode 100644 index 0000000..8d27171 --- /dev/null +++ b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListDataManager.m @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 "WXRecycleListDataManager.h" +#import "NSArray+Weex.h" +#import "WXLog.h" +#import "WXAssert.h" + +@implementation WXRecycleListDataManager +{ + NSArray *_data; +} + +- (instancetype)initWithData:(NSArray *)data +{ + if (self = [super init]) { + if (![data isKindOfClass:[NSArray class]]) { + WXLogError(@"list data must be an array!"); + } else { + _data = data; + } + } + + return self; +} + +- (void)updateData:(NSArray *)data +{ + WXAssertMainThread(); + + _data = data; +} + +- (NSArray *)data +{ + WXAssertMainThread(); + + return _data; +} + +- (NSDictionary *)dataAtIndex:(NSInteger)index +{ + WXAssertMainThread(); + + return [_data wx_safeObjectAtIndex:index]; +} + +- (NSInteger)numberOfItems +{ + WXAssertMainThread(); + + return [_data count]; +} + +@end http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListLayout.h ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListLayout.h b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListLayout.h new file mode 100644 index 0000000..6e08b94 --- /dev/null +++ b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListLayout.h @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 <UIKit/UIKit.h> + +@protocol WXRecycleListLayoutDelegate + +- (BOOL)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout isNeedStickyForIndexPath:(NSIndexPath *)indexPath; + +@end + +@interface WXRecycleListLayout : UICollectionViewFlowLayout + +@property (nonatomic, weak) id<WXRecycleListLayoutDelegate> delegate; + +@end http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListLayout.m ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListLayout.m b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListLayout.m new file mode 100644 index 0000000..73851de --- /dev/null +++ b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListLayout.m @@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 "WXRecycleListLayout.h" + +@implementation WXRecycleListLayout +{ + NSMutableDictionary<NSNumber *, UICollectionViewLayoutAttributes *> *_stickyCellsAttributes; +} + +- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect +{ + NSArray *cellAttributes = [super layoutAttributesForElementsInRect:rect]; + NSMutableDictionary *lastCellsAttributes = [NSMutableDictionary dictionary]; + + __block NSInteger currentStickyIndex = -1; + [cellAttributes enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + UICollectionViewLayoutAttributes *attributes = obj; + NSIndexPath *indexPath = attributes.indexPath; + if ([self.delegate collectionView:self.collectionView layout:self isNeedStickyForIndexPath:indexPath]) { + if (!_stickyCellsAttributes) { + _stickyCellsAttributes = [NSMutableDictionary dictionary]; + } + + currentStickyIndex = indexPath.item; + [_stickyCellsAttributes setObject:attributes forKey:@(indexPath.item)]; + } else { + [_stickyCellsAttributes removeObjectForKey:@(indexPath.item)]; + + // bottom cell above sticky cell + UICollectionViewLayoutAttributes *currentLastCell = [lastCellsAttributes objectForKey:@(currentStickyIndex)]; + if (!currentLastCell || indexPath.item > currentLastCell.indexPath.item) { + [lastCellsAttributes setObject:obj forKey:@(currentStickyIndex)]; + } + } + + attributes.zIndex = 1; + }]; + + NSMutableArray *newCellAttributes = [cellAttributes mutableCopy]; + [lastCellsAttributes enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { + UICollectionViewLayoutAttributes *attributes = obj; + + UICollectionViewLayoutAttributes *stickyCell = _stickyCellsAttributes[key]; + if (!stickyCell) { + NSInteger item = attributes.indexPath.item; + while (item >= 0) { + if (_stickyCellsAttributes[@(item)]) { + stickyCell = [self.collectionView layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:item inSection:0]]; + break; + } else { + item --; + } + } + } + + if (stickyCell) { + [newCellAttributes addObject:stickyCell]; + [self _adjustStickyForCellAttributes:stickyCell lastCellAttributes:attributes]; + } + }]; + + return newCellAttributes; +} + +- (void)_adjustStickyForCellAttributes:(UICollectionViewLayoutAttributes *)cell + lastCellAttributes:(UICollectionViewLayoutAttributes *)lastCell +{ + cell.zIndex = 99; + cell.hidden = NO; + + CGFloat maxY = CGRectGetMaxY(lastCell.frame) - cell.frame.size.height; + CGFloat minY = CGRectGetMinY(self.collectionView.bounds) + self.collectionView.contentInset.top; + CGFloat y = MIN(MAX(minY, cell.frame.origin.y), maxY); + +// NSLog(@"%zi : %zi, %.1f, %.1f, %.1f, %.1f", cell.indexPath.item, lastCell.indexPath.item, maxY, minY, cell.frame.origin.y, y); + + CGPoint origin = cell.frame.origin; + origin.y = y; + + cell.frame = (CGRect){ + origin, + cell.frame.size + }; +} + +- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds +{ + if (_stickyCellsAttributes.count > 0) { + // always return yes to trigger resetting sticky header's frame. + return YES; + } + + return [super shouldInvalidateLayoutForBoundsChange:newBounds]; +} + +@end http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListTemplateManager.h ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListTemplateManager.h b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListTemplateManager.h new file mode 100644 index 0000000..389dbb8 --- /dev/null +++ b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListTemplateManager.h @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 <Foundation/Foundation.h> +#import "WXCellSlotComponent.h" + +@interface WXRecycleListTemplateManager : NSObject + +@property (nonatomic, weak) UICollectionView *collectionView; + +- (void)addTemplate:(WXCellSlotComponent *)component; + +- (WXCellSlotComponent *)dequeueCellSlotWithType:(NSString *)type forIndexPath:(NSIndexPath *)indexPath; + +- (WXCellSlotComponent *)templateWithType:(NSString *)type; + +@end http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListTemplateManager.m ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListTemplateManager.m b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListTemplateManager.m new file mode 100644 index 0000000..87ecd90 --- /dev/null +++ b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListTemplateManager.m @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 "WXRecycleListTemplateManager.h" +#import "WXLog.h" +#import "WXAssert.h" + +@interface WXReusableCollectionViewCell : UICollectionViewCell + +@end + +@implementation WXReusableCollectionViewCell + +@end + +@implementation WXRecycleListTemplateManager +{ + NSMapTable<NSString *, WXCellSlotComponent *> *_templateTypeMap; +} + +- (instancetype)init +{ + if (self = [super init]) { + _templateTypeMap = [NSMapTable strongToWeakObjectsMapTable]; + } + + return self; +} + +- (void)setCollectionView:(UICollectionView *)collectionView +{ + WXAssertMainThread(); + + if (_collectionView == collectionView) { + return; + } + + _collectionView = collectionView; + + for (NSString *templateType in [_templateTypeMap.keyEnumerator.allObjects copy]) { + [self _registerCellClassForReuseID:templateType]; + } +} + +- (void)addTemplate:(WXCellSlotComponent *)component +{ + WXAssertMainThread(); + + NSString *templateType = component.templateType; + WXAssert(templateType != nil, @"cell-slot:%@ must have a template id!", component); + + [_templateTypeMap setObject:component forKey:templateType]; + if (_collectionView) { + [self _registerCellClassForReuseID:templateType]; + } +} + +- (WXCellSlotComponent *)dequeueCellSlotWithType:(NSString *)type forIndexPath:(NSIndexPath *)indexPath +{ + WXAssertMainThread(); + + WXCellSlotComponent *cellSlot = [_templateTypeMap objectForKey:type]; + return [cellSlot copy]; +} + +- (WXCellSlotComponent *)templateWithType:(NSString *)type +{ + return [_templateTypeMap objectForKey:type];; +} + +- (void)_registerCellClassForReuseID:(NSString *)templateID +{ + WXLogDebug(@"register cell class for template id:%@", templateID); + //TODO: register class update TemplateId + [_collectionView registerClass:[WXReusableCollectionViewCell class] forCellWithReuseIdentifier:templateID]; +} + +@end http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListUpdateManager.h ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListUpdateManager.h b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListUpdateManager.h new file mode 100644 index 0000000..1753b2b --- /dev/null +++ b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListUpdateManager.h @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 <Foundation/Foundation.h> + +typedef void(^WXRecycleListUpdateCompletion)(BOOL isFinished); +@class WXRecycleListUpdateManager; + +@protocol WXRecycleListUpdateDelegate + +- (void)updateManager:(WXRecycleListUpdateManager *)manager willUpdateData:(id)newData; + +- (void)updateManager:(WXRecycleListUpdateManager *)manager didUpdateData:(id)newData withSuccess:(BOOL)finished; + +@end + +@interface WXRecycleListUpdateManager : NSObject + +@property (nonatomic, weak) UICollectionView *collectionView; +@property (nonatomic, weak) id<WXRecycleListUpdateDelegate> delegate; + +- (void)reload; + +- (void)updateWithNewData:(NSArray *)newData + oldData:(NSArray *)oldData + completion:(WXRecycleListUpdateCompletion)completion + animation:(BOOL)isAnimated; + +- (void)updateWithAppendingData:(NSArray *)appendingData + oldData:(NSArray *)oldData + completion:(WXRecycleListUpdateCompletion)completion + animation:(BOOL)isAnimated; + +@end http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListUpdateManager.m ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListUpdateManager.m b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListUpdateManager.m new file mode 100644 index 0000000..7ad4caa --- /dev/null +++ b/ios/sdk/WeexSDK/Sources/Component/RecycleList/WXRecycleListUpdateManager.m @@ -0,0 +1,262 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 "WXRecycleListUpdateManager.h" +#import "WXLog.h" +#import "WXAssert.h" +#import "WXDiffUtil.h" + +@interface WXRecycleListDiffResult : NSObject + +@property (nonatomic, strong, readonly) NSMutableSet<NSIndexPath *> *deleteIndexPaths; +@property (nonatomic, strong, readonly) NSMutableSet<NSIndexPath *> *insertIndexPaths; +@property (nonatomic, strong, readonly) NSMutableSet<NSIndexPath *> *reloadIndexPaths; + +- (BOOL)hasChanges; + +@end + +@implementation WXRecycleListDiffResult + +- (instancetype)initWithInsertIndexPaths:(NSMutableSet<NSIndexPath *> *)insertIndexPaths + deleteIndexPaths:(NSMutableSet<NSIndexPath *> *)deleteIndexPaths + reloadIndexPaths:(NSMutableSet<NSIndexPath *> *)reloadIndexPaths +{ + if (self = [super init]) { + _insertIndexPaths = [insertIndexPaths copy]; + _deleteIndexPaths = [deleteIndexPaths copy]; + _reloadIndexPaths = [reloadIndexPaths copy]; + } + + return self; +} + +- (BOOL)hasChanges +{ + return _insertIndexPaths.count > 0 || _deleteIndexPaths.count > 0 || _reloadIndexPaths.count > 0; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"<%@: %p; insert index paths: %@; delete index paths: %@; reload index paths: %@", NSStringFromClass([self class]), self, _insertIndexPaths, _deleteIndexPaths, _reloadIndexPaths]; +} + +@end + +@interface WXRecycleListUpdateManager () + +@property (nonatomic, copy) NSArray *newerData; +@property (nonatomic, copy) NSArray *appendingData; +@property (nonatomic, copy) NSArray *olderData; +@property (nonatomic, assign) BOOL isUpdating; +@property (nonatomic, strong) NSMutableArray *completions; + +@property (nonatomic, strong) NSMutableSet<NSIndexPath *> *reloadIndexPaths; + +@end + +@implementation WXRecycleListUpdateManager + +- (instancetype)init +{ + if (self = [super init]) { + _completions = [NSMutableArray array]; + } + + return self; +} + +- (void)reload +{ + [_collectionView reloadData]; +} + +- (void)updateWithNewData:(NSArray *)newData + oldData:(NSArray *)oldData + completion:(WXRecycleListUpdateCompletion)completion + animation:(BOOL)isAnimated +{ + WXAssertMainThread(); + + if (!_collectionView) { + WXLogError(@"Update list with no collection view"); + completion(NO); + return; + } + + self.newerData = newData; + self.appendingData = nil; + self.olderData = oldData; + + if (completion) { + [_completions addObject:completion]; + } + + [self checkUpdates]; +} + +- (void)updateWithAppendingData:(NSArray *)appendingData + oldData:(NSArray *)oldData + completion:(WXRecycleListUpdateCompletion)completion + animation:(BOOL)isAnimated +{ + if (!_collectionView) { + WXLogError(@"Update list with no collection view"); + completion(NO); + return; + } + + self.appendingData = appendingData; + self.olderData = oldData; + + if (completion) { + [_completions addObject:completion]; + } + + [self checkUpdates]; +} + + +- (void)checkUpdates +{ + dispatch_async(dispatch_get_main_queue(), ^{ + if (self.isUpdating) { + return ; + } + + [self performBatchUpdates]; + }); +} + +- (void)performBatchUpdates +{ + WXAssertMainThread(); + WXAssert(!self.isUpdating, @"Can not perform updates while an updating is being performed"); + + UICollectionView *collectionView = self.collectionView; + if (!collectionView) { + return; + } + + NSArray *newData = [self.newerData copy]; + NSArray *oldData = [self.olderData copy]; + NSArray *appendingData = [self.appendingData copy]; + //TODO use completionBlocks +// NSArray *completionBlocks = [self.completions copy]; + + [self cleanup]; + + WXDiffResult *diffResult; + if (appendingData) { + newData = [oldData arrayByAddingObjectsFromArray:appendingData]; + NSIndexSet *inserts = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(oldData.count, appendingData.count)]; + diffResult = [[WXDiffResult alloc] initWithInserts:inserts deletes:nil updates:nil]; + } else if (newData){ + diffResult = [WXDiffUtil diffWithMinimumDistance:newData oldArray:oldData]; + } + + WXRecycleListDiffResult *recycleListDiffResult = [self recycleListUpdatesByDiffResult:diffResult]; + + if (![diffResult hasChanges] && self.reloadIndexPaths.count == 0) { + return; + } + + void (^updates)() = [^{ + [self.delegate updateManager:self willUpdateData:newData]; + [UIView setAnimationsEnabled:NO]; + NSLog(@"UICollectionView update:%@", recycleListDiffResult); + [self applyUpdateWithDiffResult:recycleListDiffResult]; + } copy]; + + void (^completion)(BOOL) = [^(BOOL finished) { + [UIView setAnimationsEnabled:YES]; + self.isUpdating = NO; + [self.delegate updateManager:self didUpdateData:newData withSuccess:finished]; + + [self.reloadIndexPaths removeAllObjects]; + [self checkUpdates]; + } copy]; + + self.isUpdating = YES; + + if (!self.delegate || !collectionView.dataSource) { + return; + } + + [collectionView performBatchUpdates:updates completion:completion]; +} + +- (WXRecycleListDiffResult *)recycleListUpdatesByDiffResult:(WXDiffResult *)diffResult +{ + NSMutableSet<NSIndexPath *> *reloadIndexPaths = [NSMutableSet set]; + NSMutableSet<NSIndexPath *> *deleteIndexPaths = [NSMutableSet set]; + NSMutableSet<NSIndexPath *> *insertIndexPaths = [NSMutableSet set]; + + for (WXDiffUpdateIndex *update in diffResult.updates) { + NSIndexPath *reloadIndexPath = [NSIndexPath indexPathForItem:update.oldIndex inSection:0]; + [reloadIndexPaths addObject:reloadIndexPath]; + } + + [diffResult.updates enumerateObjectsUsingBlock:^(WXDiffUpdateIndex * _Nonnull update, NSUInteger idx, BOOL * _Nonnull stop) { + NSIndexPath *reloadIndexPath = [NSIndexPath indexPathForItem:update.oldIndex inSection:0]; + [reloadIndexPaths addObject:reloadIndexPath]; + }]; + + [diffResult.inserts enumerateIndexesUsingBlock:^(NSUInteger insertIndex, BOOL * _Nonnull stop) { + NSIndexPath *insertIndexPath = [NSIndexPath indexPathForItem:insertIndex inSection:0]; + [insertIndexPaths addObject:insertIndexPath]; + }]; + + [diffResult.deletes enumerateIndexesUsingBlock:^(NSUInteger deleteIndex, BOOL * _Nonnull stop) { + NSIndexPath *deleteIndexPath = [NSIndexPath indexPathForItem:deleteIndex inSection:0]; + [deleteIndexPaths addObject:deleteIndexPath]; + }]; + + WXRecycleListDiffResult *result = [[WXRecycleListDiffResult alloc] initWithInsertIndexPaths:insertIndexPaths deleteIndexPaths:deleteIndexPaths reloadIndexPaths:reloadIndexPaths]; + + return result; +} + + +- (void)applyUpdateWithDiffResult:(WXRecycleListDiffResult *)diffResult +{ + if (!_collectionView) { + return; + } + + // reload index paths should not inculde delete index paths, otherwise it will cause crash: + // Assertion failure in + // -[UICollectionView _endItemAnimationsWithInvalidationContext:tentativelyForReordering:animator:] + NSMutableSet *reloadIndexPaths = self.reloadIndexPaths ? [[diffResult.reloadIndexPaths setByAddingObjectsFromSet:self.reloadIndexPaths] mutableCopy]: [diffResult.reloadIndexPaths mutableCopy]; + [reloadIndexPaths minusSet:diffResult.deleteIndexPaths]; + + [_collectionView deleteItemsAtIndexPaths:[diffResult.deleteIndexPaths allObjects]]; + [_collectionView insertItemsAtIndexPaths:[diffResult.insertIndexPaths allObjects]]; + [_collectionView reloadItemsAtIndexPaths:[reloadIndexPaths allObjects]]; +} + +- (void)cleanup +{ + self.newerData = nil; + self.appendingData = nil; + self.olderData = nil; + [self.completions removeAllObjects]; +} + +@end http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Component/Recycler/WXSectionDataController.m ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Component/Recycler/WXSectionDataController.m b/ios/sdk/WeexSDK/Sources/Component/Recycler/WXSectionDataController.m index af1f760..1bc7d23 100644 --- a/ios/sdk/WeexSDK/Sources/Component/Recycler/WXSectionDataController.m +++ b/ios/sdk/WeexSDK/Sources/Component/Recycler/WXSectionDataController.m @@ -65,7 +65,7 @@ return [super hash]; } -- (BOOL)isEqualToWXObject:(id<WXDiffable>)object +- (BOOL)weex_isEqualTo:(id<WXDiffable>)object { if ([object isKindOfClass:[WXSectionDataController class]]) { WXSectionDataController *controller = (WXSectionDataController *)object; http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Component/WXCellComponent.m ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Component/WXCellComponent.m b/ios/sdk/WeexSDK/Sources/Component/WXCellComponent.m index e579139..07621c7 100644 --- a/ios/sdk/WeexSDK/Sources/Component/WXCellComponent.m +++ b/ios/sdk/WeexSDK/Sources/Component/WXCellComponent.m @@ -56,7 +56,7 @@ } -- (BOOL)isEqualToWXObject:(id<WXDiffable>)object +- (BOOL)weex_isEqualTo:(id<WXDiffable>)object { return self == object; } @@ -100,7 +100,7 @@ - (void)_moveToSupercomponent:(WXComponent *)newSupercomponent atIndex:(NSUInteger)index { - if (self.delegate == newSupercomponent) { + if (self.delegate == (id<WXCellRenderDelegate>)newSupercomponent) { [self.delegate cell:self didMoveToIndex:index]; [super _removeFromSupercomponent]; [newSupercomponent _insertSubcomponent:self atIndex:index]; http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Component/WXComponent_internal.h ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Component/WXComponent_internal.h b/ios/sdk/WeexSDK/Sources/Component/WXComponent_internal.h index 0316321..5d9b687 100644 --- a/ios/sdk/WeexSDK/Sources/Component/WXComponent_internal.h +++ b/ios/sdk/WeexSDK/Sources/Component/WXComponent_internal.h @@ -25,6 +25,7 @@ @class WXTouchGestureRecognizer; @class WXThreadSafeCounter; +typedef id (^WXDataBindingBlock)(NSDictionary *data, BOOL *needUpdate); /** * The following variables and methods are used in Weex INTERNAL logic. @@ -132,6 +133,25 @@ BOOL _lazyCreateView; WXTransform *_transform; + + /** + * Data Binding + */ + BOOL _isTemplate; + WXComponent *_templateComponent; + WXDataBindingBlock _bindingMatch; + WXDataBindingBlock _bindingRepeat; + NSString *_repeatIndexIdentify; + NSString *_repeatLabelIdentify; + BOOL _isRepeating; + BOOL _isSkipUpdate; + + NSMutableDictionary<NSString *, WXDataBindingBlock> *_bindingProps; + NSMutableDictionary<NSString *, WXDataBindingBlock> *_bindingAttributes; + NSMutableDictionary<NSString *, WXDataBindingBlock> *_bindingStyles; + NSMutableDictionary<NSString *, WXDataBindingBlock> *_bindingEvents; + + NSMutableDictionary<NSString *, NSArray *> *_eventParameters; } ///-------------------------------------- @@ -207,6 +227,10 @@ - (void)_removeAllEvents; +- (void)_addEventParams:(NSDictionary *)params; + +- (NSArray *)_paramsForEvent:(NSString *)eventName; + - (void)_setupNavBarWithStyles:(NSMutableDictionary *)styles attributes:(NSMutableDictionary *)attributes; - (void)_initCompositingAttribute:(NSDictionary *)attributes; @@ -227,6 +251,10 @@ - (void)setGradientLayer; +- (void)_storeBindingsWithProps:(NSDictionary *)props styles:(NSDictionary *)styles attributes:(NSDictionary *)attributes events:(NSDictionary *)events; + +- (void)_didInserted; + @end http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.h ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.h b/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.h index 39bf0d7..db0c155 100644 --- a/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.h +++ b/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.h @@ -34,5 +34,7 @@ - (void)handleAppear; +- (CGPoint)absolutePositionForComponent:(WXComponent *)component; + @end http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.m ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.m b/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.m index a960cc6..306631b 100644 --- a/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.m +++ b/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.m @@ -566,25 +566,30 @@ WX_EXPORT_METHOD(@selector(resetLoadmore)) } } +- (CGPoint)absolutePositionForComponent:(WXComponent *)component +{ + return [component->_view.superview convertPoint:component->_view.frame.origin toView:_view]; +} + #pragma mark Private Methods - (void)scrollToTarget:(WXScrollToTarget *)target scrollRect:(CGRect)rect { WXComponent *component = target.target; - if (![component isViewLoaded]) { + if (![component isViewLoaded]) { return; } CGFloat ctop; if (component && component->_view && component->_view.superview) { - ctop = [component->_view.superview convertPoint:component->_view.frame.origin toView:_view].y; + ctop = [self absolutePositionForComponent:component].y; } else { ctop = 0.0; } CGFloat cbottom = ctop + CGRectGetHeight(component.calculatedFrame); CGFloat cleft; if (component && component->_view && component->_view.superview) { - cleft = [component->_view.superview convertPoint:component->_view.frame.origin toView:_view].x; + cleft = [self absolutePositionForComponent:component].x; } else { cleft = 0.0; } @@ -595,6 +600,7 @@ WX_EXPORT_METHOD(@selector(resetLoadmore)) if(!target.hasAppear && component){ target.hasAppear = YES; if (component->_appearEvent) { +// NSLog(@"appear:%@, %.2f", component, ctop); [component fireEvent:@"appear" params:_direction ? @{@"direction":_direction} : nil]; } } @@ -602,6 +608,7 @@ WX_EXPORT_METHOD(@selector(resetLoadmore)) if(target.hasAppear && component){ target.hasAppear = NO; if(component->_disappearEvent){ +// NSLog(@"disappear:%@", component); [component fireEvent:@"disappear" params:_direction ? @{@"direction":_direction} : nil]; } } http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Engine/WXSDKEngine.m ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Engine/WXSDKEngine.m b/ios/sdk/WeexSDK/Sources/Engine/WXSDKEngine.m index cc9373b..e86221d 100644 --- a/ios/sdk/WeexSDK/Sources/Engine/WXSDKEngine.m +++ b/ios/sdk/WeexSDK/Sources/Engine/WXSDKEngine.m @@ -108,6 +108,10 @@ [self registerComponent:@"textarea" withClass:NSClassFromString(@"WXTextAreaComponent")]; [self registerComponent:@"canvas" withClass:NSClassFromString(@"WXCanvasComponent")]; [self registerComponent:@"slider-neighbor" withClass:NSClassFromString(@"WXSliderNeighborComponent")]; + + [self registerComponent:@"recycle-list" withClass:NSClassFromString(@"WXRecycleListComponent")]; + [self registerComponent:@"cell-slot" withClass:NSClassFromString(@"WXCellSlotComponent") withProperties: @{@"append":@"tree", @"isTemplate":@YES}]; + } + (void)registerComponent:(NSString *)name withClass:(Class)clazz http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Events/WXComponent+Events.m ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Events/WXComponent+Events.m b/ios/sdk/WeexSDK/Sources/Events/WXComponent+Events.m index 8eb799b..6247f07 100644 --- a/ios/sdk/WeexSDK/Sources/Events/WXComponent+Events.m +++ b/ios/sdk/WeexSDK/Sources/Events/WXComponent+Events.m @@ -109,7 +109,10 @@ [dict addEntriesFromDictionary:params]; } - [[WXSDKManager bridgeMgr] fireEvent:self.weexInstance.instanceId ref:self.ref type:eventName params:dict domChanges:domChanges]; + NSArray *handlerArguments = [self _paramsForEvent:eventName]; + NSString *ref = _templateComponent ? _templateComponent.ref : self.ref; + + [[WXSDKManager bridgeMgr] fireEvent:self.weexInstance.instanceId ref:ref type:eventName params:dict domChanges:domChanges handlerArguments:handlerArguments]; } - (void)addEvent:(NSString *)addEventName http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Manager/WXBridgeManager.h ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Manager/WXBridgeManager.h b/ios/sdk/WeexSDK/Sources/Manager/WXBridgeManager.h index fee0be7..7ff2e01 100644 --- a/ios/sdk/WeexSDK/Sources/Manager/WXBridgeManager.h +++ b/ios/sdk/WeexSDK/Sources/Manager/WXBridgeManager.h @@ -125,12 +125,23 @@ extern void WXPerformBlockOnBridgeThread(void (^block)()); * @param instanceId instance id * @param ref : node reference * @param type : event type - * @param params : parameters + * @param params : parameters in event object * @param domChanges dom value changes, used for two-way data binding **/ - (void)fireEvent:(NSString *)instanceId ref:(NSString *)ref type:(NSString *)type params:(NSDictionary *)params domChanges:(NSDictionary *)domChanges; /** + * FireEvent + * @param instanceId instance id + * @param ref : node reference + * @param type : event type + * @param params : parameters in event object + * @param domChanges: dom value changes, used for two-way data binding + * @param eventArguments : arguments passed to event handler + **/ +- (void)fireEvent:(NSString *)instanceId ref:(NSString *)ref type:(NSString *)type params:(NSDictionary *)params domChanges:(NSDictionary *)domChanges handlerArguments:(NSArray *)handlerArguments; + +/** * callBack * * @param instanceId instanceId http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Manager/WXBridgeManager.m ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Manager/WXBridgeManager.m b/ios/sdk/WeexSDK/Sources/Manager/WXBridgeManager.m index 0bf5aca..791aceb 100644 --- a/ios/sdk/WeexSDK/Sources/Manager/WXBridgeManager.m +++ b/ios/sdk/WeexSDK/Sources/Manager/WXBridgeManager.m @@ -304,12 +304,22 @@ void WXPerformBlockOnBridgeThread(void (^block)()) - (void)fireEvent:(NSString *)instanceId ref:(NSString *)ref type:(NSString *)type params:(NSDictionary *)params domChanges:(NSDictionary *)domChanges { + [self fireEvent:instanceId ref:ref type:type params:params domChanges:domChanges handlerArguments:nil]; +} + +- (void)fireEvent:(NSString *)instanceId ref:(NSString *)ref type:(NSString *)type params:(NSDictionary *)params domChanges:(NSDictionary *)domChanges handlerArguments:(NSArray *)handlerArguments +{ if (!type || !ref) { WXLogError(@"Event type and component ref should not be nil"); return; } NSArray *args = @[ref, type, params?:@{}, domChanges?:@{}]; + if (handlerArguments) { + NSMutableArray *newArgs = [args mutableCopy]; + [newArgs addObject:@{@"params":handlerArguments}]; + args = newArgs; + } WXSDKInstance *instance = [WXSDKManager instanceForID:instanceId]; WXCallJSMethod *method = [[WXCallJSMethod alloc] initWithModuleName:nil methodName:@"fireEvent" arguments:args instance:instance]; http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Manager/WXComponentFactory.h ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Manager/WXComponentFactory.h b/ios/sdk/WeexSDK/Sources/Manager/WXComponentFactory.h index f2d2f6a..73b08e1 100644 --- a/ios/sdk/WeexSDK/Sources/Manager/WXComponentFactory.h +++ b/ios/sdk/WeexSDK/Sources/Manager/WXComponentFactory.h @@ -18,6 +18,16 @@ */ #import <Foundation/Foundation.h> +#import "WXInvocationConfig.h" + +@interface WXComponentConfig : WXInvocationConfig + +@property (nonatomic, strong) NSDictionary *properties; + +- (instancetype)initWithName:(NSString *)name class:(NSString *)clazz pros:(NSDictionary *)pros; + +@end + @interface WXComponentFactory : NSObject @@ -53,6 +63,8 @@ */ + (Class)classWithComponentName:(NSString *)name; ++ (WXComponentConfig *)configWithComponentName:(NSString *)name; + /** * @abstract Returns the registered components. */ http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Manager/WXComponentFactory.m ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Manager/WXComponentFactory.m b/ios/sdk/WeexSDK/Sources/Manager/WXComponentFactory.m index 6970376..0101d7a 100644 --- a/ios/sdk/WeexSDK/Sources/Manager/WXComponentFactory.m +++ b/ios/sdk/WeexSDK/Sources/Manager/WXComponentFactory.m @@ -20,17 +20,9 @@ #import "WXComponentFactory.h" #import "WXAssert.h" #import "WXLog.h" -#import "WXInvocationConfig.h" #import <objc/runtime.h> -@interface WXComponentConfig : WXInvocationConfig -@property (nonatomic, strong) NSDictionary *properties; - -- (instancetype)initWithName:(NSString *)name class:(NSString *)clazz pros:(NSDictionary *)pros; - -@end - @implementation WXComponentConfig - (instancetype)initWithName:(NSString *)name class:(NSString *)clazz pros:(NSDictionary *)pros @@ -82,7 +74,17 @@ + (Class)classWithComponentName:(NSString *)name { - return [[self sharedInstance] classWithComponentName:name]; + WXComponentConfig *config = [self configWithComponentName:name]; + if(!config || !config.clazz) { + return nil; + } + + return NSClassFromString(config.clazz); +} + ++ (WXComponentConfig *)configWithComponentName:(NSString *)name +{ + return [[self sharedInstance] configWithComponentName:name]; } + (void)registerComponent:(NSString *)name withClass:(Class)clazz withPros:(NSDictionary *)pros @@ -195,9 +197,9 @@ return componentDic; } -- (Class)classWithComponentName:(NSString *)name +- (WXComponentConfig *)configWithComponentName:(NSString *)name { - WXAssert(name, @"Can not find class for a nil component name"); + WXAssert(name, @"Can not find config for a nil component name"); WXComponentConfig *config = nil; @@ -209,11 +211,7 @@ } [_configLock unlock]; - if(!config || !config.clazz) { - return nil; - } - - return NSClassFromString(config.clazz); + return config; } - (void)registerComponent:(NSString *)name withClass:(Class)clazz withPros:(NSDictionary *)pros http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/5b99cc6b/ios/sdk/WeexSDK/Sources/Manager/WXComponentManager.h ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Manager/WXComponentManager.h b/ios/sdk/WeexSDK/Sources/Manager/WXComponentManager.h index 729e6d7..ff5182c 100644 --- a/ios/sdk/WeexSDK/Sources/Manager/WXComponentManager.h +++ b/ios/sdk/WeexSDK/Sources/Manager/WXComponentManager.h @@ -24,8 +24,16 @@ @class WXSDKInstance; @class WXComponent; -extern void WXPerformBlockOnComponentThread(void (^block)()); - +#ifdef __cplusplus +extern "C" { +#endif + +void WXPerformBlockOnComponentThread(void (^block)()); +void WXPerformBlockSyncOnComponentThread(void (^block)()); + +#ifdef __cplusplus +} +#endif @interface WXComponentManager : NSObject @@ -91,6 +99,7 @@ extern void WXPerformBlockOnComponentThread(void (^block)()); */ - (NSUInteger)numberOfComponents; +- (void)addComponent:(WXComponent *)component toIndexDictForRef:(NSString *)ref; ///-------------------------------------- /// @name Updating