├── RZColorful ├── Assets │ └── .gitkeep └── Classes │ ├── UICore │ ├── UITextField+SelectedRange.h │ ├── RZTapActionHelper.h │ ├── UIButton+RZColorful.m │ ├── LabelFoldHelper.h │ ├── UIButton+RZColorful.h │ ├── MarkdownRZ.h │ ├── UITextField+SelectedRange.m │ ├── UITextField+RZColorful.h │ ├── UIView+RZContinueFirstResponder.h │ ├── UITextView+RZColorful.h │ ├── UILabel+RZColorful.h │ ├── UITextField+RZColorful.m │ ├── RZTapActionHelper.m │ ├── UITextView+RZColorful.m │ ├── UILabel+RZColorful.m │ ├── LabelFoldHelper.m │ └── UIView+RZContinueFirstResponder.m │ ├── AttributeCore │ ├── NSString+RZCode.h │ ├── RZShadow.h │ ├── NSString+RZCode.m │ ├── RZColorfulConferrer.h │ ├── RZShadow.m │ ├── RZImageAttachment.h │ ├── RZParagraphStyle.h │ ├── NSAttributedString+RZHtml.h │ ├── NSAttributedString+RZColorful.h │ ├── RZImageAttachment.m │ ├── RZParagraphStyle.m │ ├── RZColorfulAttribute.h │ ├── RZColorfulConferrer.m │ ├── RZColorfulAttribute.m │ ├── NSAttributedString+RZColorful.m │ └── NSAttributedString+RZHtml.m │ └── RZColorful.h ├── _Pods.xcodeproj ├── .gitignore ├── .DS_Store ├── line.png ├── image_1.jpg ├── image_2.png ├── Example ├── Tests │ ├── en.lproj │ │ └── InfoPlist.strings │ ├── Tests-Prefix.pch │ ├── Tests.m │ └── Tests-Info.plist ├── RZColorful │ ├── en.lproj │ │ └── InfoPlist.strings │ ├── Images.xcassets │ │ ├── Contents.json │ │ ├── image.imageset │ │ │ ├── image@3x.png │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── RZViewController.h │ ├── RZAppDelegate.h │ ├── Demo │ │ ├── RZTextViewVC.h │ │ ├── RZTestViewController.h │ │ ├── MDToHtmlViewController.h │ │ ├── AttributedStringUseDemoVC.h │ │ ├── RZFoldLabelVC.h │ │ ├── RZTestViewController.m │ │ ├── RZTextViewVC.m │ │ ├── MDToHtmlViewController.m │ │ ├── AttributedStringUseDemoVC.m │ │ └── RZFoldLabelVC.m │ ├── main.m │ ├── RZColorful-Prefix.pch │ ├── RZColorful-Info.plist │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── RZAppDelegate.m │ └── RZViewController.m ├── RZColorful.xcworkspace │ ├── xcuserdata │ │ └── rztime.xcuserdatad │ │ │ ├── UserInterfaceState.xcuserstate │ │ │ └── xcdebugger │ │ │ └── Breakpoints_v2.xcbkptlist │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── RZColorful.xcodeproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── RZColorful-Example.xcscheme └── Podfile ├── LICENSE ├── RZColorful.podspec ├── UpdateLog.md └── README.md /RZColorful/Assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_Pods.xcodeproj: -------------------------------------------------------------------------------- 1 | Example/Pods/Pods.xcodeproj -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Pods/ 2 | Podfile.lock 3 | */xcuserdata/ 4 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rztime/RZColorful/HEAD/.DS_Store -------------------------------------------------------------------------------- /line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rztime/RZColorful/HEAD/line.png -------------------------------------------------------------------------------- /image_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rztime/RZColorful/HEAD/image_1.jpg -------------------------------------------------------------------------------- /image_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rztime/RZColorful/HEAD/image_2.png -------------------------------------------------------------------------------- /Example/Tests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Example/RZColorful/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Example/RZColorful/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Example/RZColorful/Images.xcassets/image.imageset/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rztime/RZColorful/HEAD/Example/RZColorful/Images.xcassets/image.imageset/image@3x.png -------------------------------------------------------------------------------- /Example/Tests/Tests-Prefix.pch: -------------------------------------------------------------------------------- 1 | // The contents of this file are implicitly included at the beginning of every test case source file. 2 | 3 | #ifdef __OBJC__ 4 | 5 | @import FBSnapshotTestCase; 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /Example/RZColorful.xcworkspace/xcuserdata/rztime.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rztime/RZColorful/HEAD/Example/RZColorful.xcworkspace/xcuserdata/rztime.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Example/RZColorful.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/RZColorful.xcworkspace/xcuserdata/rztime.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | use_frameworks! 2 | 3 | platform :ios, '11.0' 4 | 5 | target 'RZColorful_Example' do 6 | pod 'RZColorful', :path => '../' 7 | pod 'Masonry' 8 | target 'RZColorful_Tests' do 9 | inherit! :search_paths 10 | 11 | # pod 'FBSnapshotTestCase' 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /Example/RZColorful/RZViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // RZViewController.h 3 | // RZColorful 4 | // 5 | // Created by rztime on 01/14/2022. 6 | // Copyright (c) 2022 rztime. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | 11 | @interface RZViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Example/RZColorful.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/RZColorful.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/RZColorful/RZAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // RZAppDelegate.h 3 | // RZColorful 4 | // 5 | // Created by rztime on 01/14/2022. 6 | // Copyright (c) 2022 rztime. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | 11 | @interface RZAppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Example/RZColorful/Demo/RZTextViewVC.h: -------------------------------------------------------------------------------- 1 | // 2 | // RZTextViewVC.h 3 | // RZColorful_Example 4 | // 5 | // Created by rztime on 2022/1/26. 6 | // Copyright © 2022 rztime. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface RZTextViewVC : UIViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /Example/RZColorful/Demo/RZTestViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // RZTestViewController.h 3 | // RZColorful_Example 4 | // 5 | // Created by rztime on 2025/2/12. 6 | // Copyright © 2025 rztime. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface RZTestViewController : UIViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /Example/RZColorful/Demo/MDToHtmlViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // MDToHtmlViewController.h 3 | // RZColorful_Example 4 | // 5 | // Created by rztime on 2025/3/14. 6 | // Copyright © 2025 rztime. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface MDToHtmlViewController : UIViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /Example/RZColorful/Demo/AttributedStringUseDemoVC.h: -------------------------------------------------------------------------------- 1 | // 2 | // AttributedStringUseDemoVC.h 3 | // RZColorful_Example 4 | // 5 | // Created by rztime on 2022/1/25. 6 | // Copyright © 2022 rztime. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface AttributedStringUseDemoVC : UIViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /Example/RZColorful/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // RZColorful 4 | // 5 | // Created by rztime on 01/14/2022. 6 | // Copyright (c) 2022 rztime. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | #import "RZAppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) 13 | { 14 | @autoreleasepool { 15 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([RZAppDelegate class])); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Example/RZColorful/RZColorful-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #import 8 | 9 | #ifndef __IPHONE_5_0 10 | #warning "This project uses features only available in iOS SDK 5.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | @import UIKit; 15 | @import Foundation; 16 | #endif 17 | -------------------------------------------------------------------------------- /RZColorful/Classes/UICore/UITextField+SelectedRange.h: -------------------------------------------------------------------------------- 1 | // 2 | // UITextField+SelectedRange.h 3 | // RZColorfulExample 4 | // 5 | // Created by rztime on 2017/11/16. 6 | // Copyright © 2017年 rztime. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UITextField (SelectedRange) 12 | 13 | //获取焦点的位置 14 | -(NSRange)selectedRange; 15 | //设置焦点的位置 16 | -(void)setSelectedRange:(NSRange)range; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Example/RZColorful/Images.xcassets/image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "filename" : "image@3x.png", 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /RZColorful/Classes/UICore/RZTapActionHelper.h: -------------------------------------------------------------------------------- 1 | // 2 | // RZTapActionHelper.h 3 | // RZColorfulExample 4 | // 5 | // Created by xk_mac_mini on 2019/9/29. 6 | // Copyright © 2019 rztime. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface RZTapActionHelper : NSObject 15 | 16 | @property (nonatomic, weak) UITextView *textView; 17 | 18 | @end 19 | 20 | NS_ASSUME_NONNULL_END 21 | -------------------------------------------------------------------------------- /RZColorful/Classes/AttributeCore/NSString+RZCode.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+RZCode.h 3 | // RZColorfulExample 4 | // 5 | // Created by xk_mac_mini on 2019/11/20. 6 | // Copyright © 2019 rztime. All rights reserved. 7 | // 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface NSString (RZCode) 13 | // 对string进行URL encode转码 14 | - (NSString *)rz_encodedString; 15 | // 对string进行URL decode解码 16 | - (NSString *)rz_decodedString; 17 | @end 18 | 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /RZColorful/Classes/UICore/UIButton+RZColorful.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIButton+RZColorful.m 3 | // RZColorful 4 | // 5 | // Created by rztime on 2022/1/25. 6 | // 7 | 8 | #import "UIButton+RZColorful.h" 9 | 10 | @implementation UIButton (RZColorful) 11 | 12 | - (void)rz_colorfulConfer:(void (^)(RZColorfulConferrer * _Nonnull))attribute state:(UIControlState)state { 13 | NSAttributedString *string = [NSAttributedString rz_colorfulConfer:attribute]; 14 | [self setAttributedTitle:string forState:state]; 15 | } 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /RZColorful/Classes/UICore/LabelFoldHelper.h: -------------------------------------------------------------------------------- 1 | // 2 | // LabelFoldHelper.h 3 | // RZColorful 4 | // 5 | // Created by rztime on 2022/1/25. 6 | // 7 | 8 | #import 9 | #import "UILabel+RZColorful.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface UILabel(DrawText) 14 | 15 | @property (nonatomic, assign) CGRect rz_drawTextRect; 16 | + (void)rz_swizzedSelected; 17 | @end 18 | 19 | @interface LabelFoldHelper : UIView 20 | 21 | - (instancetype)initWithtarget:(UILabel *)target tapAction:(RZLabelTapAction)tapAction; 22 | 23 | @end 24 | 25 | NS_ASSUME_NONNULL_END 26 | -------------------------------------------------------------------------------- /RZColorful/Classes/UICore/UIButton+RZColorful.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIButton+RZColorful.h 3 | // RZColorful 4 | // 5 | // Created by rztime on 2022/1/25. 6 | // 7 | 8 | #import 9 | #import "RZColorfulConferrer.h" 10 | #import "NSAttributedString+RZColorful.h" 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface UIButton (RZColorful) 14 | 15 | /** 16 | 仅UILabel、UITextField、UITextView 使用有效 设置富文本,原内容将清除 17 | 18 | @param attribute block description 19 | */ 20 | - (void)rz_colorfulConfer:(void(^)( RZColorfulConferrer * _Nonnull confer))attribute state:(UIControlState)state; 21 | 22 | @end 23 | 24 | NS_ASSUME_NONNULL_END 25 | -------------------------------------------------------------------------------- /RZColorful/Classes/UICore/MarkdownRZ.h: -------------------------------------------------------------------------------- 1 | // 2 | // MarkdownRZ.h 3 | // RZColorful 4 | // 5 | // Created by rztime on 2025/3/14. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface MarkdownRZ : NSObject 13 | 14 | /// 请提前调用此方法, 加载markedjs,用于解析markdown 15 | /// - Parameter retry: 如果加载js出错,重试次数(如果加载出错,会有一个默认版本的js来处理) 16 | + (void)asyncInit:(NSInteger)retry; 17 | /// 解析md文档 18 | /// 有一些md文档正常解析中文会乱码,所以这里先将文档base64之后解析来解决中文乱码 19 | /// - Parameter md: markdown文档 20 | /// - Returns: 返回对应的html(解析md后的html,没有任何的样式,需要自行组装head里style:颜色、字号、列表、代码等等) 21 | + (NSString *)parse: (NSString *)md; 22 | @end 23 | 24 | NS_ASSUME_NONNULL_END 25 | -------------------------------------------------------------------------------- /RZColorful/Classes/RZColorful.h: -------------------------------------------------------------------------------- 1 | // 2 | // RZColorful.h 3 | // 4 | // Created by rztime 5 | // Copyright © 2017年 rztime. All rights reserved. 6 | // 使用方法请参照 RZColorfulConferrer 中的属性 7 | 8 | #import "UITextView+RZColorful.h" 9 | #import "UITextField+RZColorful.h" 10 | #import "UILabel+RZColorful.h" 11 | #import "NSAttributedString+RZColorful.h" 12 | #import "UITextField+SelectedRange.h" 13 | #import "UIView+RZContinueFirstResponder.h" 14 | #import "NSAttributedString+RZHtml.h" 15 | #import "UIButton+RZColorful.h" 16 | #import "NSString+RZCode.h" 17 | #import "RZColorfulAttribute.h" 18 | #import "RZColorfulConferrer.h" 19 | #import "RZImageAttachment.h" 20 | #import "RZParagraphStyle.h" 21 | #import "RZShadow.h" 22 | #import "MarkdownRZ.h" 23 | -------------------------------------------------------------------------------- /Example/RZColorful/Demo/RZFoldLabelVC.h: -------------------------------------------------------------------------------- 1 | // 2 | // RZFoldLabelVC.h 3 | // RZColorful_Example 4 | // 5 | // Created by rztime on 2022/1/26. 6 | // Copyright © 2022 rztime. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface FoldModel : NSObject 14 | @property (nonatomic, strong) NSAttributedString *text; 15 | @property (nonatomic, strong) NSAttributedString *showAll; 16 | @property (nonatomic, strong) NSAttributedString *showFold; 17 | @property (nonatomic, assign) BOOL isFold; 18 | @end 19 | 20 | @interface RZFoldLabelVC : UIViewController 21 | 22 | @end 23 | 24 | @interface RZFoldTestCell : UITableViewCell 25 | 26 | @property (nonatomic, strong) UILabel *titleLabel; 27 | 28 | @end 29 | 30 | NS_ASSUME_NONNULL_END 31 | -------------------------------------------------------------------------------- /Example/Tests/Tests.m: -------------------------------------------------------------------------------- 1 | // 2 | // RZColorfulTests.m 3 | // RZColorfulTests 4 | // 5 | // Created by rztime on 01/14/2022. 6 | // Copyright (c) 2022 rztime. All rights reserved. 7 | // 8 | 9 | @import XCTest; 10 | 11 | @interface Tests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation Tests 16 | 17 | - (void)setUp 18 | { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown 24 | { 25 | // Put teardown code here. This method is called after the invocation of each test method in the class. 26 | [super tearDown]; 27 | } 28 | 29 | - (void)testExample 30 | { 31 | XCTFail(@"No implementation for \"%s\"", __PRETTY_FUNCTION__); 32 | } 33 | 34 | @end 35 | 36 | -------------------------------------------------------------------------------- /Example/Tests/Tests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022 rztime 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /RZColorful/Classes/AttributeCore/RZShadow.h: -------------------------------------------------------------------------------- 1 | // 2 | // RZShadow.h 3 | // RZColorfulExample 4 | // 5 | // Created by 乄若醉灬 on 2017/7/28. 6 | // Copyright © 2017年 rztime. All rights reserved. 7 | // 8 | 9 | // 阴影的设置集合 10 | 11 | #import 12 | #import 13 | @class RZColorfulAttribute; 14 | @class RZImageAttachment; 15 | 16 | @interface RZShadow : NSObject 17 | 18 | - (instancetype)initWithAttr:(RZColorfulAttribute *)attr; 19 | - (instancetype)initWithAttach:(RZImageAttachment *)attach; 20 | 21 | - (NSShadow *)code; 22 | 23 | /** 连接词 text、htmlText可使用*/ 24 | - (RZColorfulAttribute *)and; 25 | /** 连接词 text、htmlText可使用 */ 26 | - (RZColorfulAttribute *)with; 27 | /** 连接词 text、htmlText可使用 */ 28 | - (RZColorfulAttribute *)end; 29 | 30 | /** 连接词 image、imageByUrl可使用*/ 31 | - (RZImageAttachment *)andAttach; 32 | /** 连接词 image、imageByUrl可使用*/ 33 | - (RZImageAttachment *)withAttach; 34 | /** 连接词 image、imageByUrl可使用*/ 35 | - (RZImageAttachment *)endAttach; 36 | 37 | /** 阴影偏移量 */ 38 | - (RZShadow *(^)(CGSize offset))offset; 39 | /** // blur radius of the shadow in default user space units // 值越大,越模糊 */ 40 | - (RZShadow *(^)(CGFloat radius))radius; 41 | /** 阴影颜色 */ 42 | - (RZShadow *(^)(UIColor *color))color; 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /RZColorful/Classes/UICore/UITextField+SelectedRange.m: -------------------------------------------------------------------------------- 1 | // 2 | // UITextField+SelectedRange.m 3 | // RZColorfulExample 4 | // 5 | // Created by rztime on 2017/11/16. 6 | // Copyright © 2017年 rztime. All rights reserved. 7 | // 8 | 9 | #import "UITextField+SelectedRange.h" 10 | 11 | @implementation UITextField (SelectedRange) 12 | 13 | -(NSRange)selectedRange { 14 | UITextPosition *beginning = self.beginningOfDocument; 15 | UITextRange *selectedRange = self.selectedTextRange; 16 | UITextPosition *selectionStart = selectedRange.start; 17 | UITextPosition *selectionEnd = selectedRange.end; 18 | 19 | const NSInteger location = [self offsetFromPosition:beginning toPosition:selectionStart]; 20 | const NSInteger length = [self offsetFromPosition:selectionStart toPosition:selectionEnd]; 21 | return NSMakeRange(location, length); 22 | } 23 | 24 | -(void)setSelectedRange:(NSRange)range { 25 | UITextPosition *beginning = self.beginningOfDocument; 26 | UITextPosition *startPosition = [self positionFromPosition:beginning offset:range.location]; 27 | UITextPosition *endPosition = [self positionFromPosition:beginning offset:range.location + range.length]; 28 | UITextRange *selectionRange = [self textRangeFromPosition:startPosition toPosition:endPosition]; 29 | [self setSelectedTextRange:selectionRange]; 30 | } 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /RZColorful/Classes/UICore/UITextField+RZColorful.h: -------------------------------------------------------------------------------- 1 | // 2 | // UITextField+RZColorful.h 3 | // RZColorfulExample 4 | // 5 | // Created by 若醉 on 2018/7/6. 6 | // Copyright © 2018年 rztime. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "RZColorfulConferrer.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface UITextField (RZColorful) 15 | 16 | /** 17 | 仅UILabel、UITextField、UITextView 使用有效 设置富文本,原内容将清除 18 | 19 | @param attribute block description 20 | */ 21 | - (void )rz_colorfulConfer:(void(^)( RZColorfulConferrer * _Nonnull confer))attribute; 22 | 23 | /** 24 | 仅UILabel、UITextField、UITextView 使用有效 追加新的富文本,原内容仍在 25 | 26 | @param attribute block description 27 | */ 28 | - (void )rz_colorfulConferAppend:(void (^)(RZColorfulConferrer * _Nonnull confer))attribute RZWARNING("请使用rz_colorfulConferInsetTo: append:方法"); 29 | 30 | /** 31 | 仅UILabel、UITextField、UITextView 使用有效 插入文本到头、尾、光标位置 32 | 33 | @param position 插入的位置 34 | @param attribute 新的内容 35 | */ 36 | - (void )rz_colorfulConferInsetTo:(rzConferInsertPosition)position append:(void (^)(RZColorfulConferrer * _Nonnull confer))attribute; 37 | 38 | 39 | /** 40 | 仅UILabel、UITextField、UITextView 使用有效 添加到指定位置 41 | 42 | @param location 位置 43 | @param attribute attribute 44 | */ 45 | - (void )rz_colorfulConferInsetToLocation:(NSUInteger)location append:(void (^)(RZColorfulConferrer * _Nonnull confer))attribute; 46 | 47 | @end 48 | NS_ASSUME_NONNULL_END 49 | -------------------------------------------------------------------------------- /RZColorful/Classes/AttributeCore/NSString+RZCode.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+RZCode.m 3 | // RZColorfulExample 4 | // 5 | // Created by xk_mac_mini on 2019/11/20. 6 | // Copyright © 2019 rztime. All rights reserved. 7 | // 8 | 9 | #import "NSString+RZCode.h" 10 | 11 | @implementation NSString (RZCode) 12 | 13 | - (NSString *)rz_encodedString { 14 | NSString *encodedString = (NSString *) 15 | CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, 16 | (CFStringRef)self, 17 | (CFStringRef)@"!$&'()*+,-./:;=?@_~%#[]", 18 | NULL, 19 | kCFStringEncodingUTF8)); 20 | return encodedString; 21 | } 22 | -(NSString *)rz_decodedString { 23 | NSString *encodedString = self; 24 | NSString *decodedString = (__bridge_transfer NSString *)CFURLCreateStringByReplacingPercentEscapesUsingEncoding(NULL, 25 | (__bridge CFStringRef)encodedString, 26 | CFSTR(""), CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding)); 27 | return decodedString; 28 | } 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /RZColorful/Classes/UICore/UIView+RZContinueFirstResponder.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+RZContinueFirstResponder.h 3 | // TextInputViewFocusDemo 4 | // 5 | // Created by rztime on 2017/11/10. 6 | // Copyright © 2017年 rztime. All rights reserved. 7 | // 8 | 9 | 10 | /****************** 11 | 在如tableview中加入了大量的输入框时,可以在[rzContinueFirstResponderAndExecuteCode]方法中写入刷新代码,那么刷新之后输入框焦点不会丢失,且设置UIViewResponderHelper时,可以指定刷新后焦点指定到特定的输入框 12 | *******************/ 13 | 14 | #import 15 | 16 | @interface UIViewResponderHelper : NSObject 17 | 18 | /** 19 | 指定下一个响应的tagIdentity 20 | */ 21 | @property (nonatomic, copy) NSString *nextFirstResponderTagIdentity; 22 | 23 | 24 | /** 25 | 指定下一个响应的index 如果设置了nextFirstResponderTagIdentity 则优先设置到nextFirstResponderTagIdentity 26 | */ 27 | @property (nonatomic, assign) NSInteger nextFirstResponderIndex; 28 | 29 | @end 30 | 31 | #pragma mark - 让view中的第一响应者在刷新界面过后依然保持第一响应 32 | @interface UIView (RZContinueFirstResponder) 33 | 34 | /** 35 | 扩展的身份标志,唯一 36 | 如果需要精准的在刷新界面之后,还原到之前的第一响应,或者指定下一个响应,则需要设置此id 37 | 如果未设置,则默认按照刷新前在view中的排位index,刷新之后依然按照此index还原第一响应 38 | 如果新增了或者删除了,要精确还原到原第一响应,则需要设置此id,除非是加在当前第一响应者的下边 39 | */ 40 | @property (nonatomic, copy) NSString *tagIdentity; 41 | 42 | 43 | /** 44 | 刷新界面的扩展方法 将刷新界面的code写在这个code的block中, 45 | 46 | 放心使用,没有循环引用的问题 47 | 48 | 如果确定是scrollview 如tableivew或者collectionview,则最好直接使用[tableview rzContinueFirstResponderAndExecuteCode]来刷新,这样获取界面的可响应者时,效率更高 49 | @param code nextResponder指定下一个需要响应的方法 50 | */ 51 | - (void)rzContinueFirstResponderAndExecuteCode:(void (^)(UIViewResponderHelper *nextResponder))code; 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /RZColorful/Classes/AttributeCore/RZColorfulConferrer.h: -------------------------------------------------------------------------------- 1 | // 2 | // RZAttributedMaker.h 3 | // RZAttributedStringText 4 | // 5 | // Created by 乄若醉灬 on 2017/7/21. 6 | // Copyright © 2017年 rztime. All rights reserved. 7 | // 8 | // 富文本的控制集合 9 | 10 | #import 11 | #import "RZImageAttachment.h" 12 | #import "RZColorfulAttribute.h" 13 | 14 | typedef NS_ENUM(NSInteger, rzConferInsertPosition) { 15 | rzConferInsertPositionDefault = 0, // 默认位置 默认加到光标处 16 | rzConferInsertPositionHeader = 1, // 加到头部 17 | rzConferInsertPositionEnd = 2, // 加到尾部 18 | 19 | rzConferInsertPositionCursor = 4, // 加到光标处 20 | }; 21 | 22 | #define RZWARNING(instead) NS_DEPRECATED(2_0, 2_0, 2_0, 2_0, instead) 23 | 24 | NS_ASSUME_NONNULL_BEGIN 25 | 26 | @interface RZColorfulConferrer : NSObject 27 | 28 | - (NSAttributedString *)confer RZWARNING("请勿使用及修改此方法, 具体设置请参照RZColorfulConferrer的其他属性"); 29 | 30 | /** 31 | 普通文本内容 要设置其属性,使用"."语法直接连接属性,属性参考RZColorfulAttribute.h 32 | 如: confer.text(@"内容").textColor([UIColor redColor]).font(FONT(16)); 即设置 “内容”显示为红色,16号字体 33 | text.之后的属性,仅对当前text()内容有效 34 | */ 35 | - (RZColorfulAttribute *(^)(NSString * _Nullable))text; 36 | /** 添加html格式的内容(网页源码),会通过将源码转换成普通文本内容 */ 37 | - (RZColorfulAttribute *(^)(NSString * _Nullable))htmlText; 38 | /** 添加图片,为使图片与前后排文字对齐,可在后设置size */ 39 | - (RZImageAttachment *(^)(UIImage * _Nullable))image; 40 | /** 通过url添加图片 */ 41 | - (RZImageAttachment *(^)(NSString * _Nullable))imageByUrl; 42 | /** 设置当前控件对象统一段落样式, 这里设置段落之后,请勿使用and等连接词 ,在text中设置了段落样式,则text的段落样式优先级高于统一段落样式 */ 43 | - (RZParagraphStyle * _Nullable)paragraphStyle; 44 | /** 设置统一阴影对象 , 请勿使用and等连接词,在text中设置了阴影样式,则text的阴影样式优先级高于统一阴影样式 */ 45 | - (RZShadow * _Nullable)shadow; 46 | @end 47 | NS_ASSUME_NONNULL_END 48 | -------------------------------------------------------------------------------- /Example/RZColorful/RZColorful-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /RZColorful.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod lib lint RZColorful.podspec' to ensure this is a 3 | # valid spec before submitting. 4 | # 5 | # Any lines starting with a # are optional, but their use is encouraged 6 | # To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | s.name = 'RZColorful' 11 | s.version = '1.7.0' 12 | s.summary = 'NSAttributedString富文本的快捷设置方法集合,以及UITextView、UITextField、UILabel富文本简单优雅的使用, UILabel支持富文本点击,超行折叠、展开功能' 13 | 14 | # This description is used to generate tags and improve search results. 15 | # * Think: What does it do? Why did you write it? What is the focus? 16 | # * Try to keep it short, snappy and to the point. 17 | # * Write the description between the DESC delimiters below. 18 | # * Finally, don't worry about the indent, CocoaPods strips it! 19 | 20 | s.description = <<-DESC 21 | NSAttributedString 富文本方法 以及与html互相转换, UILabel的富文本点击事件 22 | DESC 23 | 24 | s.homepage = 'https://github.com/rztime/RZColorful' 25 | # s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2' 26 | s.license = { :type => 'MIT', :file => 'LICENSE' } 27 | s.author = { 'rztime' => 'rztime@vip.qq.com' } 28 | s.source = { :git => 'https://github.com/rztime/RZColorful.git', :tag => s.version.to_s } 29 | # s.social_media_url = 'https://twitter.com/' 30 | 31 | s.ios.deployment_target = '11.0' 32 | 33 | s.source_files = 'RZColorful/Classes/**/*' 34 | 35 | # s.resource_bundles = { 36 | # 'RZColorful' => ['RZColorful/Assets/*.png'] 37 | # } 38 | 39 | # s.public_header_files = 'Pod/Classes/**/*.h' 40 | # s.frameworks = 'UIKit', 'MapKit' 41 | # s.dependency 'AFNetworking', '~> 2.3' 42 | end 43 | -------------------------------------------------------------------------------- /RZColorful/Classes/UICore/UITextView+RZColorful.h: -------------------------------------------------------------------------------- 1 | // 2 | // UITextView+RZColorful.h 3 | // RZColorfulExample 4 | // 5 | // Created by 若醉 on 2018/7/6. 6 | // Copyright © 2018年 rztime. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "RZColorfulConferrer.h" 11 | #import "NSString+RZCode.h" 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | @interface UITextView (RZColorful) 16 | 17 | /** 18 | 仅UILabel、UITextField、UITextView 使用有效 设置富文本,原内容将清除 19 | 20 | @param attribute block description 21 | */ 22 | - (void )rz_colorfulConfer:(void(^)( RZColorfulConferrer * _Nonnull confer))attribute; 23 | 24 | /** 25 | 仅UILabel、UITextField、UITextView 使用有效 追加新的富文本,原内容仍在 26 | 27 | @param attribute block description 28 | */ 29 | - (void )rz_colorfulConferAppend:(void (^)(RZColorfulConferrer * _Nonnull confer))attribute RZWARNING("请使用rz_colorfulConferInsetTo: append:方法"); 30 | 31 | /** 32 | 仅UILabel、UITextField、UITextView 使用有效 插入文本到头、尾、光标位置 33 | 34 | @param position 插入的位置 35 | @param attribute 新的内容 36 | */ 37 | - (void )rz_colorfulConferInsetTo:(rzConferInsertPosition)position append:(void (^)(RZColorfulConferrer * _Nonnull confer))attribute; 38 | 39 | 40 | /** 41 | 仅UILabel、UITextField、UITextView 使用有效 添加到指定位置 42 | 43 | @param location 位置 44 | @param attribute attribute 45 | */ 46 | - (void )rz_colorfulConferInsetToLocation:(NSUInteger)location append:(void (^)(RZColorfulConferrer * _Nonnull confer))attribute; 47 | 48 | /** 49 | 点击textView里的可以点击的字符串, 50 | tapObj:为tapId或者NSURL 51 | return: NO: 不跳转到浏览器, YES:跳转到浏览器 52 | 如果tapAction()或url()中包含了有中文,URL将会进行编码,所以请将tapObj,rz_decodedString解码 53 | */ 54 | @property (nonatomic, copy) BOOL(^rzDidTapTextView)(id __nullable tapObj); 55 | 56 | /// 获取range所在文本的位置 57 | - (CGRect)rz_rectFor:(NSRange)range; 58 | 59 | /// 获取range所在文本的位置,可能涉及多行 NSValue: CGRect 60 | - (NSArray *)rz_rectFors:(NSRange)range; 61 | 62 | @end 63 | NS_ASSUME_NONNULL_END 64 | -------------------------------------------------------------------------------- /Example/RZColorful/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Example/RZColorful/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /RZColorful/Classes/AttributeCore/RZShadow.m: -------------------------------------------------------------------------------- 1 | // 2 | // RZShadow.m 3 | // RZColorfulExample 4 | // 5 | // Created by 乄若醉灬 on 2017/7/28. 6 | // Copyright © 2017年 rztime. All rights reserved. 7 | // 8 | 9 | #import "RZShadow.h" 10 | #import "RZColorfulAttribute.h" 11 | 12 | @interface RZShadow () 13 | 14 | @property (nonatomic, strong) NSShadow *shadow; 15 | @property (nonatomic, weak) RZColorfulAttribute *colorfulsAttr; 16 | @property (nonatomic, weak) RZImageAttachment *imageAttach; 17 | 18 | @end 19 | 20 | @implementation RZShadow 21 | 22 | - (instancetype)initWithAttr:(RZColorfulAttribute *)attr { 23 | if (self = [super init]) { 24 | self.colorfulsAttr = attr; 25 | } 26 | return self; 27 | } 28 | 29 | - (instancetype)initWithAttach:(RZImageAttachment *)attach { 30 | if (self = [super init]) { 31 | self.imageAttach = attach; 32 | } 33 | return self; 34 | } 35 | 36 | - (RZColorfulAttribute *)and { 37 | return _colorfulsAttr; 38 | } 39 | - (RZColorfulAttribute *)with { 40 | return _colorfulsAttr; 41 | } 42 | - (RZColorfulAttribute *)end { 43 | return _colorfulsAttr; 44 | } 45 | 46 | - (RZImageAttachment *)andAttach { 47 | return _imageAttach; 48 | } 49 | - (RZImageAttachment *)withAttach { 50 | return _imageAttach; 51 | } 52 | - (RZImageAttachment *)endAttach { 53 | return _imageAttach; 54 | } 55 | 56 | - (NSShadow *)code { 57 | return _shadow; 58 | } 59 | 60 | - (NSShadow *)shadow { 61 | if (!_shadow) { 62 | _shadow = [[NSShadow alloc] init]; 63 | } 64 | return _shadow; 65 | } 66 | 67 | - (RZShadow *(^)(CGSize))offset { 68 | return ^id (CGSize offset) { 69 | self.shadow.shadowOffset = offset; 70 | return self; 71 | }; 72 | } 73 | 74 | - (RZShadow *(^)(CGFloat))radius { 75 | return ^id (CGFloat radius) { 76 | self.shadow.shadowBlurRadius = radius; 77 | return self; 78 | }; 79 | } 80 | 81 | - (RZShadow *(^)(UIColor *))color { 82 | return ^id (UIColor *color) { 83 | self.shadow.shadowColor = color; 84 | return self; 85 | }; 86 | } 87 | @end 88 | -------------------------------------------------------------------------------- /Example/RZColorful/Demo/RZTestViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // RZTestViewController.m 3 | // RZColorful_Example 4 | // 5 | // Created by rztime on 2025/2/12. 6 | // Copyright © 2025 rztime. All rights reserved. 7 | // 8 | 9 | #import "RZTestViewController.h" 10 | #import "Masonry.h" 11 | #import "RZColorful.h" 12 | @interface RZTestViewController () 13 | @property(nonatomic, strong) UILabel * label; 14 | @end 15 | 16 | @implementation RZTestViewController 17 | 18 | - (void)viewDidLoad { 19 | [super viewDidLoad]; 20 | self.view.backgroundColor = [UIColor whiteColor]; 21 | self.label = [[UILabel alloc] init]; 22 | self.label.numberOfLines = 0; 23 | [self.view addSubview:self.label]; 24 | [self.label mas_makeConstraints:^(MASConstraintMaker *make) { 25 | make.left.right.equalTo(self.view).inset(20); 26 | make.top.equalTo(self.view).inset(100); 27 | }]; 28 | NSString * text = @"壹贰叁\n肆伍陆柒捌玖拾壹拾壹壹拾贰壹拾叁壹拾肆壹拾伍壹拾陆壹拾柒壹拾捌壹拾玖1\n贰拾贰拾壹贰拾贰贰拾叁贰拾肆贰拾伍贰拾陆贰拾柒贰拾捌贰拾玖\n叁拾叁拾壹叁拾贰叁拾叁叁拾肆叁拾伍叁拾陆叁拾柒叁拾捌叁拾玖\n肆拾肆拾壹肆拾贰肆拾叁肆拾肆肆拾伍肆拾陆肆拾柒肆拾捌肆拾玖伍拾。"; 29 | NSAttributedString * place = [NSAttributedString rz_colorfulConfer:^(RZColorfulConferrer * _Nullable confer) { 30 | confer.text(@"...").font([UIFont systemFontOfSize:30]).textColor([UIColor redColor]).tapActionByLable(@"1"); 31 | }]; 32 | NSAttributedString * test = [NSAttributedString rz_colorfulConfer:^(RZColorfulConferrer * _Nullable confer) { 33 | confer.text(text).font([UIFont systemFontOfSize:30]).textColor([UIColor blackColor]).tapActionByLable(@"1"); 34 | }]; 35 | _label.attributedText = [test rz_attributedStringBy:2 maxWidth:self.view.frame.size.width - 40 lineBreakMode:NSLineBreakByClipping placeHolder:place]; 36 | } 37 | 38 | /* 39 | #pragma mark - Navigation 40 | 41 | // In a storyboard-based application, you will often want to do a little preparation before navigation 42 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 43 | // Get the new view controller using [segue destinationViewController]. 44 | // Pass the selected object to the new view controller. 45 | } 46 | */ 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /Example/RZColorful/RZAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // RZAppDelegate.m 3 | // RZColorful 4 | // 5 | // Created by rztime on 01/14/2022. 6 | // Copyright (c) 2022 rztime. All rights reserved. 7 | // 8 | 9 | #import "RZAppDelegate.h" 10 | #import "RZColorful.h" 11 | @implementation RZAppDelegate 12 | 13 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 14 | { 15 | // Override point for customization after application launch. 16 | [MarkdownRZ asyncInit:3]; 17 | return YES; 18 | } 19 | 20 | - (void)applicationWillResignActive:(UIApplication *)application 21 | { 22 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 23 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 24 | } 25 | 26 | - (void)applicationDidEnterBackground:(UIApplication *)application 27 | { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | - (void)applicationWillEnterForeground:(UIApplication *)application 33 | { 34 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 35 | } 36 | 37 | - (void)applicationDidBecomeActive:(UIApplication *)application 38 | { 39 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 40 | } 41 | 42 | - (void)applicationWillTerminate:(UIApplication *)application 43 | { 44 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 45 | } 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /Example/RZColorful/Demo/RZTextViewVC.m: -------------------------------------------------------------------------------- 1 | // 2 | // RZTextViewVC.m 3 | // RZColorful_Example 4 | // 5 | // Created by rztime on 2022/1/26. 6 | // Copyright © 2022 rztime. All rights reserved. 7 | // 8 | 9 | #import "RZTextViewVC.h" 10 | #import 11 | #import 12 | 13 | @interface RZTextViewVC () 14 | 15 | @property (nonatomic, strong) UITextView *textView; 16 | 17 | @end 18 | 19 | @implementation RZTextViewVC 20 | 21 | - (void)viewDidLoad { 22 | [super viewDidLoad]; 23 | self.view.backgroundColor = UIColor.whiteColor; 24 | 25 | _textView = [[UITextView alloc] init]; 26 | [self.view addSubview:_textView]; 27 | [_textView mas_makeConstraints:^(MASConstraintMaker *make) { 28 | make.top.equalTo(self.view).offset(100); 29 | make.left.equalTo(self.view).offset(20); 30 | make.right.equalTo(self.view).offset((-20)); 31 | make.height.equalTo(@300); 32 | }]; 33 | _textView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.1]; 34 | _textView.linkTextAttributes = @{}; 35 | 36 | [_textView rz_colorfulConfer:^(RZColorfulConferrer * _Nonnull confer) { 37 | confer.text(@"").font([UIFont systemFontOfSize:16]); 38 | 39 | confer.text(@"百年来,我们党团结带领人民进行的一切奋斗、一切牺牲、一切创造").font([UIFont systemFontOfSize:16]).textColor([UIColor blackColor]); 40 | confer.text(@"都是在践行为中国人民谋幸福、为中华民族谋复兴的初心使命\n中国共产党的成就和贡献,不仅是历史性的,也是世界性的").font([UIFont systemFontOfSize:16]).textColor([UIColor redColor]); 41 | confer.text(@"\n\n\n"); 42 | confer.text(@"添加链接").font([UIFont systemFontOfSize:18]).textColor([UIColor blueColor]).tapAction(@"点击事件"); 43 | }]; 44 | 45 | _textView.rzDidTapTextView = ^BOOL(id _Nullable tapObj) { 46 | NSString *obj = [((NSURL *)tapObj).absoluteString rz_decodedString]; 47 | NSLog(@"%@", obj); // http://www.baidu.com 48 | return false; 49 | }; 50 | } 51 | 52 | /* 53 | #pragma mark - Navigation 54 | 55 | // In a storyboard-based application, you will often want to do a little preparation before navigation 56 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 57 | // Get the new view controller using [segue destinationViewController]. 58 | // Pass the selected object to the new view controller. 59 | } 60 | */ 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /RZColorful/Classes/AttributeCore/RZImageAttachment.h: -------------------------------------------------------------------------------- 1 | // 2 | // RZImageAttachment.h 3 | // RZColorfulExample 4 | // 5 | // Created by 乄若醉灬 on 2017/7/28. 6 | // Copyright © 2017年 rztime. All rights reserved. 7 | // 8 | // 仅添加图片的附件 9 | #import 10 | #import 11 | #import "RZParagraphStyle.h" 12 | #import "RZShadow.h" 13 | 14 | #define RZWARNING(instead) NS_DEPRECATED(2_0, 2_0, 2_0, 2_0, instead) 15 | 16 | typedef NS_ENUM(NSInteger, RZImageAttachmentHorizontalAlign) { 17 | RZHorizontalAlignBottom = 0, 18 | RZHorizontalAlignCenter = 1, 19 | RZHorizontalAlignTop = 2, 20 | }; 21 | 22 | @interface RZImageAttachment : NSObject 23 | 24 | @property (nonatomic, assign) CGRect imageBounds RZWARNING("该属性不可使用,请使用bounds(CGRectMake(...))方法"); 25 | 26 | @property (nonatomic, assign, readonly) BOOL hadParagraphStyle; 27 | @property (nonatomic, assign, readonly) BOOL hadShadow; 28 | 29 | - (NSDictionary *)code; 30 | 31 | /** 32 | 将bounds数据转换成html格式的语句 33 | */ 34 | - (NSString *)toHTMLStringWithImageUrl:(NSString *)imageUrl; 35 | 36 | #pragma mark - 可以设置的属性 37 | /** 段落样式 */ 38 | - (RZParagraphStyle *)paragraphStyle; 39 | /** 阴影 */ 40 | - (RZShadow *)shadow; 41 | /// 需要添加更多的属性,用这个,如underline等 42 | -(RZColorfulAttribute *)more; 43 | /** 44 | 设置图片bounds时,origin.x 设置无效 size.width height 可以设置与前后排文字字体大小一样,且适当调整origin.y为负值,可以让图片文本对齐 45 | 如果是通过url设置bounds时,宽设置为0:则宽按照高度自动等比显示,高设置为0亦如此 46 | */ 47 | - (RZImageAttachment *(^)(CGRect bounds))bounds; 48 | /** 49 | 水平对齐方式 50 | align 上,中,下 51 | refer 对齐的参考系 (前后的字体) 52 | */ 53 | - (RZImageAttachment *(^)(CGSize size, RZImageAttachmentHorizontalAlign align, UIFont *font))size; 54 | 55 | /** 56 | y轴偏移量,在某些情况下,在对齐之后需要做上下偏移时,用此方法,请在设置size之后或者bounds之后使用 57 | > 0 时,向下移动位置 58 | < 0 时,向上移动位置 59 | */ 60 | - (RZImageAttachment *(^)(CGFloat yOffset))yOffset; 61 | 62 | #pragma mark - 下边附加属性也可以通过.more去调用, 63 | /** 自定义属性和值 */ 64 | - (RZColorfulAttribute * (^)(NSAttributedStringKey key, id value))custom; 65 | /** 给图片添加链接,要实现点击,需实现UITextView的delegate的url点击事件 */ 66 | - (RZImageAttachment *(^)(NSURL *url))url; 67 | /* 给属性文本添加点击事件 只有UITextView可以用,且UITextView需要实现block didTapTextView */ 68 | - (RZImageAttachment * (^)(NSString *tapId))tapAction; 69 | /* 给属性文本添加点击事件 只有UILabel可以用,且UILabel需要实现方法 rz_tapAction */ 70 | - (RZImageAttachment * (^)(NSString *tapId))tapActionByLable; 71 | 72 | @end 73 | -------------------------------------------------------------------------------- /RZColorful/Classes/UICore/UILabel+RZColorful.h: -------------------------------------------------------------------------------- 1 | // 2 | // UILabel+RZColorful.h 3 | // RZColorfulExample 4 | // 5 | // Created by 若醉 on 2018/7/6. 6 | // Copyright © 2018年 rztime. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "RZColorfulConferrer.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface UILabel (RZColorful) 15 | 16 | /** 17 | 仅UILabel、UITextField、UITextView 使用有效 设置富文本,原内容将清除 18 | 19 | @param attribute block description 20 | */ 21 | - (void )rz_colorfulConfer:(void(^)( RZColorfulConferrer * _Nonnull confer))attribute; 22 | 23 | /** 24 | 仅UILabel、UITextField、UITextView 使用有效 追加新的富文本,原内容仍在 25 | 26 | @param attribute block description 27 | */ 28 | - (void )rz_colorfulConferAppend:(void (^)(RZColorfulConferrer * _Nonnull confer))attribute RZWARNING("请使用rz_colorfulConferInsetTo: append:方法"); 29 | 30 | /** 31 | 仅UILabel、UITextField、UITextView 使用有效 插入文本到头、尾、光标位置 32 | 33 | @param position 插入的位置 34 | @param attribute 新的内容 35 | */ 36 | - (void )rz_colorfulConferInsetTo:(rzConferInsertPosition)position append:(void (^)(RZColorfulConferrer * _Nonnull confer))attribute; 37 | 38 | /** 39 | 仅UILabel、UITextField、UITextView 使用有效 添加到指定位置 40 | 41 | @param location 位置 42 | @param attribute attribute 43 | */ 44 | - (void )rz_colorfulConferInsetToLocation:(NSUInteger)location append:(void (^)(RZColorfulConferrer * _Nonnull confer))attribute; 45 | 46 | /// 设置富文本(超过行数后,自动追加“展开” “收起”) 47 | /// @param attr 原文 48 | /// @param line 最大显示行数 49 | /// @param width 最大显示宽度,这个宽度用于计算文本行 50 | /// @param fold 当前是否折叠 51 | /// @param allText 超过了行数之后,折叠状态显示的文本 如”展开“ 需要给文本设置NSTapActionByLabel属性 (tapActionByLable) 52 | /// @param foldText 超过行数之后,全部展开状态显示的文本 如”收起“ 需要给文本设置NSTapActionByLabel属性 (tapActionByLable) 53 | - (void)rz_setAttributedString:(NSAttributedString * _Nullable)attr maxLine:(NSInteger)line maxWidth:(CGFloat)width isFold:(BOOL)fold showAllText:(NSAttributedString *_Nullable)allText showFoldText:(NSAttributedString *_Nullable)foldText; 54 | 55 | typedef void(^RZLabelTapAction)(UILabel *_Nonnull label, NSString * _Nonnull tapActionId, NSRange range); 56 | /// 给label的富文本添加文本点击事件 57 | /// 注意:NSAttributedString里的每一个字,都需要设置字体,否则无法准确点击位置(内部使用UITextView计算位置,无字体时,默认给的11号字体,会导致位置计算错误) 58 | /// @param tapAction ,tapActionId 即tapActionByLable里的内容 59 | - (void)rz_tapAction:(RZLabelTapAction _Nullable)tapAction; 60 | @end 61 | NS_ASSUME_NONNULL_END 62 | -------------------------------------------------------------------------------- /UpdateLog.md: -------------------------------------------------------------------------------- 1 | ##### 1.7.0 2 | 3 | 新增了markdown文档转换为html的方法 4 | ``` 5 | // 1.初始化(可以提前初始化,用于下载最新的js解析方法) 6 | [MarkdownRZ asyncInit:3]; 7 | // 2.解析 解析md后的html,没有任何的样式,需要自行组装head里style:颜色、字号、列表、代码等等) 8 | NSString *html = [MarkdownRZ parse:md]; 9 | ``` 10 | 11 | ##### 1.6.0 12 | 13 | 新增完善iOS14-18的富文本属性 14 | 优化富文本截断方法的内部实现 15 | 图片使用.more,可以添加文本一样的属性 16 | 17 | ##### 1.5.0 18 | 19 | 新增对富文本进行阶段 20 | ``` 21 | NSAttributedString *newAttr = [attr rz_attributedStringBy:3 maxWidth:300 lineBreakMode:NSLineBreakByTruncatingTail placeHolder:[[NSAttributedString alloc] initWithString:@"..."]]; 22 | ``` 23 | 24 | 新增给paragraphStyle里添加行数限制 如: 25 | ``` 26 | /// 只对2中的文本进行行数计算,如果前后有1、3,将不适用 27 | confer.text("1"). 28 | confer.text("2")?.paragraphStyle?.numberOfLines(3, maxWidth: width).lineBreakMode(.byTruncatingMiddle) 29 | confer.text("3"). 30 | ``` 31 | 32 | 新增NSAttributedString里关键字标记 33 | 34 | ##### 1.4.0 35 | * 新增iOS15的方法 36 | * 给UILabel添加超行显示 “折叠” “收起” 功能 37 | * 给UILabel添加富文本可点击事件 38 | 39 | ##### 1.3.1 40 | * 将 RZLineStyle 替换为原系统 NSUnderlineStyle ,设置时,可设置单线双线虚线破折号线等等 41 | * NSAttributedString系统转换为html的方法,有部分标签会无效,所以新增方法,将无效的标签,通过代码注入style,将无效的标签通过css实现了。 看[- (NSString *)rz_codingToCompleteHtmlByWeb](https://github.com/rztime/RZColorful/blob/master/RZColorfulExample/RZColorful/AttributeCore/NSAttributedString+RZHtml.h) 42 | 43 | 44 | ##### 1.3.0 45 | * 新增富文本在drawRect之后,获取每一行的文本和Range 46 | * 一些其他优化 47 | 48 | 49 | ##### 1.2.5更新日志 50 | * 1.修复在html转换成富文本时,当NSLinkAttributeName对应的URL未包含http(https)时,iOS系统自动为其添加的“applewebdata://BF307C6C-5A2C-4F76-B3A0-6FD67E66CF82/”,导致所对应的url或tapId错误的bug 51 | * 2.在UITextView的rzDidTapTextView回调中新增返回值,以控制其是否跳转浏览器 52 | 53 | 54 | 55 | ##### 1.2.4更新日志 56 | 57 | * 1.新增图片的size设置方式 并可参照前后文本字体进行对齐,并在某些情况下做y轴偏移 (对齐中文效果较好,英文会有偏差) 58 | ``` 59 | confer.appendImage([UIImage imageNamed:@"image"]).size(CGSizeMake(32, 32), RZHorizontalAlignBottom, rzFont(16)).yOffset(10); 60 | ``` 61 | 62 | 63 | * 2.新增图片、文本、html的点击事件 .tapAction(@"1") 只对UITextView有效 64 | ``` 65 | confer.appendImage([UIImage imageNamed:@"image"]).size(CGSizeMake(32, 32), RZHorizontalAlignBottom, rzFont(16)).tapAction(@"22222222"); 66 | 67 | confer.text(@"点击事件1").font(rzFont(30)).tapAction(@"1").textColor(UIColor.orangeColor); 68 | 69 | confer.htmlText(htmlstring); // 此自动实现tapAction方法 70 | ``` 71 | * 需要实现UITextView的rzDidTapTextView方法 72 | ``` 73 | textView.rzDidTapTextView = ^(id _Nullable tapObj) { 74 | //tapObj :可以是tapaction里的 tapId, 也可能是htmlstring里的NSURL 75 | NSLog(@"点击了:%@", tapObj); 76 | }; 77 | ``` 78 | 79 | ``` 80 | // 点击事件默认文本会有颜色,去掉可用: 81 | textView.linkTextAttributes = @{}; 82 | ``` 83 | -------------------------------------------------------------------------------- /Example/RZColorful/RZViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // RZViewController.m 3 | // RZColorful 4 | // 5 | // Created by rztime on 01/14/2022. 6 | // Copyright (c) 2022 rztime. All rights reserved. 7 | // 8 | 9 | #import "RZViewController.h" 10 | #import 11 | #import 12 | 13 | @interface RZViewController () 14 | @property (nonatomic) UITableView *tableView; 15 | @property (nonatomic, strong) NSArray *items; 16 | @end 17 | 18 | @implementation RZViewController 19 | 20 | - (void)viewDidLoad 21 | { 22 | [super viewDidLoad]; 23 | // Do any additional setup after loading the view, typically from a nib. 24 | _tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; 25 | _tableView.delegate = self; 26 | _tableView.dataSource = self; 27 | [self.view addSubview:_tableView]; 28 | [_tableView mas_makeConstraints:^(MASConstraintMaker *make) { 29 | make.edges.equalTo(self.view); 30 | }]; 31 | _items = @[@{@"富文本常规使用": @"AttributedStringUseDemoVC"}, 32 | @{@"显示折叠展开的label": @"RZFoldLabelVC"}, 33 | @{@"UITextView常规使用": @"RZTextViewVC"}, 34 | @{@"测试": @"RZTestViewController"}, 35 | @{@"markdow to html": @"MDToHtmlViewController"}]; 36 | } 37 | 38 | - (void)didReceiveMemoryWarning 39 | { 40 | [super didReceiveMemoryWarning]; 41 | // Dispose of any resources that can be recreated. 42 | } 43 | 44 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 45 | return self.items.count; 46 | } 47 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 48 | NSDictionary *item = self.items[indexPath.row]; 49 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"]; 50 | if (cell == nil) { 51 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"cell"]; 52 | } 53 | cell.textLabel.text = item.allKeys.firstObject; 54 | cell.detailTextLabel.text = item.allValues.firstObject; 55 | return cell; 56 | } 57 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { 58 | return 70; 59 | } 60 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 61 | [tableView deselectRowAtIndexPath:indexPath animated:false]; 62 | NSDictionary *item = self.items[indexPath.row]; 63 | UIViewController *vc = (UIViewController *)[[NSClassFromString(item.allValues.firstObject) alloc] init]; 64 | [self.navigationController pushViewController:vc animated:true]; 65 | } 66 | 67 | @end 68 | -------------------------------------------------------------------------------- /RZColorful/Classes/UICore/UITextField+RZColorful.m: -------------------------------------------------------------------------------- 1 | // 2 | // UITextField+RZColorful.m 3 | // RZColorfulExample 4 | // 5 | // Created by 若醉 on 2018/7/6. 6 | // Copyright © 2018年 rztime. All rights reserved. 7 | // 8 | 9 | #import "UITextField+RZColorful.h" 10 | #import "NSAttributedString+RZColorful.h" 11 | #import "UITextField+SelectedRange.h" 12 | 13 | @implementation UITextField (RZColorful) 14 | 15 | /** 16 | 设置富文本,原内容将清除 仅UILabel、UITextField、UITextView 有效 17 | 18 | @param attribute <#block description#> 19 | */ 20 | - (void )rz_colorfulConfer:(void(^)(RZColorfulConferrer *confer))attribute { 21 | [self rzSetAttributedText:nil]; 22 | [self rz_colorfulConferInsetToLocation:0 append:attribute]; 23 | } 24 | 25 | - (void )rz_colorfulConferAppend:(void (^)(RZColorfulConferrer *confer))attribute { 26 | [self rz_colorfulConferInsetTo:rzConferInsertPositionEnd append:attribute]; 27 | } 28 | 29 | /** 30 | 插入文本 31 | 32 | @param position 插入的位置 33 | @param attribute 新的内容 34 | */ 35 | - (void )rz_colorfulConferInsetTo:(rzConferInsertPosition)position append:(void (^)(RZColorfulConferrer * _Nonnull confer))attribute { 36 | NSUInteger location; 37 | switch (position) { 38 | case rzConferInsertPositionDefault: 39 | case rzConferInsertPositionCursor: { // 默认位置 光标处 40 | location = [self getCursorLocation]; 41 | break; 42 | } 43 | case rzConferInsertPositionHeader: { // 头 44 | location = 0; 45 | break; 46 | } 47 | case rzConferInsertPositionEnd: { // 尾 48 | location = [self getEndLocation]; 49 | break; 50 | } 51 | default: 52 | location = [self getEndLocation]; 53 | break; 54 | } 55 | [self rz_colorfulConferInsetToLocation:location append:attribute]; 56 | } 57 | 58 | /** 59 | 添加到指定位置 60 | 61 | @param location <#location description#> 62 | @param attribute <#attribute description#> 63 | */ 64 | - (void )rz_colorfulConferInsetToLocation:(NSUInteger)location append:(void (^)(RZColorfulConferrer * _Nonnull))attribute { 65 | if(!attribute) { 66 | return ; 67 | } 68 | NSUInteger loc = location; 69 | NSAttributedString *conferrerColorful = [NSAttributedString rz_colorfulConfer:attribute]; 70 | if (conferrerColorful.string.length == 0) { 71 | return ; 72 | } 73 | if (loc > [self getEndLocation]) { 74 | loc = [self getEndLocation]; 75 | } 76 | NSMutableAttributedString *attr = [[NSMutableAttributedString alloc] initWithAttributedString:[self rzAttributedString]]; 77 | [attr insertAttributedString:conferrerColorful atIndex:loc]; 78 | [self rzSetAttributedText:attr]; 79 | } 80 | // 文本框的内容 81 | - (NSAttributedString *)rzAttributedString { 82 | return self.attributedText; 83 | } 84 | // 设置文本框的内容 85 | - (void)rzSetAttributedText:(NSAttributedString *)attributedString { 86 | self.attributedText = attributedString; 87 | } 88 | // 尾部的位置 89 | - (NSUInteger)getEndLocation { 90 | return self.attributedText.string.length; 91 | } 92 | // 光标的位置 93 | - (NSUInteger)getCursorLocation { 94 | return self.selectedRange.location; 95 | } 96 | 97 | @end 98 | -------------------------------------------------------------------------------- /Example/RZColorful/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /RZColorful/Classes/AttributeCore/RZParagraphStyle.h: -------------------------------------------------------------------------------- 1 | // 2 | // RZParagraphStyle.h 3 | // RZColorfulExample 4 | // 5 | // Created by 乄若醉灬 on 2017/7/31. 6 | // Copyright © 2017年 rztime. All rights reserved. 7 | // 8 | 9 | // 段落属性的设置集合 10 | 11 | 12 | #import 13 | #import 14 | @class RZColorfulAttribute; 15 | @class RZImageAttachment; 16 | 17 | #define RZWARNING(instead) NS_DEPRECATED(2_0, 2_0, 2_0, 2_0, instead) 18 | 19 | @interface RZMutableParagraphStyle : NSMutableParagraphStyle 20 | /// 行数 21 | @property (nonatomic, assign) NSUInteger numberOfLines; 22 | /// 计算时,绘制的文本的最大宽度 23 | @property (nonatomic, assign) CGFloat textDrawMaxWidth; 24 | /// 截断时,根据LineBreakMode方式,添加的文本 25 | @property (nonatomic, strong) NSAttributedString * _Nullable truncateText; 26 | 27 | + (RZMutableParagraphStyle *_Nonnull)copyWith:(RZMutableParagraphStyle *_Nonnull)para; 28 | 29 | @end 30 | 31 | @interface RZParagraphStyle : NSObject 32 | 33 | - (instancetype _Nonnull)initWithAttr:(RZColorfulAttribute *_Nonnull)attr; 34 | - (instancetype _Nonnull)initWithAttach:(RZImageAttachment *_Nonnull)attach; 35 | 36 | - (RZMutableParagraphStyle *_Nonnull)code; 37 | 38 | /** 连接词 text、htmlText可使用*/ 39 | - (RZColorfulAttribute *_Nonnull)and; 40 | /** 连接词 text、htmlText可使用 */ 41 | - (RZColorfulAttribute *_Nonnull)with; 42 | /** 连接词 text、htmlText可使用 */ 43 | - (RZColorfulAttribute *_Nonnull)end; 44 | 45 | /** 连接词 image、imageByUrl可使用*/ 46 | - (RZImageAttachment *_Nonnull)andAttach; 47 | /** 连接词 image、imageByUrl可使用*/ 48 | - (RZImageAttachment *_Nonnull)withAttach; 49 | /** 连接词 image、imageByUrl可使用*/ 50 | - (RZImageAttachment *_Nonnull)endAttach; 51 | /** 段落行距 */ 52 | - (RZParagraphStyle *_Nonnull(^_Nonnull)(CGFloat))lineSpacing; 53 | /** 段落后面的间距 */ 54 | - (RZParagraphStyle *_Nonnull(^_Nonnull)(CGFloat))paragraphSpacing; 55 | /** 文本对齐方式 */ 56 | - (RZParagraphStyle *_Nonnull(^_Nonnull)(NSTextAlignment))alignment; 57 | /** 首行文本缩进 */ 58 | - (RZParagraphStyle *_Nonnull(^_Nonnull)(CGFloat))firstLineHeadIndent; 59 | /** 非首行文本缩进 */ 60 | - (RZParagraphStyle *_Nonnull(^_Nonnull)(CGFloat))headIndent; 61 | /** 文本缩进 */ 62 | - (RZParagraphStyle *_Nonnull(^_Nonnull)(CGFloat))tailIndent; 63 | /** 文本折行方式 */ 64 | - (RZParagraphStyle *_Nonnull(^_Nonnull)(NSLineBreakMode))lineBreakMode; 65 | /** 文本最小行距 */ 66 | - (RZParagraphStyle *_Nonnull(^_Nonnull)(CGFloat))minimumLineHeight; 67 | /** 文本最大行距 */ 68 | - (RZParagraphStyle *_Nonnull(^_Nonnull)(CGFloat))maximumLineHeight; 69 | /** 文本写入方式,即显示方式,从左至右,或从右到左 */ 70 | - (RZParagraphStyle *_Nonnull(^_Nonnull)(NSWritingDirection))baseWritingDirection; 71 | /** 设置文本行间距是默认间距的倍数 */ 72 | - (RZParagraphStyle *_Nonnull(^_Nonnull)(CGFloat))lineHeightMultiple; 73 | /** 段与段之间的间距 */ 74 | - (RZParagraphStyle *_Nonnull(^_Nonnull)(CGFloat))paragraphSpacingBefore; 75 | /** 设置每行的最后单词是否截断,在0.0-1.0之间,默认为0.0,越接近1.0单词被截断的可能性越大, */ 76 | - (RZParagraphStyle *_Nonnull(^_Nonnull)(float))hyphenationFactor; 77 | /** 未知 */ 78 | - (RZParagraphStyle *_Nonnull(^_Nonnull)(CGFloat))defaultTabInterval NS_AVAILABLE(10_0, 7_0); 79 | /** 未知 */ 80 | - (RZParagraphStyle *_Nonnull(^_Nonnull)(BOOL))allowsDefaultTighteningForTruncation NS_AVAILABLE(10_11, 9_0); 81 | /** 给段落添加行数限制,搭配NSLineBreakModel,将在段落里添加...占位 */ 82 | - (RZParagraphStyle *_Nonnull(^_Nonnull)(NSInteger numberOfLines, CGFloat maxWidth, NSAttributedString *_Nullable truncateText))numberOfLines; 83 | @end 84 | -------------------------------------------------------------------------------- /RZColorful/Classes/AttributeCore/NSAttributedString+RZHtml.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSAttributedString+RZHtml.h 3 | // RZColorfulExample 4 | // 5 | // Created by rztime on 2020/8/12. 6 | // Copyright © 2020 rztime. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "NSAttributedString+RZColorful.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface NSAttributedString (RZHtml) 15 | // 将html转换成 NSAttributedString 16 | + (NSAttributedString *)htmlString:(NSString *)html; 17 | /** 18 | 将富文本完整的code成html标签,(此方法如果富文本中有图片,则图片将被丢失) 有图片时,请用[rz_codingToHtmlWithImagesURLSIfHad]方法 (备注 使用系统方法,某些标签将不能转换) 19 | @return HTML标签语言 20 | */ 21 | - (NSString *)rz_codingToCompleteHtml; 22 | 23 | /// 将富文本转换为web可用的html 24 | /// 备注 :1将系统不能转换的标签,通过外部方法写入style,转换成web可用的html 25 | /// 2 通过htmlString还原成NSAttributedString,部分属性也有可能丢失 如下划线删除线的颜色(转换时,系统取得是\ 89 | \ 90 | \ 91 | %@\ 92 | \ 93 | \ 94 | ", html]; 95 | } 96 | @end 97 | -------------------------------------------------------------------------------- /Example/RZColorful/Demo/AttributedStringUseDemoVC.m: -------------------------------------------------------------------------------- 1 | // 2 | // AttributedStringUseDemoVC.m 3 | // RZColorful_Example 4 | // 5 | // Created by rztime on 2022/1/25. 6 | // Copyright © 2022 rztime. All rights reserved. 7 | // 8 | 9 | #import "AttributedStringUseDemoVC.h" 10 | #import 11 | 12 | @interface AttributedStringUseDemoVC () 13 | @property (nonatomic, strong) UILabel *label; 14 | @end 15 | 16 | @implementation AttributedStringUseDemoVC 17 | 18 | - (void)viewDidLoad { 19 | [super viewDidLoad]; 20 | self.view.backgroundColor = UIColor.whiteColor; 21 | _label = [[UILabel alloc] init]; 22 | _label.numberOfLines = 0; 23 | [self.view addSubview:_label]; 24 | _label.frame = self.view.bounds; 25 | NSAttributedString *place = [NSAttributedString rz_colorfulConfer:^(RZColorfulConferrer * _Nullable confer) { 26 | confer.text(@"...").font([UIFont systemFontOfSize:18]).textColor([UIColor redColor]); 27 | }]; 28 | NSAttributedString *attr = [NSAttributedString rz_colorfulConfer:^(RZColorfulConferrer * _Nullable confer) { 29 | confer.htmlText(@"\n\n\n标签富文本
百度一下").font([UIFont systemFontOfSize:16]); 30 | confer.text(@"\n正文使用方法:常规 字体 + 颜色\n\n").font([UIFont systemFontOfSize:16]).textColor(UIColor.blackColor); 31 | 32 | confer.text(@"添加本地图片 ").font([UIFont systemFontOfSize:20]).textColor(UIColor.redColor); 33 | confer.image([UIImage imageNamed:@"image"]).size(CGSizeMake(40, 40), RZHorizontalAlignCenter, [UIFont systemFontOfSize:20]).tapActionByLable(@"2"); // 点击事件 34 | confer.text(@" "); 35 | confer.image([UIImage imageNamed:@"image"]).size(CGSizeMake(40, 40), RZHorizontalAlignCenter, [UIFont systemFontOfSize:20]).custom(NSTapActionByLabelAttributeName, @"3"); // 自定义点击事件 36 | confer.text(@" 图片可对齐文本\n\n").font([UIFont systemFontOfSize:20]).textColor(UIColor.redColor); 37 | 38 | confer.text(@"正文使用方法:斜体").font([UIFont systemFontOfSize:16]).textColor(UIColor.blackColor).italic(@1); 39 | confer.text(@"\n正文使用方法:删除线").font([UIFont systemFontOfSize:16]).textColor(UIColor.blackColor).strikeThrough(NSUnderlineStyleSingle).strikeThroughColor(UIColor.redColor); 40 | confer.text(@"\n正文使用方法:下划线").font([UIFont systemFontOfSize:16]).textColor(UIColor.blackColor).underLineStyle(NSUnderlineStyleSingle).underLineColor(UIColor.redColor); 41 | confer.text(@"\n正文使用方法:描边\n").font([UIFont systemFontOfSize:16]).textColor(UIColor.blackColor).strokeWidth(@1).strokeColor(UIColor.redColor); 42 | confer.text(@"正文使用方法:背景色").font([UIFont systemFontOfSize:16]).textColor(UIColor.blackColor).backgroundColor(UIColor.lightGrayColor); 43 | confer.text(@"\n文本的点击事件").font([UIFont systemFontOfSize:16]); 44 | confer.text(@"可点击文本").textColor(UIColor.redColor).font([UIFont systemFontOfSize:16]).tapActionByLable(@"点击的id"); 45 | confer.text(@"\n"); 46 | confer.text(@"段落样式的方法设置段落样式的方法设置段落样式的方法设置段落样式的方法设置段落样式的方法设置段落样式的方法设置段落样式的方法设置段落样式的方法设置段落样式的方法设置段落样式的方法设置段落样式的方法设置段落样式的方法设置段落样式的方法设置段落样式的方法设置段落样式的方法设置段落样式的方法设置段落样式的方法设置段落样式的方法设置段落样式的方法设置段落样式的方法设置段落样式的方法设置段落样式的方法设置").font([UIFont systemFontOfSize:16]).textColor(UIColor.blackColor).paragraphStyle.lineSpacing(20).paragraphSpacingBefore(10).lineBreakMode(NSLineBreakByTruncatingTail).numberOfLines(4, self.view.bounds.size.width, place); 47 | }]; 48 | _label.attributedText = [attr rz_markText:@"段落" attribute:@{NSForegroundColorAttributeName: UIColor.redColor}]; 49 | [_label rz_tapAction:^(UILabel * _Nonnull label, NSString * _Nonnull tapActionId, NSRange range) { 50 | NSLog(@"%@", tapActionId); // print: 点击的id 51 | }]; 52 | } 53 | 54 | /* 55 | #pragma mark - Navigation 56 | 57 | // In a storyboard-based application, you will often want to do a little preparation before navigation 58 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 59 | // Get the new view controller using [segue destinationViewController]. 60 | // Pass the selected object to the new view controller. 61 | } 62 | */ 63 | 64 | @end 65 | -------------------------------------------------------------------------------- /Example/RZColorful.xcodeproj/xcshareddata/xcschemes/RZColorful-Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /Example/RZColorful/Demo/RZFoldLabelVC.m: -------------------------------------------------------------------------------- 1 | // 2 | // RZFoldLabelVC.m 3 | // RZColorful_Example 4 | // 5 | // Created by rztime on 2022/1/26. 6 | // Copyright © 2022 rztime. All rights reserved. 7 | // 8 | 9 | #import "RZFoldLabelVC.h" 10 | #import 11 | #import 12 | 13 | @implementation FoldModel 14 | 15 | - (instancetype)init { 16 | if (self = [super init]) { 17 | _text = [NSAttributedString rz_colorfulConfer:^(RZColorfulConferrer * _Nullable confer) { 18 | confer.text(@"百年来,我们党团结带领人民进行的一切奋斗、一切牺牲、一切创造,都是在践行为中国人民谋幸福、为中华民族谋复兴的初心使命\n中国共产党的成就和贡献,不仅是历史性的,也是世界性的\n中国共产党人的历史自信,既是对奋斗成就的自信,也是对奋斗精神的自信\n从一大会址复原场景到遵义会议复原景观,从开国大典影像到创办经济特区图片,从脱贫攻坚数据图表到火神山、雷神山医院模型……走进中国共产党历史展览馆,一幅幅图片、一件件文物、一个个场景,全方位、全过程、全景式、史诗般展现中国共产党波澜壮阔的百年历程,让参观者从内心深处感受震撼,升腾起昂扬向上的力量。").font([UIFont systemFontOfSize:16]).textColor(UIColor.blackColor); 19 | }]; 20 | _showAll = [NSAttributedString rz_colorfulConfer:^(RZColorfulConferrer * _Nullable confer) { 21 | confer.text(@"...").font([UIFont systemFontOfSize:16]).textColor(UIColor.blackColor); 22 | confer.text(@"显示全文").font([UIFont systemFontOfSize:16]).textColor(UIColor.redColor).tapActionByLable(@"showAll"); 23 | }]; 24 | _showFold = [NSAttributedString rz_colorfulConfer:^(RZColorfulConferrer * _Nullable confer) { 25 | confer.text(@"...").font([UIFont systemFontOfSize:16]).textColor(UIColor.blackColor); 26 | confer.text(@"折叠").font([UIFont systemFontOfSize:16]).textColor(UIColor.redColor).tapActionByLable(@"showFold"); 27 | }]; 28 | _isFold = YES; 29 | } 30 | return self; 31 | } 32 | 33 | @end 34 | 35 | 36 | @interface RZFoldLabelVC () 37 | @property (nonatomic, strong) UITableView *tableView; 38 | 39 | @property (nonatomic, strong) NSMutableArray *items; 40 | @end 41 | 42 | @implementation RZFoldLabelVC 43 | 44 | - (void)viewDidLoad { 45 | [super viewDidLoad]; 46 | 47 | _tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain]; 48 | _tableView.rowHeight = UITableViewAutomaticDimension; 49 | _tableView.delegate = self; 50 | _tableView.dataSource = self; 51 | // _tableView.estimatedRowHeight = 100; 52 | [self.view addSubview:_tableView]; 53 | _items = @[].mutableCopy; 54 | for (NSInteger i = 0; i < 50; i++) { 55 | FoldModel *model = [[FoldModel alloc] init]; 56 | [_items addObject:model]; 57 | } 58 | } 59 | 60 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 61 | return _items.count; 62 | } 63 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 64 | RZFoldTestCell *cell = (RZFoldTestCell *)[tableView dequeueReusableCellWithIdentifier:@"cell"]; 65 | if (cell == nil) { 66 | cell = [[RZFoldTestCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"]; 67 | __weak typeof(self) weakSelf = self; 68 | [cell.titleLabel rz_tapAction:^(UILabel * _Nonnull label, NSString * _Nonnull tapActionId, NSRange range) { 69 | if ([tapActionId isEqualToString:@"showAll"]) { 70 | NSLog(@"显示全文"); 71 | } else if ([tapActionId isEqualToString:@"showFold"]) { 72 | NSLog(@"折叠"); 73 | } 74 | FoldModel *model = weakSelf.items[label.tag]; 75 | model.isFold = !model.isFold; 76 | [weakSelf.tableView reloadData]; 77 | }]; 78 | } 79 | cell.titleLabel.tag = indexPath.row; 80 | FoldModel *model = _items[indexPath.row]; 81 | [cell.titleLabel rz_setAttributedString:model.text maxLine:4 maxWidth:self.view.bounds.size.width - 40 isFold:model.isFold showAllText:model.showAll showFoldText:model.showFold]; 82 | return cell; 83 | } 84 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 85 | [tableView deselectRowAtIndexPath:indexPath animated:false]; 86 | } 87 | 88 | @end 89 | 90 | @implementation RZFoldTestCell 91 | 92 | - (UILabel *)titleLabel { 93 | if (!_titleLabel) { 94 | self.selectionStyle = UITableViewCellSelectionStyleNone; 95 | _titleLabel = [[UILabel alloc] init]; 96 | [self.contentView addSubview:_titleLabel]; 97 | [_titleLabel mas_makeConstraints:^(MASConstraintMaker *make) { 98 | make.edges.equalTo(self.contentView).insets(UIEdgeInsetsMake(10, 20, 10, 20)); 99 | }]; 100 | } 101 | return _titleLabel; 102 | } 103 | 104 | @end 105 | -------------------------------------------------------------------------------- /RZColorful/Classes/AttributeCore/NSAttributedString+RZColorful.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSAttributedString+RZColorful.h 3 | // RZColorfulExample 4 | // 5 | // Created by rztime on 2017/11/15. 6 | // Copyright © 2017年 rztime. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "RZColorfulConferrer.h" 11 | #import "NSAttributedString+RZHtml.h" 12 | 13 | @class RZAttributedStringInfo; 14 | 15 | typedef NS_ENUM(NSInteger, RZAttributedStringAppendCondition) { 16 | RZAttributedStringAppendConditionLess = -1, // 比 小 17 | RZAttributedStringAppendConditionEqual = 0, // 相等 18 | RZAttributedStringAppendConditionMore = 1, // 比 大 19 | }; 20 | 21 | @interface NSAttributedString (RZColorful) 22 | /** 23 | 快捷创建富文本 24 | 25 | @param attribute <#attribute description#> 26 | @return <#return value description#> 27 | */ 28 | + (NSAttributedString *_Nullable)rz_colorfulConfer:(void(^_Nullable)(RZColorfulConferrer *_Nullable confer))attribute; 29 | // 追加 30 | - (NSAttributedString *_Nullable)attributedStringByAppend:(NSAttributedString *_Nullable)attributedString; 31 | // 获取富文本中的图片 用于上传服务器 32 | - (NSArray *_Nullable)rz_images; 33 | /** 34 | 固定宽度,计算高 35 | 36 | @param width 固定宽度 37 | @return <#return value description#> 38 | */ 39 | - (CGSize)sizeWithConditionWidth:(CGFloat)width; 40 | /** 41 | 固定高度,计算宽 42 | 43 | @param height 固定高度 44 | @return <#return value description#> 45 | */ 46 | - (CGSize)sizeWithConditionHeight:(CGFloat)height; 47 | 48 | #pragma mark - 其他方法 49 | /** 50 | 获取attrName对应的NSAttributedString 51 | */ 52 | - (NSArray *_Nullable)rz_attributedStringByAttributeName:(NSAttributedStringKey _Nonnull)attrName; 53 | 54 | /// 绘制在rect范围内时, 获取每一行的文本信息, 55 | /// @param rect 绘制范围 56 | - (NSArray *_Nullable)rz_linesIfDrawInRect:(CGRect)rect; 57 | 58 | /// 将attr追加到当前文本上 ,只有在rect上绘制,小于(等于或大于)line时,才将attr追加到self之后 ,如果将attr追加上去之后,行数大于line,则会对self的字符串截取 59 | /// @param attr 要追加的文本 60 | /// @param condition 满足的条件 61 | /// @param line 行数 62 | /// @param rect 要绘制的rect 63 | - (NSAttributedString *_Nullable)rz_appendAttributedString:(NSAttributedString *_Nullable)attr when:(RZAttributedStringAppendCondition)condition line:(NSInteger)line inRect:(CGRect)rect; 64 | @end 65 | 66 | @interface NSAttributedString (RZLines) 67 | 68 | /// 计算NSAttributedString的文本在width限制条件下, 是否超过line行 69 | - (BOOL)rz_moreThan:(NSInteger)line maxWidth:(CGFloat)width; 70 | 71 | /// 通过限制宽度,行数,获取内容, 72 | /// 如果未超过 line 行数,返回原字符串 73 | /// 超过行数,折叠时,将追加showAllText(如显示全文), 全部展开时,显示showFoldText(如收起) 74 | /// - Parameters: 75 | /// - maxline: 最大显示的行数, 76 | /// - maxWidth: 最大显示宽度 77 | /// - isFold: 当前是否折叠 78 | /// - showAllText: 如 “...显示全文” fold = true时,将追加在字符串后 79 | /// - showFoldText: 如 “收起全文” flod = FALSE,表示已全部展开,将追加在后边 80 | /// - Returns: 字符串 81 | - (NSAttributedString * _Nullable)rz_attributedStringBy:(NSInteger)maxLine maxWidth:(CGFloat)width isFold:(BOOL)fold showAllText:(NSAttributedString *_Nullable)allText showFoldText:(NSAttributedString *_Nullable)foldText; 82 | 83 | /// 对富文本进行截断处理 84 | /// - Parameters: 85 | /// - maxLine: 设置超过多少截断 86 | /// - width: 显示的最大宽度 87 | /// - model: 截断方式 88 | /// - placeHolder: 截断时占位的"..."文字 89 | - (NSAttributedString * _Nullable)rz_attributedStringBy:(NSInteger)maxLine maxWidth:(CGFloat)width lineBreakMode:(NSLineBreakMode)model placeHolder:(NSAttributedString *_Nullable)placeHolder; 90 | /// 头部截断(逆向)从最后一个字反向往前保留最后maxLine行数的文字 91 | /// - Parameters: 92 | /// - maxline: 最大行数 93 | /// - maxWidth: 最大宽度 94 | /// - lineBreakMode: placeholder插入的方式 95 | /// - placeHolder: 截断时占位的"..."文字 96 | /// - 区别于方法(A)的byTruncatingHead:func attributedStringBy(maxline: Int, maxWidth: CGFloat, lineBreakMode: NSLineBreakMode, placeHolder: NSAttributedString?) -> NSAttributedString? 97 | /// - 方法(A)抛弃前半部分,在最前边插入placeholder 98 | /// - headxxx抛弃前半部分,然后根据mode再不同位置插入placeholder 99 | - (NSAttributedString * _Nullable)rz_headTruncatingAttributedStringBy:(NSInteger)maxline maxWidth:(CGFloat)width lineBreakMode:(NSLineBreakMode)model placeHolder:(NSAttributedString *_Nullable)placeHolder; 100 | @end 101 | 102 | @interface NSAttributedString (RZAttr) 103 | /// 给text 关键字 标记属性(如对关键字进行标红显示) 104 | - (NSAttributedString *_Nonnull)rz_markText:(NSString *_Nullable)text attribute:(NSDictionary *_Nonnull)attribute; 105 | /// 取self的属性,设置在text上 106 | - (NSAttributedString *_Nonnull)rz_copyAttributeToText:(NSString *_Nonnull)text; 107 | 108 | @end 109 | 110 | // 要获取的attrName对应的信息 111 | @interface RZAttributedStringInfo : NSObject 112 | /** 对应的attributedString */ 113 | @property (nonatomic, strong) NSMutableAttributedString * _Nullable attributedString; 114 | /** 所在的range */ 115 | @property (nonatomic, assign) NSRange range; 116 | /** 对应可以的值 */ 117 | @property (nonatomic, strong) id _Nullable value; 118 | /** 所要查询的attrName */ 119 | @property (nonatomic, copy) NSAttributedStringKey _Nullable attrName; 120 | 121 | @end 122 | 123 | -------------------------------------------------------------------------------- /RZColorful/Classes/UICore/RZTapActionHelper.m: -------------------------------------------------------------------------------- 1 | // 2 | // RZTapActionHelper.m 3 | // RZColorfulExample 4 | // 5 | // Created by xk_mac_mini on 2019/9/29. 6 | // Copyright © 2019 rztime. All rights reserved. 7 | // 8 | 9 | #import "RZTapActionHelper.h" 10 | #import "UITextView+RZColorful.h" 11 | 12 | @interface RZTapActionHelper () 13 | 14 | @property (nonatomic, weak) id tagert; 15 | 16 | @end 17 | @implementation RZTapActionHelper 18 | 19 | - (void)setTextView:(UITextView *)textView { 20 | _textView = textView; 21 | _tagert = textView.delegate; 22 | _textView.delegate = self; 23 | } 24 | 25 | - (BOOL)textViewShouldBeginEditing:(UITextView *)textView { 26 | if (_tagert && [_tagert respondsToSelector:@selector(textViewShouldBeginEditing:)]) { 27 | return [_tagert textViewShouldBeginEditing:textView]; 28 | } 29 | return YES; 30 | } 31 | - (BOOL)textViewShouldEndEditing:(UITextView *)textView { 32 | if (_tagert && [_tagert respondsToSelector:@selector(textViewShouldEndEditing:)]) { 33 | return [_tagert textViewShouldEndEditing:textView]; 34 | } 35 | return YES; 36 | } 37 | 38 | - (void)textViewDidBeginEditing:(UITextView *)textView { 39 | if (_tagert && [_tagert respondsToSelector:@selector(textViewDidBeginEditing:)]) { 40 | [_tagert textViewDidBeginEditing:textView]; 41 | } 42 | } 43 | - (void)textViewDidEndEditing:(UITextView *)textView { 44 | if (_tagert && [_tagert respondsToSelector:@selector(textViewDidEndEditing:)]) { 45 | [_tagert textViewDidEndEditing:textView]; 46 | } 47 | } 48 | 49 | - (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text { 50 | if (_tagert && [_tagert respondsToSelector:@selector(textView:shouldChangeTextInRange:replacementText:)]) { 51 | return [_tagert textView:textView shouldChangeTextInRange:range replacementText:text]; 52 | } 53 | return YES; 54 | } 55 | - (void)textViewDidChange:(UITextView *)textView { 56 | if (_tagert && [_tagert respondsToSelector:@selector(textViewDidChange:)]) { 57 | [_tagert textViewDidChange:textView]; 58 | } 59 | } 60 | 61 | - (void)textViewDidChangeSelection:(UITextView *)textView { 62 | if (_tagert && [_tagert respondsToSelector:@selector(textViewDidChangeSelection:)]) { 63 | [_tagert textViewDidChangeSelection:textView]; 64 | } 65 | } 66 | 67 | - (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange interaction:(UITextItemInteraction)interaction NS_AVAILABLE_IOS(10_0) { 68 | BOOL result = [self didTapActionWithId:URL]; 69 | BOOL result2 = YES; 70 | if (_tagert && [_tagert respondsToSelector:@selector(textView:shouldInteractWithURL:inRange:interaction:)]) { 71 | result2 = [_tagert textView:textView shouldInteractWithURL:URL inRange:characterRange interaction:interaction]; 72 | } 73 | if (_tagert && [_tagert respondsToSelector:@selector(textView:shouldInteractWithURL:inRange:)]) { 74 | result2 = [_tagert textView:textView shouldInteractWithURL:URL inRange:characterRange]; 75 | } 76 | return result && result2; 77 | } 78 | - (BOOL)textView:(UITextView *)textView shouldInteractWithTextAttachment:(NSTextAttachment *)textAttachment inRange:(NSRange)characterRange interaction:(UITextItemInteraction)interaction NS_AVAILABLE_IOS(10_0) { 79 | if (_tagert && [_tagert respondsToSelector:@selector(textView:shouldInteractWithTextAttachment:inRange:interaction:)]) { 80 | return [_tagert textView:textView shouldInteractWithTextAttachment:textAttachment inRange:characterRange interaction:interaction]; 81 | } 82 | return YES; 83 | } 84 | 85 | - (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange NS_DEPRECATED_IOS(7_0, 10_0, "Use textView:shouldInteractWithURL:inRange:forInteractionType: instead") { 86 | BOOL result = [self didTapActionWithId:URL]; 87 | BOOL result2 = YES; 88 | if (_tagert && [_tagert respondsToSelector:@selector(textView:shouldInteractWithURL:inRange:interaction:)]) { 89 | result2 = [_tagert textView:textView shouldInteractWithURL:URL inRange:characterRange interaction:UITextItemInteractionInvokeDefaultAction]; 90 | } 91 | if (_tagert && [_tagert respondsToSelector:@selector(textView:shouldInteractWithURL:inRange:)]) { 92 | result2 = [_tagert textView:textView shouldInteractWithURL:URL inRange:characterRange]; 93 | } 94 | return result && result2; 95 | } 96 | - (BOOL)textView:(UITextView *)textView shouldInteractWithTextAttachment:(NSTextAttachment *)textAttachment inRange:(NSRange)characterRange NS_DEPRECATED_IOS(7_0, 10_0, "Use textView:shouldInteractWithTextAttachment:inRange:forInteractionType: instead") { 97 | if (_tagert && [_tagert respondsToSelector:@selector(textView:shouldInteractWithTextAttachment:inRange:)]) { 98 | return [_tagert textView:textView shouldInteractWithTextAttachment:textAttachment inRange:characterRange]; 99 | } 100 | return YES; 101 | } 102 | 103 | - (BOOL)didTapActionWithId:(NSURL *)tapActioId { 104 | if (self.textView.rzDidTapTextView) { 105 | return self.textView.rzDidTapTextView(tapActioId); 106 | } 107 | return YES; 108 | } 109 | 110 | - (void)dealloc { 111 | _textView.delegate = _tagert; 112 | } 113 | 114 | @end 115 | -------------------------------------------------------------------------------- /RZColorful/Classes/UICore/UITextView+RZColorful.m: -------------------------------------------------------------------------------- 1 | // 2 | // UITextView+RZColorful.m 3 | // RZColorfulExample 4 | // 5 | // Created by 若醉 on 2018/7/6. 6 | // Copyright © 2018年 rztime. All rights reserved. 7 | // 8 | 9 | #import "UITextView+RZColorful.h" 10 | #import "NSAttributedString+RZColorful.h" 11 | #import "RZTapActionHelper.h" 12 | #import 13 | 14 | @interface UITextView () 15 | 16 | @property (nonatomic, strong) RZTapActionHelper *helper; 17 | 18 | @end 19 | 20 | @implementation UITextView (RZColorful) 21 | 22 | /** 23 | 设置富文本,原内容将清除 仅UILabel、UITextField、UITextView 有效 24 | 25 | @param attribute <#block description#> 26 | */ 27 | - (void )rz_colorfulConfer:(void(^)(RZColorfulConferrer *confer))attribute { 28 | [self rzSetAttributedText:nil]; 29 | [self rz_colorfulConferInsetToLocation:0 append:attribute]; 30 | } 31 | 32 | - (void )rz_colorfulConferAppend:(void (^)(RZColorfulConferrer *confer))attribute { 33 | [self rz_colorfulConferInsetTo:rzConferInsertPositionEnd append:attribute]; 34 | } 35 | 36 | /** 37 | 插入文本 38 | 39 | @param position 插入的位置 40 | @param attribute 新的内容 41 | */ 42 | - (void )rz_colorfulConferInsetTo:(rzConferInsertPosition)position append:(void (^)(RZColorfulConferrer * _Nonnull confer))attribute { 43 | NSUInteger location; 44 | switch (position) { 45 | case rzConferInsertPositionDefault: 46 | case rzConferInsertPositionCursor: { // 默认位置 光标处 47 | location = [self getCursorLocation]; 48 | break; 49 | } 50 | case rzConferInsertPositionHeader: { // 头 51 | location = 0; 52 | break; 53 | } 54 | case rzConferInsertPositionEnd: { // 尾 55 | location = [self getEndLocation]; 56 | break; 57 | } 58 | default: 59 | location = [self getEndLocation]; 60 | break; 61 | } 62 | [self rz_colorfulConferInsetToLocation:location append:attribute]; 63 | } 64 | 65 | /** 66 | 添加到指定位置 67 | 68 | @param location <#location description#> 69 | @param attribute <#attribute description#> 70 | */ 71 | - (void )rz_colorfulConferInsetToLocation:(NSUInteger)location append:(void (^)(RZColorfulConferrer * _Nonnull))attribute { 72 | if(!attribute) { 73 | return ; 74 | } 75 | NSUInteger loc = location; 76 | NSAttributedString *conferrerColorful = [NSAttributedString rz_colorfulConfer:attribute]; 77 | if (conferrerColorful.string.length == 0) { 78 | return ; 79 | } 80 | if (loc > [self getEndLocation]) { 81 | loc = [self getEndLocation]; 82 | } 83 | NSMutableAttributedString *attr = [[NSMutableAttributedString alloc] initWithAttributedString:[self rzAttributedString]]; 84 | [attr insertAttributedString:conferrerColorful atIndex:loc]; 85 | [self rzSetAttributedText:attr]; 86 | } 87 | // 文本框的内容 88 | - (NSAttributedString *)rzAttributedString { 89 | return self.attributedText; 90 | } 91 | // 设置文本框的内容 92 | - (void)rzSetAttributedText:(NSAttributedString *)attributedString { 93 | self.attributedText = attributedString; 94 | } 95 | // 尾部的位置 96 | - (NSUInteger)getEndLocation { 97 | return self.attributedText.string.length; 98 | } 99 | // 光标的位置 100 | - (NSUInteger)getCursorLocation { 101 | return self.selectedRange.location;; 102 | } 103 | 104 | - (void)setHelper:(RZTapActionHelper *)helper { 105 | objc_setAssociatedObject(self, @"rzweakHelper", helper, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 106 | } 107 | 108 | - (RZTapActionHelper *)helper { 109 | return objc_getAssociatedObject(self, @"rzweakHelper"); 110 | } 111 | 112 | - (void)setRzDidTapTextView:(BOOL(^)(id __nullable))rzDidTapTextView { 113 | if (!self.helper) { 114 | RZTapActionHelper *helper = [[RZTapActionHelper alloc] init]; 115 | helper.textView = self; 116 | self.helper = helper; 117 | } 118 | objc_setAssociatedObject(self, @"rzDidTapTextView", rzDidTapTextView, OBJC_ASSOCIATION_COPY); 119 | } 120 | - (BOOL(^)(id __nullable))rzDidTapTextView { 121 | return objc_getAssociatedObject(self, @"rzDidTapTextView"); 122 | } 123 | /// 获取range所在文本的位置 124 | - (CGRect)rz_rectFor:(NSRange)range { 125 | UITextView *textView = self; 126 | UITextPosition *beginning = textView.beginningOfDocument; 127 | UITextPosition *star = [textView positionFromPosition:beginning offset:range.location]; 128 | UITextPosition *end = [textView positionFromPosition:star offset:range.length]; 129 | UITextRange *textRange = [textView textRangeFromPosition:star toPosition:end]; 130 | return [textView firstRectForRange:textRange]; 131 | } 132 | /// 获取range所在文本的位置,可能涉及多行 NSValue: CGRect 133 | - (NSArray *)rz_rectFors:(NSRange)range { 134 | UITextView *textView = self; 135 | UITextPosition *beginning = textView.beginningOfDocument; 136 | UITextPosition *star = [textView positionFromPosition:beginning offset:range.location]; 137 | UITextPosition *end = [textView positionFromPosition:star offset:range.length]; 138 | UITextRange *textRange = [textView textRangeFromPosition:star toPosition:end]; 139 | NSArray *rects = [textView selectionRectsForRange:textRange]; 140 | NSMutableArray *res = @[].mutableCopy; 141 | [rects enumerateObjectsUsingBlock:^(UITextSelectionRect * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 142 | [res addObject:@(obj.rect)]; 143 | }]; 144 | return res; 145 | } 146 | @end 147 | -------------------------------------------------------------------------------- /RZColorful/Classes/AttributeCore/RZImageAttachment.m: -------------------------------------------------------------------------------- 1 | // 2 | // RZImageAttachment.m 3 | // RZColorfulExample 4 | // 5 | // Created by 乄若醉灬 on 2017/7/28. 6 | // Copyright © 2017年 rztime. All rights reserved. 7 | // 8 | 9 | #import "RZImageAttachment.h" 10 | #import "NSAttributedString+RZColorful.h" 11 | #import "NSString+RZCode.h" 12 | 13 | #pragma clang diagnostic push 14 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 15 | #pragma clang diagnostic ignored"-Wunused-variable" 16 | 17 | @interface RZImageAttachment () 18 | 19 | /** 设置段落样式 */ 20 | @property (nonatomic, strong) RZParagraphStyle *paragraphStyle; 21 | /** 阴影 */ 22 | @property (nonatomic, strong) RZShadow *shadow; 23 | /** */ 24 | @property (nonatomic, strong) RZColorfulAttribute *more; 25 | 26 | @property (nonatomic, strong) NSMutableDictionary *dict; 27 | 28 | @end 29 | 30 | @implementation RZImageAttachment 31 | 32 | - (NSMutableDictionary *)dict { 33 | if (!_dict) { 34 | _dict = [NSMutableDictionary new]; 35 | } 36 | return _dict; 37 | } 38 | 39 | - (NSDictionary *)code { 40 | if (_hadParagraphStyle) { 41 | self.dict[NSParagraphStyleAttributeName] = [_paragraphStyle code]; 42 | } 43 | if (_hadShadow) { 44 | self.dict[NSShadowAttributeName] = [_shadow code]; 45 | } 46 | if (_more) { 47 | [self.dict addEntriesFromDictionary:[_more code]]; 48 | } 49 | return self.dict.copy; 50 | } 51 | 52 | - (RZImageAttachment *(^)(CGRect bounds))bounds { 53 | return ^id (CGRect bounds) { 54 | self.imageBounds = bounds; 55 | return self; 56 | }; 57 | } 58 | 59 | /** 60 | 设置点击 61 | */ 62 | - (RZImageAttachment *(^)(NSURL *url))url { 63 | return ^id (NSURL *url) { 64 | self.dict[NSLinkAttributeName] = url; 65 | return self; 66 | }; 67 | } 68 | /* 69 | 给属性文本添加点击事件 只有UITextView可以用,且UITextView需要实现block didTapTextView 70 | */ 71 | - (RZImageAttachment *(^)(NSString *tapId))tapAction { 72 | return ^id(NSString *tapId) { 73 | self.dict[NSLinkAttributeName] = tapId.rz_encodedString; 74 | return self; 75 | }; 76 | } 77 | 78 | /* 给属性文本添加点击事件 只有UILabel可以用,且UILabel需要实现方法 rz_tapAction */ 79 | - (RZImageAttachment *(^)(NSString *tapId))tapActionByLable { 80 | return ^id(NSString *tapId) { 81 | self.dict[NSTapActionByLabelAttributeName] = tapId.rz_encodedString; 82 | return self; 83 | }; 84 | } 85 | /** 自定义属性和值 */ 86 | - (RZColorfulAttribute * (^)(NSAttributedStringKey key, id value))custom { 87 | return ^id(NSAttributedStringKey key, id value) { 88 | self.dict[key] = value; 89 | return self; 90 | }; 91 | } 92 | /** 93 | 水平对齐方式 94 | align 上,中,下 95 | refer 对齐的参考系 (前后的字体) 96 | */ 97 | - (RZImageAttachment *(^)(CGSize size, RZImageAttachmentHorizontalAlign align, UIFont *font))size { 98 | return ^id (CGSize size, RZImageAttachmentHorizontalAlign align, UIFont *font) { 99 | CGFloat y = 0; 100 | CGFloat fontHeight = font.ascender - font.descender; 101 | switch (align) { 102 | case RZHorizontalAlignTop: { 103 | y = -(size.height - fontHeight) + font.descender; 104 | break; 105 | } 106 | case RZHorizontalAlignCenter: { 107 | y = -(size.height - fontHeight)/2.f + font.descender; 108 | break; 109 | } 110 | case RZHorizontalAlignBottom: { 111 | y = font.descender; 112 | break; 113 | } 114 | default: 115 | break; 116 | } 117 | self.imageBounds = CGRectMake(0, y, size.width, size.height); 118 | return self; 119 | }; 120 | } 121 | /** 122 | y轴偏移量,在某些情况下,在对齐之后需要做上下偏移时,用此方法,请在设置size之后或者bounds之后使用 123 | */ 124 | - (RZImageAttachment *(^)(CGFloat yOffset))yOffset { 125 | return ^id (CGFloat yOffset) { 126 | self.imageBounds = ({ 127 | CGRect bounds = self.imageBounds; 128 | bounds.origin.y -= yOffset; 129 | bounds; 130 | }); 131 | return self; 132 | }; 133 | } 134 | /** 135 | 将bounds数据转换成html格式的语句 136 | 137 | @param imageUrl 图片url 138 | @return <#return value description#> 139 | */ 140 | - (NSString *)toHTMLStringWithImageUrl:(NSString *)imageUrl { 141 | NSString *width = @""; 142 | NSString *height = @""; 143 | if (_imageBounds.size.width > 0) { 144 | width = [NSString stringWithFormat:@"width:%fpx;", _imageBounds.size.width]; 145 | } 146 | if (_imageBounds.size.height > 0) { 147 | height = [NSString stringWithFormat:@"height:%fpx;", _imageBounds.size.height]; 148 | } 149 | NSString *html = [NSString stringWithFormat:@"", width, height, imageUrl]; 150 | return html; 151 | } 152 | 153 | /** 154 | 段落样式,具体设置请看 RZParagraphStyle.h 155 | 156 | @return <#return value description#> 157 | */ 158 | - (RZParagraphStyle *)paragraphStyle { 159 | if (!_paragraphStyle) { 160 | _paragraphStyle = [[RZParagraphStyle alloc] initWithAttach:self]; 161 | _hadParagraphStyle = YES; 162 | } 163 | return _paragraphStyle; 164 | } 165 | 166 | - (RZShadow *)shadow { 167 | if (!_shadow) { 168 | _shadow = [[RZShadow alloc] initWithAttach:self]; 169 | _hadShadow = YES; 170 | } 171 | return _shadow; 172 | } 173 | - (RZColorfulAttribute *)more { 174 | if (!_more) { 175 | _more = [[RZColorfulAttribute alloc] init]; 176 | } 177 | return _more; 178 | } 179 | #pragma clang diagnostic pop 180 | @end 181 | -------------------------------------------------------------------------------- /RZColorful/Classes/UICore/UILabel+RZColorful.m: -------------------------------------------------------------------------------- 1 | // 2 | // UILabel+RZColorful.m 3 | // RZColorfulExample 4 | // 5 | // Created by 若醉 on 2018/7/6. 6 | // Copyright © 2018年 rztime. All rights reserved. 7 | // 8 | 9 | #import "UILabel+RZColorful.h" 10 | #import "NSAttributedString+RZColorful.h" 11 | #import "LabelFoldHelper.h" 12 | 13 | @implementation UILabel (RZColorful) 14 | 15 | /** 16 | 设置富文本,原内容将清除 仅UILabel、UITextField、UITextView 有效 17 | 18 | @param attribute <#block description#> 19 | */ 20 | - (void )rz_colorfulConfer:(void(^)(RZColorfulConferrer *confer))attribute { 21 | [self rzSetAttributedText:nil]; 22 | [self rz_colorfulConferInsetToLocation:0 append:attribute]; 23 | } 24 | 25 | - (void )rz_colorfulConferAppend:(void (^)(RZColorfulConferrer *confer))attribute { 26 | [self rz_colorfulConferInsetTo:rzConferInsertPositionEnd append:attribute]; 27 | } 28 | 29 | /** 30 | 插入文本 31 | 32 | @param position 插入的位置 33 | @param attribute 新的内容 34 | */ 35 | - (void )rz_colorfulConferInsetTo:(rzConferInsertPosition)position append:(void (^)(RZColorfulConferrer * _Nonnull confer))attribute { 36 | NSUInteger location; 37 | switch (position) { 38 | case rzConferInsertPositionDefault: 39 | case rzConferInsertPositionCursor: { // 默认位置 光标处 40 | location = [self getCursorLocation]; 41 | break; 42 | } 43 | case rzConferInsertPositionHeader: { // 头 44 | location = 0; 45 | break; 46 | } 47 | case rzConferInsertPositionEnd: { // 尾 48 | location = [self getEndLocation]; 49 | break; 50 | } 51 | default: 52 | location = [self getEndLocation]; 53 | break; 54 | } 55 | [self rz_colorfulConferInsetToLocation:location append:attribute]; 56 | } 57 | 58 | /** 59 | 添加到指定位置 60 | 61 | @param location <#location description#> 62 | @param attribute <#attribute description#> 63 | */ 64 | - (void )rz_colorfulConferInsetToLocation:(NSUInteger)location append:(void (^)(RZColorfulConferrer * _Nonnull))attribute { 65 | if(!attribute) { 66 | return ; 67 | } 68 | NSUInteger loc = location; 69 | NSAttributedString *conferrerColorful = [NSAttributedString rz_colorfulConfer:attribute]; 70 | if (conferrerColorful.string.length == 0) { 71 | return ; 72 | } 73 | if (loc > [self getEndLocation]) { 74 | loc = [self getEndLocation]; 75 | } 76 | NSMutableAttributedString *attr = [[NSMutableAttributedString alloc] initWithAttributedString:[self rzAttributedString]]; 77 | [attr insertAttributedString:conferrerColorful atIndex:loc]; 78 | [self rzSetAttributedText:attr]; 79 | } 80 | // 文本框的内容 81 | - (NSAttributedString *)rzAttributedString { 82 | return self.attributedText; 83 | } 84 | // 设置文本框的内容 85 | - (void)rzSetAttributedText:(NSAttributedString *)attributedString { 86 | self.attributedText = attributedString; 87 | } 88 | // 尾部的位置 89 | - (NSUInteger)getEndLocation { 90 | return self.attributedText.string.length;; 91 | } 92 | // 光标的位置 93 | - (NSUInteger)getCursorLocation { 94 | return self.attributedText.string.length;; 95 | } 96 | 97 | /// 设置富文本(超过行数后,自动追加“展开” “收起”) 98 | /// @param attr 原文 99 | /// @param line 最大显示行数 100 | /// @param width 最大显示宽度,这个宽度用于计算文本行 101 | /// @param fold 当前是否折叠 102 | /// @param allText 超过了行数之后,折叠状态显示的文本 如”展开“ 需要给文本设置rztapLabel属性 (tapActionByLable) 103 | /// @param foldText 超过行数之后,全部展开状态显示的文本 如”收起“ 需要给文本设置rztapLabel属性 (tapActionByLable) 104 | - (void)rz_setAttributedString:(NSAttributedString *)attr maxLine:(NSInteger)line maxWidth:(CGFloat)width isFold:(BOOL)fold showAllText:(NSAttributedString *)allText showFoldText:(NSAttributedString *)foldText { 105 | self.numberOfLines = 0; 106 | if (attr == nil || attr.length == 0) { 107 | self.attributedText = attr; 108 | return; 109 | } 110 | self.attributedText = [attr rz_attributedStringBy:line maxWidth:width isFold:fold showAllText:allText showFoldText:foldText]; 111 | } 112 | 113 | - (void)rz_tapAction:(RZLabelTapAction)tapAction { 114 | self.numberOfLines = 0; 115 | self.userInteractionEnabled = true; 116 | __block LabelFoldHelper *helper; 117 | [self.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 118 | if ([obj isKindOfClass: [LabelFoldHelper class]]) { 119 | helper = (LabelFoldHelper *)obj; 120 | *stop = true; 121 | } 122 | }]; 123 | if (helper == nil) { 124 | helper = [[LabelFoldHelper alloc] initWithtarget:self tapAction:tapAction]; 125 | [self addSubview:helper]; 126 | UIView *v1 = helper; 127 | NSLayoutConstraint *left = [NSLayoutConstraint constraintWithItem:v1 attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeLeft multiplier:1 constant:0]; 128 | NSLayoutConstraint *top = [NSLayoutConstraint constraintWithItem:v1 attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTop multiplier:1 constant:0]; 129 | NSLayoutConstraint *right = [NSLayoutConstraint constraintWithItem:v1 attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeRight multiplier:1 constant:0]; 130 | NSLayoutConstraint *bottom = [NSLayoutConstraint constraintWithItem:v1 attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeBottom multiplier:1 constant:0]; 131 | [self addConstraints:@[left, top, right, bottom]]; 132 | } 133 | } 134 | 135 | @end 136 | -------------------------------------------------------------------------------- /RZColorful/Classes/UICore/LabelFoldHelper.m: -------------------------------------------------------------------------------- 1 | // 2 | // LabelFoldHelper.m 3 | // RZColorful 4 | // 5 | // Created by rztime on 2022/1/25. 6 | // 7 | 8 | #import "LabelFoldHelper.h" 9 | #import 10 | #import "UITextView+RZColorful.h" 11 | #import "NSString+RZCode.h" 12 | 13 | @interface TapPointRes : NSObject 14 | @property (nonatomic, assign) BOOL isInTarget; 15 | @property (nonatomic, copy) NSString *textLink; 16 | @property (nonatomic, assign) NSRange range; 17 | - (instancetype)initWith:(BOOL)inTarget link:(NSString *)link range:(NSRange)range; 18 | @end 19 | @implementation TapPointRes 20 | - (instancetype)initWith:(BOOL)inTarget link:(NSString *)link range:(NSRange)range { 21 | if (self = [super init]) { 22 | self.isInTarget = inTarget; 23 | self.textLink = link; 24 | self.range = range; 25 | } 26 | return self; 27 | } 28 | @end 29 | 30 | @interface LabelFoldHelper () 31 | 32 | @property (nonatomic, strong) UITextView *textView; 33 | @property (nonatomic, weak) UILabel *target; 34 | @property (nonatomic, copy) RZLabelTapAction tapAction; 35 | 36 | @end 37 | @implementation LabelFoldHelper 38 | 39 | - (instancetype)initWithtarget:(UILabel *)target tapAction:(RZLabelTapAction)tapAction { 40 | if (self = [super init]) { 41 | self.target = target; 42 | self.tapAction = tapAction; 43 | self.translatesAutoresizingMaskIntoConstraints = false; 44 | UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGesAction:)]; 45 | [self addGestureRecognizer:tap]; 46 | [UILabel rz_swizzedSelected]; 47 | [self setHidden:YES]; 48 | } 49 | return self; 50 | } 51 | 52 | - (UITextView *)textView { 53 | if (_textView == nil) { 54 | _textView = [[UITextView alloc] init]; 55 | _textView.alpha = 0; 56 | _textView.contentInset = UIEdgeInsetsZero; 57 | _textView.textContainerInset = UIEdgeInsetsZero; 58 | _textView.scrollEnabled = false; 59 | _textView.editable = false; 60 | _textView.textContainer.lineFragmentPadding = 0; 61 | _textView.showsVerticalScrollIndicator = false; 62 | _textView.showsHorizontalScrollIndicator = false; 63 | _textView.linkTextAttributes = @{}; 64 | _textView.translatesAutoresizingMaskIntoConstraints = false; 65 | } 66 | return _textView; 67 | } 68 | - (void)layoutSubviews { 69 | [super layoutSubviews]; 70 | _textView.frame = self.bounds; 71 | } 72 | 73 | - (void)tapGesAction:(UITapGestureRecognizer *)sender { 74 | CGPoint point = [sender locationInView:self]; 75 | TapPointRes *res = [self tapPoint:point]; 76 | if (res.isInTarget && self.target != nil && self.tapAction) { 77 | self.tapAction(self.target, res.textLink, res.range); 78 | } 79 | } 80 | 81 | - (TapPointRes *)tapPoint:(CGPoint)point { 82 | if (self.target == nil) { 83 | return [[TapPointRes alloc] initWith:false link:@"" range: NSMakeRange(0, 0)]; 84 | } 85 | CGRect textRect = self.target.rz_drawTextRect; 86 | CGRect rect = [self.target textRectForBounds:textRect limitedToNumberOfLines:0]; 87 | CGFloat offsetY = (textRect.size.height - rect.size.height) / 2.0 + textRect.origin.y; 88 | point.y -= offsetY; 89 | point.x -= textRect.origin.x; 90 | self.textView.frame = self.bounds; 91 | [self.textView systemLayoutSizeFittingSize:textRect.size]; 92 | _textView.attributedText = self.target.attributedText; 93 | TapPointRes *res = [[TapPointRes alloc] init]; 94 | 95 | [self.target.attributedText enumerateAttribute:NSTapActionByLabelAttributeName inRange:NSMakeRange(0, self.target.attributedText.length) options:(NSAttributedStringEnumerationLongestEffectiveRangeNotRequired) usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) { 96 | if (value == nil) { 97 | return; 98 | } 99 | NSArray *rects = [self.textView rz_rectFors:range]; 100 | [rects enumerateObjectsUsingBlock:^(NSValue * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 101 | CGRect rect = obj.CGRectValue; 102 | if (CGRectContainsPoint(rect, point)) { 103 | if ([value isKindOfClass:[NSString class]]) { 104 | res.textLink = [value rz_decodedString]; 105 | } else if ([value isKindOfClass:[NSURL class]]){ 106 | res.textLink = [((NSURL *)value).absoluteString rz_decodedString]; 107 | } 108 | res.isInTarget = true; 109 | res.range = range; 110 | *stop = true; 111 | } 112 | }]; 113 | }]; 114 | return res; 115 | } 116 | - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { 117 | TapPointRes *res = [self tapPoint:point]; 118 | if (res.isInTarget) { 119 | return self; 120 | } 121 | return nil; 122 | } 123 | @end 124 | 125 | @implementation UILabel(DrawText) 126 | 127 | - (void)setRz_drawTextRect:(CGRect)rz_drawTextRect { 128 | objc_setAssociatedObject(self, @"rz_drawTextRect", [NSValue valueWithCGRect:rz_drawTextRect], OBJC_ASSOCIATION_RETAIN_NONATOMIC); 129 | } 130 | - (CGRect)rz_drawTextRect { 131 | NSValue *value = (NSValue *)objc_getAssociatedObject(self, @"rz_drawTextRect"); 132 | return [value CGRectValue]; 133 | } 134 | 135 | + (void)rz_swizzedSelected { 136 | static dispatch_once_t onceToken; 137 | dispatch_once(&onceToken, ^{ 138 | // NSLog(@"------------UILabel drawTextInRect 方法交换 rz_drawTextInRect--------------"); 139 | Method origin = class_getInstanceMethod([UILabel class], @selector(drawTextInRect:)); 140 | Method swizee = class_getInstanceMethod([UILabel class], @selector(rz_drawTextInRect:)); 141 | method_exchangeImplementations(origin, swizee); 142 | }); 143 | } 144 | - (void)rz_drawTextInRect:(CGRect)rect { 145 | [self rz_drawTextInRect: rect]; 146 | self.rz_drawTextRect = rect; 147 | } 148 | @end 149 | -------------------------------------------------------------------------------- /RZColorful/Classes/AttributeCore/RZParagraphStyle.m: -------------------------------------------------------------------------------- 1 | // 2 | // RZParagraphStyle.m 3 | // RZColorfulExample 4 | // 5 | // Created by 乄若醉灬 on 2017/7/31. 6 | // Copyright © 2017年 rztime. All rights reserved. 7 | // 8 | 9 | #import "RZParagraphStyle.h" 10 | #import "RZColorfulAttribute.h" 11 | 12 | @interface RZMutableParagraphStyle() 13 | 14 | @end 15 | 16 | @implementation RZMutableParagraphStyle 17 | 18 | + (RZMutableParagraphStyle *)copyWith:(RZMutableParagraphStyle *)para { 19 | RZMutableParagraphStyle *style = [[RZMutableParagraphStyle alloc] init]; 20 | [style setParagraphStyle:para]; 21 | style.numberOfLines = para.numberOfLines; 22 | style.textDrawMaxWidth = para.textDrawMaxWidth; 23 | style.truncateText = para.truncateText; 24 | return style; 25 | } 26 | @end 27 | 28 | @interface RZParagraphStyle () 29 | 30 | @property (nonatomic, strong) RZMutableParagraphStyle *paragraph; 31 | @property (nonatomic, weak) RZColorfulAttribute *colorfulsAttr; 32 | @property (nonatomic, weak) RZImageAttachment *imageAttach; 33 | 34 | @end 35 | 36 | @implementation RZParagraphStyle 37 | 38 | - (instancetype)initWithAttr:(RZColorfulAttribute *)attr { 39 | if (self = [super init]) { 40 | self.colorfulsAttr = attr; 41 | } 42 | return self; 43 | } 44 | 45 | - (instancetype)initWithAttach:(RZImageAttachment *)attach { 46 | if (self = [super init]) { 47 | self.imageAttach = attach; 48 | } 49 | return self; 50 | } 51 | 52 | - (RZColorfulAttribute *)and { 53 | return _colorfulsAttr; 54 | } 55 | - (RZColorfulAttribute *)with { 56 | return _colorfulsAttr; 57 | } 58 | - (RZColorfulAttribute *)end { 59 | return _colorfulsAttr; 60 | } 61 | 62 | /** 63 | 连接词 如果阴影设置完毕,还想继续设置其他图片附件的信息,请使用andAttach,withAttach,endAttach,之后可以连接设置其他属性 64 | 65 | @return <#return value description#> 66 | */ 67 | - (RZImageAttachment *)andAttach { 68 | return _imageAttach; 69 | } 70 | - (RZImageAttachment *)withAttach { 71 | return _imageAttach; 72 | } 73 | - (RZImageAttachment *)endAttach { 74 | return _imageAttach; 75 | } 76 | - (RZMutableParagraphStyle *)code { 77 | if (_paragraph) { 78 | RZMutableParagraphStyle *p = [RZMutableParagraphStyle copyWith:_paragraph]; 79 | return p; 80 | } 81 | return nil; 82 | } 83 | 84 | - (RZMutableParagraphStyle *)paragraph { 85 | if (!_paragraph) { 86 | _paragraph = [[RZMutableParagraphStyle alloc] init]; 87 | } 88 | return _paragraph; 89 | } 90 | 91 | - (RZParagraphStyle *(^)(CGFloat))lineSpacing { 92 | return ^id (CGFloat lineSpacing) { 93 | self.paragraph.lineSpacing = lineSpacing; 94 | return self; 95 | }; 96 | } 97 | 98 | - (RZParagraphStyle *(^)(CGFloat))paragraphSpacing { 99 | return ^id (CGFloat paragraphSpacing) { 100 | self.paragraph.paragraphSpacing = paragraphSpacing; 101 | return self; 102 | }; 103 | } 104 | 105 | - (RZParagraphStyle *(^)(NSTextAlignment))alignment { 106 | return ^id (NSTextAlignment alignment) { 107 | self.paragraph.alignment = alignment; 108 | return self; 109 | }; 110 | } 111 | 112 | - (RZParagraphStyle *(^)(CGFloat))firstLineHeadIndent { 113 | return ^id (CGFloat firstLineHeadIndent) { 114 | self.paragraph.firstLineHeadIndent = firstLineHeadIndent; 115 | return self; 116 | }; 117 | } 118 | 119 | - (RZParagraphStyle *(^)(CGFloat))headIndent { 120 | return ^id (CGFloat headIndent) { 121 | self.paragraph.headIndent = headIndent; 122 | return self; 123 | }; 124 | } 125 | 126 | - (RZParagraphStyle *(^)(CGFloat))tailIndent { 127 | return ^id (CGFloat tailIndent) { 128 | self.paragraph.tailIndent = tailIndent; 129 | return self; 130 | }; 131 | } 132 | 133 | - (RZParagraphStyle *(^)(NSLineBreakMode))lineBreakMode { 134 | return ^id (NSLineBreakMode lineBreakMode) { 135 | self.paragraph.lineBreakMode = lineBreakMode; 136 | return self; 137 | }; 138 | } 139 | 140 | - (RZParagraphStyle *(^)(CGFloat))minimumLineHeight { 141 | return ^id (CGFloat minimumLineHeight) { 142 | self.paragraph.minimumLineHeight = minimumLineHeight; 143 | return self; 144 | }; 145 | } 146 | 147 | - (RZParagraphStyle *(^)(CGFloat))maximumLineHeight { 148 | return ^id (CGFloat maximumLineHeight) { 149 | self.paragraph.maximumLineHeight = maximumLineHeight; 150 | return self; 151 | }; 152 | } 153 | 154 | - (RZParagraphStyle *(^)(NSWritingDirection))baseWritingDirection { 155 | return ^id (NSWritingDirection baseWritingDirection) { 156 | self.paragraph.baseWritingDirection = baseWritingDirection; 157 | return self; 158 | }; 159 | } 160 | 161 | - (RZParagraphStyle *(^)(CGFloat))lineHeightMultiple { 162 | return ^id (CGFloat lineHeightMultiple) { 163 | self.paragraph.lineHeightMultiple = lineHeightMultiple; 164 | return self; 165 | }; 166 | } 167 | 168 | - (RZParagraphStyle *(^)(CGFloat))paragraphSpacingBefore { 169 | return ^id (CGFloat paragraphSpacingBefore) { 170 | self.paragraph.paragraphSpacingBefore = paragraphSpacingBefore; 171 | return self; 172 | }; 173 | } 174 | 175 | - (RZParagraphStyle *(^)(float))hyphenationFactor { 176 | return ^id (float hyphenationFactor) { 177 | self.paragraph.hyphenationFactor = hyphenationFactor; 178 | return self; 179 | }; 180 | } 181 | 182 | - (RZParagraphStyle *(^)(CGFloat))defaultTabInterval { 183 | return ^id (CGFloat defaultTabInterval) { 184 | self.paragraph.defaultTabInterval = defaultTabInterval; 185 | return self; 186 | }; 187 | } 188 | 189 | - (RZParagraphStyle *(^)(BOOL))allowsDefaultTighteningForTruncation { 190 | return ^id (BOOL allowsDefaultTighteningForTruncation) { 191 | self.paragraph.allowsDefaultTighteningForTruncation = allowsDefaultTighteningForTruncation; 192 | return self; 193 | }; 194 | } 195 | - (RZParagraphStyle *(^)(NSInteger numberOfLines, CGFloat maxWidth, NSAttributedString *truncateText))numberOfLines { 196 | return ^id (NSInteger numberOfLines, CGFloat maxWidth, NSAttributedString *truncateText) { 197 | self.paragraph.numberOfLines = numberOfLines; 198 | self.paragraph.textDrawMaxWidth = maxWidth; 199 | self.paragraph.truncateText = truncateText; 200 | return self; 201 | }; 202 | } 203 | @end 204 | -------------------------------------------------------------------------------- /RZColorful/Classes/AttributeCore/RZColorfulAttribute.h: -------------------------------------------------------------------------------- 1 | // 2 | // RZAttribute.h 3 | // RZAttributedStringText 4 | // 5 | // Created by 乄若醉灬 on 2017/7/21. 6 | // Copyright © 2017年 rztime. All rights reserved. 7 | // 8 | 9 | // 普通文本的所有属性的设置集合 10 | 11 | 12 | #import 13 | #import 14 | #import "RZShadow.h" 15 | #import "RZParagraphStyle.h" 16 | 17 | #define RZWARNING(instead) NS_DEPRECATED(2_0, 2_0, 2_0, 2_0, instead) 18 | 19 | /** 删除线样式 */ 20 | //typedef NS_OPTIONS(NSInteger, RZLineStyle) { 21 | // RZLineStyleNone = NSUnderlineStyleNone, // 不设置删除线 22 | // RZLineStyleSignl = NSUnderlineStyleSingle, // 删除线为细单实线 23 | // RZLineStyleThick = NSUnderlineStyleThick, // 为粗单实线 24 | // RZLineStyleDouble = NSUnderlineStyleDouble // 细双实线 25 | //}; 26 | // 替换成 NSUnderlineStyle,(NSUnderlineStyleSingle | NSUnderlineStylePatternDot )组合使用可以设置虚线、破折号等 27 | typedef NSUnderlineStyle RZLineStyle; 28 | 29 | typedef NS_ENUM(NSInteger, RZWriteDirection) { // 书写方向 30 | LRE, 31 | LRO, 32 | RLE, 33 | RLO, 34 | 35 | RZWDLeftToRight = LRO, // 从左到右 36 | RZWDRightToLeft = RLO, // 从右到左 37 | }; 38 | /// 给label添加一个点击事件的key,不用NSLink,是因为NSLink会默认加蓝色及下划线 39 | UIKIT_EXTERN NSAttributedStringKey const _Nonnull NSTapActionByLabelAttributeName; 40 | 41 | @interface RZColorfulAttribute : NSObject 42 | 43 | @property (nonatomic, assign, readonly) BOOL hadShadow; 44 | @property (nonatomic, assign, readonly) BOOL hadParagraphStyle; 45 | 46 | - (NSDictionary *_Nonnull)code; 47 | 48 | #pragma mark - 文本属性设置内容 49 | 50 | #pragma mark - 连接词 51 | /** 方便阅读用的连接词 */ 52 | - (RZColorfulAttribute *_Nonnull)and; 53 | - (RZColorfulAttribute *_Nonnull)with; 54 | 55 | #pragma mark - 基本属性设置 56 | /** 设置字体 */ 57 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(UIFont *__nullable))font; 58 | /** 设置文本颜色 */ 59 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(UIColor * __nullable))textColor; 60 | /** 当前文字的所在区域的背景颜色 */ 61 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(UIColor *__nullable))backgroundColor; 62 | /** 设置连体字,value = 0,没有连体, =1,有连体 */ 63 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(NSNumber *__nullable))ligature; 64 | /** 字间距 >0 加宽 < 0减小间距 */ 65 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(NSNumber *__nullable))wordSpace; 66 | /** 删除线 67 | 取值为 0 - 7时,效果为单实线,随着值得增加,单实线逐渐变粗, 68 | 取值为 9 - 15时,效果为双实线,取值越大,双实线越粗。 69 | 替换成 NSUnderlineStyle,(NSUnderlineStyleSingle | NSUnderlineStylePatternDot )组合使用可以设置虚线、破折号等 70 | */ 71 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(RZLineStyle))strikeThrough; 72 | /** 删除线颜色 */ 73 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(UIColor *__nullable))strikeThroughColor; 74 | /** 下划线样式 取值参照删除线,位置不同罢了 75 | 取值为 0 - 7时,效果为单实线,随着值得增加,单实线逐渐变粗, 76 | 取值为 9 - 15时,效果为双实线,取值越大,双实线越粗。 77 | 替换成 NSUnderlineStyle,(NSUnderlineStyleSingle | NSUnderlineStylePatternDot )组合使用可以设置虚线、破折号等 78 | */ 79 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(RZLineStyle))underLineStyle; 80 | /** 下划线颜色 */ 81 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(UIColor * __nullable))underLineColor; 82 | /** 描边的颜色 */ 83 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(UIColor * __nullable))strokeColor; 84 | /** 描边的笔画宽度 为3时,空心 负值填充效果,正值中空效果 */ 85 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(NSNumber * __nullable))strokeWidth; 86 | /** 基准偏移 为正:上偏移(上标) 为负:下偏移(下标) */ 87 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(NSNumber * __nullable))baselineOffset; 88 | /** 斜体字 */ 89 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(NSNumber * __nullable))italic; 90 | /** 扩张,即拉伸文字 >0 拉伸 <0压缩 */ 91 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(NSNumber * __nullable))expansion; 92 | /** 书写方向 */ 93 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(RZWriteDirection))writingDirection; 94 | /** 横竖排版 0:横版 1:竖版 (iOS中1竖版无效)*/ 95 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(NSNumber * __nullable))verticalGlyphForm; 96 | /** 特殊效果 */ 97 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(NSTextEffectStyle __nullable))textEffect; 98 | /** 自定义属性和值 */ 99 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(NSAttributedStringKey _Nonnull, id __nullable))custom; 100 | 101 | @end 102 | #pragma mark - 富文本 url,仅UITextViewd点击有效 103 | @interface RZColorfulAttribute (UILabel) 104 | /* 给文本添加点击事件的id, 仅UILabel有效,需要实现label.rz_tapAction方法 */ 105 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(NSString * __nullable))tapActionByLable; 106 | @end 107 | 108 | @interface RZColorfulAttribute (Other) 109 | #pragma mark - 设置文本段落样式 110 | /** 段落样式,具体设置请看 RZParagraphStyle.h */ 111 | - (RZParagraphStyle * _Nonnull )paragraphStyle; 112 | #pragma mark -阴影设置 113 | /** 阴影 给文本设置阴影,直接在shadow后添加阴影属性, 114 | 如阴影颜色confer.text(@"text").shadow.color(color); 115 | 如果需要继续添加text的属性,请使用and\with\end相连 116 | 如confer.text(@"text").shadow.color(color).and.textColor(tColor)... */ 117 | - (RZShadow *_Nonnull)shadow; 118 | @end 119 | 120 | #pragma mark - 富文本 url,仅UITextViewd点击有效 121 | @interface RZColorfulAttribute (UITextView) 122 | /** 给文本添加链接,并且可点击跳转浏览器打开 仅UITextView点击有效, 如果有实现点击事件, 可以替换成tapAction: 123 | 设置url属性,要实现点击,需实现UITextView的delegate的url点击事件 124 | 设置之后,url的文本会默认带蓝色下划线的样式,可以设置textView.linkTextAttributes = @{} 清除掉 125 | */ 126 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(NSURL * __nullable))url; 127 | /* 给属性文本添加点击事件 只有UITextView可以用,且UITextView需要实现block didTapTextView */ 128 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(NSString * __nullable))tapAction; 129 | @end 130 | 131 | @interface RZColorfulAttribute (iOS14) 132 | /// iOS 14 133 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(NSNumber * __nullable))tracking API_AVAILABLE(ios(14.0)); 134 | @end 135 | @interface RZColorfulAttribute (iOS15) 136 | /// 指定文本的展示意图 137 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(NSInlinePresentationIntent))inlinePresentationIntent API_AVAILABLE(ios(15.0)); 138 | /// 替代描述alter 139 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(NSString * __nullable))alternateDescription API_AVAILABLE(ios(15.0)); 140 | /// 与文本关联的图片的 URL 141 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(NSURL * __nullable))imageURL API_AVAILABLE(ios(15.0)); 142 | /// 标识文本的语言 如 zh-Hans 143 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(NSString * __nullable))languageIdentifier API_AVAILABLE(ios(15.0)); 144 | /// 文本替换的索引 145 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(NSInteger))replacementIndex API_AVAILABLE(ios(15.0)); 146 | /// 语法形态 147 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(NSMorphology * __nullable))morphology API_AVAILABLE(ios(15.0)); 148 | /// 变形规则,可搭配morphology使用 149 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(NSInflectionRule * __nullable))inflectionRule API_AVAILABLE(ios(15.0)); 150 | /// 指定替代的变体形式 151 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(NSString * __nullable))inflectionAlternative API_AVAILABLE(ios(15.0)); 152 | /// 标记文本的语义意图 153 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(NSPresentationIntent * __nullable))presentationIntentAttributeName API_AVAILABLE(ios(15.0)); 154 | @end 155 | @interface RZColorfulAttribute (iOS16) 156 | /// iOS 16 157 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(NSAttributedStringMarkdownSourcePosition * __nullable))markdownSourcePosition API_AVAILABLE(ios(16.0)); 158 | @end 159 | @interface RZColorfulAttribute (iOS17) 160 | /// iOS 17 161 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(id __nullable))agreeWithArgument API_AVAILABLE(ios(17.0)); 162 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(id __nullable))agreeWithConcept API_AVAILABLE(ios(17.0)); 163 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(id __nullable))referentConcept API_AVAILABLE(ios(17.0)); 164 | @end 165 | @interface RZColorfulAttribute (iOS18) 166 | /// iOS 18 167 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(id __nullable))localizedNumberFormat API_AVAILABLE(ios(18.0)); 168 | @end 169 | -------------------------------------------------------------------------------- /RZColorful/Classes/UICore/UIView+RZContinueFirstResponder.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+RZContinueFirstResponder.m 3 | // TextInputViewFocusDemo 4 | // 5 | // Created by rztime on 2017/11/10. 6 | // Copyright © 2017年 rztime. All rights reserved. 7 | // 8 | 9 | #import "UIView+RZContinueFirstResponder.h" 10 | #import 11 | 12 | @implementation UIViewResponderHelper 13 | 14 | - (instancetype)init { 15 | if (self = [super init]) { 16 | _nextFirstResponderTagIdentity = @""; 17 | _nextFirstResponderIndex = -1; 18 | } 19 | return self; 20 | } 21 | 22 | @end 23 | 24 | @implementation UIView (RZContinueFirstResponder) 25 | 26 | - (void)setTagIdentity:(NSString *)tagIdentity { 27 | objc_setAssociatedObject(self, @"rzTagIdentity", tagIdentity, OBJC_ASSOCIATION_COPY_NONATOMIC); 28 | } 29 | 30 | - (NSString *)tagIdentity { 31 | return objc_getAssociatedObject(self, @"rzTagIdentity"); 32 | } 33 | 34 | // 1. 获取当前view的所有的输入文本框 35 | // 2. 拿到当前view第一响应者的tagIdentity,以及其在所有的文本框中的index, 36 | // 3. 保存第一响应键盘 37 | // 4. 刷新 38 | // 5. 指定下一个nextFirstResponderTagIdentity第一响应的文本框 39 | // 6. 或者按原tagIdentity还原第一响应 40 | // 7. 如果没有tagIdentity, 则按之前的index,还原, 41 | // 8. 如果index越界,则取最后一个 42 | 43 | - (void)rzContinueFirstResponderAndExecuteCode:(void (^)(UIViewResponderHelper *nextResponder))code { 44 | // 1 45 | NSMutableArray *array = [NSMutableArray arrayWithArray:[self getAllTextField]]; 46 | NSInteger index = -1; 47 | NSString *tagIdentity = @""; 48 | id tagView; 49 | // 2 50 | for (int i = 0; i < array.count; i++) { 51 | UIView *view = array[i]; 52 | if ([view isFirstResponder]) { 53 | index = i; 54 | tagIdentity = view.tagIdentity; 55 | tagView = view; 56 | break ; 57 | } 58 | } 59 | // 3 保持界面第一响应 60 | id copyView; 61 | if (tagView) { 62 | copyView = [NSClassFromString(NSStringFromClass([tagView class])) new]; 63 | [[self superview] addSubview:copyView]; 64 | [copyView becomeFirstResponder]; 65 | } 66 | // 4.刷新 67 | [array removeAllObjects]; 68 | 69 | UIViewResponderHelper *responderHelper = [[UIViewResponderHelper alloc]init]; 70 | if (code) { 71 | code(responderHelper); 72 | } 73 | [self layoutIfNeeded]; 74 | 75 | array = [NSMutableArray arrayWithArray:[self getAllTextField]]; 76 | // 没有可用文本框,则结束 77 | if (array.count == 0) { 78 | [copyView resignFirstResponder]; 79 | [copyView removeFromSuperview]; 80 | return; 81 | } 82 | 83 | 84 | UIView *firstResponderView; 85 | // 5 指定下一个 86 | if (responderHelper.nextFirstResponderTagIdentity.length != 0) { 87 | for (UIView * view in array) { 88 | if ([view.tagIdentity isEqualToString:responderHelper.nextFirstResponderTagIdentity]) { 89 | firstResponderView = view; 90 | break; 91 | } 92 | } 93 | } 94 | 95 | // 之前的和下一个没找到,则按index寻找,越界则取最后一个 96 | if (!firstResponderView && responderHelper.nextFirstResponderIndex >= 0) { 97 | if (responderHelper.nextFirstResponderIndex >= array.count - 1) { 98 | firstResponderView = [array lastObject]; 99 | } else { 100 | firstResponderView = array[responderHelper.nextFirstResponderIndex]; 101 | } 102 | } 103 | 104 | // 6 还原之前的 105 | if (!firstResponderView && tagIdentity.length != 0) { 106 | for (UIView * view in array) { 107 | if ([view.tagIdentity isEqualToString:tagIdentity]) { 108 | firstResponderView = view; 109 | break; 110 | } 111 | } 112 | } 113 | // 之前的和下一个没找到,则按index寻找,越界则取最后一个 114 | if (!firstResponderView && index >= 0) { 115 | if (index >= array.count - 1) { 116 | firstResponderView = [array lastObject]; 117 | } else { 118 | firstResponderView = array[index]; 119 | } 120 | } 121 | if (firstResponderView) { 122 | [firstResponderView becomeFirstResponder]; 123 | } 124 | // 移除 125 | [copyView removeFromSuperview]; 126 | } 127 | 128 | #pragma mark - 获取当前view的所有的可输入文本框 129 | // 获得当前控制器的所有的文本框 130 | - (NSArray *)getAllTextField { 131 | // 获得所有的文本框,默认按照位置排序 132 | NSArray *textArray = [self deepResponderViews:self]; 133 | 134 | return textArray; 135 | } 136 | 137 | // 通过递归,来获得控件中的所有的textField 138 | - (NSArray*)deepResponderViews:(UIView *)superView { 139 | NSMutableArray *textFields = [[NSMutableArray alloc] init]; 140 | for (UIView *textField in superView.subviews) { 141 | if ([self canBecomeFirstResponder:textField]) { 142 | [textFields addObject:textField]; 143 | } 144 | if (textField.subviews.count && [textField isUserInteractionEnabled] && ![textField isHidden] && [textField alpha]!=0.0) { 145 | [textFields addObjectsFromArray:[self deepResponderViews:textField]]; 146 | } 147 | } 148 | // 按照位置从上到下,(从左到右进行排序) 149 | __weak typeof(self) weakself = self; 150 | return [textFields sortedArrayUsingComparator:^NSComparisonResult(UIView *view1, UIView *view2) { 151 | CGRect frame1 = [view1 convertRect:view1.bounds toView:weakself]; 152 | CGRect frame2 = [view2 convertRect:view2.bounds toView:weakself]; 153 | 154 | CGFloat x1 = CGRectGetMinX(frame1); 155 | CGFloat y1 = CGRectGetMinY(frame1); 156 | CGFloat x2 = CGRectGetMinX(frame2); 157 | CGFloat y2 = CGRectGetMinY(frame2); 158 | 159 | if (y1 < y2) { 160 | return NSOrderedAscending; 161 | } else if (y1 > y2) { 162 | return NSOrderedDescending; 163 | } 164 | //Else both y are same so checking for x positions 165 | else if (x1 < x2) { 166 | return NSOrderedAscending; 167 | } else if (x1 > x2) { 168 | return NSOrderedDescending; 169 | } else { 170 | return NSOrderedSame; 171 | } 172 | }]; 173 | 174 | return textFields; 175 | } 176 | 177 | // 是否是可以响应的文本框 178 | -(BOOL)canBecomeFirstResponder:(UIView *)view { 179 | BOOL canBecomeFirstResponder = NO; 180 | if ([view isKindOfClass:[UITextField class]]) { 181 | canBecomeFirstResponder = [(UITextField*)view isEnabled]; 182 | } else if ([view isKindOfClass:[UITextView class]]) { 183 | canBecomeFirstResponder = [(UITextView*)view isEditable]; 184 | } 185 | if (canBecomeFirstResponder == YES) { 186 | canBecomeFirstResponder = ([view isUserInteractionEnabled] && ![view isHidden] && [view alpha]!=0.0 && ![self isAlertViewTextField:view] && ![self isSearchBarTextField:view]); 187 | } 188 | return canBecomeFirstResponder; 189 | } 190 | 191 | // 是否是alertView中的文本框 192 | -(BOOL)isAlertViewTextField:(UIView *)view { 193 | UIResponder *alertViewController = [self viewController]; 194 | BOOL isAlertViewTextField = NO; 195 | while (alertViewController && isAlertViewTextField == NO) { 196 | if ([alertViewController isKindOfClass:[UIAlertController class]]) { 197 | isAlertViewTextField = YES; 198 | break; 199 | } 200 | alertViewController = [alertViewController nextResponder]; 201 | } 202 | return isAlertViewTextField; 203 | } 204 | 205 | // 是否是搜索框 206 | -(BOOL)isSearchBarTextField:(UIView *)view { 207 | UIResponder *searchBar = [self nextResponder]; 208 | BOOL isSearchBarTextField = NO; 209 | while (isSearchBarTextField == NO) { 210 | if ([searchBar isKindOfClass:[UISearchBar class]]) { 211 | isSearchBarTextField = YES; 212 | break; 213 | } else if ([searchBar isKindOfClass:[UIViewController class]]) { 214 | break; 215 | } 216 | searchBar = [searchBar nextResponder]; 217 | } 218 | return isSearchBarTextField; 219 | } 220 | 221 | - (UIViewController *)viewController { 222 | UIResponder *next = [self nextResponder]; 223 | do { 224 | if ([next isKindOfClass:[UIViewController class]]) { 225 | return (UIViewController *)next; 226 | } 227 | next = [next nextResponder]; 228 | } while (next != nil); 229 | return nil; 230 | } 231 | 232 | @end 233 | -------------------------------------------------------------------------------- /RZColorful/Classes/AttributeCore/RZColorfulConferrer.m: -------------------------------------------------------------------------------- 1 | // 2 | // RZAttributedMaker.m 3 | // RZAttributedStringText 4 | // 5 | // Created by 乄若醉灬 on 2017/7/21. 6 | // Copyright © 2017年 rztime. All rights reserved. 7 | // 8 | 9 | #import "RZColorfulConferrer.h" 10 | #import "NSAttributedString+RZColorful.h" 11 | #import "NSAttributedString+RZHtml.h" 12 | 13 | #import 14 | 15 | #define rzColorPlainText @"rzColorPlainText" // 普通文本 16 | #define rzColorImageText @"rzColorImageText" // 图片文本 17 | #define rzColorHTMLText @"rzColorHTMLText" // 网页文本 18 | #define rzColorImageUrlText @"rzColorImageUrlText" // 只有url的图片 19 | 20 | typedef NS_ENUM(NSInteger, RZColorfulAttributeBoxType) { 21 | RZColorfulAttributeBoxTypePlainText, 22 | RZColorfulAttributeBoxTypeImage, 23 | RZColorfulAttributeBoxTypeHTMLText, 24 | RZColorfulAttributeBoxTypeImageURL, 25 | }; 26 | 27 | @interface RZColorfulAttributeBox : NSObject 28 | 29 | @property (nonatomic, assign) RZColorfulAttributeBoxType type; 30 | @property (nonatomic, copy) NSString *text; 31 | @property (nonatomic, strong) UIImage *image; 32 | @property (nonatomic, strong) RZImageAttachment *attach; 33 | @property (nonatomic, strong) RZColorfulAttribute *attribute; 34 | 35 | - (NSAttributedString *)package:(RZParagraphStyle *)para shadow:(RZShadow *)shadow; 36 | 37 | @end 38 | 39 | @implementation RZColorfulAttributeBox 40 | #pragma clang diagnostic push 41 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 42 | - (NSAttributedString *)package:(RZParagraphStyle *)para shadow:(RZShadow *)shadow { 43 | NSMutableDictionary *dict = [self.attribute code].mutableCopy; 44 | if (!dict) { 45 | dict = NSMutableDictionary.new; 46 | } 47 | if (shadow && !self.attribute.hadShadow) { 48 | [dict setObject:[shadow code].copy forKey:NSShadowAttributeName]; 49 | } 50 | if (para && !self.attribute.hadParagraphStyle) { 51 | [dict setObject:[para code].copy forKey:NSParagraphStyleAttributeName]; 52 | } 53 | NSMutableAttributedString *text = NSMutableAttributedString.new; 54 | switch (self.type) { 55 | case RZColorfulAttributeBoxTypePlainText: { 56 | [text appendAttributedString:[[NSAttributedString alloc] initWithString:self.text]]; 57 | break; 58 | } 59 | case RZColorfulAttributeBoxTypeImage: { 60 | NSTextAttachment *attchment = [[NSTextAttachment alloc] init]; 61 | attchment.image = self.image; 62 | attchment.bounds = self.attach.imageBounds; 63 | NSDictionary *c = [self.attach code]; 64 | if (c) { 65 | [dict addEntriesFromDictionary:c]; 66 | } 67 | [text appendAttributedString: [NSAttributedString attributedStringWithAttachment:attchment]]; 68 | break; 69 | } 70 | case RZColorfulAttributeBoxTypeHTMLText: { 71 | [text appendAttributedString: [NSAttributedString htmlString:self.text]]; 72 | break; 73 | } 74 | case RZColorfulAttributeBoxTypeImageURL: { 75 | NSString *html = [self.attach toHTMLStringWithImageUrl:self.text]; 76 | NSAttributedString *imageString = [NSAttributedString htmlString:html].copy; 77 | NSDictionary *c = [self.attach code]; 78 | if (c) { 79 | [dict addEntriesFromDictionary:c]; 80 | } 81 | [text appendAttributedString:imageString]; 82 | break; 83 | } 84 | default: 85 | break; 86 | } 87 | if (dict != nil) { 88 | [text addAttributes:dict range:NSMakeRange(0, text.length)]; 89 | } 90 | return text; 91 | } 92 | 93 | @end 94 | 95 | RZColorfulAttributeBox *RZ_ATTRIBUTEBOXBY(id content, RZColorfulAttributeBoxType type) { 96 | RZColorfulAttributeBox *box = [RZColorfulAttributeBox new]; 97 | box.type = type; 98 | if (type == RZColorfulAttributeBoxTypePlainText || type == RZColorfulAttributeBoxTypeHTMLText) { 99 | box.attribute = [[RZColorfulAttribute alloc] init]; 100 | } else { 101 | box.attach = [[RZImageAttachment alloc] init]; 102 | } 103 | if ([content isKindOfClass:[NSString class]]) { 104 | box.text = content; 105 | } else if ([content isKindOfClass:[UIImage class]]) { 106 | box.image = content; 107 | } 108 | return box; 109 | } 110 | 111 | @interface RZColorfulConferrer() 112 | 113 | @property (nonatomic, strong) NSMutableArray *contents; 114 | @property (nonatomic, strong) RZParagraphStyle *paragraphStyle; 115 | @property (nonatomic, strong) RZShadow *shadow; 116 | 117 | @end 118 | 119 | @implementation RZColorfulConferrer 120 | 121 | - (NSAttributedString *)confer { 122 | NSMutableAttributedString *string = [[NSMutableAttributedString alloc]init]; 123 | for (RZColorfulAttributeBox *box in self.contents) { 124 | NSAttributedString *text = [box package:_paragraphStyle shadow:_shadow]; 125 | if (text.length == 0) { 126 | [string appendAttributedString:text]; 127 | continue; 128 | } 129 | RZMutableParagraphStyle *style = [text attribute:NSParagraphStyleAttributeName atIndex:0 effectiveRange:nil]; 130 | if ([style isKindOfClass:[RZMutableParagraphStyle class]]) { 131 | if (style.numberOfLines > 0 && style.textDrawMaxWidth > 0) { 132 | NSAttributedString *placeholder = style.truncateText; 133 | if (!placeholder) { 134 | placeholder = [text rz_copyAttributeToText:@"..."]; 135 | } 136 | NSLineBreakMode mode = style.lineBreakMode; 137 | style.lineBreakMode = NSLineBreakByWordWrapping; 138 | NSAttributedString *temp = [text rz_attributedStringBy:style.numberOfLines maxWidth:style.textDrawMaxWidth lineBreakMode:mode placeHolder:placeholder]; 139 | [string appendAttributedString:temp]; 140 | continue; 141 | } 142 | } 143 | [string appendAttributedString:text]; 144 | } 145 | return string.copy; 146 | } 147 | 148 | - (RZColorfulAttribute *(^)(NSString *))text{ 149 | return ^id(NSString *text) { 150 | if (!text) { 151 | text = @""; 152 | } 153 | RZColorfulAttributeBox *box = RZ_ATTRIBUTEBOXBY(text, RZColorfulAttributeBoxTypePlainText); 154 | [self.contents addObject:box]; 155 | return box.attribute; 156 | }; 157 | } 158 | 159 | /** 160 | 添加html格式的内容 161 | */ 162 | - (RZColorfulAttribute *(^)(NSString *))htmlText { 163 | return ^id (NSString *htmlText) { 164 | if (!htmlText) { 165 | htmlText = @""; 166 | } 167 | RZColorfulAttributeBox *box = RZ_ATTRIBUTEBOXBY(htmlText, RZColorfulAttributeBoxTypeHTMLText); 168 | [self.contents addObject:box]; 169 | return box.attribute; 170 | }; 171 | } 172 | 173 | - (RZImageAttachment *(^)(UIImage *))image { 174 | return ^id (UIImage *image){ 175 | if (!image) { 176 | image = [[UIImage alloc] init]; 177 | } 178 | RZColorfulAttributeBox *box = RZ_ATTRIBUTEBOXBY(image, RZColorfulAttributeBoxTypeImage); 179 | [self.contents addObject:box]; 180 | return box.attach; 181 | }; 182 | } 183 | /** 184 | 通过url添加图片 185 | */ 186 | - (RZImageAttachment *(^)(NSString * _Nullable))imageByUrl { 187 | return ^id (NSString *imageUrl){ 188 | if (!imageUrl) { 189 | imageUrl = @""; 190 | } 191 | RZColorfulAttributeBox *box = RZ_ATTRIBUTEBOXBY(imageUrl, RZColorfulAttributeBoxTypeImageURL); 192 | [self.contents addObject:box]; 193 | return box.attach; 194 | }; 195 | } 196 | /** 197 | 设置当前控件对象统一段落样式 198 | */ 199 | - (RZParagraphStyle * _Nullable)paragraphStyle { 200 | if (!_paragraphStyle) { 201 | _paragraphStyle = [[RZParagraphStyle alloc] init]; 202 | } 203 | return _paragraphStyle; 204 | } 205 | 206 | 207 | /** 208 | 设置统一阴影对象 209 | 210 | @return <#return value description#> 211 | */ 212 | - (RZShadow * _Nullable)shadow { 213 | if (!_shadow) { 214 | _shadow = [[RZShadow alloc] init]; 215 | } 216 | return _shadow; 217 | } 218 | 219 | - (NSMutableArray *)contents { 220 | if (!_contents) { 221 | _contents = [NSMutableArray new]; 222 | } 223 | return _contents; 224 | } 225 | #pragma clang diagnostic pop 226 | @end 227 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RZColorful 2 | 3 | [![CI Status](https://img.shields.io/travis/rztime/RZColorful.svg?style=flat)](https://travis-ci.org/rztime/RZColorful) 4 | [![Version](https://img.shields.io/cocoapods/v/RZColorful.svg?style=flat)](https://cocoapods.org/pods/RZColorful) 5 | [![License](https://img.shields.io/cocoapods/l/RZColorful.svg?style=flat)](https://cocoapods.org/pods/RZColorful) 6 | [![Platform](https://img.shields.io/cocoapods/p/RZColorful.svg?style=flat)](https://cocoapods.org/pods/RZColorful) 7 | 8 | ## Example 9 | 10 | To run the example project, clone the repo, and run `pod install` from the Example directory first. 11 | 12 | ## Requirements 13 | 14 | ## Installation 15 | 16 | RZColorful is available through [CocoaPods](https://cocoapods.org). To install 17 | it, simply add the following line to your Podfile: 18 | 19 | ```ruby 20 | pod 'RZColorful' 21 | ``` 22 | 23 | ## Author 24 | 25 | rztime, rztime@vip.qq.com QQ群:580839749 26 | 27 | ## License 28 | 29 | RZColorful is available under the MIT license. See the LICENSE file for more info. 30 | 31 | 32 | # RZColorful 33 | iOS NSAttributedString 富文本方法 (图文混排、多样式文本); 文本超行,自动添加"折叠"、"全部"; markdown 转 html; 34 | 支持UILabel、UITextView、UITextField中可点击带链接的文本 35 | 36 | ## 更新日志 37 | [更新日志](https://github.com/rztime/RZColorful/blob/master/UpdateLog.md) 38 | 39 | swift版本[RZColorfulSwift](https://github.com/rztime/RZColorfulSwift) 40 | 41 | UITextView实现的富文本编辑器[RZRichTextView](https://github.com/rztime/RZRichTextView) 42 | 43 | * NSAttributedString 的多样化设置(文字字体、颜色、阴影、段落样式、url、下划线,以及图文混排等等) 44 | * 添加UITextField、UITextView、UILabel的attributedText的富文本设置。 45 | * 扩展:添加一个刷新界面时保持文本框焦点的方法 [demo查看](https://github.com/rztime/ContinueFirsterResponder) 46 | * 富文本方法内容可单独抽出来,在下边这个文件夹中 47 | ``` 48 | #import "NSAttributedString+RZColorful.h" 49 | ``` 50 | 51 | ## 关于RZColorful 52 | * 支持UILabel、UITextView、UITextField的attributedText的设置。 53 | * 支持获取NSAttributedString中的图片 54 | * 支持 HTML 与 NSAttributedString互换(支持图片) 55 | * 支持 markdown 文档转换为html 56 | * 包含的属性快捷设置: 57 | * 段落样式 58 | * 阴影 59 | * 文本字体、颜色 60 | * 文本所在区域对应的背景颜色 61 | * 连体字 62 | * 字间距 63 | * 删除线、下划线,及其线条颜色 64 | * 描边,及其颜色 65 | * 斜体字 66 | * 拉伸 67 | * 上下标 68 | * 书写方向(从左到右,从右到左) 69 | * 通过html源码加载富文本 70 | * 通过url添加图片到富文本 71 | 72 | ## How to use 73 | * 请在需要使用的地方加上 74 | 75 | ```objc 76 | #import "RZColorful.h" 77 | ``` 78 | 79 | * 主要的功能: 80 | * RZColorfulConferrer 81 | * text -- 添加文本 82 | * htmlText -- 添加html源码 83 | * image -- 添加图片 84 | * imageByUrl -- 添加图片(通过图片的URL添加) 85 | * paragraphStyle -- 全局的段落样式 86 | * shadow -- 全局的阴影样式 87 | 88 | * RZColorfulAttribute -- 设置文本的所有的属性 89 | * RZImageAttachment -- 设置图片的所有的属性 90 | 91 | ### 图示说明 92 | 93 |

