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"