├── .gitignore ├── Example ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── SIXEditorController.h ├── SIXEditorController.m ├── example.txt └── main.m ├── LICENSE ├── README.md ├── SIXEditor ├── SIXEditor.bundle │ ├── Editor_bold@2x.png │ ├── Editor_bold@3x.png │ ├── Editor_bold_select@2x.png │ ├── Editor_bold_select@3x.png │ ├── Editor_fontSize@2x.png │ ├── Editor_fontSize@3x.png │ ├── Editor_fontSize_select@2x.png │ ├── Editor_fontSize_select@3x.png │ ├── Editor_image@2x.png │ ├── Editor_image@3x.png │ ├── Editor_image_select@2x.png │ ├── Editor_image_select@3x.png │ ├── Editor_itatic@2x.png │ ├── Editor_itatic@3x.png │ ├── Editor_itatic_select@2x.png │ ├── Editor_itatic_select@3x.png │ ├── Editor_keyboard@2x.png │ ├── Editor_keyboard@3x.png │ ├── Editor_keyboard_select@2x.png │ ├── Editor_keyboard_select@3x.png │ ├── Editor_textColor@2x.png │ ├── Editor_textColor@3x.png │ ├── Editor_textColor_select@2x.png │ ├── Editor_textColor_select@3x.png │ ├── Editor_underline@2x.png │ ├── Editor_underline@3x.png │ ├── Editor_underline_select@2x.png │ ├── Editor_underline_select@3x.png │ ├── Root.plist │ └── en.lproj │ │ └── Root.strings ├── SIXEditorFontSizePicker.h ├── SIXEditorFontSizePicker.m ├── SIXEditorHeader.h ├── SIXEditorImagePicker.h ├── SIXEditorImagePicker.m ├── SIXEditorTextColorPicker.h ├── SIXEditorTextColorPicker.m ├── SIXEditorToolBar.h ├── SIXEditorToolBar.m ├── SIXEditorToolController.h ├── SIXEditorToolController.m ├── SIXEditorView.h ├── SIXEditorView.m ├── SIXHTMLParser.h ├── SIXHTMLParser.m ├── UIFont+Category.h └── UIFont+Category.m ├── SIXRichEditor.podspec ├── SIXRichEditor.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcuserdata │ └── liujiliu.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist └── images ├── re1.png └── re2.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | *.DS_Store 31 | 32 | # CocoaPods 33 | # 34 | # We recommend against adding the Pods directory to your .gitignore. However 35 | # you should judge for yourself, the pros and cons are mentioned at: 36 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 37 | # 38 | # Pods/ 39 | # 40 | # Add this line if you want to avoid checking in source code from the Xcode workspace 41 | *.xcworkspace 42 | 43 | # Carthage 44 | # 45 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 46 | # Carthage/Checkouts 47 | 48 | Carthage/Build 49 | 50 | # fastlane 51 | # 52 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 53 | # screenshots whenever they are needed. 54 | # For more information about the recommended setup visit: 55 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 56 | 57 | fastlane/report.xml 58 | fastlane/Preview.html 59 | fastlane/screenshots/**/*.png 60 | fastlane/test_output 61 | 62 | # Code Injection 63 | # 64 | # After new code Injection tools there's a generated folder /iOSInjectionProject 65 | # https://github.com/johnno1962/injectionforxcode 66 | 67 | iOSInjectionProject/ -------------------------------------------------------------------------------- /Example/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // SIXRichEditor 4 | // 5 | // Created by on 2018/7/29. 6 | // Copyright © 2018年 liujiliu. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /Example/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // SIXRichEditor 4 | // 5 | // Created by on 2018/7/29. 6 | // Copyright © 2018年 liujiliu. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | 24 | - (void)applicationWillResignActive:(UIApplication *)application { 25 | // 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. 26 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 27 | } 28 | 29 | 30 | - (void)applicationDidEnterBackground:(UIApplication *)application { 31 | // 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. 32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 33 | } 34 | 35 | 36 | - (void)applicationWillEnterForeground:(UIApplication *)application { 37 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 38 | } 39 | 40 | 41 | - (void)applicationDidBecomeActive:(UIApplication *)application { 42 | // 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. 43 | } 44 | 45 | 46 | - (void)applicationWillTerminate:(UIApplication *)application { 47 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 48 | } 49 | 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /Example/Assets.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 | } -------------------------------------------------------------------------------- /Example/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/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 | -------------------------------------------------------------------------------- /Example/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 | -------------------------------------------------------------------------------- /Example/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | NSAppTransportSecurity 24 | 25 | NSAllowsArbitraryLoads 26 | 27 | 28 | NSCameraUsageDescription 29 | 是否允许设备调用您的相机? 30 | NSPhotoLibraryUsageDescription 31 | 是否允许设备调用您的相册? 32 | UILaunchStoryboardName 33 | LaunchScreen 34 | UIMainStoryboardFile 35 | Main 36 | UIRequiredDeviceCapabilities 37 | 38 | armv7 39 | 40 | UISupportedInterfaceOrientations 41 | 42 | UIInterfaceOrientationPortrait 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | UISupportedInterfaceOrientations~ipad 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationPortraitUpsideDown 50 | UIInterfaceOrientationLandscapeLeft 51 | UIInterfaceOrientationLandscapeRight 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /Example/SIXEditorController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SIXEditorController.h 3 | // SIXRichEditor 4 | // 5 | // Created by on 2018/7/31. 6 | // Copyright © 2018年 liujiliu. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SIXEditorController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Example/SIXEditorController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SIXEditorController.m 3 | // SIXRichEditor 4 | // 5 | // Created by on 2018/7/31. 6 | // Copyright © 2018年 liujiliu. All rights reserved. 7 | // 8 | 9 | #import "SIXEditorController.h" 10 | #import "SIXEditorView.h" 11 | #import "SIXHTMLParser.h" 12 | 13 | @interface SIXEditorController () 14 | 15 | @property (nonatomic, strong) SIXEditorView *editorView; 16 | 17 | @end 18 | 19 | 20 | @implementation SIXEditorController 21 | 22 | - (void)viewDidLoad { 23 | [super viewDidLoad]; 24 | // Do any additional setup after loading the view. 25 | 26 | self.title = @"富文本编辑器"; 27 | self.automaticallyAdjustsScrollViewInsets = NO; 28 | self.view.backgroundColor = [UIColor whiteColor]; 29 | 30 | CGFloat top = self.navigationController.navigationBar.frame.size.height; 31 | top += [[UIApplication sharedApplication] statusBarFrame].size.height; 32 | CGRect frame = UIEdgeInsetsInsetRect(self.view.frame, UIEdgeInsetsMake(top, 0, 0, 0)); 33 | self.editorView = [[SIXEditorView alloc] initWithFrame:frame]; 34 | [self.view addSubview:self.editorView]; 35 | 36 | if (@available(iOS 11.0, *)) { 37 | self.editorView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; 38 | } 39 | 40 | self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"html" style:(UIBarButtonItemStylePlain) target:self action:@selector(showHtmlString)]; 41 | 42 | __weak typeof(self) weakSelf = self; 43 | [self.editorView setHtml:[self exampleHTML] completion:^{ 44 | __weak typeof(self) strongSelf = weakSelf; 45 | [strongSelf.editorView becomeFirstResponder]; 46 | }]; 47 | } 48 | 49 | - (void)showHtmlString { 50 | static NSAttributedString *attributedText = nil; 51 | [self.editorView resignFirstResponder]; 52 | self.editorView.editable = !self.editorView.editable; 53 | if (self.editorView.editable) { 54 | self.editorView.attributedText = attributedText; 55 | return; 56 | } 57 | attributedText = self.editorView.attributedText; 58 | [self.editorView getHtml:^(NSString *html) { 59 | if(html == nil) return; 60 | NSMutableString *mHtml = html.mutableCopy; 61 | [mHtml replaceOccurrencesOfString:@"/>" withString:@"/>\n" options:0 range:NSMakeRange(0, mHtml.length)]; 62 | [mHtml replaceOccurrencesOfString:@"/span>" withString:@"/span>\n" options:0 range:NSMakeRange(0, mHtml.length)]; 63 | self.editorView.attributedText = [[NSAttributedString alloc] initWithString:mHtml.copy attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:12], NSForegroundColorAttributeName:[UIColor blackColor]}]; 64 | }]; 65 | } 66 | 67 | - (NSString *)exampleHTML { 68 | NSString *path = [[NSBundle mainBundle] pathForResource:@"example" ofType:@"txt"]; 69 | NSError *error = nil; 70 | NSString *txt = [NSString stringWithContentsOfFile:path encoding:(NSUTF8StringEncoding) error:&error]; 71 | return txt; 72 | } 73 | 74 | @end 75 | -------------------------------------------------------------------------------- /Example/example.txt: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /Example/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // SIXRichEditor 4 | // 5 | // Created by on 2018/7/29. 6 | // Copyright © 2018年 liujiliu. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 jiliuliu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 一个简易的富文本编辑器。支持html格式,仅仅基于UITextView实现,原理简单,易于扩展。
3 | 支持功能:
4 | 1.添加本地图片
5 | 2.字体可以调整粗细、大小、字色、斜体。

6 | 7 | 说明:
8 | 实际使用时,需要添加自己的图片上传请求。
9 | 可以参照SIXHTMLParser类中(+ (void)sync_htmlStringWithAttributedText:)方法,在末尾的注释进行添加。 10 |

11 |