94 | 95 |

96 | 97 | ### 讲解 98 | 99 | * `RZColorfulConferrer` 中 `paragraphStyle` 和 `shadow` 属于全局的设置,在block中任何位置都可设置 100 | * 文字和图片都可设置`paragraphStyle` 和 `shadow`,对齐方式是`paragraphStyle` 的`alignment`,设置后,当前行文字图片的全局样式将被此覆盖,即全局样式无效,不影响其他行。 101 | 102 | 103 | ### 基本的简单使用方法 104 | ```objc 105 | [cell.textLabel rz_colorfulConfer:^(RZColorfulConferrer * _Nonnull confer) { 106 | confer.paragraphStyle.paragraphSpacing(15); // 设置段落之间的行距 107 | 108 | confer.text(@"日常用处:(图片+标题+描述)\n").font(rzFont(17)).textColor([UIColor redColor]); 109 | 110 | confer.appendImage([UIImage imageNamed:@"test.jpg"]).bounds(CGRectMake(0, -2, 15, 15)); // 图片 111 | confer.text(@" 姓 名: ").font(rzFont(15)).textColor(RGBA(151, 151, 151, 11)); 112 | confer.text(@"rztime").font(rzFont(15)).textColor(RGBA(51, 51, 51, 1)); 113 | 114 | confer.text(@"\n"); 115 | confer.appendImage([UIImage imageNamed:@"test.jpg"]).bounds(CGRectMake(0, -2, 15, 15)); 116 | confer.text(@" 时 间: ").font(rzFont(15)).textColor(RGBA(151, 151, 151, 11)); 117 | confer.text([NSString stringWithFormat:@"%@", [NSDate new]]).font(rzFont(15)).textColor(RGBA(51, 51, 51, 1)); 118 | 119 | confer.text(@"\n"); 120 | confer.appendImage([UIImage imageNamed:@"test.jpg"]).bounds(CGRectMake(0, -2, 15, 15)); 121 | confer.text(@" 当次消费: ").font(rzFont(15)).textColor(RGBA(151, 151, 151, 11)); 122 | confer.text(@"¥").font(rzFont(15)).textColor(RGBA(51, 51, 51, 1)); 123 | confer.text(@"100").font(rzFont(15)).textColor(RGBA(251, 51, 51, 1)); 124 | confer.text(@"元").font(rzFont(15)).textColor(RGBA(51, 51, 51, 1)); 125 | }]; 126 | ``` 127 | 效果如下 128 |

