cxfeng1 closed pull request #1799: [iOS] Add richtext component. URL: https://github.com/apache/incubator-weex/pull/1799
This is a PR merged from a forked repository. As GitHub hides the original diff on merge, it is displayed below for the sake of provenance: As this is a foreign pull request (from a fork), the diff is supplied below (as it won't show otherwise due to GitHub magic): diff --git a/ios/sdk/WeexSDK.xcodeproj/project.pbxproj b/ios/sdk/WeexSDK.xcodeproj/project.pbxproj index 11eb08fa2d..d6aca1de3f 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 0000000000..b0c768cc15 --- /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 0000000000..833c7d48af --- /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 26ca8b7714..980d4fd8b4 100644 --- a/ios/sdk/WeexSDK/Sources/Engine/WXSDKEngine.m +++ b/ios/sdk/WeexSDK/Sources/Engine/WXSDKEngine.m @@ -100,6 +100,8 @@ + (void)_registerDefaultComponents [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 1f6791a392..a611390951 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" ---------------------------------------------------------------- This is an automated message from the Apache Git Service. To respond to the message, please log on GitHub and use the URL above to go to the specific comment. For queries about this service, please contact Infrastructure at: us...@infra.apache.org With regards, Apache Git Services