12 |
13 | 14 |
15 |
16 | 17 |
18 | -------------------------------------------------------------------------------- /SIXEditor/SIXEditor.bundle/Editor_bold@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiliuliu/SIXRichEditor/d78c866f239c7c1f0c400f191a8baf8813e31123/SIXEditor/SIXEditor.bundle/Editor_bold@2x.png -------------------------------------------------------------------------------- /SIXEditor/SIXEditor.bundle/Editor_bold@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiliuliu/SIXRichEditor/d78c866f239c7c1f0c400f191a8baf8813e31123/SIXEditor/SIXEditor.bundle/Editor_bold@3x.png -------------------------------------------------------------------------------- /SIXEditor/SIXEditor.bundle/Editor_bold_select@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiliuliu/SIXRichEditor/d78c866f239c7c1f0c400f191a8baf8813e31123/SIXEditor/SIXEditor.bundle/Editor_bold_select@2x.png -------------------------------------------------------------------------------- /SIXEditor/SIXEditor.bundle/Editor_bold_select@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiliuliu/SIXRichEditor/d78c866f239c7c1f0c400f191a8baf8813e31123/SIXEditor/SIXEditor.bundle/Editor_bold_select@3x.png -------------------------------------------------------------------------------- /SIXEditor/SIXEditor.bundle/Editor_fontSize@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiliuliu/SIXRichEditor/d78c866f239c7c1f0c400f191a8baf8813e31123/SIXEditor/SIXEditor.bundle/Editor_fontSize@2x.png -------------------------------------------------------------------------------- /SIXEditor/SIXEditor.bundle/Editor_fontSize@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiliuliu/SIXRichEditor/d78c866f239c7c1f0c400f191a8baf8813e31123/SIXEditor/SIXEditor.bundle/Editor_fontSize@3x.png -------------------------------------------------------------------------------- /SIXEditor/SIXEditor.bundle/Editor_fontSize_select@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiliuliu/SIXRichEditor/d78c866f239c7c1f0c400f191a8baf8813e31123/SIXEditor/SIXEditor.bundle/Editor_fontSize_select@2x.png -------------------------------------------------------------------------------- /SIXEditor/SIXEditor.bundle/Editor_fontSize_select@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiliuliu/SIXRichEditor/d78c866f239c7c1f0c400f191a8baf8813e31123/SIXEditor/SIXEditor.bundle/Editor_fontSize_select@3x.png -------------------------------------------------------------------------------- /SIXEditor/SIXEditor.bundle/Editor_image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiliuliu/SIXRichEditor/d78c866f239c7c1f0c400f191a8baf8813e31123/SIXEditor/SIXEditor.bundle/Editor_image@2x.png -------------------------------------------------------------------------------- /SIXEditor/SIXEditor.bundle/Editor_image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiliuliu/SIXRichEditor/d78c866f239c7c1f0c400f191a8baf8813e31123/SIXEditor/SIXEditor.bundle/Editor_image@3x.png -------------------------------------------------------------------------------- /SIXEditor/SIXEditor.bundle/Editor_image_select@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiliuliu/SIXRichEditor/d78c866f239c7c1f0c400f191a8baf8813e31123/SIXEditor/SIXEditor.bundle/Editor_image_select@2x.png -------------------------------------------------------------------------------- /SIXEditor/SIXEditor.bundle/Editor_image_select@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiliuliu/SIXRichEditor/d78c866f239c7c1f0c400f191a8baf8813e31123/SIXEditor/SIXEditor.bundle/Editor_image_select@3x.png -------------------------------------------------------------------------------- /SIXEditor/SIXEditor.bundle/Editor_itatic@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiliuliu/SIXRichEditor/d78c866f239c7c1f0c400f191a8baf8813e31123/SIXEditor/SIXEditor.bundle/Editor_itatic@2x.png -------------------------------------------------------------------------------- /SIXEditor/SIXEditor.bundle/Editor_itatic@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiliuliu/SIXRichEditor/d78c866f239c7c1f0c400f191a8baf8813e31123/SIXEditor/SIXEditor.bundle/Editor_itatic@3x.png -------------------------------------------------------------------------------- /SIXEditor/SIXEditor.bundle/Editor_itatic_select@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiliuliu/SIXRichEditor/d78c866f239c7c1f0c400f191a8baf8813e31123/SIXEditor/SIXEditor.bundle/Editor_itatic_select@2x.png -------------------------------------------------------------------------------- /SIXEditor/SIXEditor.bundle/Editor_itatic_select@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiliuliu/SIXRichEditor/d78c866f239c7c1f0c400f191a8baf8813e31123/SIXEditor/SIXEditor.bundle/Editor_itatic_select@3x.png -------------------------------------------------------------------------------- /SIXEditor/SIXEditor.bundle/Editor_keyboard@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiliuliu/SIXRichEditor/d78c866f239c7c1f0c400f191a8baf8813e31123/SIXEditor/SIXEditor.bundle/Editor_keyboard@2x.png -------------------------------------------------------------------------------- /SIXEditor/SIXEditor.bundle/Editor_keyboard@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiliuliu/SIXRichEditor/d78c866f239c7c1f0c400f191a8baf8813e31123/SIXEditor/SIXEditor.bundle/Editor_keyboard@3x.png -------------------------------------------------------------------------------- /SIXEditor/SIXEditor.bundle/Editor_keyboard_select@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiliuliu/SIXRichEditor/d78c866f239c7c1f0c400f191a8baf8813e31123/SIXEditor/SIXEditor.bundle/Editor_keyboard_select@2x.png -------------------------------------------------------------------------------- /SIXEditor/SIXEditor.bundle/Editor_keyboard_select@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiliuliu/SIXRichEditor/d78c866f239c7c1f0c400f191a8baf8813e31123/SIXEditor/SIXEditor.bundle/Editor_keyboard_select@3x.png -------------------------------------------------------------------------------- /SIXEditor/SIXEditor.bundle/Editor_textColor@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiliuliu/SIXRichEditor/d78c866f239c7c1f0c400f191a8baf8813e31123/SIXEditor/SIXEditor.bundle/Editor_textColor@2x.png -------------------------------------------------------------------------------- /SIXEditor/SIXEditor.bundle/Editor_textColor@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiliuliu/SIXRichEditor/d78c866f239c7c1f0c400f191a8baf8813e31123/SIXEditor/SIXEditor.bundle/Editor_textColor@3x.png -------------------------------------------------------------------------------- /SIXEditor/SIXEditor.bundle/Editor_textColor_select@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiliuliu/SIXRichEditor/d78c866f239c7c1f0c400f191a8baf8813e31123/SIXEditor/SIXEditor.bundle/Editor_textColor_select@2x.png -------------------------------------------------------------------------------- /SIXEditor/SIXEditor.bundle/Editor_textColor_select@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiliuliu/SIXRichEditor/d78c866f239c7c1f0c400f191a8baf8813e31123/SIXEditor/SIXEditor.bundle/Editor_textColor_select@3x.png -------------------------------------------------------------------------------- /SIXEditor/SIXEditor.bundle/Editor_underline@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiliuliu/SIXRichEditor/d78c866f239c7c1f0c400f191a8baf8813e31123/SIXEditor/SIXEditor.bundle/Editor_underline@2x.png -------------------------------------------------------------------------------- /SIXEditor/SIXEditor.bundle/Editor_underline@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiliuliu/SIXRichEditor/d78c866f239c7c1f0c400f191a8baf8813e31123/SIXEditor/SIXEditor.bundle/Editor_underline@3x.png -------------------------------------------------------------------------------- /SIXEditor/SIXEditor.bundle/Editor_underline_select@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiliuliu/SIXRichEditor/d78c866f239c7c1f0c400f191a8baf8813e31123/SIXEditor/SIXEditor.bundle/Editor_underline_select@2x.png -------------------------------------------------------------------------------- /SIXEditor/SIXEditor.bundle/Editor_underline_select@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiliuliu/SIXRichEditor/d78c866f239c7c1f0c400f191a8baf8813e31123/SIXEditor/SIXEditor.bundle/Editor_underline_select@3x.png -------------------------------------------------------------------------------- /SIXEditor/SIXEditor.bundle/Root.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | StringsTable 6 | Root 7 | PreferenceSpecifiers 8 | 9 | 10 | Type 11 | PSGroupSpecifier 12 | Title 13 | Group 14 | 15 | 16 | Type 17 | PSTextFieldSpecifier 18 | Title 19 | Name 20 | Key 21 | name_preference 22 | DefaultValue 23 | 24 | IsSecure 25 | 26 | KeyboardType 27 | Alphabet 28 | AutocapitalizationType 29 | None 30 | AutocorrectionType 31 | No 32 | 33 | 34 | Type 35 | PSToggleSwitchSpecifier 36 | Title 37 | Enabled 38 | Key 39 | enabled_preference 40 | DefaultValue 41 | 42 | 43 | 44 | Type 45 | PSSliderSpecifier 46 | Key 47 | slider_preference 48 | DefaultValue 49 | 0.5 50 | MinimumValue 51 | 0 52 | MaximumValue 53 | 1 54 | MinimumValueImage 55 | 56 | MaximumValueImage 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /SIXEditor/SIXEditor.bundle/en.lproj/Root.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiliuliu/SIXRichEditor/d78c866f239c7c1f0c400f191a8baf8813e31123/SIXEditor/SIXEditor.bundle/en.lproj/Root.strings -------------------------------------------------------------------------------- /SIXEditor/SIXEditorFontSizePicker.h: -------------------------------------------------------------------------------- 1 | // 2 | // SIXEditorFontSizePicker.h 3 | // SIXRichEditor 4 | // 5 | // Created by 刘吉六 on 2023/6/16. 6 | // Copyright © 2023 liujiliu. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @protocol SIXEditorFontSizePickerProtocol 14 | 15 | - (void)showWithTextView:(UITextView *)textView 16 | fontSize:(CGFloat)fontSize 17 | completion:(void (^) (CGFloat))completion; 18 | 19 | @end 20 | 21 | @interface SIXEditorFontSizePicker : UIView 22 | 23 | @end 24 | 25 | NS_ASSUME_NONNULL_END 26 | -------------------------------------------------------------------------------- /SIXEditor/SIXEditorFontSizePicker.m: -------------------------------------------------------------------------------- 1 | // 2 | // SIXEditorFontSizePicker.m 3 | // SIXRichEditor 4 | // 5 | // Created by 刘吉六 on 2023/6/16. 6 | // Copyright © 2023 liujiliu. All rights reserved. 7 | // 8 | 9 | #import "SIXEditorFontSizePicker.h" 10 | #import "SIXEditorHeader.h" 11 | 12 | @interface SIXEditorFontSizeCell: UICollectionViewCell 13 | @property (nonatomic, strong) UILabel *label; 14 | @property (nonatomic, strong) UIView *colorView; 15 | @end 16 | 17 | @implementation SIXEditorFontSizeCell 18 | 19 | - (UILabel *)label { 20 | if (!_label) { 21 | _label = [[UILabel alloc] initWithFrame:self.bounds]; 22 | _label.textAlignment = NSTextAlignmentCenter; 23 | _label.textColor = [UIColor blackColor]; 24 | [self.contentView addSubview:_label]; 25 | } 26 | return _label; 27 | } 28 | 29 | @end 30 | 31 | 32 | @interface SIXEditorFontSizePicker() 33 | 36 | { 37 | NSInteger itemCount; 38 | CGSize itemSize; 39 | } 40 | 41 | @property (nonatomic, strong) UICollectionView *collectionView; 42 | 43 | @property (nonatomic, assign) CGFloat selectedFontSize; 44 | @property (nonatomic, strong) NSArray *fontSizes; 45 | 46 | @property (nonatomic, copy) void (^selectFontSizeBlock) (CGFloat); 47 | 48 | @end 49 | 50 | @implementation SIXEditorFontSizePicker 51 | 52 | - (instancetype)initWithFrame:(CGRect)frame 53 | { 54 | self = [super initWithFrame:frame]; 55 | if (self) { 56 | self.backgroundColor = [UIColor whiteColor]; 57 | 58 | CGFloat width = frame.size.width; 59 | itemCount = self.fontSizes.count; 60 | itemSize = CGSizeMake(width-30, 35); 61 | 62 | CALayer *line = [CALayer layer]; 63 | line.backgroundColor = [UIColor lightGrayColor].CGColor; 64 | line.frame = CGRectMake(0, 0, width, 0.5); 65 | 66 | [self addSubview:self.collectionView]; 67 | [self.layer addSublayer:line]; 68 | } 69 | return self; 70 | } 71 | 72 | - (void)layoutSubviews { 73 | [super layoutSubviews]; 74 | _collectionView.frame = self.bounds; 75 | } 76 | 77 | #pragma - mark SIXEditorTextColorPickerProtocol 78 | 79 | - (void)showWithTextView:(UITextView *)textView 80 | fontSize:(CGFloat)fontSize 81 | completion:(void (^)(CGFloat))completion { 82 | if (textView.inputView == self) { 83 | textView.inputView = nil; 84 | [textView becomeFirstResponder]; 85 | return; 86 | } 87 | _selectedFontSize = [self.fontSizes containsObject:@(fontSize)] ? fontSize : 0; 88 | _selectFontSizeBlock = completion; 89 | [_collectionView reloadData]; 90 | textView.inputView = self; 91 | [textView resignFirstResponder]; 92 | [textView becomeFirstResponder]; 93 | } 94 | 95 | 96 | #pragma - mark UICollectionViewDelegate 97 | 98 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { 99 | return itemCount; 100 | } 101 | 102 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { 103 | return itemSize; 104 | } 105 | 106 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { 107 | SIXEditorFontSizeCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"fontSize" forIndexPath:indexPath]; 108 | CGFloat size = [self.fontSizes[indexPath.item] floatValue]; 109 | cell.label.font = [UIFont systemFontOfSize:size]; 110 | cell.label.text = [NSString stringWithFormat:@"%@px ABC", self.fontSizes[indexPath.item]]; 111 | if (size == self.selectedFontSize) { 112 | cell.label.textColor = [UIColor blueColor]; 113 | } else { 114 | cell.label.textColor = [UIColor blackColor]; 115 | } 116 | return cell; 117 | } 118 | 119 | - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { 120 | CGFloat hex = [self.fontSizes[indexPath.item] floatValue]; 121 | _selectFontSizeBlock(hex); 122 | _selectFontSizeBlock = nil; 123 | [collectionView reloadData]; 124 | } 125 | 126 | - (UICollectionView *)collectionView { 127 | if (!_collectionView) { 128 | UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; 129 | layout.sectionInset = UIEdgeInsetsMake(20, 15, 20, 15); 130 | _collectionView = [[UICollectionView alloc] initWithFrame:self.bounds collectionViewLayout:layout]; 131 | _collectionView.backgroundColor = [UIColor whiteColor]; 132 | _collectionView.showsVerticalScrollIndicator = NO; 133 | _collectionView.showsHorizontalScrollIndicator = NO; 134 | _collectionView.delegate = self; 135 | _collectionView.dataSource = self; 136 | [_collectionView registerClass:SIXEditorFontSizeCell.class forCellWithReuseIdentifier:@"fontSize"]; 137 | } 138 | return _collectionView; 139 | } 140 | 141 | - (NSArray *)fontSizes { 142 | if (!_fontSizes) { 143 | _fontSizes = @[@12, @14, @16, @18, @20, @24, @28]; 144 | } 145 | return _fontSizes; 146 | } 147 | 148 | @end 149 | 150 | -------------------------------------------------------------------------------- /SIXEditor/SIXEditorHeader.h: -------------------------------------------------------------------------------- 1 | // 2 | // SIXEditorConst.m 3 | // SIXRichEditor 4 | // 5 | // Created by on 2018/7/29. 6 | // Copyright © 2018年 liujiliu. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | typedef NS_ENUM(NSUInteger, SIXEditorAction) { 13 | SIXEditorActionNone, 14 | SIXEditorActionBold, 15 | SIXEditorActionItatic, 16 | SIXEditorActionUnderline, 17 | SIXEditorActionTextColor, 18 | SIXEditorActionFontSize, 19 | SIXEditorActionImage, 20 | SIXEditorActionKeyboard, 21 | }; 22 | 23 | static CGFloat const Editor_ToolBar_Height = 40; 24 | 25 | NS_INLINE UIColor * _Nonnull six_colorWithHex(NSInteger hex) { 26 | return [UIColor colorWithRed:((float)((hex & 0xFF0000) >> 16))/255.0 27 | green:((float)((hex & 0xFF00) >> 8))/255.0 28 | blue:((float)(hex & 0xFF))/255.0 alpha:1.0]; 29 | } 30 | 31 | typedef void (^ _Nonnull SIXEditorTextStyleBlock) (CGFloat fontSize, UIColor * _Nullable textColor, BOOL isItatic, BOOL isUnderline, BOOL isBold); 32 | 33 | @protocol SIXEditorProtocol 34 | 35 | @property (nonatomic, copy) SIXEditorTextStyleBlock textStyleUpdated; 36 | 37 | - (void)handleAction:(SIXEditorAction)action andValue:(id _Nullable )value; 38 | 39 | @end 40 | 41 | @protocol SIXEditorImageUploader 42 | 43 | - (void)upload:(NSArray *_Nonnull)images 44 | completion:(void (^_Nonnull)(NSDictionary * _Nonnull map))completion; 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /SIXEditor/SIXEditorImagePicker.h: -------------------------------------------------------------------------------- 1 | // 2 | // SIXEditorImagePicker.h 3 | // SIXRichEditor 4 | // 5 | // Created by 刘吉六 on 2023/6/16. 6 | // Copyright © 2023 liujiliu. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol SIXEditorImagePickerProtocol 12 | 13 | - (void)showWithCompletion:(void (^) (UIImage *))completion; 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_BEGIN 18 | 19 | @interface SIXEditorImagePicker : NSObject 20 | 21 | @end 22 | 23 | NS_ASSUME_NONNULL_END 24 | -------------------------------------------------------------------------------- /SIXEditor/SIXEditorImagePicker.m: -------------------------------------------------------------------------------- 1 | // 2 | // SIXEditorImagePicker.m 3 | // SIXRichEditor 4 | // 5 | // Created by 刘吉六 on 2023/6/16. 6 | // Copyright © 2023 liujiliu. All rights reserved. 7 | // 8 | 9 | #import "SIXEditorImagePicker.h" 10 | 11 | @interface SIXEditorImagePicker() 12 | 13 | @property (nonatomic, strong) UIImagePickerController *pickerController; 14 | @property (nonatomic, copy) void (^selectImage) (UIImage *); 15 | 16 | @end 17 | 18 | @implementation SIXEditorImagePicker 19 | 20 | - (UIImagePickerController *)pickerController { 21 | if (_pickerController) return _pickerController; 22 | UIImagePickerController *pickerController = [[UIImagePickerController alloc] init]; 23 | pickerController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; 24 | pickerController.allowsEditing = YES; 25 | pickerController.delegate = self; 26 | _pickerController = pickerController; 27 | return pickerController; 28 | } 29 | 30 | - (void)showWithCompletion:(void (^)(UIImage *))completion { 31 | _selectImage = completion; 32 | UIViewController *rootController = [UIApplication sharedApplication].delegate.window.rootViewController; 33 | [rootController presentViewController:self.pickerController animated:YES completion:nil]; 34 | } 35 | 36 | - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { 37 | UIImage *image = nil; 38 | if ([picker allowsEditing]){ //获取用户编辑之后的图像 39 | image = [info objectForKey:UIImagePickerControllerEditedImage]; 40 | } else { // 照片的元数据参数 41 | image = [info objectForKey:UIImagePickerControllerOriginalImage]; 42 | } 43 | _selectImage(image); 44 | _selectImage = nil; 45 | [picker dismissViewControllerAnimated:YES completion:^{}]; 46 | } 47 | 48 | - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker { 49 | _selectImage(nil); 50 | _selectImage = nil; 51 | [picker dismissViewControllerAnimated:YES completion:^{}]; 52 | } 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /SIXEditor/SIXEditorTextColorPicker.h: -------------------------------------------------------------------------------- 1 | // 2 | // SIXEditorTextColorPicker.h 3 | // SIXRichEditor 4 | // 5 | // Created by 刘吉六 on 2023/6/16. 6 | // Copyright © 2023 liujiliu. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol SIXEditorTextColorPickerProtocol 12 | 13 | - (void)showWithTextView:(UITextView *)textView 14 | textColor:(UIColor *)textColor 15 | completion:(void (^) (UIColor *))completion; 16 | 17 | @end 18 | 19 | NS_ASSUME_NONNULL_BEGIN 20 | 21 | @interface SIXEditorTextColorPicker : UIView 22 | 23 | @end 24 | 25 | NS_ASSUME_NONNULL_END 26 | -------------------------------------------------------------------------------- /SIXEditor/SIXEditorTextColorPicker.m: -------------------------------------------------------------------------------- 1 | // 2 | // SIXEditorTextColorPicker.m 3 | // SIXRichEditor 4 | // 5 | // Created by 刘吉六 on 2023/6/16. 6 | // Copyright © 2023 liujiliu. All rights reserved. 7 | // 8 | 9 | #import "SIXEditorTextColorPicker.h" 10 | #import "SIXEditorHeader.h" 11 | 12 | @interface SIXEditorTextColorCell: UICollectionViewCell 13 | @property (nonatomic, strong) UIView *colorView; 14 | @end 15 | 16 | @implementation SIXEditorTextColorCell 17 | 18 | - (UIView *)colorView { 19 | if (!_colorView) { 20 | _colorView = [[UIView alloc] initWithFrame:self.bounds]; 21 | _colorView.layer.masksToBounds = YES; 22 | [self.contentView addSubview:_colorView]; 23 | } 24 | return _colorView; 25 | } 26 | 27 | @end 28 | 29 | 30 | @interface SIXEditorTextColorPicker() 31 | 34 | { 35 | NSInteger itemCount; 36 | CGSize itemSize; 37 | } 38 | 39 | @property (nonatomic, strong) UICollectionView *collectionView; 40 | 41 | @property (nonatomic, strong) UIColor *selectedTextColor; 42 | @property (nonatomic, strong) NSArray *textColors; 43 | 44 | @property (nonatomic, copy) void (^selectTextColorBlock) (UIColor *); 45 | 46 | @end 47 | 48 | @implementation SIXEditorTextColorPicker 49 | 50 | - (instancetype)initWithFrame:(CGRect)frame 51 | { 52 | self = [super initWithFrame:frame]; 53 | if (self) { 54 | self.backgroundColor = [UIColor whiteColor]; 55 | 56 | CGFloat width = frame.size.width; 57 | itemCount = self.textColors.count; 58 | itemSize = CGSizeMake((width - 30) / 6 - 11, (width - 30) / 6 - 11); 59 | 60 | CALayer *line = [CALayer layer]; 61 | line.backgroundColor = [UIColor lightGrayColor].CGColor; 62 | line.frame = CGRectMake(0, 0, width, 0.5); 63 | 64 | [self addSubview:self.collectionView]; 65 | [self.layer addSublayer:line]; 66 | } 67 | return self; 68 | } 69 | 70 | - (void)layoutSubviews { 71 | [super layoutSubviews]; 72 | _collectionView.frame = self.bounds; 73 | } 74 | 75 | #pragma - mark SIXEditorTextColorPickerProtocol 76 | 77 | - (void)showWithTextView:(UITextView *)textView 78 | textColor:(UIColor *)textColor 79 | completion:(void (^)(UIColor *))completion { 80 | _selectedTextColor = nil; 81 | for (NSNumber *colorHex in self.textColors) { 82 | UIColor *color = six_colorWithHex(colorHex.integerValue); 83 | if (CGColorEqualToColor(color.CGColor, textColor.CGColor)) { 84 | _selectedTextColor = color; 85 | break; 86 | } 87 | } 88 | _selectTextColorBlock = completion; 89 | [_collectionView reloadData]; 90 | textView.inputView = self; 91 | [textView resignFirstResponder]; 92 | [textView becomeFirstResponder]; 93 | } 94 | 95 | 96 | #pragma - mark UICollectionViewDelegate 97 | 98 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { 99 | return itemCount; 100 | } 101 | 102 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { 103 | return itemSize; 104 | } 105 | 106 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { 107 | SIXEditorTextColorCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"textColor" forIndexPath:indexPath]; 108 | NSInteger hex = [self.textColors[indexPath.item] integerValue]; 109 | cell.colorView.backgroundColor = six_colorWithHex(hex); 110 | 111 | if (CGColorEqualToColor(cell.colorView.backgroundColor.CGColor, self.selectedTextColor.CGColor)) { 112 | cell.colorView.layer.cornerRadius = itemSize.width * 0.5; 113 | } else { 114 | cell.colorView.layer.cornerRadius = 0; 115 | } 116 | return cell; 117 | } 118 | 119 | - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { 120 | NSInteger hex = [self.textColors[indexPath.item] integerValue]; 121 | _selectedTextColor = six_colorWithHex(hex); 122 | _selectTextColorBlock(_selectedTextColor); 123 | _selectTextColorBlock = nil; 124 | [collectionView reloadData]; 125 | } 126 | 127 | - (UICollectionView *)collectionView { 128 | if (!_collectionView) { 129 | UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; 130 | layout.sectionInset = UIEdgeInsetsMake(20, 15, 20, 15); 131 | _collectionView = [[UICollectionView alloc] initWithFrame:self.bounds collectionViewLayout:layout]; 132 | _collectionView.backgroundColor = [UIColor whiteColor]; 133 | _collectionView.showsVerticalScrollIndicator = NO; 134 | _collectionView.showsHorizontalScrollIndicator = NO; 135 | _collectionView.delegate = self; 136 | _collectionView.dataSource = self; 137 | [_collectionView registerClass:SIXEditorTextColorCell.class forCellWithReuseIdentifier:@"textColor"]; 138 | } 139 | return _collectionView; 140 | } 141 | 142 | - (NSArray *)textColors { 143 | if (!_textColors) { 144 | _textColors = @[@0xEF6931, @0x0079FE, @0x6DE0FF, @0xFFD200, 145 | @0x4CD900, @0xFF2D3C, @0x995200, @0x8659F7, 146 | @0x009E4A, @0x009FB8, @0x0064CF, @0xE6B800, 147 | @0xFF9500, @0xD42111, @0x000000, @0x666666, 148 | @0x999999, @0xC0C0C0]; 149 | } 150 | return _textColors; 151 | } 152 | 153 | @end 154 | 155 | -------------------------------------------------------------------------------- /SIXEditor/SIXEditorToolBar.h: -------------------------------------------------------------------------------- 1 | // 2 | // SIXEditorToolBar.h 3 | // SIXRichEditor 4 | // 5 | // Created by on 2018/7/29. 6 | // Copyright © 2018年 liujiliu. All rights reserved. 7 | // 8 | 9 | #import "SIXEditorHeader.h" 10 | 11 | 12 | @interface SIXEditorToolBar : UIView 13 | 14 | // actions: NSArray 15 | - (instancetype)initWithActions:(NSArray *)actions toolButtonTapped:(void (^)(SIXEditorAction, BOOL))tapped; 16 | 17 | - (void)updateWithItatic:(BOOL)isItatic isUnderline:(BOOL)isUnderline isBold:(BOOL)isBold; 18 | 19 | - (void)resetButton:(SIXEditorAction)action; 20 | 21 | @end 22 | 23 | -------------------------------------------------------------------------------- /SIXEditor/SIXEditorToolBar.m: -------------------------------------------------------------------------------- 1 | // 2 | // SIXEditorToolBar.m 3 | // SIXRichEditor 4 | // 5 | // Created by on 2018/7/29. 6 | // Copyright © 2018年 liujiliu. All rights reserved. 7 | // 8 | 9 | #import "SIXEditorToolBar.h" 10 | 11 | @interface SIXEditorToolBar () 12 | { 13 | CGFloat _itemWidth; 14 | } 15 | 16 | @property (nonatomic, strong) UIScrollView *scrollView; 17 | @property (nonatomic, strong) UIStackView *stackView; 18 | @property (nonatomic, strong) NSArray *actions; 19 | @property (nonatomic, strong) NSMutableDictionary *actionCache; 20 | @property (nonatomic, strong) void (^toolButtonTapped) (SIXEditorAction, BOOL); 21 | 22 | @end 23 | 24 | @implementation SIXEditorToolBar 25 | 26 | - (instancetype)initWithActions:(NSArray *)actions toolButtonTapped:(void (^)(SIXEditorAction, BOOL))tapped { 27 | CGFloat screenW = [UIScreen mainScreen].bounds.size.width; 28 | self = [super initWithFrame:CGRectMake(0, 0, screenW, Editor_ToolBar_Height)]; 29 | if (self) { 30 | _itemWidth = screenW / 7.0; 31 | _actionCache = [NSMutableDictionary dictionary]; 32 | _actions = actions; 33 | _toolButtonTapped = tapped; 34 | [self setupUI]; 35 | [self createActions]; 36 | } 37 | return self; 38 | } 39 | 40 | - (void)setupUI { 41 | self.backgroundColor = [UIColor whiteColor]; 42 | 43 | UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width-_itemWidth, self.frame.size.height)]; 44 | scrollView.showsVerticalScrollIndicator = NO; 45 | scrollView.showsHorizontalScrollIndicator = NO; 46 | [self addSubview:scrollView]; 47 | _scrollView = scrollView; 48 | 49 | if (@available(iOS 11.0, *)) { 50 | _scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; 51 | } 52 | 53 | UIStackView *stackView = [[UIStackView alloc] init]; 54 | stackView.distribution = UIStackViewDistributionFillEqually; 55 | [scrollView addSubview:stackView]; 56 | _stackView = stackView; 57 | 58 | UIButton *keyboardButton = [self itemButton:SIXEditorActionKeyboard]; 59 | keyboardButton.frame = CGRectMake(CGRectGetMaxX(scrollView.frame), 0, _itemWidth, self.frame.size.height); 60 | [self addSubview:keyboardButton]; 61 | 62 | CALayer *line = [CALayer layer]; 63 | line.backgroundColor = [UIColor lightGrayColor].CGColor; 64 | line.frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, 1); 65 | [self.layer addSublayer:line]; 66 | } 67 | 68 | - (void)createActions { 69 | for (UIView *view in self.actionCache.allValues) { 70 | [_stackView removeArrangedSubview:view]; 71 | } 72 | [self.actionCache removeAllObjects]; 73 | 74 | for (int i = 0; i < _actions.count; i++) { 75 | UIButton *itemButton = [self itemButton:[_actions[i] integerValue]]; 76 | [self.actionCache setObject:itemButton forKey:_actions[i]]; 77 | [_stackView addArrangedSubview:itemButton]; 78 | } 79 | 80 | _scrollView.contentSize = CGSizeMake(_itemWidth * _actions.count , self.frame.size.height); 81 | _stackView.frame = CGRectMake(0, 0, _scrollView.contentSize.width, _scrollView.contentSize.height); 82 | } 83 | 84 | - (UIButton *)itemButton:(SIXEditorAction)action { 85 | UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; 86 | [button addTarget:self action:@selector(onToolButtonTapped:) forControlEvents:UIControlEventTouchUpInside]; 87 | 88 | NSBundle *bundle = [NSBundle bundleWithPath:[[NSBundle bundleForClass:[self class]] pathForResource:@"SIXEditor" ofType:@"bundle"]]; 89 | 90 | NSString *imageName = [self itemImageName:action]; 91 | UIImage *image = [UIImage imageNamed:imageName inBundle:bundle compatibleWithTraitCollection:nil]; 92 | [button setImage:image forState:(UIControlStateNormal)]; 93 | 94 | imageName = [imageName stringByAppendingString:@"_select"]; 95 | image = [UIImage imageNamed:imageName inBundle:bundle compatibleWithTraitCollection:nil]; 96 | [button setImage:image forState:(UIControlStateSelected)]; 97 | button.tag = action; 98 | return button; 99 | } 100 | 101 | - (void)onToolButtonTapped:(UIButton *)button { 102 | button.selected = !button.isSelected; 103 | SIXEditorAction action = button.tag; 104 | UIButton *colorButton = self.actionCache[@(SIXEditorActionTextColor)]; 105 | UIButton *fontButton = self.actionCache[@(SIXEditorActionFontSize)]; 106 | switch (action) { 107 | case SIXEditorActionFontSize: 108 | colorButton.selected = NO; 109 | break; 110 | case SIXEditorActionTextColor: 111 | fontButton.selected = NO; 112 | break; 113 | case SIXEditorActionImage: 114 | case SIXEditorActionKeyboard: 115 | button.selected = NO; 116 | default: { 117 | colorButton.selected = NO; 118 | fontButton.selected = NO; 119 | } 120 | break; 121 | } 122 | self.toolButtonTapped(action, button.isSelected); 123 | } 124 | 125 | - (NSString *)itemImageName:(SIXEditorAction)action { 126 | switch (action) { 127 | case SIXEditorActionBold: 128 | return @"Editor_bold"; 129 | case SIXEditorActionItatic: 130 | return @"Editor_itatic"; 131 | case SIXEditorActionUnderline: 132 | return @"Editor_underline"; 133 | case SIXEditorActionFontSize: 134 | return @"Editor_fontSize"; 135 | case SIXEditorActionTextColor: 136 | return @"Editor_textColor"; 137 | case SIXEditorActionImage: 138 | return @"Editor_image"; 139 | case SIXEditorActionKeyboard: 140 | return @"Editor_keyboard"; 141 | 142 | default: 143 | return nil; 144 | } 145 | } 146 | 147 | - (void)updateWithItatic:(BOOL)isItatic isUnderline:(BOOL)isUnderline isBold:(BOOL)isBold { 148 | _actionCache[@(SIXEditorActionBold)].selected = isBold; 149 | _actionCache[@(SIXEditorActionItatic)].selected = isItatic; 150 | _actionCache[@(SIXEditorActionUnderline)].selected = isUnderline; 151 | } 152 | 153 | - (void)resetButton:(SIXEditorAction)action { 154 | _actionCache[@(action)].selected = NO; 155 | } 156 | 157 | @end 158 | -------------------------------------------------------------------------------- /SIXEditor/SIXEditorToolController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SIXEditorToolController.h 3 | // SIXRichEditor 4 | // 5 | // Created by 刘吉六 on 2023/6/16. 6 | // Copyright © 2023 liujiliu. All rights reserved. 7 | // 8 | 9 | #import "SIXEditorHeader.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface SIXEditorToolController : NSObject 14 | 15 | - (instancetype)initWithEditor:(UITextView *)editor; 16 | 17 | @end 18 | 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /SIXEditor/SIXEditorToolController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SIXEditorToolController.m 3 | // SIXRichEditor 4 | // 5 | // Created by 刘吉六 on 2023/6/16. 6 | // Copyright © 2023 liujiliu. All rights reserved. 7 | // 8 | 9 | #import "SIXEditorToolController.h" 10 | #import "SIXEditorHeader.h" 11 | #import "SIXEditorToolBar.h" 12 | #import "SIXEditorTextColorPicker.h" 13 | #import "SIXEditorImagePicker.h" 14 | #import "SIXEditorFontSizePicker.h" 15 | 16 | @interface SIXEditorToolController() 17 | { 18 | CGFloat editorInsetsBottom; 19 | CGFloat keyboardHeight; 20 | } 21 | 22 | @property (nonatomic, weak) UITextView *editor; 23 | @property (nonatomic, strong) SIXEditorToolBar *toolBar; 24 | @property (nonatomic, strong) id imagePicker; 25 | @property (nonatomic, strong) UIView * textColorPicker; 26 | @property (nonatomic, strong) UIView *fontSizePicker; 27 | @property (nonatomic, assign) BOOL editable; 28 | @property (nonatomic, assign) CGFloat fontSize; 29 | @property (nonatomic, assign) UIColor *textColor; 30 | 31 | @end 32 | 33 | 34 | @implementation SIXEditorToolController 35 | 36 | - (instancetype)initWithEditor:(UITextView *)editor { 37 | self = [super init]; 38 | if (self) { 39 | self.editor = editor; 40 | [self addKeyboardNotifications]; 41 | } 42 | return self; 43 | } 44 | 45 | - (void)dealloc { 46 | [self removeKeyboardNotifications]; 47 | } 48 | 49 | #pragma - mark get set 50 | 51 | - (void)setEditor:(UITextView *)editor { 52 | _editor = editor; 53 | _editor.inputAccessoryView = self.toolBar; 54 | __weak typeof(self) weakSelf = self; 55 | _editor.textStyleUpdated = ^(CGFloat fontSize, UIColor * _Nullable textColor, BOOL isItatic, BOOL isUnderline, BOOL isBold) { 56 | __strong typeof(self) strongSelf = weakSelf; 57 | strongSelf.fontSize = fontSize; 58 | strongSelf.textColor = textColor; 59 | [strongSelf.toolBar updateWithItatic:isItatic isUnderline:isUnderline isBold:isBold]; 60 | }; 61 | } 62 | 63 | - (SIXEditorToolBar *)toolBar { 64 | if (_toolBar) return _toolBar; 65 | NSArray *actions = @[@(SIXEditorActionBold), 66 | @(SIXEditorActionUnderline), 67 | @(SIXEditorActionItatic), 68 | @(SIXEditorActionFontSize), 69 | @(SIXEditorActionTextColor), 70 | @(SIXEditorActionImage)]; 71 | __weak typeof(self) weakSelf = self; 72 | _toolBar = [[SIXEditorToolBar alloc] initWithActions:actions toolButtonTapped:^(SIXEditorAction action, BOOL isSelected) { 73 | __strong typeof(self) strongSelf = weakSelf; 74 | [strongSelf toolButtonTapped:action isSelected:isSelected]; 75 | }]; 76 | return _toolBar; 77 | } 78 | 79 | - (id)imagePicker { 80 | if (_imagePicker) return _imagePicker; 81 | _imagePicker = [[SIXEditorImagePicker alloc] init]; 82 | return _imagePicker; 83 | } 84 | 85 | - (UIView *)textColorPicker { 86 | if (_textColorPicker) return _textColorPicker; 87 | CGSize size = [UIScreen mainScreen].bounds.size; 88 | CGRect frame = CGRectMake(0, 0, size.width, keyboardHeight - Editor_ToolBar_Height); 89 | _textColorPicker = [[SIXEditorTextColorPicker alloc] initWithFrame:frame]; 90 | return _textColorPicker; 91 | } 92 | 93 | - (UIView *)fontSizePicker { 94 | if (_fontSizePicker) return _fontSizePicker; 95 | CGSize size = [UIScreen mainScreen].bounds.size; 96 | CGRect frame = CGRectMake(0, 0, size.width, keyboardHeight - Editor_ToolBar_Height); 97 | _fontSizePicker = [[SIXEditorFontSizePicker alloc] initWithFrame:frame]; 98 | return _fontSizePicker; 99 | } 100 | 101 | #pragma - mark SIXEditorToolDelegate 102 | 103 | - (void)toolButtonTapped:(SIXEditorAction)action isSelected:(BOOL)isSelected { 104 | if ((action == SIXEditorActionFontSize || action == SIXEditorActionTextColor) && !isSelected) { 105 | self.editor.inputView = nil; 106 | [self.editor resignFirstResponder]; 107 | [self.editor becomeFirstResponder]; 108 | return; 109 | } 110 | switch (action) { 111 | case SIXEditorActionNone: 112 | break; 113 | case SIXEditorActionBold: 114 | case SIXEditorActionItatic: 115 | case SIXEditorActionUnderline: 116 | [self resetKeyboardIfNeed]; 117 | [self.editor handleAction:action andValue:@(isSelected)]; 118 | break; 119 | case SIXEditorActionKeyboard: 120 | self.editor.inputView = nil; 121 | [self.editor resignFirstResponder]; 122 | break; 123 | case SIXEditorActionFontSize: { 124 | [self.fontSizePicker showWithTextView:self.editor fontSize:_fontSize completion:^(CGFloat val) { 125 | self.fontSize = val; 126 | [self.editor handleAction:action andValue:@(val)]; 127 | [self resetKeyboardIfNeed]; 128 | [self.toolBar resetButton:SIXEditorActionFontSize]; 129 | }]; 130 | } 131 | break; 132 | case SIXEditorActionTextColor: { 133 | [self.textColorPicker showWithTextView:self.editor textColor:_textColor completion:^(UIColor *val) { 134 | self.textColor = val; 135 | [self.editor handleAction:action andValue:val]; 136 | [self resetKeyboardIfNeed]; 137 | [self.toolBar resetButton:SIXEditorActionTextColor]; 138 | }]; 139 | } 140 | break; 141 | case SIXEditorActionImage: 142 | [self.imagePicker showWithCompletion:^(UIImage *image) { 143 | [self.editor handleAction:action andValue:image]; 144 | }]; 145 | break; 146 | } 147 | } 148 | 149 | - (void)resetKeyboardIfNeed { 150 | if (self.editor.inputView == nil) return; 151 | self.editor.inputView = nil; 152 | [self.editor resignFirstResponder]; 153 | [self.editor becomeFirstResponder]; 154 | } 155 | 156 | #pragma - mark keyboard Notifications 157 | 158 | - (void)addKeyboardNotifications { 159 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShowOrHide:) name:UIKeyboardWillShowNotification object:nil]; 160 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShowOrHide:) name:UIKeyboardWillHideNotification object:nil]; 161 | } 162 | 163 | - (void)removeKeyboardNotifications { 164 | [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil]; 165 | [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil]; 166 | } 167 | 168 | - (void)keyboardWillShowOrHide:(NSNotification *)notification { 169 | 170 | // Orientation 171 | UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; 172 | 173 | // User Info 174 | NSDictionary *info = notification.userInfo; 175 | CGFloat duration = [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue]; 176 | int curve = [[info objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]; 177 | CGRect keyboardEnd = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]; 178 | 179 | // Toolbar Sizes 180 | CGFloat sizeOfToolbar = 0; 181 | 182 | // Keyboard Size 183 | //Checks if IOS8, gets correct keyboard height 184 | if (keyboardHeight == 0) { 185 | keyboardHeight = UIInterfaceOrientationIsLandscape(orientation) ? ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.000000) ? keyboardEnd.size.height : keyboardEnd.size.width : keyboardEnd.size.height; 186 | } 187 | // Correct Curve 188 | UIViewAnimationOptions animationOptions = curve << 16; 189 | 190 | const int extraHeight = 20; 191 | 192 | if ([notification.name isEqualToString:UIKeyboardWillShowNotification]) { 193 | [UIView animateWithDuration:duration delay:0 options:animationOptions animations:^{ 194 | UIEdgeInsets insets = self.editor.contentInset; 195 | insets.bottom = self->keyboardHeight + sizeOfToolbar + extraHeight + self->editorInsetsBottom; 196 | self.editor.contentInset = insets; 197 | } completion:nil]; 198 | } else { 199 | [UIView animateWithDuration:duration delay:0 options:animationOptions animations:^{ 200 | UIEdgeInsets insets = self.editor.contentInset; 201 | insets.bottom = self->editorInsetsBottom; 202 | self.editor.contentInset = insets; 203 | } completion:^(BOOL finished) { }]; 204 | } 205 | } 206 | 207 | @end 208 | -------------------------------------------------------------------------------- /SIXEditor/SIXEditorView.h: -------------------------------------------------------------------------------- 1 | // 2 | // SIXTextView.h 3 | // SIXRichEditor 4 | // 5 | // Created by on 2018/7/29. 6 | // Copyright © 2018年 liujiliu. All rights reserved. 7 | // 8 | 9 | #import "SIXEditorHeader.h" 10 | 11 | @interface SIXEditorView : UITextView 12 | 13 | // 本地图片上传代理 14 | - (void)setImageUploader:(id )uploader; 15 | 16 | // html:设置html 17 | // completion:设置完成回调 18 | - (void)setHtml:(NSString *)html completion:(void (^)(void))completion; 19 | 20 | // completion:获取html 21 | - (void)getHtml:(void (^)(NSString *html))completion; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /SIXEditor/SIXEditorView.m: -------------------------------------------------------------------------------- 1 | // 2 | // SIXTextView.m 3 | // SIXRichEditor 4 | // 5 | // Created by on 2018/7/29. 6 | // Copyright © 2018年 liujiliu. All rights reserved. 7 | // 8 | 9 | #import "SIXEditorView.h" 10 | #import "SIXEditorToolBar.h" 11 | #import "SIXEditorToolController.h" 12 | #import "UIFont+Category.h" 13 | #import "SIXHTMLParser.h" 14 | 15 | @interface SIXEditorView () 16 | { 17 | CGFloat fontSize; 18 | UIColor *textColor; 19 | 20 | BOOL isBold; 21 | BOOL isItalic; 22 | BOOL isUnderline; 23 | 24 | SIXEditorAction action; 25 | } 26 | 27 | @property (nonatomic, strong) SIXEditorToolController *toolController; 28 | @property (nonatomic, strong) SIXHTMLParser *parser; 29 | 30 | @property (nonatomic, strong) NSString *html; 31 | 32 | @end 33 | 34 | @implementation SIXEditorView 35 | @synthesize textStyleUpdated; 36 | 37 | - (instancetype)initWithFrame:(CGRect)frame 38 | { 39 | self = [super initWithFrame:frame]; 40 | if (self) { 41 | fontSize = 16; 42 | textColor = [UIColor blackColor]; 43 | isBold = NO; 44 | isItalic = NO; 45 | isUnderline = NO; 46 | action = SIXEditorActionNone; 47 | 48 | self.textColor = textColor; 49 | self.selectable = YES; 50 | self.allowsEditingTextAttributes = YES; 51 | self.showsHorizontalScrollIndicator = NO; 52 | self.font = [UIFont systemFontOfSize:fontSize]; 53 | 54 | _parser = [[SIXHTMLParser alloc] init]; 55 | _toolController = [[SIXEditorToolController alloc] initWithEditor:self]; 56 | [self resetTypingAttributes]; 57 | [self sendTextStyleUpdate]; 58 | } 59 | return self; 60 | } 61 | 62 | - (void)setImageUploader:(id)uploader { 63 | self.parser.imageUploader = uploader; 64 | } 65 | 66 | - (void)setEditable:(BOOL)editable { 67 | [super setEditable:editable]; 68 | if (editable) { 69 | self.toolController = [[SIXEditorToolController alloc] initWithEditor:self]; 70 | } else { 71 | self.inputAccessoryView = nil; 72 | self.toolController = nil; 73 | } 74 | } 75 | 76 | - (void)resetTypingAttributes { 77 | if (self.selectedRange.length) return; 78 | self.typingAttributes = [self currentAttributes]; 79 | } 80 | 81 | - (NSMutableDictionary *)currentAttributes { 82 | NSMutableDictionary *dict = self.typingAttributes.mutableCopy; 83 | 84 | switch (action) { 85 | case SIXEditorActionBold: { 86 | UIFont *font = dict[NSFontAttributeName]; 87 | dict[NSFontAttributeName] = [font copyWithBold:isBold]; 88 | } 89 | break; 90 | case SIXEditorActionItatic: { 91 | UIFont *font = dict[NSFontAttributeName]; 92 | dict[NSFontAttributeName] = [font copyWithItatic:isItalic]; 93 | } 94 | break; 95 | case SIXEditorActionUnderline: { 96 | if (isUnderline) { 97 | UIColor *color = dict[NSForegroundColorAttributeName]; 98 | dict[NSUnderlineColorAttributeName] = color; 99 | dict[NSUnderlineStyleAttributeName] = @1; 100 | } else { 101 | [dict removeObjectForKey:NSUnderlineColorAttributeName]; 102 | [dict removeObjectForKey:NSUnderlineStyleAttributeName]; 103 | } 104 | } 105 | break; 106 | case SIXEditorActionFontSize: { 107 | UIFont *font = dict[NSFontAttributeName]; 108 | dict[NSFontAttributeName] = [font copyWithFontSize:fontSize]; 109 | } 110 | break; 111 | case SIXEditorActionTextColor: { 112 | dict[NSForegroundColorAttributeName] = textColor; 113 | } 114 | break; 115 | default: 116 | break; 117 | } 118 | 119 | return dict.copy; 120 | } 121 | 122 | 123 | #pragma - mark ------ SIXEditorProtocol ------- 124 | 125 | - (void)handleAction:(SIXEditorAction)newAction andValue:(id)value { 126 | action = newAction; 127 | SEL rangeSelector = nil; 128 | 129 | switch (action) { 130 | case SIXEditorActionBold: 131 | isBold = [value boolValue]; 132 | rangeSelector = @selector(setBoldInRange); 133 | break; 134 | case SIXEditorActionItatic: 135 | isItalic = [value boolValue]; 136 | rangeSelector = @selector(setItaticInRange); 137 | break; 138 | case SIXEditorActionUnderline: 139 | isUnderline = [value boolValue]; 140 | rangeSelector = @selector(setUnderlineInRange); 141 | break; 142 | case SIXEditorActionFontSize: 143 | fontSize = [value floatValue]; 144 | rangeSelector = @selector(setFontSizeInRange); 145 | break; 146 | case SIXEditorActionTextColor: 147 | textColor = (UIColor *)value; 148 | rangeSelector = @selector(setTextColorInRange); 149 | break; 150 | case SIXEditorActionImage: 151 | [self insertImageInRange:(UIImage *)value]; 152 | return; 153 | default: 154 | break; 155 | } 156 | 157 | if (self.selectedRange.length > 0) { 158 | #pragma clang diagnostic push 159 | #pragma clang diagnostic ignored "-Warc-performSelector-leaks" 160 | [self performSelector:rangeSelector]; 161 | #pragma clang diagnostic pop 162 | } else { 163 | [self resetTypingAttributes]; 164 | } 165 | } 166 | 167 | - (void)modifyAttributedText:(void (^)(NSMutableAttributedString *attributedString))block { 168 | NSRange range = self.selectedRange; 169 | NSMutableAttributedString *attributedString = self.attributedText.mutableCopy; 170 | block(attributedString); 171 | self.attributedText = attributedString; 172 | self.selectedRange = range; 173 | [self scrollRangeToVisible:range]; 174 | } 175 | 176 | - (void)setBoldInRange { 177 | [self modifyAttributedText:^(NSMutableAttributedString *attributedString) { 178 | [attributedString enumerateAttribute:NSFontAttributeName inRange:self.selectedRange options:(NSAttributedStringEnumerationLongestEffectiveRangeNotRequired) usingBlock:^(id _Nullable value, NSRange range0, BOOL * _Nonnull stop) { 179 | if ([value isKindOfClass:[UIFont class]]) { 180 | UIFont *font = value; 181 | [attributedString addAttribute:NSFontAttributeName value:[font copyWithBold:self->isBold] range:range0]; 182 | } 183 | }]; 184 | }]; 185 | } 186 | 187 | - (void)setItaticInRange { 188 | [self modifyAttributedText:^(NSMutableAttributedString *attributedString) { 189 | [attributedString enumerateAttribute:NSFontAttributeName inRange:self.selectedRange options:(NSAttributedStringEnumerationLongestEffectiveRangeNotRequired) usingBlock:^(id _Nullable value, NSRange range0, BOOL * _Nonnull stop) { 190 | if ([value isKindOfClass:[UIFont class]]) { 191 | UIFont *font = value; 192 | [attributedString addAttribute:NSFontAttributeName value:[font copyWithItatic:self->isItalic] range:range0]; 193 | } 194 | }]; 195 | }]; 196 | } 197 | 198 | - (void)setUnderlineInRange { 199 | [self modifyAttributedText:^(NSMutableAttributedString *attributedString) { 200 | if (self->isUnderline == NO) { 201 | [attributedString removeAttribute:NSUnderlineStyleAttributeName range:self.selectedRange]; 202 | [attributedString removeAttribute:NSUnderlineColorAttributeName range:self.selectedRange]; 203 | self.attributedText = attributedString.copy; 204 | return; 205 | } 206 | 207 | [attributedString enumerateAttribute:NSForegroundColorAttributeName inRange:self.selectedRange options:(NSAttributedStringEnumerationLongestEffectiveRangeNotRequired) usingBlock:^(id _Nullable value, NSRange range0, BOOL * _Nonnull stop) { 208 | 209 | if ([value isKindOfClass:[UIColor class]]) { 210 | UIColor *color = value; 211 | [attributedString addAttribute:NSUnderlineColorAttributeName value:color range:range0]; 212 | [attributedString addAttribute:NSUnderlineStyleAttributeName value:@1 range:range0]; 213 | } 214 | }]; 215 | }]; 216 | } 217 | 218 | - (void)setFontSizeInRange { 219 | [self modifyAttributedText:^(NSMutableAttributedString *attributedString) { 220 | [attributedString enumerateAttribute:NSFontAttributeName inRange:self.selectedRange options:(NSAttributedStringEnumerationLongestEffectiveRangeNotRequired) usingBlock:^(id _Nullable value, NSRange range0, BOOL * _Nonnull stop) { 221 | if ([value isKindOfClass:[UIFont class]]) { 222 | UIFont *font = value; 223 | [attributedString addAttribute:NSFontAttributeName value:[font copyWithFontSize:self->fontSize] range:range0]; 224 | } 225 | }]; 226 | }]; 227 | } 228 | 229 | - (void)setTextColorInRange { 230 | [self modifyAttributedText:^(NSMutableAttributedString *attributedString) { 231 | [attributedString addAttribute:NSForegroundColorAttributeName value:self->textColor range:self.selectedRange]; 232 | }]; 233 | } 234 | 235 | - (void)insertImageInRange:(UIImage *)image { 236 | if (image == nil) { 237 | [self becomeFirstResponder]; 238 | return; 239 | } 240 | 241 | CGFloat width = self.frame.size.width-self.textContainer.lineFragmentPadding*2; 242 | 243 | NSMutableAttributedString *mAttributedString = self.attributedText.mutableCopy; 244 | 245 | NSTextAttachment *attachment = [[NSTextAttachment alloc] init]; 246 | attachment.bounds = CGRectMake(0, 0, width, width * image.size.height / image.size.width); 247 | attachment.image = image; 248 | 249 | NSMutableAttributedString *attachmentString = [[NSMutableAttributedString alloc] initWithAttributedString:[NSAttributedString attributedStringWithAttachment:attachment]]; 250 | [attachmentString addAttributes:[self currentAttributes] range:NSMakeRange(0, attachmentString.length)]; 251 | 252 | [mAttributedString insertAttributedString:attachmentString atIndex:NSMaxRange(self.selectedRange)]; 253 | 254 | //更新attributedText 255 | NSInteger location = NSMaxRange(self.selectedRange) + 1; 256 | self.attributedText = mAttributedString.copy; 257 | 258 | //恢复焦点 259 | self.selectedRange = NSMakeRange(location, 0); 260 | [self becomeFirstResponder]; 261 | } 262 | 263 | #pragma - mark ------ update toolbar item color ------- 264 | 265 | /* 266 | 当焦点发生变化时,tool bar 的状态需要同步更新 267 | */ 268 | - (void)sendTextStyleUpdate { 269 | if ([self isFirstResponder] == NO) return; 270 | NSDictionary *attrs = self.typingAttributes; 271 | UIFont *font = attrs[NSFontAttributeName]; 272 | isItalic = font.isItatic; 273 | isBold = font.isBold; 274 | isUnderline = [attrs.allKeys containsObject:NSUnderlineStyleAttributeName]; 275 | textColor = attrs[NSForegroundColorAttributeName]; 276 | fontSize = font.fontSize; 277 | self.textStyleUpdated(fontSize, textColor, isItalic, isUnderline, isBold); 278 | } 279 | 280 | - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { 281 | static UIEvent *e = nil; 282 | 283 | if (e != nil && e == event) { 284 | e = nil; 285 | return [super hitTest:point withEvent:event]; 286 | } 287 | 288 | e = event; 289 | 290 | if (event.type == UIEventTypeTouches) { 291 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 292 | [self sendTextStyleUpdate]; 293 | }); 294 | } 295 | return [super hitTest:point withEvent:event]; 296 | } 297 | 298 | #pragma - mark ------ html parse ------- 299 | 300 | - (void)setHtml:(NSString *)html completion:(void (^)(void))completion { 301 | _html = html; 302 | if (html.length == 0) { 303 | self.attributedText = nil; 304 | return; 305 | } 306 | CGFloat imageWidth = self.frame.size.width - self.textContainer.lineFragmentPadding * 2; 307 | [self.parser attributedWithHtml:html imageWidth:imageWidth completion:^(NSAttributedString *attributedText) { 308 | self.attributedText = attributedText; 309 | dispatch_async(dispatch_get_main_queue(), ^{ 310 | completion(); 311 | }); 312 | }]; 313 | } 314 | 315 | - (void)getHtml:(void (^)(NSString *))completion { 316 | [self.parser htmlWithAttributed:self.attributedText orignalHtml:self.html completion: completion]; 317 | } 318 | 319 | @end 320 | -------------------------------------------------------------------------------- /SIXEditor/SIXHTMLParser.h: -------------------------------------------------------------------------------- 1 | // 2 | // XKHTMLParser.h 3 | // XKKEditor 4 | // 5 | // Copyright © 2018年 liujiliu. All rights reserved. 6 | // 7 | 8 | #import "SIXEditorHeader.h" 9 | 10 | /** 11 | 数据转换过程:(双向) 12 | NSAttributedString <-> HTML 13 | */ 14 | @interface SIXHTMLParser : NSObject 15 | 16 | @property (nonatomic, strong) id imageUploader; 17 | 18 | - (void)htmlWithAttributed:(NSAttributedString *)attributed 19 | orignalHtml:(NSString *)orignalHtml 20 | completion:(void (^)(NSString *html))completion; 21 | 22 | - (void)attributedWithHtml:(NSString *)html 23 | imageWidth:(CGFloat)imageWidth 24 | completion:(void (^)(NSAttributedString *attributedText))completion; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /SIXEditor/SIXHTMLParser.m: -------------------------------------------------------------------------------- 1 | // 2 | // XKHTMLParser.m 3 | // XKKEditor 4 | // 5 | // Copyright © 2018年 liujiliu. All rights reserved. 6 | // 7 | 8 | #import "SIXHTMLParser.h" 9 | #import "SIXEditorHeader.h" 10 | #import "UIFont+Category.h" 11 | 12 | NSString * const ImagePlaceholderTag = @"\U0000fffc"; 13 | 14 | @implementation SIXHTMLParser 15 | 16 | #pragma - mark NSAttributedString -> html 17 | 18 | - (void)htmlWithAttributed:(NSAttributedString *)attributed 19 | orignalHtml:(NSString *)orignalHtml 20 | completion:(void (^)(NSString *html))completion { 21 | void (^mainThreadCompletion) (NSString *html) = ^(NSString *html) { 22 | dispatch_async(dispatch_get_main_queue(), ^{ 23 | completion(html); 24 | }); 25 | }; 26 | dispatch_async(dispatch_get_global_queue(0, 0), ^{ 27 | [self inner_htmlWithAttributed:attributed orignalHtml:orignalHtml completion:mainThreadCompletion]; 28 | }); 29 | } 30 | 31 | - (void)inner_htmlWithAttributed:(NSAttributedString *)attributed 32 | orignalHtml:(NSString *)orignalHtml 33 | completion:(void (^)(NSString *html))completion { 34 | if (attributed.length == 0) { 35 | completion(nil); 36 | return; 37 | } 38 | 39 | NSMutableString *html = [NSMutableString string]; 40 | NSString *string = attributed.string; 41 | 42 | //保存UIImage数组 43 | NSMutableArray *images = [NSMutableArray array]; 44 | //获取html中的图片地址数组 45 | NSArray *imageUrls = [self imageUrls:orignalHtml]; 46 | 47 | [attributed enumerateAttributesInRange:NSMakeRange(0, attributed.length) options:(NSAttributedStringEnumerationLongestEffectiveRangeNotRequired) usingBlock:^(NSDictionary * _Nonnull attrs, NSRange range, BOOL * _Nonnull stop) { 48 | NSString *selectString = [string substringWithRange:range]; 49 | 50 | if ([selectString isEqualToString:ImagePlaceholderTag]) { 51 | NSTextAttachment *attachment = attrs[NSAttachmentAttributeName]; 52 | if (attachment.image) { 53 | [html appendFormat:@"", images.count]; 54 | [images addObject:attachment.image]; 55 | } else { 56 | NSString *imageName = [[attachment.fileWrapper.preferredFilename stringByDeletingPathExtension] stringByDeletingPathExtension]; 57 | for (NSString *imageUrl in imageUrls) { 58 | if ([imageUrl containsString:imageName]) { 59 | [html appendFormat:@"", imageUrl]; 60 | } 61 | } 62 | } 63 | } 64 | else if ([selectString isEqualToString:@"\n"]) { 65 | [html appendString:selectString]; 66 | } 67 | else { 68 | UIFont *font = attrs[NSFontAttributeName]; 69 | //字色 16进制 70 | NSString *textColor = [self hexStringFromColor:attrs[NSForegroundColorAttributeName]]; 71 | //字号 72 | CGFloat fontSize = font.fontSize; 73 | CGFloat location = html.length; 74 | [html appendFormat:@"%@", textColor, fontSize, selectString]; 75 | 76 | //斜体 77 | if (font.isItatic) { 78 | [html insertString:@"" atIndex:location]; 79 | [html appendString:@""]; 80 | } 81 | //下划线 82 | if ([attrs.allKeys containsObject:NSUnderlineColorAttributeName]) { 83 | [html insertString:@"" atIndex:location]; 84 | [html appendString:@""]; 85 | } 86 | //粗体 87 | if (font.isBold) { 88 | [html insertString:@"" atIndex:location]; 89 | [html appendString:@""]; 90 | } 91 | } 92 | }]; 93 | 94 | [html replaceOccurrencesOfString:@"\n" withString:@"
" options:0 range:NSMakeRange(0, html.length)]; 95 | [html replaceOccurrencesOfString:@"null" withString:@"" options:0 range:NSMakeRange(0, html.length)]; 96 | 97 | if (images.count) { //上传图片 98 | [self.imageUploader upload:images completion:^(NSDictionary * _Nonnull map) { 99 | for (int i=0; i NSAttributedString 113 | 114 | - (void)attributedWithHtml:(NSString *)html 115 | imageWidth:(CGFloat)imageWidth 116 | completion:(void (^)(NSAttributedString *attributedText))completion { 117 | dispatch_async(dispatch_get_global_queue(0, 0), ^{ 118 | NSAttributedString *attributed = [self attributedWithHtml:html imageWidth:imageWidth]; 119 | dispatch_async(dispatch_get_main_queue(), ^{ 120 | completion(attributed); 121 | }); 122 | }); 123 | } 124 | 125 | - (NSAttributedString *)attributedWithHtml:(NSString *)htmlString imageWidth:(CGFloat)imageWidth { 126 | NSData *data = [htmlString dataUsingEncoding:NSUTF8StringEncoding]; 127 | NSDictionary *dic = @{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, 128 | NSCharacterEncodingDocumentOption: @(NSUTF8StringEncoding)}; 129 | NSAttributedString *attributedString = [[NSAttributedString alloc] 130 | initWithData:data options:dic 131 | documentAttributes:nil error:nil]; 132 | //斜体适配 133 | NSMutableAttributedString *mAttributedString = attributedString.mutableCopy; 134 | [mAttributedString enumerateAttribute:NSFontAttributeName inRange:NSMakeRange(0, mAttributedString.length) options:(NSAttributedStringEnumerationLongestEffectiveRangeNotRequired) usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) { 135 | if ([value isKindOfClass:[UIFont class]]) { 136 | UIFont *font = value; 137 | if (font.isItatic) { 138 | [mAttributedString addAttribute:NSFontAttributeName value:[font copyWithItatic:true] range:range]; 139 | } 140 | } 141 | }]; 142 | 143 | //为了调整图片尺寸 需要在图片名后面拼接有图片宽高 例如:img-880x568.jpg 144 | [mAttributedString enumerateAttribute:NSAttachmentAttributeName inRange:NSMakeRange(0, mAttributedString.length) options:(NSAttributedStringEnumerationLongestEffectiveRangeNotRequired) usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) { 145 | 146 | if ([value isKindOfClass:[NSTextAttachment class]]) { 147 | NSTextAttachment *attachment = value; 148 | NSString *imageName = [[attachment.fileWrapper.preferredFilename stringByDeletingPathExtension] stringByDeletingPathExtension]; 149 | NSArray *sizeArr = [[imageName componentsSeparatedByString:@"-"].lastObject componentsSeparatedByString:@"x"]; 150 | if (sizeArr.count == 2) { 151 | CGFloat width0 = [sizeArr[0] floatValue]; 152 | CGFloat height0 = [sizeArr[1] floatValue]; 153 | attachment.bounds = CGRectMake(0, 0, imageWidth, imageWidth * height0 / width0); 154 | } else { 155 | attachment.bounds = CGRectMake(0, 0, imageWidth, imageWidth * 0.5); 156 | } 157 | } 158 | }]; 159 | return mAttributedString.copy; 160 | } 161 | 162 | 163 | #pragma - mark tool 164 | 165 | // UIColor转#ffffff格式的字符串 166 | - (NSString *)hexStringFromColor:(UIColor *)color { 167 | CGFloat r, g, b, a; 168 | [color getRed:&r green:&g blue:&b alpha:&a]; 169 | int rgb = (int) (r * 255.0f)<<16 | (int) (g * 255.0f)<<8 | (int) (b * 255.0f)<<0; 170 | return [NSString stringWithFormat:@"#%06x", rgb]; 171 | } 172 | 173 | //获取html中的图片地址数组 174 | - (NSArray *)imageUrls:(NSString *)html { 175 | if (html.length == 0) return @[]; 176 | 177 | NSMutableArray *array = [NSMutableArray array]; 178 | 179 | NSError *error = NULL; 180 | NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"()+?" 181 | options:NSRegularExpressionCaseInsensitive 182 | error:&error]; 183 | [regex enumerateMatchesInString:html 184 | options:0 185 | range:NSMakeRange(0, [html length]) 186 | usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { 187 | 188 | NSString *url = [html substringWithRange:[result rangeAtIndex:2]]; 189 | NSLog(@"img src %@",url); 190 | [array addObject:url]; 191 | }]; 192 | return array.copy; 193 | } 194 | 195 | /* 196 | NSMutableArray *exclude = [NSMutableArray array]; 197 | // 如果要使用XHTML文档,可以注释掉下面这行,否则会使用HTML 4.01。 198 | [exclude addObject:@"XML"]; 199 | // 如果要使用HTML Transitional,注释掉下面这行。否则就用HTML Strict。 200 | // 不过HTML Transitional支持font标签,而这显然不是我们想要的。 201 | [exclude addObjectsFromArray:[NSArray arrayWithObjects:@"APPLET", @"BASEFONT", @"CENTER", @"DIR", @"FONT", @"ISINDEX", @"MENU", @"S", @"STRIKE", @"U", nil]]; 202 | // 如果要将CSS放入HTML的head标签中,可以注释掉下面这样: 203 | [exclude addObject:@"STYLE"]; 204 | // 如果要完全不用CSS,同时注释掉上面这样和下面这行。 205 | // [exclude addObject:@"SPAN"]; 206 | // 如果要保留空格字符,注释掉下面几行——因为HTML会把任意长的空格合成一个空格的。 207 | [exclude addObject:@"Apple-converted-space"]; 208 | [exclude addObject:@"Apple-converted-tab"]; 209 | [exclude addObject:@"Apple-interchange-newline"]; 210 | // 如果不要完整的HTML结构,则取消注释下面一行。 211 | [exclude addObjectsFromArray:[NSArray arrayWithObjects:@"doctype", @"html", @"head", @"body", nil]]; 212 | 213 | NSDictionary *htmlAtt = [NSDictionary dictionaryWithObjectsAndKeys: 214 | NSHTMLTextDocumentType, NSDocumentTypeDocumentAttribute, 215 | exclude, @"ExcludedElements", nil]; 216 | NSError *error; 217 | NSData *htmlData = [attributedString dataFromRange:NSMakeRange(0, attributedString.length) documentAttributes:htmlAtt error:&error ]; 218 | NSString *htmlString = [[NSString alloc] initWithData:htmlData encoding:NSUTF8StringEncoding]; 219 | */ 220 | 221 | @end 222 | -------------------------------------------------------------------------------- /SIXEditor/UIFont+Category.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIFont+Category.h 3 | // SIXRichEditor 4 | // 5 | // Created by 刘吉六 on 2023/6/21. 6 | // Copyright © 2023 liujiliu. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface UIFont (Category) 14 | 15 | @property (nonatomic, readonly) BOOL isBold; 16 | @property (nonatomic, readonly) BOOL isItatic; 17 | @property (nonatomic, readonly) CGFloat fontSize; 18 | 19 | - (UIFont *)copyWithItatic:(BOOL)isItatic; 20 | - (UIFont *)copyWithBold:(BOOL)isBold; 21 | - (UIFont *)copyWithFontSize:(CGFloat)fontSize; 22 | @end 23 | 24 | NS_ASSUME_NONNULL_END 25 | -------------------------------------------------------------------------------- /SIXEditor/UIFont+Category.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIFont+Category.m 3 | // SIXRichEditor 4 | // 5 | // Created by 刘吉六 on 2023/6/21. 6 | // Copyright © 2023 liujiliu. All rights reserved. 7 | // 8 | 9 | #import "UIFont+Category.h" 10 | 11 | @implementation UIFont (Category) 12 | 13 | - (BOOL)isBold { 14 | return (self.fontDescriptor.symbolicTraits & UIFontDescriptorTraitBold) > 0; 15 | } 16 | 17 | - (BOOL)isItatic { 18 | return (self.fontDescriptor.symbolicTraits & UIFontDescriptorTraitItalic) > 0; 19 | } 20 | 21 | - (CGFloat)fontSize { 22 | return [self.fontDescriptor.fontAttributes[UIFontDescriptorSizeAttribute] floatValue]; 23 | } 24 | 25 | - (UIFont *)copyWithItatic:(BOOL)isItatic { 26 | return [self copyWithSymbolicTrait:UIFontDescriptorTraitItalic add:isItatic]; 27 | } 28 | 29 | - (UIFont *)copyWithBold:(BOOL)isBold { 30 | return [self copyWithSymbolicTrait:UIFontDescriptorTraitBold add:isBold]; 31 | } 32 | 33 | - (UIFont *)copyWithFontSize:(CGFloat)fontSize { 34 | return [UIFont fontWithDescriptor:self.fontDescriptor size:fontSize]; 35 | } 36 | 37 | - (UIFont *)copyWithSymbolicTrait:(UIFontDescriptorSymbolicTraits)symbolicTrait add:(BOOL)isAdd { 38 | UIFontDescriptorSymbolicTraits symbolicTraits = self.fontDescriptor.symbolicTraits; 39 | BOOL currentSymbolicTrait = (symbolicTraits & symbolicTrait) > 0; 40 | if (!currentSymbolicTrait && isAdd) { 41 | symbolicTraits |= symbolicTrait; 42 | } 43 | if (currentSymbolicTrait && !isAdd) { 44 | symbolicTraits &= (~symbolicTrait); 45 | } 46 | UIFontDescriptor * fontDescriptor = [self.fontDescriptor fontDescriptorWithSymbolicTraits:symbolicTraits]; 47 | if ((symbolicTraits & UIFontDescriptorTraitItalic) > 0) { 48 | CGAffineTransform matrix = CGAffineTransformMake(1, 0, tanf(20 * (CGFloat)M_PI / 180), 1, 0, 0); 49 | fontDescriptor = [fontDescriptor fontDescriptorWithMatrix:matrix]; 50 | } 51 | return [UIFont fontWithDescriptor:fontDescriptor size:0]; 52 | } 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /SIXRichEditor.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod spec lint SIXRichEditor.podspec' to ensure this is a 3 | # valid spec and to remove all comments including this before submitting the spec. 4 | # 5 | # To learn more about Podspec attributes see http://docs.cocoapods.org/specification.html 6 | # To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/ 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | 11 | # ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 12 | # 13 | # These will help people to find your library, and whilst it 14 | # can feel like a chore to fill in it's definitely to your advantage. The 15 | # summary should be tweet-length, and the description more in depth. 16 | # 17 | 18 | s.name = "SIXRichEditor" 19 | s.version = "1.0.5" 20 | s.summary = "基于UITextView的富文本编辑器." 21 | 22 | # This description issed to generate tags and improve search results. 23 | # * Think: What does it do? Why did you write it? What is the focus? 24 | # * Try to keep it short, snappy and to the point. 25 | # * Write the description between the DESC delimiters below. 26 | # * Finally, don't worry about the indent, CocoaPods strips it! 27 | 28 | s.homepage = "https://github.com/jiliuliu/SIXRichEditor" 29 | # s.screenshots = "www.example.com/screenshots_1.gif", "www.example.com/screenshots_2.gif" 30 | 31 | 32 | # ――― Spec License ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 33 | 34 | # Licensing your code is important. See http://choosealicense.com for more info. 35 | # CocoaPods will detect a license file if there is a named LICENSE* 36 | # Popular ones are 'MIT', 'BSD' and 'Apache License, Version 2.0'. 37 | # 38 | 39 | s.license = "MIT" 40 | # s.license = { :type => "MIT", :file => "FILE_LICENSE" } 41 | 42 | 43 | # ――― Author Metadata ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 44 | # 45 | # Specify the authors of the library, with email addresses. Email addresses 46 | # of the authors are extracted from the SCM log. E.g. $ git log. CocoaPods also 47 | # accepts just a name if you'd rather not provide an email address. 48 | # 49 | # Specify a social_media_url where others can refer to, for example a twitter 50 | # profile URL. 51 | # 52 | 53 | s.author = { "ljl" => "liujiliu1989@163.com" } 54 | # Or just: s.author = "ljl" 55 | # s.authors = { "ljl" => "liujiliu1989@163.com" } 56 | # s.social_media_url = "http://twitter.com/ljl" 57 | 58 | # ――― Platform Specifics ―――――――――――――――――――――――――――――――――――――――――――――――――――――― # 59 | # 60 | # If this Pod runs only on iOS or OS X, then specify the platform and 61 | # the deployment target. You can optionally include the target after the platform. 62 | # 63 | 64 | # s.platform = :ios 65 | s.platform = :ios, "7.0" 66 | 67 | # When using multiple platforms 68 | # s.ios.deployment_target = "5.0" 69 | # s.osx.deployment_target = "10.7" 70 | # s.watchos.deployment_target = "2.0" 71 | # s.tvos.deployment_target = "9.0" 72 | 73 | 74 | # ――― Source Location ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 75 | # 76 | # Specify the location from where the source should be retrieved. 77 | # Supports git, hg, bzr, svn and HTTP. 78 | # 79 | 80 | s.source = { :git => "https://github.com/jiliuliu/SIXRichEditor.git", :tag => s.version } 81 | 82 | 83 | # ――― Source Code ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 84 | # 85 | # CocoaPods is smart about how it includes source code. For source files 86 | # giving a folder will include any swift, h, m, mm, c & cpp files. 87 | # For header files it will include any header in the folder. 88 | # Not including the public_header_files will make all headers public. 89 | # 90 | 91 | s.source_files = 'SIXEditor/**/*.{h,m}' 92 | s.resource = 'SIXEditor/SIXEditor.bundle' 93 | # s.exclude_files = "Classes/Exclude" 94 | 95 | # s.public_header_files = "Classes/**/*.h" 96 | 97 | 98 | # ――― Resources ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 99 | # 100 | # A list of resources included with the Pod. These are copied into the 101 | # target bundle with a build phase script. Anything else will be cleaned. 102 | # You can preserve files from being cleaned, please don't preserve 103 | # non-essential files like tests, examples and documentation. 104 | # 105 | 106 | # s.resource = "icon.png" 107 | # s.resources = "Resources/*.png" 108 | 109 | # s.preserve_paths = "FilesToSave", "MoreFilesToSave" 110 | 111 | 112 | # ――― Project Linking ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 113 | # 114 | # Link your library with frameworks, or libraries. Libraries do not include 115 | # the lib prefix of their name. 116 | # 117 | 118 | # s.framework = "SomeFramework" 119 | # s.frameworks = "", "AnotherFramework" 120 | 121 | # s.library = "iconv" 122 | # s.libraries = "iconv", "xml2" 123 | 124 | 125 | # ――― Project Settings ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 126 | # 127 | # If your library depends on compiler flags you can set them in the xcconfig hash 128 | # where they will only apply to your library. If you depend on other Podspecs 129 | # you can include multiple dependencies to ensure it works. 130 | 131 | s.requires_arc = true 132 | 133 | # s.xcconfig = { "HEADER_SEARCH_PATHS" => "$(SDKROOT)/usr/include/libxml2" } 134 | # s.dependency "JSONKit", "~> 1.4" 135 | 136 | end 137 | -------------------------------------------------------------------------------- /SIXRichEditor.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | EA0AB15C218562A50097DEA8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EAF104C1210D53170009CED7 /* Assets.xcassets */; }; 11 | EA0AB15E2185840B0097DEA8 /* SIXEditor.bundle in Resources */ = {isa = PBXBuildFile; fileRef = EA0AB15D2185840B0097DEA8 /* SIXEditor.bundle */; }; 12 | EA7607A52A3C15C800C2B115 /* SIXEditorToolController.m in Sources */ = {isa = PBXBuildFile; fileRef = EA7607A42A3C15C800C2B115 /* SIXEditorToolController.m */; }; 13 | EA7607A82A3C4F4B00C2B115 /* SIXEditorImagePicker.m in Sources */ = {isa = PBXBuildFile; fileRef = EA7607A72A3C4F4B00C2B115 /* SIXEditorImagePicker.m */; }; 14 | EA7607AB2A3C5F2C00C2B115 /* SIXEditorTextColorPicker.m in Sources */ = {isa = PBXBuildFile; fileRef = EA7607AA2A3C5F2C00C2B115 /* SIXEditorTextColorPicker.m */; }; 15 | EA7607AE2A3C5F3900C2B115 /* SIXEditorFontSizePicker.m in Sources */ = {isa = PBXBuildFile; fileRef = EA7607AD2A3C5F3900C2B115 /* SIXEditorFontSizePicker.m */; }; 16 | EA7607B12A42DF0500C2B115 /* UIFont+Category.m in Sources */ = {isa = PBXBuildFile; fileRef = EA7607B02A42DF0500C2B115 /* UIFont+Category.m */; }; 17 | EABDC4C62110915A005E4157 /* SIXEditorController.m in Sources */ = {isa = PBXBuildFile; fileRef = EABDC4C52110915A005E4157 /* SIXEditorController.m */; }; 18 | EACA51982112E61C0027552E /* SIXHTMLParser.m in Sources */ = {isa = PBXBuildFile; fileRef = EACA51972112E61C0027552E /* SIXHTMLParser.m */; }; 19 | EAF104BA210D53160009CED7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = EAF104B9210D53160009CED7 /* AppDelegate.m */; }; 20 | EAF104C0210D53160009CED7 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EAF104BE210D53160009CED7 /* Main.storyboard */; }; 21 | EAF104C5210D53170009CED7 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EAF104C3210D53170009CED7 /* LaunchScreen.storyboard */; }; 22 | EAF104C8210D53170009CED7 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = EAF104C7210D53170009CED7 /* main.m */; }; 23 | EAF104E5210D556D0009CED7 /* SIXEditorView.m in Sources */ = {isa = PBXBuildFile; fileRef = EAF104E4210D556D0009CED7 /* SIXEditorView.m */; }; 24 | EAF104EA210D685C0009CED7 /* SIXEditorToolBar.m in Sources */ = {isa = PBXBuildFile; fileRef = EAF104E9210D685C0009CED7 /* SIXEditorToolBar.m */; }; 25 | EAF4B2CE2A47DEC400E37224 /* example.txt in Resources */ = {isa = PBXBuildFile; fileRef = EAF4B2CD2A47DEC400E37224 /* example.txt */; }; 26 | /* End PBXBuildFile section */ 27 | 28 | /* Begin PBXFileReference section */ 29 | EA0AB15D2185840B0097DEA8 /* SIXEditor.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = SIXEditor.bundle; sourceTree = ""; }; 30 | EA7607A32A3C15C800C2B115 /* SIXEditorToolController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SIXEditorToolController.h; sourceTree = ""; }; 31 | EA7607A42A3C15C800C2B115 /* SIXEditorToolController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SIXEditorToolController.m; sourceTree = ""; }; 32 | EA7607A62A3C4F4B00C2B115 /* SIXEditorImagePicker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SIXEditorImagePicker.h; sourceTree = ""; }; 33 | EA7607A72A3C4F4B00C2B115 /* SIXEditorImagePicker.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SIXEditorImagePicker.m; sourceTree = ""; }; 34 | EA7607A92A3C5F2C00C2B115 /* SIXEditorTextColorPicker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SIXEditorTextColorPicker.h; sourceTree = ""; }; 35 | EA7607AA2A3C5F2C00C2B115 /* SIXEditorTextColorPicker.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SIXEditorTextColorPicker.m; sourceTree = ""; }; 36 | EA7607AC2A3C5F3900C2B115 /* SIXEditorFontSizePicker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SIXEditorFontSizePicker.h; sourceTree = ""; }; 37 | EA7607AD2A3C5F3900C2B115 /* SIXEditorFontSizePicker.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SIXEditorFontSizePicker.m; sourceTree = ""; }; 38 | EA7607AF2A42DF0500C2B115 /* UIFont+Category.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIFont+Category.h"; sourceTree = ""; }; 39 | EA7607B02A42DF0500C2B115 /* UIFont+Category.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIFont+Category.m"; sourceTree = ""; }; 40 | EABDC4C42110915A005E4157 /* SIXEditorController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SIXEditorController.h; sourceTree = ""; }; 41 | EABDC4C52110915A005E4157 /* SIXEditorController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SIXEditorController.m; sourceTree = ""; }; 42 | EACA51962112E61C0027552E /* SIXHTMLParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SIXHTMLParser.h; sourceTree = ""; }; 43 | EACA51972112E61C0027552E /* SIXHTMLParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SIXHTMLParser.m; sourceTree = ""; }; 44 | EAF104B5210D53160009CED7 /* SIXRichEditor.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SIXRichEditor.app; sourceTree = BUILT_PRODUCTS_DIR; }; 45 | EAF104B8210D53160009CED7 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 46 | EAF104B9210D53160009CED7 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 47 | EAF104BF210D53160009CED7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 48 | EAF104C1210D53170009CED7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 49 | EAF104C4210D53170009CED7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 50 | EAF104C6210D53170009CED7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 51 | EAF104C7210D53170009CED7 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 52 | EAF104E3210D556D0009CED7 /* SIXEditorView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SIXEditorView.h; sourceTree = ""; }; 53 | EAF104E4210D556D0009CED7 /* SIXEditorView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SIXEditorView.m; sourceTree = ""; }; 54 | EAF104E6210D58FE0009CED7 /* SIXEditorHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SIXEditorHeader.h; sourceTree = ""; }; 55 | EAF104E8210D685C0009CED7 /* SIXEditorToolBar.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SIXEditorToolBar.h; sourceTree = ""; }; 56 | EAF104E9210D685C0009CED7 /* SIXEditorToolBar.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SIXEditorToolBar.m; sourceTree = ""; }; 57 | EAF4B2CD2A47DEC400E37224 /* example.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = example.txt; sourceTree = ""; }; 58 | /* End PBXFileReference section */ 59 | 60 | /* Begin PBXFrameworksBuildPhase section */ 61 | EAF104B2210D53160009CED7 /* Frameworks */ = { 62 | isa = PBXFrameworksBuildPhase; 63 | buildActionMask = 2147483647; 64 | files = ( 65 | ); 66 | runOnlyForDeploymentPostprocessing = 0; 67 | }; 68 | /* End PBXFrameworksBuildPhase section */ 69 | 70 | /* Begin PBXGroup section */ 71 | EAF104AC210D53160009CED7 = { 72 | isa = PBXGroup; 73 | children = ( 74 | EAF104E2210D54C20009CED7 /* SIXEditor */, 75 | EAF104B7210D53160009CED7 /* Example */, 76 | EAF104B6210D53160009CED7 /* Products */, 77 | ); 78 | sourceTree = ""; 79 | }; 80 | EAF104B6210D53160009CED7 /* Products */ = { 81 | isa = PBXGroup; 82 | children = ( 83 | EAF104B5210D53160009CED7 /* SIXRichEditor.app */, 84 | ); 85 | name = Products; 86 | sourceTree = ""; 87 | }; 88 | EAF104B7210D53160009CED7 /* Example */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | EAF104B8210D53160009CED7 /* AppDelegate.h */, 92 | EAF104B9210D53160009CED7 /* AppDelegate.m */, 93 | EAF4B2CD2A47DEC400E37224 /* example.txt */, 94 | EABDC4C42110915A005E4157 /* SIXEditorController.h */, 95 | EABDC4C52110915A005E4157 /* SIXEditorController.m */, 96 | EAF104BE210D53160009CED7 /* Main.storyboard */, 97 | EAF104C1210D53170009CED7 /* Assets.xcassets */, 98 | EAF104C3210D53170009CED7 /* LaunchScreen.storyboard */, 99 | EAF104C6210D53170009CED7 /* Info.plist */, 100 | EAF104C7210D53170009CED7 /* main.m */, 101 | ); 102 | path = Example; 103 | sourceTree = ""; 104 | }; 105 | EAF104E2210D54C20009CED7 /* SIXEditor */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | EA0AB15D2185840B0097DEA8 /* SIXEditor.bundle */, 109 | EAF104E3210D556D0009CED7 /* SIXEditorView.h */, 110 | EAF104E4210D556D0009CED7 /* SIXEditorView.m */, 111 | EA7607AF2A42DF0500C2B115 /* UIFont+Category.h */, 112 | EA7607B02A42DF0500C2B115 /* UIFont+Category.m */, 113 | EAF104E8210D685C0009CED7 /* SIXEditorToolBar.h */, 114 | EAF104E9210D685C0009CED7 /* SIXEditorToolBar.m */, 115 | EA7607A62A3C4F4B00C2B115 /* SIXEditorImagePicker.h */, 116 | EA7607A72A3C4F4B00C2B115 /* SIXEditorImagePicker.m */, 117 | EA7607A92A3C5F2C00C2B115 /* SIXEditorTextColorPicker.h */, 118 | EA7607AA2A3C5F2C00C2B115 /* SIXEditorTextColorPicker.m */, 119 | EA7607AC2A3C5F3900C2B115 /* SIXEditorFontSizePicker.h */, 120 | EA7607AD2A3C5F3900C2B115 /* SIXEditorFontSizePicker.m */, 121 | EA7607A32A3C15C800C2B115 /* SIXEditorToolController.h */, 122 | EA7607A42A3C15C800C2B115 /* SIXEditorToolController.m */, 123 | EACA51962112E61C0027552E /* SIXHTMLParser.h */, 124 | EACA51972112E61C0027552E /* SIXHTMLParser.m */, 125 | EAF104E6210D58FE0009CED7 /* SIXEditorHeader.h */, 126 | ); 127 | path = SIXEditor; 128 | sourceTree = ""; 129 | }; 130 | /* End PBXGroup section */ 131 | 132 | /* Begin PBXNativeTarget section */ 133 | EAF104B4210D53160009CED7 /* SIXRichEditor */ = { 134 | isa = PBXNativeTarget; 135 | buildConfigurationList = EAF104CB210D53170009CED7 /* Build configuration list for PBXNativeTarget "SIXRichEditor" */; 136 | buildPhases = ( 137 | EAF104B1210D53160009CED7 /* Sources */, 138 | EAF104B2210D53160009CED7 /* Frameworks */, 139 | EAF104B3210D53160009CED7 /* Resources */, 140 | ); 141 | buildRules = ( 142 | ); 143 | dependencies = ( 144 | ); 145 | name = SIXRichEditor; 146 | productName = SIXRichEditor; 147 | productReference = EAF104B5210D53160009CED7 /* SIXRichEditor.app */; 148 | productType = "com.apple.product-type.application"; 149 | }; 150 | /* End PBXNativeTarget section */ 151 | 152 | /* Begin PBXProject section */ 153 | EAF104AD210D53160009CED7 /* Project object */ = { 154 | isa = PBXProject; 155 | attributes = { 156 | LastUpgradeCheck = 0940; 157 | ORGANIZATIONNAME = liujiliu; 158 | TargetAttributes = { 159 | EAF104B4210D53160009CED7 = { 160 | CreatedOnToolsVersion = 9.4.1; 161 | }; 162 | }; 163 | }; 164 | buildConfigurationList = EAF104B0210D53160009CED7 /* Build configuration list for PBXProject "SIXRichEditor" */; 165 | compatibilityVersion = "Xcode 9.3"; 166 | developmentRegion = en; 167 | hasScannedForEncodings = 0; 168 | knownRegions = ( 169 | en, 170 | Base, 171 | ); 172 | mainGroup = EAF104AC210D53160009CED7; 173 | productRefGroup = EAF104B6210D53160009CED7 /* Products */; 174 | projectDirPath = ""; 175 | projectRoot = ""; 176 | targets = ( 177 | EAF104B4210D53160009CED7 /* SIXRichEditor */, 178 | ); 179 | }; 180 | /* End PBXProject section */ 181 | 182 | /* Begin PBXResourcesBuildPhase section */ 183 | EAF104B3210D53160009CED7 /* Resources */ = { 184 | isa = PBXResourcesBuildPhase; 185 | buildActionMask = 2147483647; 186 | files = ( 187 | EA0AB15C218562A50097DEA8 /* Assets.xcassets in Resources */, 188 | EAF104C5210D53170009CED7 /* LaunchScreen.storyboard in Resources */, 189 | EA0AB15E2185840B0097DEA8 /* SIXEditor.bundle in Resources */, 190 | EAF104C0210D53160009CED7 /* Main.storyboard in Resources */, 191 | EAF4B2CE2A47DEC400E37224 /* example.txt in Resources */, 192 | ); 193 | runOnlyForDeploymentPostprocessing = 0; 194 | }; 195 | /* End PBXResourcesBuildPhase section */ 196 | 197 | /* Begin PBXSourcesBuildPhase section */ 198 | EAF104B1210D53160009CED7 /* Sources */ = { 199 | isa = PBXSourcesBuildPhase; 200 | buildActionMask = 2147483647; 201 | files = ( 202 | EA7607A52A3C15C800C2B115 /* SIXEditorToolController.m in Sources */, 203 | EAF104E5210D556D0009CED7 /* SIXEditorView.m in Sources */, 204 | EAF104C8210D53170009CED7 /* main.m in Sources */, 205 | EAF104EA210D685C0009CED7 /* SIXEditorToolBar.m in Sources */, 206 | EABDC4C62110915A005E4157 /* SIXEditorController.m in Sources */, 207 | EA7607AE2A3C5F3900C2B115 /* SIXEditorFontSizePicker.m in Sources */, 208 | EA7607B12A42DF0500C2B115 /* UIFont+Category.m in Sources */, 209 | EAF104BA210D53160009CED7 /* AppDelegate.m in Sources */, 210 | EA7607AB2A3C5F2C00C2B115 /* SIXEditorTextColorPicker.m in Sources */, 211 | EACA51982112E61C0027552E /* SIXHTMLParser.m in Sources */, 212 | EA7607A82A3C4F4B00C2B115 /* SIXEditorImagePicker.m in Sources */, 213 | ); 214 | runOnlyForDeploymentPostprocessing = 0; 215 | }; 216 | /* End PBXSourcesBuildPhase section */ 217 | 218 | /* Begin PBXVariantGroup section */ 219 | EAF104BE210D53160009CED7 /* Main.storyboard */ = { 220 | isa = PBXVariantGroup; 221 | children = ( 222 | EAF104BF210D53160009CED7 /* Base */, 223 | ); 224 | name = Main.storyboard; 225 | sourceTree = ""; 226 | }; 227 | EAF104C3210D53170009CED7 /* LaunchScreen.storyboard */ = { 228 | isa = PBXVariantGroup; 229 | children = ( 230 | EAF104C4210D53170009CED7 /* Base */, 231 | ); 232 | name = LaunchScreen.storyboard; 233 | sourceTree = ""; 234 | }; 235 | /* End PBXVariantGroup section */ 236 | 237 | /* Begin XCBuildConfiguration section */ 238 | EAF104C9210D53170009CED7 /* Debug */ = { 239 | isa = XCBuildConfiguration; 240 | buildSettings = { 241 | ALWAYS_SEARCH_USER_PATHS = NO; 242 | CLANG_ANALYZER_NONNULL = YES; 243 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 244 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 245 | CLANG_CXX_LIBRARY = "libc++"; 246 | CLANG_ENABLE_MODULES = YES; 247 | CLANG_ENABLE_OBJC_ARC = YES; 248 | CLANG_ENABLE_OBJC_WEAK = YES; 249 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 250 | CLANG_WARN_BOOL_CONVERSION = YES; 251 | CLANG_WARN_COMMA = YES; 252 | CLANG_WARN_CONSTANT_CONVERSION = YES; 253 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 254 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 255 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 256 | CLANG_WARN_EMPTY_BODY = YES; 257 | CLANG_WARN_ENUM_CONVERSION = YES; 258 | CLANG_WARN_INFINITE_RECURSION = YES; 259 | CLANG_WARN_INT_CONVERSION = YES; 260 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 261 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 262 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 263 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 264 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 265 | CLANG_WARN_STRICT_PROTOTYPES = YES; 266 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 267 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 268 | CLANG_WARN_UNREACHABLE_CODE = YES; 269 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 270 | CODE_SIGN_IDENTITY = "iPhone Developer"; 271 | COPY_PHASE_STRIP = NO; 272 | DEBUG_INFORMATION_FORMAT = dwarf; 273 | ENABLE_STRICT_OBJC_MSGSEND = YES; 274 | ENABLE_TESTABILITY = YES; 275 | GCC_C_LANGUAGE_STANDARD = gnu11; 276 | GCC_DYNAMIC_NO_PIC = NO; 277 | GCC_NO_COMMON_BLOCKS = YES; 278 | GCC_OPTIMIZATION_LEVEL = 0; 279 | GCC_PREPROCESSOR_DEFINITIONS = ( 280 | "DEBUG=1", 281 | "$(inherited)", 282 | ); 283 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 284 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 285 | GCC_WARN_UNDECLARED_SELECTOR = YES; 286 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 287 | GCC_WARN_UNUSED_FUNCTION = YES; 288 | GCC_WARN_UNUSED_VARIABLE = YES; 289 | IPHONEOS_DEPLOYMENT_TARGET = 11.4; 290 | MTL_ENABLE_DEBUG_INFO = YES; 291 | ONLY_ACTIVE_ARCH = YES; 292 | SDKROOT = iphoneos; 293 | }; 294 | name = Debug; 295 | }; 296 | EAF104CA210D53170009CED7 /* Release */ = { 297 | isa = XCBuildConfiguration; 298 | buildSettings = { 299 | ALWAYS_SEARCH_USER_PATHS = NO; 300 | CLANG_ANALYZER_NONNULL = YES; 301 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 302 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 303 | CLANG_CXX_LIBRARY = "libc++"; 304 | CLANG_ENABLE_MODULES = YES; 305 | CLANG_ENABLE_OBJC_ARC = YES; 306 | CLANG_ENABLE_OBJC_WEAK = YES; 307 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 308 | CLANG_WARN_BOOL_CONVERSION = YES; 309 | CLANG_WARN_COMMA = YES; 310 | CLANG_WARN_CONSTANT_CONVERSION = YES; 311 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 312 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 313 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 314 | CLANG_WARN_EMPTY_BODY = YES; 315 | CLANG_WARN_ENUM_CONVERSION = YES; 316 | CLANG_WARN_INFINITE_RECURSION = YES; 317 | CLANG_WARN_INT_CONVERSION = YES; 318 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 319 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 320 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 321 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 322 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 323 | CLANG_WARN_STRICT_PROTOTYPES = YES; 324 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 325 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 326 | CLANG_WARN_UNREACHABLE_CODE = YES; 327 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 328 | CODE_SIGN_IDENTITY = "iPhone Developer"; 329 | COPY_PHASE_STRIP = NO; 330 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 331 | ENABLE_NS_ASSERTIONS = NO; 332 | ENABLE_STRICT_OBJC_MSGSEND = YES; 333 | GCC_C_LANGUAGE_STANDARD = gnu11; 334 | GCC_NO_COMMON_BLOCKS = YES; 335 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 336 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 337 | GCC_WARN_UNDECLARED_SELECTOR = YES; 338 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 339 | GCC_WARN_UNUSED_FUNCTION = YES; 340 | GCC_WARN_UNUSED_VARIABLE = YES; 341 | IPHONEOS_DEPLOYMENT_TARGET = 11.4; 342 | MTL_ENABLE_DEBUG_INFO = NO; 343 | SDKROOT = iphoneos; 344 | VALIDATE_PRODUCT = YES; 345 | }; 346 | name = Release; 347 | }; 348 | EAF104CC210D53170009CED7 /* Debug */ = { 349 | isa = XCBuildConfiguration; 350 | buildSettings = { 351 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 352 | CODE_SIGN_IDENTITY = "iPhone Developer"; 353 | CODE_SIGN_STYLE = Automatic; 354 | DEVELOPMENT_TEAM = E82T5RLF74; 355 | INFOPLIST_FILE = Example/Info.plist; 356 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 357 | LD_RUNPATH_SEARCH_PATHS = ( 358 | "$(inherited)", 359 | "@executable_path/Frameworks", 360 | ); 361 | PRODUCT_BUNDLE_IDENTIFIER = "github.com-jiliuliu.SIXRichEditor"; 362 | PRODUCT_NAME = "$(TARGET_NAME)"; 363 | PROVISIONING_PROFILE_SPECIFIER = ""; 364 | TARGETED_DEVICE_FAMILY = "1,2"; 365 | }; 366 | name = Debug; 367 | }; 368 | EAF104CD210D53170009CED7 /* Release */ = { 369 | isa = XCBuildConfiguration; 370 | buildSettings = { 371 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 372 | CODE_SIGN_IDENTITY = "iPhone Developer"; 373 | CODE_SIGN_STYLE = Automatic; 374 | DEVELOPMENT_TEAM = E82T5RLF74; 375 | INFOPLIST_FILE = Example/Info.plist; 376 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 377 | LD_RUNPATH_SEARCH_PATHS = ( 378 | "$(inherited)", 379 | "@executable_path/Frameworks", 380 | ); 381 | PRODUCT_BUNDLE_IDENTIFIER = "github.com-jiliuliu.SIXRichEditor"; 382 | PRODUCT_NAME = "$(TARGET_NAME)"; 383 | PROVISIONING_PROFILE_SPECIFIER = ""; 384 | TARGETED_DEVICE_FAMILY = "1,2"; 385 | }; 386 | name = Release; 387 | }; 388 | /* End XCBuildConfiguration section */ 389 | 390 | /* Begin XCConfigurationList section */ 391 | EAF104B0210D53160009CED7 /* Build configuration list for PBXProject "SIXRichEditor" */ = { 392 | isa = XCConfigurationList; 393 | buildConfigurations = ( 394 | EAF104C9210D53170009CED7 /* Debug */, 395 | EAF104CA210D53170009CED7 /* Release */, 396 | ); 397 | defaultConfigurationIsVisible = 0; 398 | defaultConfigurationName = Release; 399 | }; 400 | EAF104CB210D53170009CED7 /* Build configuration list for PBXNativeTarget "SIXRichEditor" */ = { 401 | isa = XCConfigurationList; 402 | buildConfigurations = ( 403 | EAF104CC210D53170009CED7 /* Debug */, 404 | EAF104CD210D53170009CED7 /* Release */, 405 | ); 406 | defaultConfigurationIsVisible = 0; 407 | defaultConfigurationName = Release; 408 | }; 409 | /* End XCConfigurationList section */ 410 | }; 411 | rootObject = EAF104AD210D53160009CED7 /* Project object */; 412 | } 413 | -------------------------------------------------------------------------------- /SIXRichEditor.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SIXRichEditor.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SIXRichEditor.xcodeproj/xcuserdata/liujiliu.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /SIXRichEditor.xcodeproj/xcuserdata/liujiliu.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | SIXRichEditor.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | SIXRichEditor.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 0 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /images/re1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiliuliu/SIXRichEditor/d78c866f239c7c1f0c400f191a8baf8813e31123/images/re1.png -------------------------------------------------------------------------------- /images/re2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiliuliu/SIXRichEditor/d78c866f239c7c1f0c400f191a8baf8813e31123/images/re2.png --------------------------------------------------------------------------------