129 | 130 |

131 | 132 | ### 段落样式、阴影(局部与全局统一的区别) 133 | 如果设置有局部样式,则全局样式无效 134 | ```objc 135 | [cell.textLabel rz_colorfulConfer:^(RZColorfulConferrer * _Nonnull confer) { 136 | confer.paragraphStyle.lineSpacing(5).paragraphSpacingBefore(5).alignment(NSTextAlignmentCenter); // 段落全局样式 137 | confer.shadow.color(RGBA(255, 0, 0, 0.3)).offset(CGSizeMake(1, 1)); // 阴影全局 138 | 139 | // 此部分显示全局样式的风格 (红色阴影,居中对齐,段落行距等) 140 | confer.appendImage([UIImage imageNamed:@"test.jpg"]).bounds(CGRectMake(0, -2, 15, 15)); 141 | confer.text(@" 姓名: ").font(rzFont(15)).textColor(RGBA(151, 151, 151, 11)); 142 | confer.text(@"rztime").font(rzFont(15)).textColor(RGBA(51, 51, 51, 1)); 143 | 144 | confer.text(@"\n"); 145 | confer.appendImage([UIImage imageNamed:@"test.jpg"]).bounds(CGRectMake(0, -2, 15, 15)); 146 | confer.text(@" 时间: ").font(rzFont(15)).textColor(RGBA(151, 151, 151, 11)); 147 | confer.text([NSString stringWithFormat:@"%@", [NSDate new]]).font(rzFont(15)).textColor(RGBA(51, 51, 51, 1)); 148 | 149 | // 此部分显示全局样式的风格 (居中对齐,段落行距等) 阴影将被局部覆盖(灰色) 150 | confer.text(@"\n地址: ").font(rzFont(15)).textColor(RGBA(151, 151, 151, 11)).paragraphStyle.paragraphSpacingBefore(20).and.shadow.color(GRAY(151)).offset(CGSizeMake(3, 3));; 151 | confer.text(@"成都-软件园").font(rzFont(15)).textColor(RGBA(51, 51, 51, 1)).shadow.color(GRAY(151)).offset(CGSizeMake(3, 3)); 152 | 153 | // 此部分段落样式被局部覆盖 阴影显示全局的 154 | confer.text(@"\n爱好: ").font(rzFont(15)).textColor(RGBA(151, 151, 151, 11)).paragraphStyle.paragraphSpacingBefore(20); 155 | confer.text(@"游山、").font(rzFont(15)).textColor(RGBA(151, 51, 51, 1)); 156 | confer.text(@"玩水、").font(rzFont(10)).textColor(RGBA(51, 151, 51, 1)); 157 | confer.text(@"听歌、").font(rzFont(18)).textColor(RGBA(51, 51, 151, 1)); 158 | confer.text(@"美食、").font(rzFont(17)).textColor(RGBA(51, 151, 51, 1)); 159 | confer.text(@"看电影、").font(rzFont(16)).textColor(RGBA(151, 51, 51, 1)); 160 | confer.text(@"撸代码、").font(rzFont(15)).textColor(RGBA(51, 151, 51, 1)); 161 | confer.text(@"等等\n\n").font(rzFont(15)).textColor(RGBA(251, 51, 51, 1)); 162 | }]; 163 | ``` 164 | 效果如下 165 |

