This is an automated email from the ASF dual-hosted git repository.

cxfeng pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-weex.git


The following commit(s) were added to refs/heads/master by this push:
     new 53919f2  [iOS] Add richtext component. (#1799)
53919f2 is described below

commit 53919f2d7bdc9adf64b8dfe16b73e64dab78e4e0
Author: wqyfavor <wqyfa...@163.com>
AuthorDate: Thu Nov 22 17:28:11 2018 +0800

    [iOS] Add richtext component. (#1799)
    
    * [iOS] Add richtext component.
    
    * [iOS] Richtext source format.
---
 ios/sdk/WeexSDK.xcodeproj/project.pbxproj       |  16 +-
 ios/sdk/WeexSDK/Sources/Component/WXRichText.h  |  24 ++
 ios/sdk/WeexSDK/Sources/Component/WXRichText.mm | 508 ++++++++++++++++++++++++
 ios/sdk/WeexSDK/Sources/Engine/WXSDKEngine.m    |   2 +
 ios/sdk/WeexSDK/Sources/WeexSDK.h               |   1 +
 5 files changed, 549 insertions(+), 2 deletions(-)

diff --git a/ios/sdk/WeexSDK.xcodeproj/project.pbxproj 
b/ios/sdk/WeexSDK.xcodeproj/project.pbxproj
index 11eb08f..d6aca1d 100644
--- a/ios/sdk/WeexSDK.xcodeproj/project.pbxproj
+++ b/ios/sdk/WeexSDK.xcodeproj/project.pbxproj
@@ -285,6 +285,10 @@
                74EF31C31DE6935600667A07 /* WXURLRewriteTests.m in Sources */ = 
{isa = PBXBuildFile; fileRef = 74EF31C21DE6935600667A07 /* WXURLRewriteTests.m 
*/; };
                74F7BFF51DC782EC004D0871 /* testRootView.js in Resources */ = 
{isa = PBXBuildFile; fileRef = 74F7BFF41DC782EC004D0871 /* testRootView.js */; 
};
                74FD6E041C7C0E9600DBEB6D /* WXScrollerProtocol.h in Headers */ 
= {isa = PBXBuildFile; fileRef = 74FD6E031C7C0E9600DBEB6D /* 
WXScrollerProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; };
+               7715EB6221A69DD9001F1108 /* WXRichText.h in Headers */ = {isa = 
PBXBuildFile; fileRef = 7715EB6021A69DD8001F1108 /* WXRichText.h */; settings = 
{ATTRIBUTES = (Public, ); }; };
+               7715EB6321A69DD9001F1108 /* WXRichText.h in Headers */ = {isa = 
PBXBuildFile; fileRef = 7715EB6021A69DD8001F1108 /* WXRichText.h */; settings = 
{ATTRIBUTES = (Public, ); }; };
+               7715EB6421A69DD9001F1108 /* WXRichText.mm in Sources */ = {isa 
= PBXBuildFile; fileRef = 7715EB6121A69DD9001F1108 /* WXRichText.mm */; };
+               7715EB6521A69DD9001F1108 /* WXRichText.mm in Sources */ = {isa 
= PBXBuildFile; fileRef = 7715EB6121A69DD9001F1108 /* WXRichText.mm */; };
                775BEE4E1C16F993008D1629 /* WXDefine.h in Headers */ = {isa = 
PBXBuildFile; fileRef = 775BEE4D1C16F993008D1629 /* WXDefine.h */; settings = 
{ATTRIBUTES = (Public, ); }; };
                775BEE6E1C1BD8F4008D1629 /* WXImgLoaderProtocol.h in Headers */ 
= {isa = PBXBuildFile; fileRef = 775BEE6C1C1BD8F4008D1629 /* 
WXImgLoaderProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; };
                775BEE711C1BD977008D1629 /* WXModuleProtocol.h in Headers */ = 
{isa = PBXBuildFile; fileRef = 775BEE701C1BD977008D1629 /* WXModuleProtocol.h 
*/; settings = {ATTRIBUTES = (Public, ); }; };
@@ -1287,6 +1291,8 @@
                74EF31C21DE6935600667A07 /* WXURLRewriteTests.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path 
= WXURLRewriteTests.m; sourceTree = "<group>"; };
                74F7BFF41DC782EC004D0871 /* testRootView.js */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; 
path = testRootView.js; sourceTree = "<group>"; };
                74FD6E031C7C0E9600DBEB6D /* WXScrollerProtocol.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 
WXScrollerProtocol.h; sourceTree = "<group>"; };
+               7715EB6021A69DD8001F1108 /* WXRichText.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 
WXRichText.h; sourceTree = "<group>"; };
+               7715EB6121A69DD9001F1108 /* WXRichText.mm */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; 
path = WXRichText.mm; sourceTree = "<group>"; };
                775BEE4D1C16F993008D1629 /* WXDefine.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 
WXDefine.h; sourceTree = "<group>"; };
                775BEE6C1C1BD8F4008D1629 /* WXImgLoaderProtocol.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 
WXImgLoaderProtocol.h; sourceTree = "<group>"; };
                775BEE701C1BD977008D1629 /* WXModuleProtocol.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 
WXModuleProtocol.h; sourceTree = "<group>"; };
@@ -2159,12 +2165,14 @@
                77E65A0A1C155E6E008B8775 /* Component */ = {
                        isa = PBXGroup;
                        children = (
-                               C4B3D6D21E6954300013F38D /* WXEditComponent.h 
*/,
-                               C4B3D6D31E6954300013F38D /* WXEditComponent.mm 
*/,
                                74CFDD361F45937D007A1A66 /* RecycleList */,
                                74D8DB401E4825920078B667 /* Recycler */,
                                2A837AAC1CD9DE9200AEDF03 /* 
WXLoadingComponent.h */,
                                2A837AAD1CD9DE9200AEDF03 /* 
WXLoadingComponent.mm */,
+                               7715EB6021A69DD8001F1108 /* WXRichText.h */,
+                               7715EB6121A69DD9001F1108 /* WXRichText.mm */,
+                               C4B3D6D21E6954300013F38D /* WXEditComponent.h 
*/,
+                               C4B3D6D31E6954300013F38D /* WXEditComponent.mm 
*/,
                                2A837AAE1CD9DE9200AEDF03 /* 
WXLoadingIndicator.h */,
                                DCC77C111D770AE300CE7288 /* 
WXSliderNeighborComponent.mm */,
                                DCC77C121D770AE300CE7288 /* 
WXSliderNeighborComponent.h */,
@@ -2689,6 +2697,7 @@
                        isa = PBXHeadersBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
+                               7715EB6221A69DD9001F1108 /* WXRichText.h in 
Headers */,
                                4532670A213FC84A00DAA620 /* 
WXDisplayLinkManager.h in Headers */,
                                B8D66C1B21255730003960BD /* style.h in Headers 
*/,
                                B8D66C2321255730003960BD /* layout.h in Headers 
*/,
@@ -2977,6 +2986,7 @@
                        isa = PBXHeadersBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
+                               7715EB6321A69DD9001F1108 /* WXRichText.h in 
Headers */,
                                4532670C213FCF2300DAA620 /* 
WXDisplayLinkManager.h in Headers */,
                                B8D66C1C21255730003960BD /* style.h in Headers 
*/,
                                B8D66C2421255730003960BD /* layout.h in Headers 
*/,
@@ -3587,6 +3597,7 @@
                                B8D66BF32125572F003960BD /* object.cc in 
Sources */,
                                2AC750251C7565690041D390 /* 
WXIndicatorComponent.m in Sources */,
                                591DD3311D23AD5800BE8709 /* WXErrorView.m in 
Sources */,
+                               7715EB6421A69DD9001F1108 /* WXRichText.mm in 
Sources */,
                                B8D66C1121255730003960BD /* 
core_side_in_script.cpp in Sources */,
                                B8D66C032125572F003960BD /* 
css_value_getter.cpp in Sources */,
                                B8394F3921468AF100CA1EFF /* 
render_action_trigger_vsync.cpp in Sources */,
@@ -3807,6 +3818,7 @@
                                DCA4454C1EFA55B300D0CFA8 /* WXComponent.mm in 
Sources */,
                                B8D66C4821255730003960BD /* 
render_action_add_event.cpp in Sources */,
                                B8D66BAE2125572F003960BD /* code_generator.cc 
in Sources */,
+                               7715EB6521A69DD9001F1108 /* WXRichText.mm in 
Sources */,
                                DCA4454D1EFA55B300D0CFA8 /* WXDivComponent.m in 
Sources */,
                                DCA4454E1EFA55B300D0CFA8 /* WXImageComponent.m 
in Sources */,
                                B8D66BB02125572F003960BD /* ast_factory.cc in 
Sources */,
diff --git a/ios/sdk/WeexSDK/Sources/Component/WXRichText.h 
b/ios/sdk/WeexSDK/Sources/Component/WXRichText.h
new file mode 100644
index 0000000..b0c768c
--- /dev/null
+++ b/ios/sdk/WeexSDK/Sources/Component/WXRichText.h
@@ -0,0 +1,24 @@
+/*
+ * 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 "WXComponent.h"
+
+@interface WXRichText : WXComponent<UITextViewDelegate>
+
+@end
diff --git a/ios/sdk/WeexSDK/Sources/Component/WXRichText.mm 
b/ios/sdk/WeexSDK/Sources/Component/WXRichText.mm
new file mode 100644
index 0000000..833c7d4
--- /dev/null
+++ b/ios/sdk/WeexSDK/Sources/Component/WXRichText.mm
@@ -0,0 +1,508 @@
+/*
+ * 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 "WXRichText.h"
+#import "WXSDKManager.h"
+#import "WXSDKEngine.h"
+#import "WXConvert.h"
+#import "WXSDKInstance.h"
+#import "WXComponent+Layout.h"
+#import "WXNavigationProtocol.h"
+#import "WXImgLoaderProtocol.h"
+#include <pthread/pthread.h>
+
+@interface WXRichNode : NSObject
+
+@property (nonatomic, strong) NSString  *type;
+@property (nonatomic, strong) NSString  *text;
+@property (nonatomic, strong) UIColor   *color;
+@property (nonatomic, strong) UIColor   *backgroundColor;
+@property (nonatomic, strong) NSString  *fontFamily;
+@property (nonatomic, assign) CGFloat   fontSize;
+@property (nonatomic, assign) CGFloat   fontWeight;
+@property (nonatomic, assign) WXTextStyle  fontStyle;
+@property (nonatomic, assign) WXTextDecoration textDecoration;
+@property (nonatomic, strong) NSString  *pseudoRef;
+@property (nonatomic, assign) CGFloat width;
+@property (nonatomic, assign) CGFloat height;
+@property (nonatomic, strong) NSURL *href;
+@property (nonatomic, strong) NSURL *src;
+@property (nonatomic, assign) NSRange range;
+
+@end
+
+@implementation WXRichNode
+
+@end
+
+@interface WXRichTextView : UITextView
+
+@end
+
+@implementation WXRichTextView
+
+- (instancetype)initWithFrame:(CGRect)frame
+{
+    if ((self = [super initWithFrame:frame])) {
+        self.isAccessibilityElement = YES;
+        self.accessibilityTraits |= UIAccessibilityTraitStaticText;
+        self.opaque = NO;
+        self.editable = NO;
+        self.selectable = YES;
+        self.contentMode = UIViewContentModeRedraw;
+        self.textContainerInset = UIEdgeInsetsZero;
+        self.textContainer.lineFragmentPadding = 0.0f;
+        self.textContainer.lineBreakMode = NSLineBreakByClipping;
+    }
+    return self;
+}
+
+@end
+
+#define WX_STYLE_FILL_RICHTEXT(key, type)\
+do {\
+    id value = styles[@#key]; \
+    if (value) { \
+        node.key = [WXConvert type:value];\
+    } else if (!([@#key isEqualToString:@"backgroundColor"] || [@#key 
isEqualToString:@"textDecoration"]) && superNode.key ) { \
+        node.key = superNode.key; \
+    } \
+} while(0);
+
+#define WX_STYLE_FILL_RICHTEXT_PIXEL(key)\
+do {\
+    id value = styles[@#key];\
+    if (value) {\
+        node.key = [WXConvert WXPixelType:value 
scaleFactor:self.weexInstance.pixelScaleFactor];\
+    } else if (superNode.key ) { \
+        node.key = superNode.key; \
+    } \
+} while(0);
+
+
+@implementation WXRichText
+{
+    WXRichTextView *textView;
+    NSMutableArray *_richNodes;
+    NSMutableDictionary *_nodeRanges;
+    NSMutableDictionary *_styles;
+    NSMutableDictionary *_attributes;
+    UIEdgeInsets _padding;
+    NSTextAlignment _textAlign;
+    UIColor *_backgroundColor;
+    NSMutableAttributedString *_attributedString;
+    pthread_mutex_t _attributedStringMutex;
+    pthread_mutexattr_t _propertMutexAttr;
+    CGFloat _lineHeight;
+}
+
+- (void)dealloc
+{
+    pthread_mutex_destroy(&_attributedStringMutex);
+    pthread_mutexattr_destroy(&_propertMutexAttr);
+}
+
+- (WXRichTextView *)textView
+{
+    if (!textView) {
+        textView = [[WXRichTextView alloc]init];
+        textView.delegate = self;
+        textView.scrollEnabled = NO;
+    }
+    return textView;
+}
+
+- (instancetype)initWithRef:(NSString *)ref
+                       type:(NSString *)type
+                     styles:(NSDictionary *)styles
+                 attributes:(NSDictionary *)attributes
+                     events:(NSArray *)events
+               weexInstance:(WXSDKInstance *)weexInstance
+{
+    self = [super initWithRef:ref type:type styles:styles 
attributes:attributes events:events weexInstance:weexInstance];
+    if (self) {
+        _richNodes = [NSMutableArray new];
+        _nodeRanges = [NSMutableDictionary new];
+        _styles = [NSMutableDictionary dictionaryWithDictionary:styles];
+        _attributes = [NSMutableDictionary 
dictionaryWithDictionary:attributes];
+        _textAlign = styles[@"textAlign"] ? [WXConvert 
NSTextAlignment:styles[@"textAlign"]] : NSTextAlignmentLeft;
+        _lineHeight = styles[@"lineHeight"] ? [WXConvert 
CGFloat:styles[@"lineHeight"]] / 2: 0;
+        pthread_mutexattr_init(&(_propertMutexAttr));
+        pthread_mutexattr_settype(&(_propertMutexAttr), 
PTHREAD_MUTEX_RECURSIVE);
+        pthread_mutex_init(&(_attributedStringMutex), &(_propertMutexAttr));
+    }
+    return self;
+}
+
+- (void)fillAttributes:(NSDictionary *)attributes
+{
+    id value = attributes[@"value"];
+    if ([value isKindOfClass: [NSArray class]]) {
+        [_richNodes removeAllObjects];
+        
+        WXRichNode *rootNode = [[WXRichNode alloc]init];
+        [_richNodes addObject:rootNode];
+        
+        //记录richtext根节点styles,仅用于子节点的样式继承
+        rootNode.type = @"root";
+        if (_styles) {
+            [self fillCSSStyles:_styles toNode:rootNode superNode:nil];
+        }
+        
+        for (NSDictionary *dict in value) {
+            [self recursivelyAddChildNode:dict toSuperNode:rootNode];
+        }
+        
+        _backgroundColor = rootNode.backgroundColor?:[UIColor whiteColor];
+    }
+}
+
+- (void)fillCSSStyles:(NSDictionary *)styles toNode:(WXRichNode *)node 
superNode:(WXRichNode *)superNode
+{
+    WX_STYLE_FILL_RICHTEXT(color, UIColor)
+    WX_STYLE_FILL_RICHTEXT(backgroundColor, UIColor)
+    WX_STYLE_FILL_RICHTEXT(fontFamily, NSString)
+    WX_STYLE_FILL_RICHTEXT_PIXEL(fontSize)
+    WX_STYLE_FILL_RICHTEXT(fontWeight, WXTextWeight)
+    WX_STYLE_FILL_RICHTEXT(fontStyle, WXTextStyle)
+    WX_STYLE_FILL_RICHTEXT(textDecoration, WXTextDecoration)
+    WX_STYLE_FILL_RICHTEXT_PIXEL(width)
+    WX_STYLE_FILL_RICHTEXT_PIXEL(height)
+}
+
+- (void)fillAttributes:(NSDictionary *)attributes toNode:(WXRichNode *)node 
superNode:(WXRichNode *)superNode
+{
+    if (attributes[@"pseudoRef"]) {
+        node.pseudoRef = attributes[@"pseudoRef"];
+        node.href = [NSURL URLWithString:@"click://"];
+    }
+    
+    if (attributes[@"href"]) {
+        node.href = [NSURL URLWithString:attributes[@"href"]];
+    }
+    else if (superNode.href) {
+        node.href = superNode.href;
+    }
+    
+    if (attributes[@"src"]) {
+        node.src = [NSURL URLWithString:attributes[@"src"]];
+    }
+    
+    if (attributes[@"value"] ) {
+        id value = attributes[@"value"];
+        if ([value isKindOfClass:[NSString class]]) {
+            node.text = (NSString *)value;
+        }
+    }
+}
+
+- (void)recursivelyAddChildNode:(NSDictionary *)nodeValue 
toSuperNode:(WXRichNode *)superNode
+{
+    WXRichNode *node = [[WXRichNode alloc]init];
+    [_richNodes addObject:node];
+    
+    node.type = nodeValue[@"type"];
+
+    [self fillCSSStyles:nodeValue[@"style"] toNode:node superNode:superNode];
+
+    if (nodeValue[@"attr"]) {
+        [self fillAttributes:nodeValue[@"attr"] toNode:node 
superNode:superNode];
+    }
+    
+    if (nodeValue[@"children"]) {
+        id value = nodeValue[@"children"];
+        if ([value isKindOfClass:[NSArray class]]) {
+            NSArray *children = (NSArray *)value;
+            for(NSDictionary *childValue in children){
+                [self recursivelyAddChildNode:childValue toSuperNode:node];
+            }
+        }
+    }
+}
+
+#pragma mark - Subclass
+
+- (UIView *)loadView
+{
+    return  [self textView];
+}
+
+- (void)viewDidUnload
+{
+    textView = nil;
+}
+
+- (void)viewDidLoad
+{
+    [self innerLayout];
+}
+
+- (void)layoutDidFinish
+{
+    [self innerLayout];
+}
+
+- (void)innerLayout
+{
+    if (self.flexCssNode == nullptr) {
+        return;
+    }
+    UIEdgeInsets padding = {
+            
WXFloorPixelValue(self.flexCssNode->getPaddingTop()+self.flexCssNode->getBorderWidthTop()),
+            
WXFloorPixelValue(self.flexCssNode->getPaddingLeft()+self.flexCssNode->getBorderWidthLeft()),
+            
WXFloorPixelValue(self.flexCssNode->getPaddingBottom()+self.flexCssNode->getBorderWidthBottom()),
+            
WXFloorPixelValue(self.flexCssNode->getPaddingRight()+self.flexCssNode->getBorderWidthRight())
+        };
+
+    
+    _padding = padding;
+    
+    _attributedString = [self buildAttributeString];
+
+    [self textView].attributedText = _attributedString;
+    [self textView].textContainerInset = _padding;
+    [self textView].backgroundColor = [UIColor clearColor];
+}
+
+- (CGSize (^)(CGSize))measureBlock
+{
+    __weak typeof(self) weakSelf = self;
+    
+    return ^CGSize (CGSize constrainedSize) {
+        
+        NSMutableAttributedString *attributedString = [weakSelf 
buildAttributeString];
+        
+        CGFloat width = constrainedSize.width;
+        if (isnan(width)) {
+            width = CGFLOAT_MAX;
+        }
+        
+        CGRect rect = [attributedString boundingRectWithSize:CGSizeMake(width, 
CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin | 
NSStringDrawingUsesFontLeading context:nil];
+        CGSize computedSize = rect.size;
+        if(weakSelf.flexCssNode != nullptr){
+            if (!isnan(weakSelf.flexCssNode->getMinWidth())) {
+                computedSize.width = MAX(computedSize.width, 
weakSelf.flexCssNode->getMinWidth());
+            }
+            if (!isnan(weakSelf.flexCssNode->getMaxWidth())) {
+                computedSize.width = MIN(computedSize.width, 
weakSelf.flexCssNode->getMaxWidth());
+            }
+            if (!isnan(weakSelf.flexCssNode->getMinHeight())) {
+                computedSize.width = MAX(computedSize.height, 
weakSelf.flexCssNode->getMinHeight());
+            }
+            if (!isnan(weakSelf.flexCssNode->getMaxHeight())) {
+                computedSize.width = MIN(computedSize.height, 
weakSelf.flexCssNode->getMaxHeight());
+            }
+        }
+        return (CGSize) {
+            WXCeilPixelValue(computedSize.width),
+            WXCeilPixelValue(computedSize.height)
+        };
+    };
+}
+
+#pragma mark Text Building
+
+- (NSMutableAttributedString *)buildAttributeString
+{
+    pthread_mutex_lock(&(_attributedStringMutex));
+    [self fillAttributes:_attributes];
+    NSMutableArray *array = [NSMutableArray arrayWithArray:_richNodes];
+    pthread_mutex_unlock(&(_attributedStringMutex));
+    
+    NSMutableDictionary *nodeRange = [NSMutableDictionary dictionary];
+    NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] 
init];
+    NSUInteger location;
+    
+    __weak typeof(self) weakSelf = self;
+    for (WXRichNode *node in array) {
+        location = attrStr.length;
+        
+        if ([node.type isEqualToString:@"span"]) {
+            if (node.text && [node.text length] > 0) {
+                NSString *text = node.text;
+                [attrStr.mutableString appendString:text];
+                
+                NSRange range = NSMakeRange(location, text.length);
+                [attrStr addAttribute:NSForegroundColorAttributeName 
value:node.color ?: [UIColor blackColor] range:range];
+                [attrStr addAttribute:NSBackgroundColorAttributeName 
value:node.backgroundColor ?: _backgroundColor range:range];
+                
+                UIFont *font = [WXUtility fontWithSize:node.fontSize 
textWeight:node.fontWeight textStyle:WXTextStyleNormal 
fontFamily:node.fontFamily scaleFactor:self.weexInstance.pixelScaleFactor];
+                if (font) {
+                    [attrStr addAttribute:NSFontAttributeName value:font 
range:range];
+                }
+                if (node.fontStyle == WXTextStyleItalic) {
+                    [attrStr addAttribute:NSObliquenessAttributeName 
value:@0.3 range:range];
+                }
+                else
+                {
+                    [attrStr addAttribute:NSObliquenessAttributeName value:@0 
range:range];
+                }
+                [attrStr addAttribute:NSUnderlineStyleAttributeName 
value:[NSNumber numberWithInteger:WXTextDecorationNone] range:range];
+                [attrStr addAttribute:NSStrikethroughStyleAttributeName 
value:[NSNumber numberWithInteger:WXTextDecorationNone] range:range];
+                
+                if (node.textDecoration == WXTextDecorationUnderline) {
+                    [attrStr addAttribute:NSUnderlineStyleAttributeName 
value:[NSNumber numberWithInteger:WXTextDecorationUnderline] range:range];
+                }
+                else if (node.textDecoration == WXTextDecorationLineThrough) {
+                    [attrStr addAttribute:NSStrikethroughStyleAttributeName 
value:[NSNumber numberWithInteger:WXTextDecorationLineThrough] range:range];
+                }
+                
+                if (node.href) {
+                    [attrStr addAttribute:NSLinkAttributeName value:node.href 
range:range];
+                }
+                else {
+                    [attrStr removeAttribute:NSLinkAttributeName range:range];
+                }
+                
+                NSMutableParagraphStyle *paragraphStyle = 
[[NSMutableParagraphStyle alloc] init];
+                paragraphStyle.alignment = _textAlign;
+                if(_lineHeight != 0 )
+                {
+                    paragraphStyle.minimumLineHeight = _lineHeight;
+                    paragraphStyle.maximumLineHeight = _lineHeight;
+                    [attrStr addAttribute:NSBaselineOffsetAttributeName 
value:@((_lineHeight - font.lineHeight)/2) range:range];
+                }
+                [attrStr addAttribute:NSParagraphStyleAttributeName 
value:paragraphStyle range:range];
+                
+                [nodeRange setObject:node forKey:NSStringFromRange(range)];
+            }
+        }
+        else if ([node.type isEqualToString:@"image"]) {
+            NSTextAttachment *imgAttachment = [[NSTextAttachment alloc]init];
+            imgAttachment.bounds = CGRectMake(0, 0, node.width, node.height);
+            
+            NSAttributedString *attachAttriStr = [NSAttributedString 
attributedStringWithAttachment:imgAttachment];
+            [attrStr appendAttributedString:attachAttriStr];
+            
+            NSRange range = NSMakeRange(location, attachAttriStr.length);
+            [attrStr addAttribute:NSFontAttributeName value: [UIFont 
systemFontOfSize:node.height] range:range];
+            
+            if (node.href) {
+                [attrStr addAttribute:NSLinkAttributeName value:node.href 
range:range];
+            }
+            else {
+                [attrStr removeAttribute:NSLinkAttributeName range:range];
+            }
+            
+            [nodeRange setObject:node forKey:NSStringFromRange(range)];
+            
+            if (node.src) {
+                [[self imageLoader] 
downloadImageWithURL:node.src.absoluteString imageFrame:imgAttachment.bounds 
userInfo:nil completed:^(UIImage *image, NSError *error, BOOL finished) {
+                    dispatch_async(dispatch_get_main_queue(), ^{
+                        imgAttachment.image = image;
+                        [[weakSelf textView].layoutManager 
invalidateDisplayForCharacterRange:range];
+                    });
+                }];
+            }
+        }
+    }
+    
+    pthread_mutex_lock(&(_attributedStringMutex));
+    [_nodeRanges removeAllObjects];
+    _nodeRanges = [NSMutableDictionary dictionaryWithDictionary:nodeRange];
+    pthread_mutex_unlock(&(_attributedStringMutex));
+    
+    return attrStr;
+}
+
+- (void)updateStyles:(NSDictionary *)styles {
+    
+    if (styles[@"textAlign"]) {
+        _textAlign = [WXConvert NSTextAlignment:styles[@"textAlign"]];
+    }
+    if (styles[@"lineHeight"]) {
+        _lineHeight = [WXConvert CGFloat:styles[@"lineHeight"]] / 2;
+    }
+    [_styles addEntriesFromDictionary:styles];
+    [self syncTextStorageForView];
+}
+
+- (void)updateAttributes:(NSDictionary *)attributes {
+    _attributes = [NSMutableDictionary dictionaryWithDictionary:attributes];
+    [self syncTextStorageForView];
+}
+
+- (void)syncTextStorageForView {
+    pthread_mutex_lock(&(_attributedStringMutex));
+    [self fillAttributes:_attributes];
+    pthread_mutex_unlock(&(_attributedStringMutex));
+    
+    if (_styles[@"height"]) {
+       [self innerLayout];
+    }
+    else {
+       [self setNeedsLayout];
+    }
+}
+
+#pragma mark - UITextView Delegate
+
+- (BOOL)textView:(UITextView *)textView 
shouldInteractWithTextAttachment:(NSTextAttachment *)textAttachment 
inRange:(NSRange)characterRange {
+    return NO;
+}
+
+- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL 
inRange:(NSRange)characterRange {
+    if (!URL) {
+        return NO;
+    }
+    
+    NSString *rangeStr = NSStringFromRange(characterRange);
+    WXRichNode *node = [_nodeRanges objectForKey:rangeStr];
+    
+    if (![[node.href absoluteString] isEqualToString:@"click://"]) {
+        id<WXNavigationProtocol> navigationHandler = [self navigationHandler];
+        if ([navigationHandler 
respondsToSelector:@selector(pushViewControllerWithParam:
+                                                            completion:
+                                                            withContainer:)]) {
+            [navigationHandler 
pushViewControllerWithParam:@{@"url":URL.absoluteString} completion:^(NSString 
*code, NSDictionary *responseData) {
+            } withContainer:self.weexInstance.viewController];
+        } else {
+            WXLogError(@"Event handler of class %@ does not respond to 
pushViewControllerWithParam", NSStringFromClass([navigationHandler class]));
+        }
+    }
+    else if (node.pseudoRef) {
+        NSMutableDictionary *params = [NSMutableDictionary new];
+        [params setObject:node.pseudoRef forKey:@"pseudoRef"];
+        [[WXSDKManager bridgeMgr] fireEvent:self.weexInstance.instanceId 
ref:self.ref type:@"itemclick" params:params domChanges:nil];
+    }
+    
+    return NO;
+}
+
+# pragma mark - imageLoader
+
+- (id<WXImgLoaderProtocol>)imageLoader {
+    static id<WXImgLoaderProtocol> imageLoader;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        imageLoader = [WXSDKEngine 
handlerForProtocol:@protocol(WXImgLoaderProtocol)];
+    });
+    return imageLoader;
+}
+
+- (id<WXNavigationProtocol>)navigationHandler {
+    static id<WXNavigationProtocol> navigationHandler;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        navigationHandler = [WXSDKEngine 
handlerForProtocol:@protocol(WXNavigationProtocol)];
+    });
+    return navigationHandler;
+}
+
+@end
diff --git a/ios/sdk/WeexSDK/Sources/Engine/WXSDKEngine.m 
b/ios/sdk/WeexSDK/Sources/Engine/WXSDKEngine.m
index 26ca8b7..980d4fd 100644
--- a/ios/sdk/WeexSDK/Sources/Engine/WXSDKEngine.m
+++ b/ios/sdk/WeexSDK/Sources/Engine/WXSDKEngine.m
@@ -100,6 +100,8 @@
     [self registerComponent:@"div" withClass:NSClassFromString(@"WXComponent") 
withProperties:nil];
     [self registerComponent:@"text" 
withClass:NSClassFromString(@"WXTextComponent") withProperties:nil];
     [self registerComponent:@"image" 
withClass:NSClassFromString(@"WXImageComponent") withProperties:nil];
+    [self registerComponent:@"richtext" 
withClass:NSClassFromString(@"WXRichText") withProperties:nil];
+    
     [self registerComponent:@"scroller" 
withClass:NSClassFromString(@"WXScrollerComponent") withProperties:nil];
     [self registerComponent:@"list" 
withClass:NSClassFromString(@"WXListComponent") withProperties:nil];
     [self registerComponent:@"recycler" 
withClass:NSClassFromString(@"WXRecyclerComponent") withProperties:nil];
diff --git a/ios/sdk/WeexSDK/Sources/WeexSDK.h 
b/ios/sdk/WeexSDK/Sources/WeexSDK.h
index 1f6791a..a611390 100644
--- a/ios/sdk/WeexSDK/Sources/WeexSDK.h
+++ b/ios/sdk/WeexSDK/Sources/WeexSDK.h
@@ -38,6 +38,7 @@
 #import "WXSDKError.h"
 #import "WXSDKEngine.h"
 #import "WXRootViewController.h"
+#import "WXRichText.h"
 #import "WXResourceResponse.h"
 #import "WXResourceRequestHandler.h"
 #import "WXResourceRequest.h"

Reply via email to