166 | 167 |

168 | 169 | 通过url加载图片 170 | ```objc 171 | [cell.textLabel rz_colorfulConfer:^(RZColorfulConferrer * _Nonnull confer) { 172 | confer.appendImageByUrl(@"http://pic28.photophoto.cn/20130830/0005018667531249_b.jpg").bounds(CGRectMake(0, 0, 200, 0)).paragraphStyle.alignment(NSTextAlignmentLeft); // 宽或高为0时,即自动宽/高按照图片比例来 173 | }]; 174 | ``` 175 | 通过html源码加载文本 176 | ```objc 177 | [cell.textLabel rz_colorfulConfer:^(RZColorfulConferrer * _Nonnull confer) { 178 | NSString *resourcePath = [[NSBundle mainBundle] resourcePath]; 179 | NSString *filePath =[resourcePath stringByAppendingPathComponent:@"test.html"]; 180 | NSString *htmlstring=[[NSString alloc] initWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil]; 181 | confer.htmlText(htmlstring); 182 | }]; 183 | ``` 184 | 185 | 给UILabel添加富文本可点击功能 给文本添加tapActionByLable属性 186 | ```objc 187 | [_label rz_colorfulConfer:^(RZColorfulConferrer * _Nonnull confer) { 188 | confer.text(@"可点击的:").font([UIFont systemFontOfSize:17]).textColor(UIColor.blackColor); 189 | confer.text(@"文本").font([UIFont systemFontOfSize:17]).textColor(UIColor.redColor).tapActionByLable(@"1"); 190 | }]; 191 | [_label rz_tapAction:^(UILabel * _Nonnull label, NSString * _Nonnull tapActionId, NSRange range) { 192 | NSLog(@"%@", tapActionId); // print: 1 193 | }]; 194 | ``` 195 | 196 | 给UILabel添加超行之后的折叠、展开功能 197 | ```objc 198 | UILabel 199 | 200 | /// 设置富文本(超过行数后,自动追加“展开” “收起”) 201 | /// @param attr 原文 202 | /// @param line 最大显示行数 203 | /// @param width 最大显示宽度,这个宽度用于计算文本行 204 | /// @param fold 当前是否折叠 205 | /// @param allText 超过了行数之后,折叠状态显示的文本 如”展开“ 需要给文本设置NSTapActionByLabel属性 (tapActionByLable) 206 | /// @param foldText 超过行数之后,全部展开状态显示的文本 如”收起“ 需要给文本设置NSTapActionByLabel属性 (tapActionByLable) 207 | - (void)rz_setAttributedString:(NSAttributedString * _Nullable)attr maxLine:(NSInteger)line maxWidth:(CGFloat)width isFold:(BOOL)fold showAllText:(NSAttributedString *_Nullable)allText showFoldText:(NSAttributedString *_Nullable)foldText; 208 | ``` 209 | 210 | # 备注: 211 | * 多种属性使用名请参考对应的文件。 212 | * UILabel、UITextFile是同样的使用方法。 213 | * 在UILabel、UITextFiled上url点击方法无效。 214 | * 在UITextView中若要设置文本和图片的点击事件(即对文本和图片设置附带URL的属性),请先设置其editable = NO, 并实现代理。 215 | * 设置了URL属性的文本,内部自带蓝色和下划线,若要去掉,可设置 `textView.linkTextAttributes = @{};` 216 | 217 | ```objc 218 | // 实现下列方法 (二选一、或都可以实现,当都返回YES时,可能会打开safari浏览器) 219 | textView.rzDidTapTextView = ^BOOL(id _Nullable tapObj) { 220 | NSString *url = tapObj; 221 | if ([tapObj isKindOfClass:[NSURL class]]) { 222 | url = [(NSURL *)tapObj absoluteString]; 223 | } 224 | url = url.rz_decodedString; 225 | 226 | NSLog(@"rzDidTapTextView:tapObj:%@ \n如果url中包含了有中文,URL将会进行编码,所以请rz_decodedString解码之后查看:%@", tapObj, url); 227 | return NO; 228 | }; 229 | 230 | - (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange interaction:(UITextItemInteraction)interaction { 231 | NSLog(@"URL:%@", URL); 232 | // return NO;则不跳转,这里可以做一些基本判断在执行是否跳转浏览器打开url 233 | return YES; 234 | } 235 | ``` 236 | 237 | ## 注意 238 | 239 | * 尽管我已经在代码中已经处理过(弱)引用问题,但是在实际运用写入text时,还是请尽量检查避免循环引用 240 | 241 | 242 | ## 最后 243 | * 在使用过程中,如果您发现有什么问题,欢迎向我反馈,谢谢 244 | -------------------------------------------------------------------------------- /RZColorful/Classes/AttributeCore/RZColorfulAttribute.m: -------------------------------------------------------------------------------- 1 | // 2 | // RZAttribute.m 3 | // RZAttributedStringText 4 | // 5 | // Created by 乄若醉灬 on 2017/7/21. 6 | // Copyright © 2017年 rztime. All rights reserved. 7 | // 8 | 9 | #import "RZColorfulAttribute.h" 10 | #import "NSString+RZCode.h" 11 | 12 | @interface RZColorfulAttribute () 13 | 14 | @property (nonatomic, strong) NSMutableDictionary *colorfuls; 15 | @property (nonatomic, strong) RZShadow *shadow; 16 | @property (nonatomic, strong) RZParagraphStyle *paragraphStyle; 17 | 18 | @end 19 | 20 | @implementation RZColorfulAttribute 21 | 22 | #pragma clang diagnostic push 23 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 24 | 25 | - (instancetype)init { 26 | if (self = [super init]) { 27 | _colorfuls = [NSMutableDictionary new]; 28 | } 29 | return self; 30 | } 31 | - (RZColorfulAttribute *)and { 32 | return self; 33 | } 34 | - (RZColorfulAttribute *)with { 35 | return self; 36 | } 37 | - (NSDictionary *)code { 38 | if (_hadShadow) { 39 | [self.colorfuls setObject:[_shadow code] forKey:NSShadowAttributeName]; 40 | } 41 | if (_hadParagraphStyle) { 42 | RZMutableParagraphStyle *style = [RZMutableParagraphStyle copyWith:[_paragraphStyle code]]; 43 | [self.colorfuls setObject:style forKey:NSParagraphStyleAttributeName]; 44 | } 45 | return _colorfuls.copy; 46 | } 47 | /** 设置字体 */ 48 | - (RZColorfulAttribute *(^)(UIFont *))font { 49 | return ^id(UIFont *font) { 50 | self.colorfuls[NSFontAttributeName] = font; 51 | return self; 52 | }; 53 | } 54 | /** 设置文本颜色 */ 55 | - (RZColorfulAttribute *(^) (UIColor *)) textColor { 56 | return ^id(UIColor *textColor) { 57 | self.colorfuls[NSForegroundColorAttributeName] = textColor; 58 | return self; 59 | }; 60 | } 61 | /** 设置文字背景颜色 */ 62 | - (RZColorfulAttribute *(^)(UIColor *))backgroundColor { 63 | return ^id (UIColor *backgroundColor) { 64 | self.colorfuls[NSBackgroundColorAttributeName] = backgroundColor; 65 | return self; 66 | }; 67 | } 68 | /** 设置连体字,value = 0,没有连体, =1,有连体 */ 69 | - (RZColorfulAttribute *(^)(NSNumber *))ligature { 70 | return ^id (NSNumber *ligature) { 71 | self.colorfuls[NSLigatureAttributeName] = ligature; 72 | return self; 73 | }; 74 | } 75 | /** 字间距 >0 加宽 < 0减小间距 */ 76 | - (RZColorfulAttribute *(^)(NSNumber *))wordSpace { 77 | return ^id (NSNumber *wordSpace) { 78 | self.colorfuls[NSKernAttributeName] = wordSpace; 79 | return self; 80 | }; 81 | } 82 | /** 删除线 */ 83 | - (RZColorfulAttribute *(^)(RZLineStyle))strikeThrough { 84 | return ^id (RZLineStyle strikeThrough) { 85 | self.colorfuls[NSStrikethroughStyleAttributeName] = @(strikeThrough); 86 | return self; 87 | }; 88 | } 89 | /** 删除线颜色 */ 90 | - (RZColorfulAttribute *(^)(UIColor *))strikeThroughColor { 91 | return ^id (UIColor *strikeThroughColor) { 92 | self.colorfuls[NSStrikethroughColorAttributeName] = strikeThroughColor; 93 | return self; 94 | }; 95 | } 96 | /** 下划线样式 */ 97 | - (RZColorfulAttribute *(^)(RZLineStyle))underLineStyle { 98 | return ^id (RZLineStyle underLineStyle) { 99 | self.colorfuls[NSUnderlineStyleAttributeName] = @(underLineStyle); 100 | return self; 101 | }; 102 | } 103 | /** 下划线颜色 */ 104 | - (RZColorfulAttribute *(^)(UIColor *))underLineColor { 105 | return ^id (UIColor *underLineColor) { 106 | self.colorfuls[NSUnderlineColorAttributeName] = underLineColor; 107 | return self; 108 | }; 109 | } 110 | /** 描边的颜色 */ 111 | - (RZColorfulAttribute *(^)(UIColor *))strokeColor { 112 | return ^id (UIColor *strokeColor) { 113 | self.colorfuls[NSStrokeColorAttributeName] = strokeColor; 114 | return self; 115 | }; 116 | } 117 | /** 描边的笔画宽度 为3时,空心 */ 118 | - (RZColorfulAttribute *(^)(NSNumber *))strokeWidth { 119 | return ^id (NSNumber *strokeWidth) { 120 | self.colorfuls[NSStrokeWidthAttributeName] = strokeWidth; 121 | return self; 122 | }; 123 | } 124 | /** 上下标 */ 125 | - (RZColorfulAttribute *(^)(NSNumber *))baselineOffset { 126 | return ^id (NSNumber *baselineOffset) { 127 | self.colorfuls[NSBaselineOffsetAttributeName] = baselineOffset; 128 | return self; 129 | }; 130 | } 131 | /** 斜体字 */ 132 | - (RZColorfulAttribute *(^)(NSNumber *))italic { 133 | return ^id (NSNumber *italic) { 134 | self.colorfuls[NSObliquenessAttributeName] = italic; 135 | return self; 136 | }; 137 | } 138 | /** 扩张,即拉伸文字 >0 拉伸 <0压缩 */ 139 | - (RZColorfulAttribute *(^)(NSNumber *))expansion { 140 | return ^id (NSNumber *expansion) { 141 | self.colorfuls[NSExpansionAttributeName] = expansion; 142 | return self; 143 | }; 144 | } 145 | /** 书写方向 */ 146 | - (RZColorfulAttribute *(^)(RZWriteDirection))writingDirection { 147 | return ^id (RZWriteDirection rzwriteDirection) { 148 | NSInteger O = 0; 149 | NSInteger E = 0; 150 | if (@available(iOS 9.0, *)) { 151 | O = NSWritingDirectionOverride; 152 | E = NSWritingDirectionEmbedding; 153 | } else { 154 | O = NSTextWritingDirectionOverride; 155 | E = NSTextWritingDirectionEmbedding; 156 | } 157 | id value; 158 | switch (rzwriteDirection) { 159 | case LRE:{ 160 | value = @[@(NSWritingDirectionLeftToRight | E)]; 161 | break; 162 | } 163 | case LRO:{ 164 | value = @[@(NSWritingDirectionLeftToRight | O)]; 165 | break; 166 | } 167 | case RLE:{ 168 | value = @[@(NSWritingDirectionRightToLeft | E)]; 169 | break; 170 | } 171 | case RLO:{ 172 | value = @[@(NSWritingDirectionRightToLeft | O)]; 173 | break; 174 | } 175 | } 176 | self.colorfuls[NSWritingDirectionAttributeName] = value; 177 | return self; 178 | }; 179 | } 180 | /** 横竖排版 0:横版 1:竖版 */ 181 | - (RZColorfulAttribute *(^)(NSNumber *))verticalGlyphForm { 182 | return ^id (NSNumber *verticalGlyphForm) { 183 | self.colorfuls[NSVerticalGlyphFormAttributeName] = verticalGlyphForm; 184 | return self; 185 | }; 186 | } 187 | /** 特殊效果 */ 188 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(NSTextEffectStyle))textEffect { 189 | return ^id (NSTextEffectStyle style) { 190 | self.colorfuls[NSTextEffectAttributeName] = style; 191 | return self; 192 | }; 193 | } 194 | /** 自定义属性和值 */ 195 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(NSAttributedStringKey _Nonnull, id __nullable))custom { 196 | return ^id (NSAttributedStringKey key, id value) { 197 | self.colorfuls[key] = value; 198 | return self; 199 | }; 200 | } 201 | 202 | /** 给文本添加链接,并且可点击跳转浏览器打开 */ 203 | - (RZColorfulAttribute *(^)(NSURL *))url { 204 | return ^id (NSURL *url) { 205 | self.colorfuls[NSLinkAttributeName] = url; 206 | return self; 207 | }; 208 | } 209 | /* 给属性文本添加点击事件 只有UITextView可以用,且UITextView需要实现block didTapTextView */ 210 | - (RZColorfulAttribute *(^)(NSString *))tapAction { 211 | return ^id(NSString *tapId) { 212 | self.colorfuls[NSLinkAttributeName] = tapId.rz_encodedString; 213 | return self; 214 | }; 215 | } 216 | 217 | NSAttributedStringKey const NSTapActionByLabelAttributeName = @"RZTapActionByLabel"; 218 | 219 | /* 给文本添加点击事件的id, 仅UILabel有效,需要实现label.rz_tapAction方法 */ 220 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(NSString * __nullable))tapActionByLable { 221 | return ^id (NSString *tapId) { 222 | self.colorfuls[NSTapActionByLabelAttributeName] = tapId.rz_encodedString;; 223 | return self; 224 | }; 225 | } 226 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(NSNumber *))tracking API_AVAILABLE(ios(14.0)) { 227 | return ^id(NSNumber * tracking) { 228 | self.colorfuls[NSTrackingAttributeName] = tracking; 229 | return self; 230 | }; 231 | } 232 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(NSInlinePresentationIntent))inlinePresentationIntent API_AVAILABLE(ios(15.0)) { 233 | return ^id(NSInlinePresentationIntent intent) { 234 | self.colorfuls[NSInlinePresentationIntentAttributeName] = @(intent); 235 | return self; 236 | }; 237 | } 238 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(NSString * __nullable))alternateDescription API_AVAILABLE(ios(15.0)) { 239 | return ^id(id alternateDescription) { 240 | self.colorfuls[NSAlternateDescriptionAttributeName] = alternateDescription; 241 | return self; 242 | }; 243 | } 244 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(NSURL * __nullable))imageURL API_AVAILABLE(ios(15.0)) { 245 | return ^id(id imageURL) { 246 | self.colorfuls[NSImageURLAttributeName] = imageURL; 247 | return self; 248 | }; 249 | } 250 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(NSString * __nullable))languageIdentifier API_AVAILABLE(ios(15.0)) { 251 | return ^id(id languageIdentifier) { 252 | self.colorfuls[NSLanguageIdentifierAttributeName] = languageIdentifier; 253 | return self; 254 | }; 255 | } 256 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(NSInteger))replacementIndex API_AVAILABLE(ios(15.0)) { 257 | return ^id(NSInteger replacementIndex) { 258 | self.colorfuls[NSReplacementIndexAttributeName] = @(replacementIndex); 259 | return self; 260 | }; 261 | } 262 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(NSMorphology * __nullable))morphology API_AVAILABLE(ios(15.0)) { 263 | return ^id(id morphology) { 264 | self.colorfuls[NSMorphologyAttributeName] = morphology; 265 | return self; 266 | }; 267 | } 268 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(NSInflectionRule * __nullable))inflectionRule API_AVAILABLE(ios(15.0)) { 269 | return ^id(id inflectionRule) { 270 | self.colorfuls[NSInflectionRuleAttributeName] = inflectionRule; 271 | return self; 272 | }; 273 | } 274 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(NSString * __nullable))inflectionAlternative API_AVAILABLE(ios(15.0)) { 275 | return ^id(id inflectionAlternative) { 276 | self.colorfuls[NSInflectionAlternativeAttributeName] = inflectionAlternative; 277 | return self; 278 | }; 279 | } 280 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(NSPresentationIntent * __nullable))presentationIntentAttributeName API_AVAILABLE(ios(15.0)) { 281 | return ^id(id presentationIntentAttributeName) { 282 | self.colorfuls[NSPresentationIntentAttributeName] = presentationIntentAttributeName; 283 | return self; 284 | }; 285 | } 286 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(NSAttributedStringMarkdownSourcePosition * __nullable))markdownSourcePosition API_AVAILABLE(ios(16.0)) { 287 | return ^id(id markdownSourcePosition) { 288 | self.colorfuls[NSMarkdownSourcePositionAttributeName] = markdownSourcePosition; 289 | return self; 290 | }; 291 | } 292 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(id __nullable))agreeWithArgument API_AVAILABLE(ios(17.0)) { 293 | return ^id(id agreeWithArgument) { 294 | self.colorfuls[NSInflectionAgreementArgumentAttributeName] = agreeWithArgument; 295 | return self; 296 | }; 297 | } 298 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(id __nullable))agreeWithConcept API_AVAILABLE(ios(17.0)) { 299 | return ^id(id agreeWithConcept) { 300 | self.colorfuls[NSInflectionAgreementConceptAttributeName] = agreeWithConcept; 301 | return self; 302 | }; 303 | } 304 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(id __nullable))referentConcept API_AVAILABLE(ios(17.0)) { 305 | return ^id(id referentConcept) { 306 | self.colorfuls[NSInflectionReferentConceptAttributeName] = referentConcept; 307 | return self; 308 | }; 309 | } 310 | - (RZColorfulAttribute * _Nonnull(^_Nonnull)(id __nullable))localizedNumberFormat API_AVAILABLE(ios(18.0)) { 311 | return ^id(id localizedNumberFormat) { 312 | self.colorfuls[NSLocalizedNumberFormatAttributeName] = localizedNumberFormat; 313 | return self; 314 | }; 315 | } 316 | // 阴影 317 | - (RZShadow *)shadow { 318 | if (!_shadow) { 319 | _shadow = [[RZShadow alloc] initWithAttr:self]; 320 | _hadShadow = YES; 321 | } 322 | return _shadow; 323 | } 324 | /** 段落样式,具体设置请看 RZParagraphStyle.h */ 325 | - (RZParagraphStyle *)paragraphStyle { 326 | if (!_paragraphStyle) { 327 | _paragraphStyle = [[RZParagraphStyle alloc] initWithAttr:self]; 328 | _hadParagraphStyle = YES; 329 | } 330 | return _paragraphStyle; 331 | } 332 | 333 | #pragma clang diagnostic pop 334 | @end 335 | -------------------------------------------------------------------------------- /RZColorful/Classes/AttributeCore/NSAttributedString+RZColorful.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSAttributedString+RZColorful.m 3 | // RZColorfulExample 4 | // 5 | // Created by rztime on 2017/11/15. 6 | // Copyright © 2017年 rztime. All rights reserved. 7 | // 8 | 9 | #import "NSAttributedString+RZColorful.h" 10 | #import 11 | #import 12 | #import "NSAttributedString+RZHtml.h" 13 | 14 | #pragma clang diagnostic push 15 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 16 | 17 | @implementation NSAttributedString (RZColorful) 18 | 19 | + (NSAttributedString *)rz_colorfulConfer:(void(^)(RZColorfulConferrer *confer))attribute { 20 | if(!attribute) { 21 | return [NSAttributedString new]; 22 | } 23 | RZColorfulConferrer *conferrer = [[RZColorfulConferrer alloc]init]; 24 | attribute(conferrer); 25 | NSAttributedString *attr = [conferrer confer]; 26 | return attr; 27 | } 28 | 29 | - (NSAttributedString *)attributedStringByAppend:(NSAttributedString *)attributedString { 30 | NSMutableAttributedString *attr = [[NSMutableAttributedString alloc] initWithAttributedString:self]; 31 | [attr appendAttributedString:attributedString]; 32 | return attr.copy; 33 | } 34 | 35 | /** 36 | 固定宽度,计算高 37 | 38 | @param width 固定宽度 39 | @return <#return value description#> 40 | */ 41 | - (CGSize)sizeWithConditionWidth:(CGFloat)width { 42 | CGSize size = [self sizeWithCondition:CGSizeMake(width, CGFLOAT_MAX)]; 43 | size.width = width; 44 | return size; 45 | } 46 | 47 | 48 | /** 49 | 固定高度,计算宽 50 | 51 | @param height 固定高度 52 | @return <#return value description#> 53 | */ 54 | - (CGSize)sizeWithConditionHeight:(CGFloat)height { 55 | CGSize size = [self sizeWithCondition:CGSizeMake(CGFLOAT_MAX, height)]; 56 | size.height = height; 57 | return size; 58 | } 59 | /** 60 | 计算富文本的size 61 | 如需计算高度 size = CGSizeMake(300, CGFLOAT_MAX) 62 | 如需计算宽度 size = CGSizeMake(CGFLOAT_MAX, 18) 63 | 64 | @param size 约定的size,以宽高做条件,定宽时,计算得到高度(此时忽略宽度) 65 | 定高时,计算得到宽度 (此时忽略高度) 66 | @return <#return value description#> 67 | */ 68 | - (CGSize)sizeWithCondition:(CGSize)size { 69 | CGRect rect = [self boundingRectWithSize:size options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading context:nil]; 70 | return rect.size; 71 | } 72 | 73 | 74 | // 获取富文本中的图片 75 | - (NSArray *)rz_images { 76 | NSMutableArray *arrays = [NSMutableArray new]; 77 | [self enumerateAttribute:NSAttachmentAttributeName inRange:NSMakeRange(0, self.length) options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) { 78 | if ([value isKindOfClass:[NSTextAttachment class]]) { 79 | NSTextAttachment *imageMent = value; 80 | if (imageMent.image) { 81 | [arrays addObject:imageMent.image]; 82 | } else if(imageMent.fileWrapper.regularFileContents) { 83 | UIImage *image = [UIImage imageWithData:imageMent.fileWrapper.regularFileContents]; 84 | if (image) { 85 | [arrays addObject:image]; 86 | } 87 | } 88 | } 89 | }]; 90 | return arrays.copy; 91 | } 92 | 93 | 94 | - (NSArray *)rz_attributedStringByAttributeName:(NSAttributedStringKey)attrName { 95 | if (self.length == 0) { 96 | return nil; 97 | } 98 | NSMutableArray *attris = [NSMutableArray new]; 99 | __weak typeof (self) weakSelf = self; 100 | [self enumerateAttribute:attrName inRange:NSMakeRange(0, self.length) options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) { 101 | if (value) { 102 | RZAttributedStringInfo *info = [RZAttributedStringInfo new]; 103 | info.attributedString = [weakSelf attributedSubstringFromRange:range].mutableCopy; 104 | info.range = range; 105 | info.value = value; 106 | info.attrName = attrName; 107 | [attris addObject:info]; 108 | } 109 | }]; 110 | return attris; 111 | } 112 | 113 | /// 绘制在rect范围内时, 获取每一行的文本信息, 114 | /// @param rect 绘制范围 115 | - (NSArray *)rz_linesIfDrawInRect:(CGRect)rect { 116 | CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)self); 117 | CGMutablePathRef path = CGPathCreateMutable(); 118 | CGPathAddRect(path, NULL, rect); 119 | CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, NULL); 120 | NSArray *lines = (__bridge NSArray *)CTFrameGetLines(frame); 121 | 122 | NSMutableArray *infos = [NSMutableArray new]; 123 | for (id line in lines) { 124 | CTLineRef lineRef = (__bridge CTLineRef )line; 125 | CFRange lineRange = CTLineGetStringRange(lineRef); 126 | NSRange range = NSMakeRange(lineRange.location, lineRange.length); 127 | 128 | RZAttributedStringInfo *info = [RZAttributedStringInfo new]; 129 | info.attributedString = [self attributedSubstringFromRange:range].mutableCopy; 130 | info.range = range; 131 | [infos addObject:info]; 132 | } 133 | return infos.copy; 134 | } 135 | 136 | /// 将attr追加到当前文本上 ,只有在rect上绘制,小于(等于或大于)line时,才将attr追加到self之后 137 | /// @param attr 要追加的文本 138 | /// @param condition 满足的条件 139 | /// @param line 行数 140 | /// @param rect 要绘制的rect 141 | - (NSAttributedString *)rz_appendAttributedString:(NSAttributedString *)attr when:(RZAttributedStringAppendCondition)condition line:(NSInteger)line inRect:(CGRect)rect { 142 | if (!attr) { 143 | attr = [NSAttributedString new]; 144 | } 145 | NSMutableAttributedString *tempAttr = self.mutableCopy; 146 | NSArray *lines = [tempAttr rz_linesIfDrawInRect:rect]; 147 | NSComparisonResult result = [@(lines.count) compare:@(line)]; 148 | if ((RZAttributedStringAppendCondition)result == condition) { 149 | for (NSInteger lineNum = MIN(lines.count, line) - 1; lineNum >= 0; lineNum--) { 150 | BOOL flag = NO; 151 | RZAttributedStringInfo *lastLineInfo = lines[lineNum]; 152 | for (NSInteger row = lastLineInfo.range.length; row >= 0; row--) { 153 | NSInteger length = lastLineInfo.range.location + row; 154 | 155 | NSMutableAttributedString *temp = [tempAttr attributedSubstringFromRange:NSMakeRange(0, length)].mutableCopy; 156 | [temp appendAttributedString:attr]; 157 | NSArray *tempLines = [temp rz_linesIfDrawInRect:rect]; 158 | if (tempLines.count <= line) { 159 | RZAttributedStringInfo *tempLast = tempLines.lastObject; 160 | CGSize size = [tempLast.attributedString sizeWithConditionHeight:CGFLOAT_MAX]; 161 | if (size.width <= rect.size.width) { 162 | flag = YES; 163 | 164 | if ([temp.string hasSuffix:@"\n"]) { 165 | [temp replaceCharactersInRange:NSMakeRange(temp.length - 1, 1) withString:@""]; 166 | } 167 | tempAttr = temp.copy; 168 | break; 169 | } 170 | } 171 | } 172 | if (flag) { 173 | break; 174 | } 175 | } 176 | } 177 | return tempAttr.copy; 178 | } 179 | 180 | @end 181 | 182 | @implementation NSAttributedString(RZLines) 183 | 184 | - (BOOL)rz_moreThan:(NSInteger)line maxWidth:(CGFloat)width { 185 | UILabel *label = [[UILabel alloc] init]; 186 | label.numberOfLines = 0; 187 | label.attributedText = self; 188 | CGSize tempSize = CGSizeMake(width, CGFLOAT_MAX); 189 | CGFloat allHeight = [label textRectForBounds:CGRectMake(0, 0, tempSize.width, tempSize.height) limitedToNumberOfLines:0].size.height; 190 | CGFloat lineHeight = [label textRectForBounds:CGRectMake(0, 0, tempSize.width, tempSize.height) limitedToNumberOfLines:line].size.height; 191 | return ceil(allHeight) > ceil(lineHeight); 192 | } 193 | 194 | - (NSAttributedString *)rz_attributedStringBy:(NSInteger)maxLine maxWidth:(CGFloat)width isFold:(BOOL)fold showAllText:(NSAttributedString *)allText showFoldText:(NSAttributedString *)foldText { 195 | if (self.length == 0 || maxLine == 0 || width == 0) { 196 | return self; 197 | } 198 | if (![self rz_moreThan:maxLine maxWidth:width]) { 199 | return self; 200 | } 201 | if (!fold) { 202 | NSMutableAttributedString *attr = [[NSMutableAttributedString alloc] initWithAttributedString:self]; 203 | if (foldText != nil) { 204 | [attr appendAttributedString:foldText]; 205 | } 206 | return attr; 207 | } 208 | return [self rz_attributedStringBy:maxLine maxWidth:width lineBreakMode:NSLineBreakByTruncatingTail placeHolder:allText]; 209 | } 210 | /// 对富文本进行截断处理 211 | /// - Parameters: 212 | /// - maxLine: 设置超过多少截断 213 | /// - width: 显示的最大宽度 214 | /// - model: 截断方式 215 | /// - placeHolder: 截断时占位的"..."文字 216 | - (NSAttributedString * _Nullable)rz_attributedStringBy:(NSInteger)maxLine maxWidth:(CGFloat)width lineBreakMode:(NSLineBreakMode)model placeHolder:(NSAttributedString *_Nullable)placeHolder { 217 | if (self.length == 0 || maxLine == 0 || width == 0) { 218 | return self; 219 | } 220 | if (![self rz_moreThan:maxLine maxWidth:width]) { 221 | return self; 222 | } 223 | NSAttributedString *holder = placeHolder == nil ? [NSAttributedString new] : placeHolder; 224 | NSInteger left = 0; 225 | NSInteger right = self.length; 226 | NSInteger location = 0; 227 | NSAttributedString *resultAttr; 228 | switch (model) { 229 | case NSLineBreakByWordWrapping: 230 | case NSLineBreakByCharWrapping: 231 | case NSLineBreakByClipping: 232 | case NSLineBreakByTruncatingTail: 233 | while (left <= right) { 234 | location = (left + right) / 2; 235 | NSAttributedString *sub = [self attributedSubstringFromRange:NSMakeRange(0, location)]; 236 | NSMutableAttributedString *temp = sub.mutableCopy; 237 | if (model == NSLineBreakByTruncatingTail) { 238 | [temp appendAttributedString:holder]; 239 | } 240 | BOOL more = [temp rz_moreThan:maxLine maxWidth:width]; 241 | if (more) { 242 | right = location - 1; 243 | } else { 244 | left = location + 1; 245 | resultAttr = temp; 246 | } 247 | } 248 | break; 249 | case NSLineBreakByTruncatingHead: 250 | while (left <= right) { 251 | location = (left + right) / 2; 252 | NSAttributedString *sub = [self attributedSubstringFromRange:NSMakeRange(location, self.length - location)]; 253 | NSMutableAttributedString *temp = sub.mutableCopy; 254 | [temp insertAttributedString:holder atIndex:0]; 255 | BOOL more = [temp rz_moreThan:maxLine maxWidth:width]; 256 | if (more) { 257 | left = location + 1; 258 | } else { 259 | right = location - 1; 260 | resultAttr = temp; 261 | } 262 | } 263 | break; 264 | case NSLineBreakByTruncatingMiddle: { 265 | // 将富文本分为左右两部分,一左一右交替二分查找获取截断位置 266 | // 当maxline = 1行时,效果不太理想(有好的方法可以反馈一下) 267 | /// 左边 268 | NSInteger left_l = 0; 269 | NSInteger left_r = self.length / 2; 270 | 271 | /// 右边 272 | NSInteger right_l = self.length / 2; 273 | NSInteger right_r = self.length; 274 | 275 | /// 左右mid 276 | NSInteger left_mid = 0; 277 | NSInteger right_mid = right_l; 278 | 279 | /// 每次计算,双数时计算左侧,单数时计算右侧 280 | /// 优先截断左侧,当一侧截断位置后未超行,times不变,继续算这一侧,直到这一侧超行之后,在截取另一侧 281 | /// 即超行之后,才times+=1, 282 | /// 如times=双数,即左侧,当次未超行时times不变,下一次循环依然会截断左侧,直到超行时,才去截断右侧 283 | NSInteger times = 0; 284 | while (left_l <= left_r || right_l <= right_r) { 285 | NSMutableAttributedString *temp = [[NSMutableAttributedString alloc] init]; 286 | 287 | // 交替计算左侧和右侧的中间位置 288 | if (times % 2 == 0) { /// 算左侧 289 | left_mid = (left_l + left_r) / 2; 290 | } else { 291 | right_mid = (right_l + right_r) / 2; 292 | } 293 | 294 | NSAttributedString *h = [self attributedSubstringFromRange:NSMakeRange(0, left_mid)]; 295 | NSAttributedString *e = [self attributedSubstringFromRange:NSMakeRange(right_mid, self.length - right_mid)]; 296 | 297 | [temp appendAttributedString:h]; 298 | [temp appendAttributedString:holder]; 299 | [temp appendAttributedString:e]; 300 | 301 | if ([temp rz_moreThan:maxLine maxWidth:width]) { 302 | if (times % 2 == 0) { /// 算左侧 303 | left_r = left_mid - 1; 304 | } else { 305 | right_l = right_mid + 1; 306 | } 307 | times += 1; 308 | } else { 309 | if (times % 2 == 0) { /// 算左侧 310 | left_l = left_mid + 1; 311 | } else { 312 | right_r = right_mid - 1; 313 | } 314 | 315 | /// 左侧已到临界点,则重置times仅算右侧,同理右侧临界点 316 | if (left_l > left_r) { 317 | times = 1; 318 | } else if (right_l > right_r) { 319 | times = 0; 320 | } 321 | resultAttr = temp; 322 | } 323 | } 324 | break; 325 | } 326 | default: 327 | return self; 328 | } 329 | return resultAttr; 330 | } 331 | /// 头部截断(逆向)从最后一个字反向往前保留最后maxLine行数的文字 332 | /// - Parameters: 333 | /// - maxLine: 设置超过多少截断 334 | /// - width: 显示的最大宽度 335 | /// - model: 截断方式 336 | /// - placeHolder: 截断时占位的"..."文字 337 | - (NSAttributedString * _Nullable)rz_headTruncatingAttributedStringBy:(NSInteger)maxLine maxWidth:(CGFloat)width lineBreakMode:(NSLineBreakMode)model placeHolder:(NSAttributedString *_Nullable)placeHolder { 338 | if (self.length == 0 || maxLine == 0 || width == 0) { 339 | return self; 340 | } 341 | if (![self rz_moreThan:maxLine maxWidth:width]) { 342 | return self; 343 | } 344 | NSAttributedString *holder = placeHolder == nil ? [NSAttributedString new] : placeHolder; 345 | NSInteger min = 0; 346 | NSInteger max = self.length; 347 | NSInteger star = 0; 348 | NSAttributedString *resultAttr; 349 | while (min <= max) { 350 | star = (min + max) / 2; 351 | NSAttributedString *sub = [self attributedSubstringFromRange:NSMakeRange(star, self.length - star)]; 352 | NSMutableAttributedString *tempAttr = [[NSMutableAttributedString alloc] initWithAttributedString:sub]; 353 | switch (model) { 354 | case NSLineBreakByWordWrapping: { break; } 355 | case NSLineBreakByCharWrapping: { break; } 356 | case NSLineBreakByClipping: { break; } 357 | case NSLineBreakByTruncatingHead: { 358 | [tempAttr insertAttributedString:holder atIndex:0]; 359 | break; 360 | } 361 | case NSLineBreakByTruncatingTail: { 362 | [tempAttr appendAttributedString:holder]; 363 | break; 364 | } 365 | case NSLineBreakByTruncatingMiddle: { 366 | [tempAttr insertAttributedString:holder atIndex:(tempAttr.length / 2)]; 367 | break; 368 | } 369 | default: 370 | break; 371 | } 372 | BOOL more = [tempAttr rz_moreThan:maxLine maxWidth:width]; 373 | if (more) { 374 | min = star + 1; 375 | } else { 376 | max = star - 1; 377 | resultAttr = tempAttr; 378 | } 379 | } 380 | return resultAttr; 381 | } 382 | /// 给text 关键字 标记属性(如对关键字进行标红显示) 383 | - (NSAttributedString *_Nonnull)rz_markText:(NSString *_Nullable)text attribute:(NSDictionary *_Nonnull)attribute { 384 | if ((text == nil) || (text.length == 0)) { 385 | return self; 386 | } 387 | NSMutableAttributedString *attr = [[NSMutableAttributedString alloc] initWithAttributedString:self]; 388 | NSString *t = self.string; 389 | NSRange range = NSMakeRange(0, t.length); 390 | while (range.length > 0) { 391 | NSRange r = [t rangeOfString:text options:NSCaseInsensitiveSearch range:range]; 392 | if (r.location != NSNotFound) { 393 | [attr addAttributes:attribute range:r]; 394 | NSInteger location = r.location + r.length; 395 | range = NSMakeRange(location, (t.length - location)); 396 | } else { 397 | break; 398 | } 399 | } 400 | return attr; 401 | } 402 | /// 取self的属性,设置在text上 403 | - (NSAttributedString *_Nonnull)rz_copyAttributeToText:(NSString *_Nonnull)text { 404 | NSDictionary *dict = [self attributesAtIndex:0 effectiveRange:nil]; 405 | if (!dict) { 406 | dict = NSDictionary.new; 407 | } 408 | return [[NSAttributedString alloc] initWithString:text attributes:dict]; 409 | } 410 | 411 | @end 412 | 413 | @implementation RZAttributedStringInfo 414 | 415 | @end 416 | 417 | 418 | #pragma clang diagnostic pop 419 | -------------------------------------------------------------------------------- /RZColorful/Classes/AttributeCore/NSAttributedString+RZHtml.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSAttributedString+RZHtml.m 3 | // RZColorfulExample 4 | // 5 | // Created by rztime on 2020/8/12. 6 | // Copyright © 2020 rztime. All rights reserved. 7 | // 8 | 9 | #import "NSAttributedString+RZHtml.h" 10 | 11 | @implementation NSAttributedString (RZHtml) 12 | // 将html转换成 NSAttributedString 13 | + (NSAttributedString *)htmlString:(NSString *)html { 14 | if (!html) { 15 | return [NSAttributedString new]; 16 | } 17 | NSDictionary *options = @{NSDocumentTypeDocumentAttribute : NSHTMLTextDocumentType, 18 | NSCharacterEncodingDocumentAttribute: @(NSUTF8StringEncoding)}; 19 | NSError *error; 20 | NSMutableAttributedString *htmlString = [[NSAttributedString alloc] initWithData:[html dataUsingEncoding:NSUnicodeStringEncoding] options:options documentAttributes:nil error:&error].mutableCopy; 21 | if (!error) { 22 | // 修复URL在未设置http时,会自动添加如 “applewebdata://BF307C6C-5A2C-4F76-B3A0-6FD67E66CF82/” 23 | [htmlString enumerateAttribute:NSLinkAttributeName inRange:NSMakeRange(0, htmlString.length) options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) { 24 | NSURL *url; 25 | if ([value isKindOfClass:[NSString class]]) { 26 | url = [NSURL URLWithString:((NSString *)value)]; 27 | } else if ([value isKindOfClass:[NSURL class]]) { 28 | url = (NSURL *)value; 29 | } 30 | if ([url.scheme isEqualToString:@"applewebdata"]) { 31 | NSString *tempUrl = url.absoluteString; 32 | NSString *originUrl = [tempUrl stringByReplacingOccurrencesOfString:@"^(?:applewebdata://[0-9A-Z-]*/?)" withString:@"" options:NSRegularExpressionSearch range:NSMakeRange(0, tempUrl.length)]; 33 | if (originUrl.length > 0) { 34 | url = [NSURL URLWithString:originUrl]; 35 | [htmlString addAttribute:NSLinkAttributeName value:url range:range]; 36 | } 37 | } 38 | }]; 39 | return htmlString.copy; 40 | } else { 41 | NSLog(@"__%s__\n 富文本html转换有误:\n%@", __FUNCTION__, error); 42 | return [NSAttributedString new]; 43 | } 44 | } 45 | /** 46 | 将富文本编码成html标签,如果有图片,用此方法 47 | 48 | @param urls 图片的url,url需要先获取图片,然后自行上传到服务器,最后按照【- (NSArray *)images;】此方法得到的图片顺序排列url 49 | @return HTML标签 50 | */ 51 | - (NSString *)rz_codingToHtmlWithImagesURLSIfHad:(NSArray *)urls { 52 | NSMutableAttributedString *tempAttr = self.mutableCopy; 53 | // 先将图片占位,等替换完成html标签之后,在将图片url替换回准确的 54 | __block NSInteger idx = 0; 55 | NSMutableArray *tempPlaceHolders = [NSMutableArray new]; 56 | [tempAttr enumerateAttribute:NSAttachmentAttributeName inRange:NSMakeRange(0, tempAttr.length) options:NSAttributedStringEnumerationReverse usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) { 57 | if ([value isKindOfClass:[NSTextAttachment class]]) { 58 | NSString *placeHolder = [NSString stringWithFormat:@"rz_attributed_image_placeHolder_index_%lu", (unsigned long)idx]; 59 | idx++; 60 | [tempAttr replaceCharactersInRange:range withString:placeHolder]; 61 | [tempPlaceHolders addObject:placeHolder]; 62 | } 63 | }]; 64 | NSString *html = [tempAttr rz_codingToCompleteHtml]; 65 | NSInteger index = 0; 66 | for (NSInteger i = tempPlaceHolders.count - 1; i >= 0; i--) { 67 | NSString *placeholder = tempPlaceHolders[i]; 68 | NSString *url = index < urls.count? urls[index]:@""; 69 | NSString *img = [NSString stringWithFormat:@"\"图片缺失\"", url]; 70 | index++; 71 | html = [html stringByReplacingOccurrencesOfString:placeholder withString:img]; 72 | } 73 | return html; 74 | } 75 | - (NSString *)rz_codingToHtmlByWebWithImagesURLSIfHad:(NSArray *)urls { 76 | NSMutableAttributedString *tempAttr = self.mutableCopy; 77 | // 先将图片占位,等替换完成html标签之后,在将图片url替换回准确的 78 | __block NSInteger idx = 0; 79 | NSMutableArray *tempPlaceHolders = [NSMutableArray new]; 80 | [tempAttr enumerateAttribute:NSAttachmentAttributeName inRange:NSMakeRange(0, tempAttr.length) options:NSAttributedStringEnumerationReverse usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) { 81 | if ([value isKindOfClass:[NSTextAttachment class]]) { 82 | NSString *placeHolder = [NSString stringWithFormat:@"rz_attributed_image_placeHolder_index_%lu", (unsigned long)idx]; 83 | idx++; 84 | [tempAttr replaceCharactersInRange:range withString:placeHolder]; 85 | [tempPlaceHolders addObject:placeHolder]; 86 | } 87 | }]; 88 | NSString *html = [tempAttr rz_codingToCompleteHtmlByWeb]; 89 | NSInteger index = 0; 90 | for (NSInteger i = tempPlaceHolders.count - 1; i >= 0; i--) { 91 | NSString *placeholder = tempPlaceHolders[i]; 92 | NSString *url = index < urls.count? urls[index]:@""; 93 | NSString *img = [NSString stringWithFormat:@"\"图片缺失\"", url]; 94 | index++; 95 | html = [html stringByReplacingOccurrencesOfString:placeholder withString:img]; 96 | } 97 | return html; 98 | } 99 | - (NSString *)rz_codingToCompleteHtml { 100 | NSDictionary *exportParams = @{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: @(NSUTF8StringEncoding)}; 101 | NSData *htmlData = [self dataFromRange:NSMakeRange(0, self.length) documentAttributes:exportParams error:nil]; 102 | NSString *htmlString = [[NSString alloc] initWithData:htmlData encoding:NSUTF8StringEncoding]; 103 | htmlString = [htmlString stringByReplacingOccurrencesOfString:@"pt;" withString:@"px;"]; 104 | htmlString = [htmlString stringByReplacingOccurrencesOfString:@"pt}" withString:@"px}"]; 105 | return htmlString; 106 | } 107 | 108 | - (NSString *)rz_codingToCompleteHtmlByWeb { 109 | NSMutableAttributedString *tempAttr = self.mutableCopy; 110 | NSArray *labels = RZHTMLWebLabels; 111 | 112 | NSMutableArray *temparray = NSMutableArray.new; // 记录转换了多少个标签,最后需要还原 113 | // 循环遍历每一个块,将iOS不支持转换为web标签的属性,替换成web标签 114 | [self enumerateAttributesInRange:NSMakeRange(0, tempAttr.length) options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:^(NSDictionary * _Nonnull attrs, NSRange range, BOOL * _Nonnull stop) { 115 | // 当前遍历的文本 116 | NSMutableAttributedString *tempAttributed = [tempAttr attributedSubstringFromRange:range].mutableCopy; 117 | NSMutableDictionary *tempAttrDict = attrs.mutableCopy; 118 | 119 | RZHtmlTransform *form = [[RZHtmlTransform alloc] init]; 120 | // 记录其url 121 | form.url = tempAttrDict[NSLinkAttributeName]; 122 | if ([form.url isKindOfClass:[NSURL class]]) { 123 | form.url = ((NSURL *)form.url).absoluteString; 124 | } 125 | // 遍历所有需要转换的标签(可以自定义添加) 126 | [labels enumerateObjectsUsingBlock:^(RZHtmlTransform * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 127 | // 如果当前文本包含了需要替换的标签,则将其转换为style,并移除文本中当前的属性 128 | if (obj.keys.count > 0 && [tempAttrDict.allKeys rz_containsArray:obj.keys]) { // 优先处理要组合的 129 | NSString *style = obj.styleConfigure(@"", tempAttrDict); 130 | form.style = [NSString stringWithFormat:@"%@%@", (form.style == nil ? @"" : form.style), style ]; 131 | [obj.keys enumerateObjectsUsingBlock:^(NSString * _Nonnull key, NSUInteger idx, BOOL * _Nonnull stop) { 132 | [tempAttributed removeAttribute:key range:NSMakeRange(0, tempAttributed.length)]; 133 | }]; 134 | [tempAttrDict removeObjectsForKeys:obj.keys]; 135 | } else if ([tempAttrDict.allKeys containsObject:obj.key]) { 136 | id value = tempAttrDict[obj.key]; 137 | NSString *style = obj.styleConfigure(value, tempAttrDict); 138 | form.style = [NSString stringWithFormat:@"%@%@", (form.style == nil ? @"" : form.style), style ]; 139 | [tempAttributed removeAttribute:obj.key range:NSMakeRange(0, tempAttributed.length)]; 140 | [tempAttrDict removeObjectForKey:obj.key]; 141 | } 142 | }]; 143 | 144 | NSString *newUrl = form.mergeUrlAndStyle; 145 | if (newUrl.length > 0) {// 合并url和style,将其写入到NSLink中, 146 | [tempAttributed addAttribute:NSLinkAttributeName value:newUrl range:NSMakeRange(0, tempAttributed.length)]; 147 | [tempAttr replaceCharactersInRange:range withAttributedString:tempAttributed]; 148 | [temparray addObject:form]; 149 | } 150 | }]; 151 | __block NSString *text = tempAttr.rz_codingToCompleteHtml; 152 | // url 和 style 合并到href中了,这里需要还原, href="url" style="".... 153 | [temparray enumerateObjectsUsingBlock:^(RZHtmlTransform * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 154 | text = [obj creatHtmlLabelWith:text]; 155 | }]; 156 | return text; 157 | } 158 | 159 | @end 160 | 161 | 162 | @implementation RZHtmlTransform 163 | + (instancetype)share { 164 | static dispatch_once_t onceToken; 165 | static RZHtmlTransform *form = nil; 166 | dispatch_once(&onceToken, ^{ 167 | form = [RZHtmlTransform.alloc init]; 168 | }); 169 | return form; 170 | } 171 | // 初始化,单个需要处理的属性 172 | - (instancetype)initWith:(NSString *)key styleConfigure:(RZStyleConfigure)conf { 173 | if (self = [super init]) { 174 | self.key = key; 175 | self.styleConfigure = conf; 176 | } 177 | return self; 178 | } 179 | // 初始化,需要组合的属性 180 | - (instancetype)initWithArray:(NSArray *)keys styleConfigure:(RZStyleConfigure)conf { 181 | if (self = [super init]) { 182 | self.keys = keys; 183 | self.styleConfigure = conf; 184 | } 185 | return self; 186 | } 187 | 188 | /// 将需要转换为web标签的key,记录下来, 多个key要组合的,需要放在数组前(优先级更高)循环遍历需要先处理 189 | - (NSMutableArray *)webLabels { 190 | if (!_webLabels) { 191 | _webLabels = @[ 192 | // 删除线 下划线 组合到一起 下划线和删除线 不支持单独设置颜色 193 | [RZHtmlTransform.alloc initWithArray:@[NSStrikethroughStyleAttributeName, NSUnderlineStyleAttributeName] styleConfigure:^NSString * _Nullable(id _Nonnull value, NSDictionary * _Nonnull attrs) { 194 | UIColor *color = attrs[NSStrikethroughColorAttributeName]; // 取删除线或者下划线颜色 195 | if (!color) { 196 | color = attrs[NSUnderlineColorAttributeName]; 197 | } 198 | if (!color) { 199 | color = attrs[NSForegroundColorAttributeName]; 200 | } 201 | NSString *colorString = @""; 202 | if (color) { 203 | colorString = [NSString stringWithFormat:@"%@", color.rz_hexString]; 204 | } 205 | return [NSString stringWithFormat:@"text-decoration: line-through underline; text-decoration-color: %@;", colorString]; 206 | }], 207 | // 斜体, 扩张 组合到一起 208 | [RZHtmlTransform.alloc initWithArray:@[NSObliquenessAttributeName, NSExpansionAttributeName] styleConfigure:^NSString * _Nullable(id _Nonnull value, NSDictionary * _Nonnull attrs) { 209 | float i = ((NSNumber *)attrs[NSObliquenessAttributeName]).floatValue; 210 | float e = ((NSNumber *)attrs[NSExpansionAttributeName]).floatValue; 211 | float itemp = [RZHtmlTransform italicTrans:i]; 212 | float etemp = [RZHtmlTransform expansionTrans:e]; 213 | return [NSString stringWithFormat:@"transform:skew(%fdeg) scaleX(%f);transform-origin: 0 0; display:inline-block;", itemp, etemp]; 214 | }], 215 | // 背景色 216 | [RZHtmlTransform.alloc initWith:NSBackgroundColorAttributeName styleConfigure:^NSString * _Nullable(UIColor * value, NSDictionary * _Nonnull attrs) { 217 | return [NSString stringWithFormat:@"background-color: %@;", value.rz_hexString]; 218 | }], 219 | // 删除线 220 | [RZHtmlTransform.alloc initWith:NSStrikethroughStyleAttributeName styleConfigure:^NSString * _Nullable(id _Nonnull value, NSDictionary * _Nonnull attrs) { 221 | UIColor *color = attrs[NSStrikethroughColorAttributeName]; 222 | NSString *colorString = @""; 223 | if (!color) { 224 | color = attrs[NSForegroundColorAttributeName]; 225 | } 226 | if (color) { 227 | colorString = [NSString stringWithFormat:@"%@", color.rz_hexString]; 228 | } 229 | return [NSString stringWithFormat:@"text-decoration: line-through; text-decoration-color: %@;", colorString]; 230 | }], 231 | // 下划线 232 | [RZHtmlTransform.alloc initWith:NSUnderlineStyleAttributeName styleConfigure:^NSString * _Nullable(id _Nonnull value, NSDictionary * _Nonnull attrs) { 233 | UIColor *color = attrs[NSUnderlineColorAttributeName]; 234 | NSString *colorString = @""; 235 | if (!color) { 236 | color = attrs[NSForegroundColorAttributeName]; 237 | } 238 | if (color) { 239 | colorString = [NSString stringWithFormat:@"%@", color.rz_hexString]; 240 | } 241 | return [NSString stringWithFormat:@"text-decoration: underline; text-decoration-color: %@;", colorString]; 242 | }], 243 | // 描边 244 | [RZHtmlTransform.alloc initWith:NSStrokeWidthAttributeName styleConfigure:^NSString * _Nullable(NSNumber * _Nonnull value, NSDictionary * _Nonnull attrs) { 245 | float v = value.floatValue; 246 | UIFont *font = attrs[NSFontAttributeName]; 247 | UIColor *color = attrs[NSStrokeColorAttributeName]; 248 | NSString *colorString = @""; 249 | if (color) { 250 | colorString = [NSString stringWithFormat:@"%@", color.rz_hexString]; 251 | } 252 | if (v <= 0) { 253 | return [NSString stringWithFormat:@"-webkit-text-stroke: %fpx %@;", -v * (font.pointSize / 100.f), colorString]; 254 | } else { 255 | return [NSString stringWithFormat:@"-webkit-text-stroke: %fpx %@; color:#00000000;", v * (font.pointSize / 100.f), colorString]; // 大于0时,是中空,用透明色来模拟 256 | } 257 | }], 258 | // 斜体 259 | [RZHtmlTransform.alloc initWith:NSObliquenessAttributeName styleConfigure:^NSString * _Nullable(NSNumber * _Nonnull value, NSDictionary * _Nonnull attrs) { 260 | float v = value.floatValue; 261 | float temp = [RZHtmlTransform italicTrans:v]; 262 | return [NSString stringWithFormat:@"transform:skew(%fdeg); transform-origin: 10 0; display:inline-block;", temp]; 263 | }], 264 | // 扩张,即拉伸文字 265 | [RZHtmlTransform.alloc initWith:NSExpansionAttributeName styleConfigure:^NSString * _Nullable(NSNumber * _Nonnull value, NSDictionary * _Nonnull attrs) { 266 | float v = value.floatValue; 267 | float temp = [RZHtmlTransform expansionTrans:v]; 268 | return [NSString stringWithFormat:@"transform:scaleX(%f); transform-origin: 0 0; display:inline-block;", temp]; 269 | }], 270 | // 书写方向 271 | [RZHtmlTransform.alloc initWith:NSWritingDirectionAttributeName styleConfigure:^NSString * _Nullable(NSArray * _Nonnull value, NSDictionary * _Nonnull attrs) { 272 | RZWriteDirection dir = [RZHtmlTransform directionTrans:value.firstObject]; 273 | switch (dir) { 274 | case LRE: { 275 | return @"direction: ltr; unicode-bidi: embed"; 276 | } 277 | case LRO: { 278 | return @"direction: ltr; unicode-bidi: bidi-override"; 279 | } 280 | case RLE: { 281 | return @"direction: rtl; unicode-bidi: embed"; 282 | } 283 | case RLO: { 284 | return @"direction: rtl; unicode-bidi: bidi-override"; 285 | } 286 | } 287 | return @""; 288 | }], 289 | ].mutableCopy; 290 | } 291 | return _webLabels; 292 | } 293 | 294 | - (NSString *)mergeUrlAndStyle { 295 | NSString *text = @""; 296 | if (self.url.length > 0) { 297 | text = [text stringByAppendingString:self.url]; 298 | } 299 | if (self.style.length > 0) { 300 | text = [text stringByAppendingString:self.style]; 301 | } 302 | return text; 303 | } 304 | - (NSString *)creatHtmlLabelWith:(NSString *)html { 305 | NSString *mr = self.mergeUrlAndStyle; 306 | if (mr.length > 0) { 307 | NSString *href = [NSString stringWithFormat:@"href=\"%@\"", mr]; 308 | NSString *u = @""; 309 | if (self.url.length > 0) { 310 | u = [NSString stringWithFormat:@"href=\"%@\"", self.url]; 311 | } 312 | NSString *s = @""; 313 | if (self.style.length > 0) { 314 | s = [NSString stringWithFormat:@" style=\"%@\"", self.style]; 315 | } 316 | html = [html stringByReplacingOccurrencesOfString:href withString:[NSString stringWithFormat:@"%@%@", u, s]]; 317 | } 318 | return html; 319 | } 320 | // 将iOS系统的斜体转换为web对应的角度 321 | + (float)italicTrans:(float)value { 322 | float temp = atanf(fabsf(value)) * 180 / M_PI; 323 | if (value > 0) { 324 | temp = -temp; 325 | } 326 | return temp; 327 | } 328 | /// 将iOS系统的扩展倍数转换为web对应的倍数 官方的拉伸压缩比例,暂未找到资料,下边的数据,仅做相似参考,如果你知道,请反馈给我吧,谢谢 329 | + (float)expansionTrans:(float)value { 330 | float v = value; 331 | float temp = v; // NSExpansionAttributeName 332 | if (v < -1) { 333 | temp = -1/(v * 2 * v + 1); 334 | } else if (v < 0) { 335 | temp = v/1.5 + 1; 336 | } else if (v <= 1) { 337 | temp = v * 1.5 + 1; 338 | } else if (v > 1) { 339 | temp = v * 2 * v + 1; 340 | } 341 | return temp; 342 | } 343 | + (RZWriteDirection)directionTrans:(NSNumber *)value { 344 | if (!value) { 345 | return LRO; 346 | } 347 | NSInteger O = 0; 348 | NSInteger E = 0; 349 | if (@available(iOS 9.0, *)) { 350 | O = NSWritingDirectionOverride; 351 | E = NSWritingDirectionEmbedding; 352 | } else { 353 | O = NSTextWritingDirectionOverride; 354 | E = NSTextWritingDirectionEmbedding; 355 | } 356 | if ([value isEqualToNumber:@(NSWritingDirectionLeftToRight | E)]) {return LRE;} 357 | if ([value isEqualToNumber:@(NSWritingDirectionLeftToRight | O)]) {return LRO;} 358 | if ([value isEqualToNumber:@(NSWritingDirectionRightToLeft | E)]) {return RLE;} 359 | if ([value isEqualToNumber:@(NSWritingDirectionRightToLeft | O)]) {return RLO;} 360 | return LRO; 361 | } 362 | @end 363 | 364 | @implementation UIColor (RZColorful) 365 | - (NSString *)rz_hexString { 366 | //颜色值个数,rgb和alpha 367 | NSInteger component = CGColorGetNumberOfComponents(self.CGColor); 368 | const CGFloat *components = CGColorGetComponents(self.CGColor); 369 | CGFloat red = components[0];//红色 370 | CGFloat green = components[1];//绿色 371 | if (component == 2) { 372 | return [NSString stringWithFormat:@"#%02lX%02lX%02lX%02lX", lroundf(red * 255), lroundf(red * 255), lroundf(red * 255), lroundf(green * 255)];; 373 | } 374 | CGFloat blue = components[2];//蓝色 375 | if (component == 4) { 376 | CGFloat alpha = components[3];//透明度 377 | return [NSString stringWithFormat:@"#%02lX%02lX%02lX%02lX", lroundf(red * 255), lroundf(green * 255), lroundf(blue * 255), lroundf(alpha * 255)]; 378 | } else { 379 | return [NSString stringWithFormat:@"#%02lX%02lX%02lX", lroundf(red * 255), lroundf(green * 255), lroundf(blue * 255)]; 380 | } 381 | } 382 | @end 383 | @implementation NSArray (RZColorful) 384 | // 数组是否包含数组 385 | - (BOOL)rz_containsArray:(NSArray *)array { 386 | __block BOOL flag = false; 387 | [array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 388 | flag = [self containsObject:obj]; 389 | if (!flag) { 390 | *stop = true; 391 | } 392 | }]; 393 | return flag; 394 | } 395 | @end 396 | 397 | --------------------------------------------------------------------------------