├── ATDemo(OC) ├── ATTextView │ ├── ATTextView.h │ ├── ATTextView.m │ └── TextViewBinding │ │ ├── ATTextViewBinding.h │ │ └── ATTextViewBinding.m ├── AtDemo(OC).xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ ├── hn.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ │ │ └── xwh.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ ├── xcshareddata │ │ └── xcschemes │ │ │ └── AtDemo(OC).xcscheme │ └── xcuserdata │ │ ├── hn.xcuserdatad │ │ ├── xcdebugger │ │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ │ └── xcschememanagement.plist │ │ └── xwh.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ └── xcschememanagement.plist ├── AtDemo │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ ├── ListVC │ │ ├── ListViewController.h │ │ ├── ListViewController.m │ │ ├── ListViewController.xib │ │ └── Model │ │ │ ├── User.h │ │ │ └── User.m │ ├── Model │ │ ├── DataModel.h │ │ └── DataModel.m │ ├── View │ │ ├── TableViewOCCell.h │ │ ├── TableViewOCCell.m │ │ └── TableViewOCCell.xib │ ├── ViewController.h │ ├── ViewController.m │ └── main.m └── Lib │ ├── HNWKeyboardMonitor │ ├── HNWKeyboardMonitor.h │ └── HNWKeyboardMonitor.m │ └── YYText │ ├── Component │ ├── YYTextContainerView.h │ ├── YYTextContainerView.m │ ├── YYTextDebugOption.h │ ├── YYTextDebugOption.m │ ├── YYTextEffectWindow.h │ ├── YYTextEffectWindow.m │ ├── YYTextInput.h │ ├── YYTextInput.m │ ├── YYTextKeyboardManager.h │ ├── YYTextKeyboardManager.m │ ├── YYTextLayout.h │ ├── YYTextLayout.m │ ├── YYTextLine.h │ ├── YYTextLine.m │ ├── YYTextMagnifier.h │ ├── YYTextMagnifier.m │ ├── YYTextSelectionView.h │ └── YYTextSelectionView.m │ ├── String │ ├── YYTextArchiver.h │ ├── YYTextArchiver.m │ ├── YYTextAttribute.h │ ├── YYTextAttribute.m │ ├── YYTextParser.h │ ├── YYTextParser.m │ ├── YYTextRubyAnnotation.h │ ├── YYTextRubyAnnotation.m │ ├── YYTextRunDelegate.h │ └── YYTextRunDelegate.m │ ├── Utility │ ├── NSAttributedString+YYText.h │ ├── NSAttributedString+YYText.m │ ├── NSParagraphStyle+YYText.h │ ├── NSParagraphStyle+YYText.m │ ├── UIPasteboard+YYText.h │ ├── UIPasteboard+YYText.m │ ├── UIView+YYText.h │ ├── UIView+YYText.m │ ├── YYTextAsyncLayer.h │ ├── YYTextAsyncLayer.m │ ├── YYTextTransaction.h │ ├── YYTextTransaction.m │ ├── YYTextUtilities.h │ ├── YYTextUtilities.m │ ├── YYTextWeakProxy.h │ └── YYTextWeakProxy.m │ ├── YYLabel.h │ ├── YYLabel.m │ ├── YYText.h │ ├── YYTextView.h │ └── YYTextView.m ├── ATDemo(Swift) ├── ATDemo(Swift).xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ ├── hn.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ │ │ └── xwh.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ ├── xcshareddata │ │ └── xcschemes │ │ │ └── ATDemo(Swift).xcscheme │ └── xcuserdata │ │ └── xwh.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ └── xcschememanagement.plist └── ATDemo │ ├── ATTextView │ ├── ATTextView.swift │ └── ATTextViewBinding.swift │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── DataModel.swift │ ├── Info.plist │ ├── Lib │ └── Extension_String.swift │ ├── ListViewController.swift │ ├── SceneDelegate.swift │ ├── SwiftOC-Header.h │ ├── TableViewSwiftCell.swift │ ├── TableViewSwiftCell.xib │ ├── User.swift │ └── ViewController.swift ├── ATDemo.xcworkspace ├── contents.xcworkspacedata ├── xcshareddata │ └── IDEWorkspaceChecks.plist └── xcuserdata │ ├── hn.xcuserdatad │ ├── UserInterfaceState.xcuserstate │ └── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ └── xwh.xcuserdatad │ └── UserInterfaceState.xcuserstate ├── README.md └── screenshots └── 2021-04-30.png /ATDemo(OC)/ATTextView/ATTextView.h: -------------------------------------------------------------------------------- 1 | // 2 | // ATTextView.h 3 | // AtDemo 4 | // 5 | // Created by XWH on 2021/5/12. 6 | // 7 | 8 | #import 9 | 10 | #import "ATTextViewBinding.h" 11 | 12 | /// placeholder 属性参考了【SZTextView】 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | @class ATTextView; 17 | @protocol ATTextViewDelegate 18 | 19 | @optional 20 | - (void)atTextViewDidChange:(ATTextView *)textView; 21 | 22 | - (void)atTextViewDidBeginEditing:(ATTextView *)textView; 23 | 24 | - (void)atTextViewDidEndEditing:(ATTextView *)textView; 25 | 26 | /// 检查到输入特殊文本的回调 27 | - (void)atTextViewDidInputSpecialText:(ATTextView *)textView type:(ATTextViewBindingType)type; 28 | 29 | @end 30 | 31 | @interface ATTextView : UITextView 32 | 33 | /// 特殊文本【艾特、话题】列表,内容可自定义 34 | @property (copy, nonatomic) NSArray *atUserList; 35 | /// 是否为特殊文本【艾特、话题】 36 | @property (assign, nonatomic, getter=isSpecialText) BOOL bSpecialText; 37 | 38 | @property (copy, nonatomic) IBInspectable NSString *placeholder; 39 | @property (nonatomic) IBInspectable double fadeTime; 40 | @property (copy, nonatomic) NSAttributedString *attributedPlaceholder; 41 | @property (strong, nonatomic) UIColor *placeholderTextColor UI_APPEARANCE_SELECTOR; 42 | 43 | /// 最大长度设置,默认1000 44 | @property (assign, nonatomic) NSInteger maxTextLength; 45 | @property (strong, nonatomic) UIColor *attributedTextColor; 46 | /// 默认特殊文本高亮颜色,默认UIColor.redColor 47 | @property (strong, nonatomic) UIColor *hightTextColor; 48 | @property (assign, nonatomic) NSInteger lineSpacing; 49 | /// 支持自动检测特殊文本【艾特、话题】,默认YES 50 | @property (assign, nonatomic, getter=isSupport) BOOL bSupport; 51 | @property (nonatomic, weak) id atDelegate; 52 | 53 | - (void)insertWithBindingModel:(ATTextViewBinding *)bindingModel; 54 | 55 | @end 56 | 57 | NS_ASSUME_NONNULL_END 58 | -------------------------------------------------------------------------------- /ATDemo(OC)/ATTextView/TextViewBinding/ATTextViewBinding.h: -------------------------------------------------------------------------------- 1 | // 2 | // ATTextViewBinding.h 3 | // AtDemo 4 | // 5 | // Created by HN on 2021/4/29. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | #define ATTextBindingAttributeName @"TextViewBingDingFlagName" 13 | 14 | typedef NS_ENUM(NSInteger, ATTextViewBindingType) { 15 | ATTextViewBindingTypeUser = 0, 16 | ATTextViewBindingTypeTopic, 17 | }; 18 | 19 | @interface ATTextViewBinding : NSObject 20 | 21 | @property (nonatomic, strong) NSString *name; 22 | @property (nonatomic, assign) NSInteger userId; 23 | @property (nonatomic, assign) NSRange range; 24 | @property (nonatomic, assign) ATTextViewBindingType bindingType; 25 | 26 | - (instancetype)initWithName:(NSString*)name 27 | userId:(NSInteger)userId; 28 | 29 | @end 30 | 31 | NS_ASSUME_NONNULL_END 32 | -------------------------------------------------------------------------------- /ATDemo(OC)/ATTextView/TextViewBinding/ATTextViewBinding.m: -------------------------------------------------------------------------------- 1 | // 2 | // ATTextViewBinding.m 3 | // AtDemo 4 | // 5 | // Created by HN on 2021/4/29. 6 | // 7 | 8 | #import "ATTextViewBinding.h" 9 | 10 | @implementation ATTextViewBinding 11 | 12 | - (instancetype)initWithName:(NSString*)name 13 | userId:(NSInteger)userId { 14 | if (self = [super init]) { 15 | _name = name; 16 | _userId = userId; 17 | } 18 | return self; 19 | } 20 | @end 21 | -------------------------------------------------------------------------------- /ATDemo(OC)/AtDemo(OC).xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ATDemo(OC)/AtDemo(OC).xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ATDemo(OC)/AtDemo(OC).xcodeproj/project.xcworkspace/xcuserdata/hn.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkey6468/ATDemo/6cfeb4ff0adf57aec20946fd231db14721fa95dc/ATDemo(OC)/AtDemo(OC).xcodeproj/project.xcworkspace/xcuserdata/hn.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /ATDemo(OC)/AtDemo(OC).xcodeproj/project.xcworkspace/xcuserdata/xwh.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkey6468/ATDemo/6cfeb4ff0adf57aec20946fd231db14721fa95dc/ATDemo(OC)/AtDemo(OC).xcodeproj/project.xcworkspace/xcuserdata/xwh.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /ATDemo(OC)/AtDemo(OC).xcodeproj/xcshareddata/xcschemes/AtDemo(OC).xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /ATDemo(OC)/AtDemo(OC).xcodeproj/xcuserdata/hn.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 17 | 18 | 19 | 21 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /ATDemo(OC)/AtDemo(OC).xcodeproj/xcuserdata/hn.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | AtDemo.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /ATDemo(OC)/AtDemo(OC).xcodeproj/xcuserdata/xwh.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | AtDemo(OC).xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /ATDemo(OC)/AtDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // AtDemo 4 | // 5 | // Created by HN on 2021/4/19. 6 | // 7 | 8 | #import 9 | 10 | @interface AppDelegate : UIResponder 11 | 12 | @property (strong, nonatomic) UIWindow *window; 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /ATDemo(OC)/AtDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // AtDemo 4 | // 5 | // Created by HN on 2021/4/19. 6 | // 7 | 8 | #import "AppDelegate.h" 9 | 10 | @interface AppDelegate () 11 | 12 | @end 13 | 14 | @implementation AppDelegate 15 | 16 | 17 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 18 | // Override point for customization after application launch. 19 | return YES; 20 | } 21 | 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /ATDemo(OC)/AtDemo/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ATDemo(OC)/AtDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /ATDemo(OC)/AtDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ATDemo(OC)/AtDemo/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 | -------------------------------------------------------------------------------- /ATDemo(OC)/AtDemo/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 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSupportsIndirectInputEvents 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /ATDemo(OC)/AtDemo/ListVC/ListViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ListViewController.h 3 | // AtDemo 4 | // 5 | // Created by HN on 2021/4/19. 6 | // 7 | 8 | #import 9 | 10 | #import "User.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | typedef NS_ENUM(NSInteger, ATType) { 15 | ATTypeUser = 0, 16 | ATTypeTopic, 17 | }; 18 | 19 | @interface ListViewController : UIViewController 20 | 21 | @property (copy, nonatomic) void(^block) (NSInteger index, User *user); 22 | @property (assign, nonatomic) ATType type; 23 | 24 | @end 25 | 26 | NS_ASSUME_NONNULL_END 27 | -------------------------------------------------------------------------------- /ATDemo(OC)/AtDemo/ListVC/ListViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ListViewController.m 3 | // AtDemo 4 | // 5 | // Created by HN on 2021/4/19. 6 | // 7 | 8 | #import "ListViewController.h" 9 | 10 | @interface ListViewController () 11 | @property (weak, nonatomic) IBOutlet UITableView *tableView; 12 | @property (strong, nonatomic) NSMutableArray *dataArray; 13 | @end 14 | 15 | @implementation ListViewController 16 | 17 | - (void)viewDidLoad { 18 | [super viewDidLoad]; 19 | // Do any additional setup after loading the view from its nib. 20 | 21 | self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"取消" style:UIBarButtonItemStylePlain target:self action:@selector(done)]; 22 | 23 | self.tableView.tableFooterView = UIView.new; 24 | } 25 | 26 | #pragma mark - other 27 | - (void)done 28 | { 29 | [self dismissViewControllerAnimated:YES completion:nil]; 30 | } 31 | 32 | #pragma mark - UITableViewDelegate, UITableViewDelegate 33 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 34 | return self.dataArray.count; 35 | } 36 | 37 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 38 | { 39 | static NSString *identify = @"cell"; 40 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identify]; 41 | if (!cell) { 42 | cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identify]; 43 | } 44 | User *user = self.dataArray[indexPath.row]; 45 | cell.textLabel.text = user.name; 46 | return cell; 47 | } 48 | 49 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 50 | if (self.block) { 51 | [self done]; 52 | 53 | User *user = self.dataArray[indexPath.row]; 54 | self.block(indexPath.row, user); 55 | } 56 | } 57 | #pragma mark - get data 58 | - (NSMutableArray *)dataArray { 59 | if (!_dataArray) { 60 | _dataArray = [NSMutableArray array]; 61 | for (int i = 0; i < 10; i++) { 62 | User *user = [[User alloc]init]; 63 | if (self.type == ATTypeUser) { 64 | user.name = [NSString stringWithFormat:@"用户0%d_A",i+1]; 65 | } else { 66 | user.name = [NSString stringWithFormat:@"话题0%d_A",i+1]; 67 | } 68 | user.userId = i+1; 69 | [_dataArray addObject:user]; 70 | } 71 | } 72 | return _dataArray; 73 | } 74 | @end 75 | -------------------------------------------------------------------------------- /ATDemo(OC)/AtDemo/ListVC/ListViewController.xib: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /ATDemo(OC)/AtDemo/ListVC/Model/User.h: -------------------------------------------------------------------------------- 1 | // 2 | // User.h 3 | // AtDemo 4 | // 5 | // Created by HN on 2021/4/19. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface User : NSObject 13 | @property (assign, nonatomic) NSInteger userId; // 用户ID 14 | @property (copy, nonatomic) NSString *name; 15 | @property (nonatomic, assign) NSRange range; 16 | 17 | @end 18 | 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /ATDemo(OC)/AtDemo/ListVC/Model/User.m: -------------------------------------------------------------------------------- 1 | // 2 | // User.m 3 | // AtDemo 4 | // 5 | // Created by HN on 2021/4/19. 6 | // 7 | 8 | #import "User.h" 9 | 10 | @implementation User 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /ATDemo(OC)/AtDemo/Model/DataModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // DataModel.h 3 | // AtDemo 4 | // 5 | // Created by XWH on 2021/4/24. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface DataModel : NSObject 13 | 14 | @property (nonatomic, copy) NSString *text; 15 | @property (nonatomic, copy) NSArray *userList; 16 | 17 | @end 18 | 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /ATDemo(OC)/AtDemo/Model/DataModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // DataModel.m 3 | // AtDemo 4 | // 5 | // Created by XWH on 2021/4/24. 6 | // 7 | 8 | #import "DataModel.h" 9 | 10 | @implementation DataModel 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /ATDemo(OC)/AtDemo/View/TableViewOCCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // TableViewOCCell.h 3 | // AtDemo 4 | // 5 | // Created by XWH on 2021/4/24. 6 | // 7 | 8 | #import 9 | 10 | #import "DataModel.h" 11 | 12 | #define LABEL_HEIGHT(string,width,font) [string boundingRectWithSize:CGSizeMake(width, 0) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:font]} context:nil].size.height 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | @interface TableViewOCCell : UITableViewCell 17 | 18 | @property (strong, nonatomic) DataModel *model; 19 | 20 | + (CGFloat)rowHeightWithModel:(DataModel *)model; 21 | 22 | @end 23 | 24 | NS_ASSUME_NONNULL_END 25 | -------------------------------------------------------------------------------- /ATDemo(OC)/AtDemo/View/TableViewOCCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // TableViewOCCell.m 3 | // AtDemo 4 | // 5 | // Created by XWH on 2021/4/24. 6 | // 7 | 8 | #import "TableViewOCCell.h" 9 | 10 | #import "YYText.h" 11 | #import "ATTextViewBinding.h" 12 | 13 | @interface TableViewOCCell () 14 | 15 | @property (weak, nonatomic) IBOutlet YYLabel *yyLabel; 16 | 17 | @end 18 | 19 | @implementation TableViewOCCell 20 | 21 | - (void)awakeFromNib { 22 | [super awakeFromNib]; 23 | // Initialization code 24 | self.yyLabel.numberOfLines = 0; 25 | [self setupGesture]; 26 | } 27 | 28 | - (void)setupGesture 29 | { 30 | UITapGestureRecognizer *contentTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTapReply:)]; 31 | contentTap.delegate = self; 32 | [self.yyLabel addGestureRecognizer:contentTap]; 33 | } 34 | 35 | - (void)onTapReply:(UITapGestureRecognizer *)sender { 36 | NSLog(@"点击了cell22"); 37 | } 38 | 39 | #pragma mark - UIGestureRecognizerDelegate 40 | - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch { 41 | if ([touch.view isKindOfClass:[YYLabel class]]) { 42 | YYLabel *label = (YYLabel *)touch.view; 43 | NSRange highlightRange; 44 | YYTextHighlight *highlight = [label _getHighlightAtPoint:[touch locationInView:label] range:&highlightRange]; 45 | if (highlight) { 46 | return NO; 47 | } 48 | return YES; 49 | } 50 | return YES; 51 | } 52 | 53 | - (void)setModel:(DataModel *)model { 54 | _model = model; 55 | 56 | NSMutableAttributedString *muAttriSting = [[NSMutableAttributedString alloc]initWithString:model.text]; 57 | muAttriSting.yy_font = [UIFont systemFontOfSize:25]; 58 | muAttriSting.yy_color = UIColor.purpleColor; 59 | 60 | for (ATTextViewBinding *bindingModel in model.userList) { 61 | [muAttriSting yy_setTextHighlightRange:bindingModel.range 62 | color:UIColor.redColor 63 | backgroundColor:UIColor.darkGrayColor 64 | userInfo:@{@"id":@(bindingModel.userId)} 65 | tapAction:^(UIView * _Nonnull containerView, NSAttributedString * _Nonnull text, NSRange range, CGRect rect) { 66 | YYLabel *yyLabel = (YYLabel *)containerView; 67 | YYTextHighlight *highlight = (YYTextHighlight *)[yyLabel valueForKey:@"_highlight"]; 68 | NSDictionary *dictInfo = highlight.userInfo; 69 | if (dictInfo == nil) { 70 | return; 71 | } 72 | 73 | NSInteger toUserId = [dictInfo[@"id"] intValue]; 74 | NSLog(@"点击了: %ld",toUserId); 75 | } longPressAction:nil]; 76 | } 77 | 78 | self.yyLabel.attributedText = muAttriSting; 79 | } 80 | 81 | + (CGFloat)rowHeightWithModel:(DataModel *)model { 82 | CGFloat h = LABEL_HEIGHT(model.text, [UIScreen mainScreen].bounds.size.width, 25)+5; 83 | return h; 84 | } 85 | @end 86 | -------------------------------------------------------------------------------- /ATDemo(OC)/AtDemo/View/TableViewOCCell.xib: -------------------------------------------------------------------------------- 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 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /ATDemo(OC)/AtDemo/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // AtDemo 4 | // 5 | // Created by HN on 2021/4/19. 6 | // 7 | 8 | #import 9 | 10 | @interface ViewController : UIViewController 11 | 12 | 13 | @end 14 | 15 | -------------------------------------------------------------------------------- /ATDemo(OC)/AtDemo/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | 4 | #import "ViewController.h" 5 | #import "ListViewController.h" 6 | 7 | #import "TableViewOCCell.h" 8 | #import "ATTextView.h" 9 | 10 | #import "HNWKeyboardMonitor.h" 11 | 12 | #define k_defaultFont [UIFont systemFontOfSize:15] /// 默认字体大小 13 | #define k_defaultColor [UIColor blueColor] /// 默认特殊文本高亮颜色 14 | 15 | @interface ViewController () 16 | @property (weak, nonatomic) IBOutlet UITableView *tableView; 17 | @property (weak, nonatomic) IBOutlet ATTextView *textView; 18 | 19 | @property (weak, nonatomic) IBOutlet NSLayoutConstraint *textViewConstraintH; 20 | @property (weak, nonatomic) IBOutlet NSLayoutConstraint *bottomViewConstraintB; 21 | 22 | @property (strong, nonatomic) NSMutableArray *dataArray; 23 | @property (assign, nonatomic, getter=isNeedShowKeyboard) BOOL bNeedShowKeyboard; 24 | 25 | @end 26 | 27 | @implementation ViewController 28 | 29 | - (void)viewDidLoad { 30 | [super viewDidLoad]; 31 | 32 | self.navigationItem.title = @"ATTextView_OC"; 33 | 34 | [self settingUI]; 35 | [self initTableView]; 36 | } 37 | 38 | - (void)viewWillAppear:(BOOL)animated { 39 | [super viewWillAppear:animated]; 40 | [HNWKeyboardMonitor addDelegate:self]; 41 | if (self.isNeedShowKeyboard) { 42 | self.bNeedShowKeyboard = NO; 43 | [self.textView becomeFirstResponder]; 44 | } 45 | } 46 | 47 | - (void)viewDidDisappear:(BOOL)animated { 48 | [super viewDidDisappear:animated]; 49 | [HNWKeyboardMonitor removeDelegate:self]; 50 | } 51 | 52 | #pragma mark - UI 53 | - (void)settingUI { 54 | if ([self respondsToSelector:@selector(setAutomaticallyAdjustsScrollViewInsets:)]) { 55 | self.automaticallyAdjustsScrollViewInsets = NO; 56 | } 57 | 58 | self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"完成" style:UIBarButtonItemStylePlain target:self action:@selector(done)]; 59 | 60 | self.textView.atDelegate = self; 61 | self.textView.placeholder = @"我是测试placeholder"; 62 | self.textView.font = k_defaultFont; 63 | self.textView.attributedTextColor = k_defaultColor; 64 | 65 | // self.textView.maxTextLength = 20; 66 | // self.textView.placeholderTextColor = [UIColor colorWithRed:0.9 green:0.9 blue:0.9 alpha:0.75]; 67 | // self.textView.bSupport = NO; 68 | // self.textView.hightTextColor = UIColor.yellowColor; 69 | [self.textView becomeFirstResponder]; 70 | } 71 | 72 | - (void)initTableView { 73 | self.tableView.tableFooterView = UIView.new; 74 | 75 | [self.tableView registerNib:[UINib nibWithNibName:NSStringFromClass(TableViewOCCell.class) 76 | bundle:nil] 77 | forCellReuseIdentifier:NSStringFromClass(TableViewOCCell.class)]; 78 | } 79 | 80 | - (void)updateUI { 81 | CGRect frame = [self.textView.attributedText boundingRectWithSize:CGSizeMake([UIScreen mainScreen].bounds.size.width-85, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin context:nil]; 82 | CGFloat h = frame.size.height; 83 | if (h <= 80) { 84 | h = 80; 85 | } 86 | self.textViewConstraintH.constant = h; 87 | } 88 | 89 | #pragma mark - other 90 | - (IBAction)onActionInsertUser:(UIButton *)sender { 91 | self.textView.bSpecialText = NO; 92 | [self pushListVc:ATTypeUser]; 93 | } 94 | 95 | - (IBAction)onActionInsertTopic:(UIButton *)sender { 96 | self.textView.bSpecialText = NO; 97 | [self pushListVc:ATTypeTopic]; 98 | } 99 | 100 | - (void)pushListVc:(ATType)type { 101 | self.bNeedShowKeyboard = YES; 102 | ListViewController *vc = [[ListViewController alloc]init]; 103 | vc.type = type; 104 | UINavigationController *nav = [[UINavigationController alloc]initWithRootViewController:vc]; 105 | nav.modalPresentationStyle = UIModalPresentationFullScreen; 106 | [self presentViewController:nav animated:YES completion:nil]; 107 | 108 | __weak typeof(self) weakSelf = self; 109 | vc.block = ^(NSInteger index, User * _Nonnull user) { 110 | 111 | ATTextViewBinding *bindingModel = [[ATTextViewBinding alloc]initWithName:user.name 112 | userId:user.userId]; 113 | bindingModel.bindingType = type == ATTypeUser ? ATTextViewBindingTypeUser : ATTextViewBindingTypeTopic; 114 | [weakSelf.textView insertWithBindingModel:bindingModel]; 115 | }; 116 | } 117 | 118 | - (void)done { 119 | [self.view endEditing:YES]; 120 | 121 | NSArray *results = self.textView.atUserList; 122 | 123 | NSLog(@"输出打印:"); 124 | for (ATTextViewBinding *model in results) { 125 | NSLog(@"user info - name:%@ - location:%ld",model.name, model.range.location); 126 | } 127 | 128 | DataModel *model = [[DataModel alloc]init]; 129 | model.userList = results; 130 | model.text = self.textView.text; 131 | [self.dataArray addObject:model]; 132 | 133 | self.textView.text = nil; 134 | [self updateUI]; 135 | self.textView.typingAttributes = @{NSFontAttributeName:k_defaultFont, NSForegroundColorAttributeName:k_defaultColor}; 136 | 137 | [self.tableView reloadData]; 138 | } 139 | 140 | #pragma mark - ATTextViewDelegate 141 | - (void)atTextViewDidChange:(ATTextView *)textView { 142 | NSLog(@"%@",textView.text); 143 | [self updateUI]; 144 | } 145 | 146 | - (void)atTextViewDidInputSpecialText:(ATTextView *)textView type:(ATTextViewBindingType)type { 147 | #warning <#message#> 148 | if (type == ATTextViewBindingTypeUser) { 149 | [self pushListVc:ATTypeUser]; 150 | } else { 151 | [self pushListVc:ATTypeTopic]; 152 | } 153 | } 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | #pragma mark - UITableViewDataSource 171 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 172 | return self.dataArray.count; 173 | } 174 | 175 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { 176 | DataModel *model = self.dataArray[indexPath.row]; 177 | return [TableViewOCCell rowHeightWithModel:model]; 178 | } 179 | 180 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 181 | { 182 | TableViewOCCell *cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass(TableViewOCCell.class)]; 183 | cell.selectionStyle = UITableViewCellSelectionStyleNone; 184 | cell.model = self.dataArray[indexPath.row]; 185 | return cell; 186 | } 187 | 188 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 189 | { 190 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 191 | 192 | NSLog(@"点击了cell"); 193 | } 194 | 195 | #pragma mark HNWKeyboardMonitorDelegate 196 | - (void)keyboardMonitor:(HNWKeyboardMonitor *)keyboardMonitor keyboardWillShow:(HNWKeyboardInfo *)info { 197 | if (@available(iOS 11.0, *)) { 198 | self.bottomViewConstraintB.constant = info.keyboardEndFrame.size.height-self.view.safeAreaInsets.bottom; 199 | } else { 200 | self.bottomViewConstraintB.constant = info.keyboardEndFrame.size.height; 201 | } 202 | [UIView animateWithDuration:info.animationDuration animations:^{ 203 | [self.view layoutIfNeeded]; 204 | }]; 205 | } 206 | 207 | - (void)keyboardMonitor:(HNWKeyboardMonitor *)keyboardMonitor keyboardWillHide:(HNWKeyboardInfo *)info { 208 | self.bottomViewConstraintB.constant = 0; 209 | [UIView animateWithDuration:info.animationDuration animations:^{ 210 | [self.view layoutIfNeeded]; 211 | }]; 212 | } 213 | 214 | #pragma mark - get data 215 | - (NSMutableArray *)dataArray { 216 | if (!_dataArray) { 217 | _dataArray = [NSMutableArray array]; 218 | } 219 | return _dataArray; 220 | } 221 | 222 | @end 223 | -------------------------------------------------------------------------------- /ATDemo(OC)/AtDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // AtDemo 4 | // 5 | // Created by HN on 2021/4/19. 6 | // 7 | 8 | #import 9 | #import "AppDelegate.h" 10 | 11 | int main(int argc, char * argv[]) { 12 | NSString * appDelegateClassName; 13 | @autoreleasepool { 14 | // Setup code that might create autoreleased objects goes here. 15 | appDelegateClassName = NSStringFromClass([AppDelegate class]); 16 | } 17 | return UIApplicationMain(argc, argv, nil, appDelegateClassName); 18 | } 19 | -------------------------------------------------------------------------------- /ATDemo(OC)/Lib/HNWKeyboardMonitor/HNWKeyboardMonitor.h: -------------------------------------------------------------------------------- 1 | // 2 | // HNWKeyboardMonitor.h 3 | // huinongwang 4 | // 5 | // Created by yangyongzheng on 2019/8/12. 6 | // Copyright © 2019 cnhnb. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface HNWKeyboardInfo : NSObject 15 | @property (nonatomic, readonly) CGRect keyboardBeginFrame; 16 | @property (nonatomic, readonly) CGRect keyboardEndFrame; 17 | @property (nonatomic, readonly) double animationDuration; 18 | @end 19 | 20 | 21 | 22 | @class HNWKeyboardMonitor; 23 | 24 | @protocol HNWKeyboardMonitorDelegate 25 | 26 | @optional 27 | - (void)keyboardMonitor:(HNWKeyboardMonitor *)keyboardMonitor keyboardWillShow:(HNWKeyboardInfo *)info; 28 | - (void)keyboardMonitor:(HNWKeyboardMonitor *)keyboardMonitor keyboardDidShow:(HNWKeyboardInfo *)info; 29 | 30 | - (void)keyboardMonitor:(HNWKeyboardMonitor *)keyboardMonitor keyboardWillHide:(HNWKeyboardInfo *)info; 31 | - (void)keyboardMonitor:(HNWKeyboardMonitor *)keyboardMonitor keyboardDidHide:(HNWKeyboardInfo *)info; 32 | 33 | - (void)keyboardMonitor:(HNWKeyboardMonitor *)keyboardMonitor keyboardWillChangeFrame:(HNWKeyboardInfo *)info; 34 | - (void)keyboardMonitor:(HNWKeyboardMonitor *)keyboardMonitor keyboardDidChangeFrame:(HNWKeyboardInfo *)info; 35 | 36 | @end 37 | 38 | @interface HNWKeyboardMonitor : NSObject 39 | 40 | /** 添加代理 */ 41 | + (void)addDelegate:(id )delegate; 42 | 43 | /** 移除代理 */ 44 | + (void)removeDelegate:(id )delegate; 45 | 46 | @end 47 | 48 | NS_ASSUME_NONNULL_END 49 | -------------------------------------------------------------------------------- /ATDemo(OC)/Lib/HNWKeyboardMonitor/HNWKeyboardMonitor.m: -------------------------------------------------------------------------------- 1 | // 2 | // HNWKeyboardMonitor.m 3 | // huinongwang 4 | // 5 | // Created by yangyongzheng on 2019/8/12. 6 | // Copyright © 2019 cnhnb. All rights reserved. 7 | // 8 | 9 | #import "HNWKeyboardMonitor.h" 10 | 11 | @implementation HNWKeyboardInfo 12 | 13 | + (HNWKeyboardInfo *)keyboardInfoWithNotification:(NSNotification *)noti { 14 | HNWKeyboardInfo *info = [[HNWKeyboardInfo alloc] init]; 15 | info->_keyboardBeginFrame = [noti.userInfo[UIKeyboardFrameBeginUserInfoKey] CGRectValue]; 16 | info->_keyboardEndFrame = [noti.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; 17 | info->_animationDuration = [noti.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; 18 | return info; 19 | } 20 | 21 | @end 22 | 23 | 24 | 25 | #define HNWSharedKeyboardMonitor [HNWKeyboardMonitor sharedMonitor] 26 | 27 | @interface HNWKeyboardMonitor () 28 | @property (nonatomic, strong) NSHashTable *delegateContainer; 29 | @end 30 | 31 | @implementation HNWKeyboardMonitor 32 | 33 | #pragma mark - Lifecycle 34 | - (instancetype)init { 35 | if (self = [super init]) { 36 | [self setupDefaultConfiguration]; 37 | } 38 | return self; 39 | } 40 | 41 | + (HNWKeyboardMonitor *)sharedMonitor { 42 | static HNWKeyboardMonitor *monitor = nil; 43 | static dispatch_once_t onceToken; 44 | dispatch_once(&onceToken, ^{ 45 | monitor = [[HNWKeyboardMonitor alloc] init]; 46 | }); 47 | return monitor; 48 | } 49 | 50 | + (void)load { 51 | if (self == [HNWKeyboardMonitor self]) { 52 | [HNWKeyboardMonitor sharedMonitor]; 53 | } 54 | } 55 | 56 | - (void)dealloc { 57 | [NSNotificationCenter.defaultCenter removeObserver:self]; 58 | } 59 | 60 | #pragma mark - Public 61 | + (void)addDelegate:(id)delegate { 62 | if (delegate) { 63 | @synchronized (HNWSharedKeyboardMonitor.delegateContainer) { 64 | [HNWSharedKeyboardMonitor.delegateContainer addObject:delegate]; 65 | } 66 | } 67 | } 68 | 69 | + (void)removeDelegate:(id)delegate { 70 | @synchronized (HNWSharedKeyboardMonitor.delegateContainer) { 71 | [HNWSharedKeyboardMonitor.delegateContainer removeObject:delegate]; 72 | } 73 | } 74 | 75 | #pragma mark - Notifications 76 | - (void)keyboardWillShowNotification:(NSNotification *)noti { 77 | [self performDelegateSelector:@selector(keyboardMonitor:keyboardWillShow:) 78 | withKeyboardInfo:[HNWKeyboardInfo keyboardInfoWithNotification:noti]]; 79 | } 80 | 81 | - (void)keyboardDidShowNotification:(NSNotification *)noti { 82 | [self performDelegateSelector:@selector(keyboardMonitor:keyboardDidShow:) 83 | withKeyboardInfo:[HNWKeyboardInfo keyboardInfoWithNotification:noti]]; 84 | } 85 | 86 | - (void)keyboardWillHideNotification:(NSNotification *)noti { 87 | [self performDelegateSelector:@selector(keyboardMonitor:keyboardWillHide:) 88 | withKeyboardInfo:[HNWKeyboardInfo keyboardInfoWithNotification:noti]]; 89 | } 90 | 91 | - (void)keyboardDidHideNotification:(NSNotification *)noti { 92 | [self performDelegateSelector:@selector(keyboardMonitor:keyboardDidHide:) 93 | withKeyboardInfo:[HNWKeyboardInfo keyboardInfoWithNotification:noti]]; 94 | } 95 | 96 | - (void)keyboardWillChangeFrameNotification:(NSNotification *)noti { 97 | [self performDelegateSelector:@selector(keyboardMonitor:keyboardWillChangeFrame:) 98 | withKeyboardInfo:[HNWKeyboardInfo keyboardInfoWithNotification:noti]]; 99 | } 100 | 101 | - (void)keyboardDidChangeFrameNotification:(NSNotification *)noti { 102 | [self performDelegateSelector:@selector(keyboardMonitor:keyboardDidChangeFrame:) 103 | withKeyboardInfo:[HNWKeyboardInfo keyboardInfoWithNotification:noti]]; 104 | } 105 | 106 | #pragma mark - Misc 107 | - (void)setupDefaultConfiguration { 108 | self.delegateContainer = [NSHashTable weakObjectsHashTable]; 109 | [NSNotificationCenter.defaultCenter addObserver:self 110 | selector:@selector(keyboardWillShowNotification:) 111 | name:UIKeyboardWillShowNotification 112 | object:nil]; 113 | [NSNotificationCenter.defaultCenter addObserver:self 114 | selector:@selector(keyboardDidShowNotification:) 115 | name:UIKeyboardDidShowNotification 116 | object:nil]; 117 | [NSNotificationCenter.defaultCenter addObserver:self 118 | selector:@selector(keyboardWillHideNotification:) 119 | name:UIKeyboardWillHideNotification 120 | object:nil]; 121 | [NSNotificationCenter.defaultCenter addObserver:self 122 | selector:@selector(keyboardDidHideNotification:) 123 | name:UIKeyboardDidHideNotification 124 | object:nil]; 125 | [NSNotificationCenter.defaultCenter addObserver:self 126 | selector:@selector(keyboardWillChangeFrameNotification:) 127 | name:UIKeyboardWillChangeFrameNotification 128 | object:nil]; 129 | [NSNotificationCenter.defaultCenter addObserver:self 130 | selector:@selector(keyboardDidChangeFrameNotification:) 131 | name:UIKeyboardDidChangeFrameNotification 132 | object:nil]; 133 | } 134 | 135 | - (void)performDelegateSelector:(SEL)aSelector withKeyboardInfo:(HNWKeyboardInfo *)keyboardInfo { 136 | NSArray *allDelegates = self.delegateContainer.allObjects; 137 | for (id delegate in allDelegates) { 138 | if ([self.delegateContainer containsObject:delegate] && [delegate respondsToSelector:aSelector]) { 139 | #pragma clang diagnostic push 140 | #pragma clang diagnostic ignored "-Warc-performSelector-leaks" 141 | [delegate performSelector:aSelector withObject:self withObject:keyboardInfo]; 142 | #pragma clang diagnostic pop 143 | } 144 | } 145 | } 146 | 147 | @end 148 | -------------------------------------------------------------------------------- /ATDemo(OC)/Lib/YYText/Component/YYTextContainerView.h: -------------------------------------------------------------------------------- 1 | // 2 | // YYTextContainerView.h 3 | // YYText 4 | // 5 | // Created by ibireme on 15/4/21. 6 | // Copyright (c) 2015 ibireme. 7 | // 8 | // This source code is licensed under the MIT-style license found in the 9 | // LICENSE file in the root directory of this source tree. 10 | // 11 | 12 | #import 13 | 14 | #if __has_include() 15 | #import 16 | #else 17 | #import "YYTextLayout.h" 18 | #endif 19 | 20 | NS_ASSUME_NONNULL_BEGIN 21 | 22 | /** 23 | A simple view to diaplay `YYTextLayout`. 24 | 25 | @discussion This view can become first responder. If this view is first responder, 26 | all the action (such as UIMenu's action) would forward to the `hostView` property. 27 | Typically, you should not use this class directly. 28 | 29 | @warning All the methods in this class should be called on main thread. 30 | */ 31 | @interface YYTextContainerView : UIView 32 | 33 | /// First responder's aciton will forward to this view. 34 | @property (nullable, nonatomic, weak) UIView *hostView; 35 | 36 | /// Debug option for layout debug. Set this property will let the view redraw it's contents. 37 | @property (nullable, nonatomic, copy) YYTextDebugOption *debugOption; 38 | 39 | /// Text vertical alignment. 40 | @property (nonatomic) YYTextVerticalAlignment textVerticalAlignment; 41 | 42 | /// Text layout. Set this property will let the view redraw it's contents. 43 | @property (nullable, nonatomic, strong) YYTextLayout *layout; 44 | 45 | /// The contents fade animation duration when the layout's contents changed. Default is 0 (no animation). 46 | @property (nonatomic) NSTimeInterval contentsFadeDuration; 47 | 48 | /// Convenience method to set `layout` and `contentsFadeDuration`. 49 | /// @param layout Same as `layout` property. 50 | /// @param fadeDuration Same as `contentsFadeDuration` property. 51 | - (void)setLayout:(nullable YYTextLayout *)layout withFadeDuration:(NSTimeInterval)fadeDuration; 52 | 53 | @end 54 | 55 | NS_ASSUME_NONNULL_END 56 | -------------------------------------------------------------------------------- /ATDemo(OC)/Lib/YYText/Component/YYTextContainerView.m: -------------------------------------------------------------------------------- 1 | // 2 | // YYTextContainerView.m 3 | // YYText 4 | // 5 | // Created by ibireme on 15/4/21. 6 | // Copyright (c) 2015 ibireme. 7 | // 8 | // This source code is licensed under the MIT-style license found in the 9 | // LICENSE file in the root directory of this source tree. 10 | // 11 | 12 | #import "YYTextContainerView.h" 13 | 14 | @implementation YYTextContainerView { 15 | BOOL _attachmentChanged; 16 | NSMutableArray *_attachmentViews; 17 | NSMutableArray *_attachmentLayers; 18 | } 19 | 20 | - (instancetype)initWithFrame:(CGRect)frame { 21 | self = [super initWithFrame:frame]; 22 | if (!self) return nil; 23 | self.backgroundColor = [UIColor clearColor]; 24 | _attachmentViews = [NSMutableArray array]; 25 | _attachmentLayers = [NSMutableArray array]; 26 | return self; 27 | } 28 | 29 | - (void)setDebugOption:(YYTextDebugOption *)debugOption { 30 | BOOL needDraw = _debugOption.needDrawDebug; 31 | _debugOption = debugOption.copy; 32 | if (_debugOption.needDrawDebug != needDraw) { 33 | [self setNeedsDisplay]; 34 | } 35 | } 36 | 37 | - (void)setTextVerticalAlignment:(YYTextVerticalAlignment)textVerticalAlignment { 38 | if (_textVerticalAlignment == textVerticalAlignment) return; 39 | _textVerticalAlignment = textVerticalAlignment; 40 | [self setNeedsDisplay]; 41 | } 42 | 43 | - (void)setContentsFadeDuration:(NSTimeInterval)contentsFadeDuration { 44 | if (_contentsFadeDuration == contentsFadeDuration) return; 45 | _contentsFadeDuration = contentsFadeDuration; 46 | if (contentsFadeDuration <= 0) { 47 | [self.layer removeAnimationForKey:@"contents"]; 48 | } 49 | } 50 | 51 | - (void)setLayout:(YYTextLayout *)layout { 52 | if (_layout == layout) return; 53 | _layout = layout; 54 | _attachmentChanged = YES; 55 | [self setNeedsDisplay]; 56 | } 57 | 58 | - (void)setLayout:(YYTextLayout *)layout withFadeDuration:(NSTimeInterval)fadeDuration { 59 | self.contentsFadeDuration = fadeDuration; 60 | self.layout = layout; 61 | } 62 | 63 | - (void)drawRect:(CGRect)rect { 64 | // fade content 65 | [self.layer removeAnimationForKey:@"contents"]; 66 | if (_contentsFadeDuration > 0) { 67 | CATransition *transition = [CATransition animation]; 68 | transition.duration = _contentsFadeDuration; 69 | transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; 70 | transition.type = kCATransitionFade; 71 | [self.layer addAnimation:transition forKey:@"contents"]; 72 | } 73 | 74 | // update attachment 75 | if (_attachmentChanged) { 76 | for (UIView *view in _attachmentViews) { 77 | if (view.superview == self) [view removeFromSuperview]; 78 | } 79 | for (CALayer *layer in _attachmentLayers) { 80 | if (layer.superlayer == self.layer) [layer removeFromSuperlayer]; 81 | } 82 | [_attachmentViews removeAllObjects]; 83 | [_attachmentLayers removeAllObjects]; 84 | } 85 | 86 | // draw layout 87 | CGSize boundingSize = _layout.textBoundingSize; 88 | CGPoint point = CGPointZero; 89 | if (_textVerticalAlignment == YYTextVerticalAlignmentCenter) { 90 | if (_layout.container.isVerticalForm) { 91 | point.x = -(self.bounds.size.width - boundingSize.width) * 0.5; 92 | } else { 93 | point.y = (self.bounds.size.height - boundingSize.height) * 0.5; 94 | } 95 | } else if (_textVerticalAlignment == YYTextVerticalAlignmentBottom) { 96 | if (_layout.container.isVerticalForm) { 97 | point.x = -(self.bounds.size.width - boundingSize.width); 98 | } else { 99 | point.y = (self.bounds.size.height - boundingSize.height); 100 | } 101 | } 102 | [_layout drawInContext:UIGraphicsGetCurrentContext() size:self.bounds.size point:point view:self layer:self.layer debug:_debugOption cancel:nil]; 103 | 104 | // update attachment 105 | if (_attachmentChanged) { 106 | _attachmentChanged = NO; 107 | for (YYTextAttachment *a in _layout.attachments) { 108 | if ([a.content isKindOfClass:[UIView class]]) [_attachmentViews addObject:a.content]; 109 | if ([a.content isKindOfClass:[CALayer class]]) [_attachmentLayers addObject:a.content]; 110 | } 111 | } 112 | } 113 | 114 | - (void)setFrame:(CGRect)frame { 115 | CGSize oldSize = self.bounds.size; 116 | [super setFrame:frame]; 117 | if (!CGSizeEqualToSize(oldSize, self.bounds.size)) { 118 | [self setNeedsLayout]; 119 | } 120 | } 121 | 122 | - (void)setBounds:(CGRect)bounds { 123 | CGSize oldSize = self.bounds.size; 124 | [super setBounds:bounds]; 125 | if (!CGSizeEqualToSize(oldSize, self.bounds.size)) { 126 | [self setNeedsLayout]; 127 | } 128 | } 129 | 130 | #pragma mark - UIResponder forward 131 | 132 | - (BOOL)canBecomeFirstResponder { 133 | return YES; 134 | } 135 | 136 | - (BOOL)canPerformAction:(SEL)action withSender:(id)sender { 137 | return [self.hostView canPerformAction:action withSender:sender]; 138 | } 139 | 140 | - (id)forwardingTargetForSelector:(SEL)aSelector { 141 | return self.hostView; 142 | } 143 | 144 | @end 145 | -------------------------------------------------------------------------------- /ATDemo(OC)/Lib/YYText/Component/YYTextDebugOption.h: -------------------------------------------------------------------------------- 1 | // 2 | // YYTextDebugOption.h 3 | // YYText 4 | // 5 | // Created by ibireme on 15/4/8. 6 | // Copyright (c) 2015 ibireme. 7 | // 8 | // This source code is licensed under the MIT-style license found in the 9 | // LICENSE file in the root directory of this source tree. 10 | // 11 | 12 | #import 13 | 14 | @class YYTextDebugOption; 15 | 16 | NS_ASSUME_NONNULL_BEGIN 17 | 18 | /** 19 | The YYTextDebugTarget protocol defines the method a debug target should implement. 20 | A debug target can be add to the global container to receive the shared debug 21 | option changed notification. 22 | */ 23 | @protocol YYTextDebugTarget 24 | 25 | @required 26 | /** 27 | When the shared debug option changed, this method would be called on main thread. 28 | It should return as quickly as possible. The option's property should not be changed 29 | in this method. 30 | 31 | @param option The shared debug option. 32 | */ 33 | - (void)setDebugOption:(nullable YYTextDebugOption *)option; 34 | @end 35 | 36 | 37 | 38 | /** 39 | The debug option for YYText. 40 | */ 41 | @interface YYTextDebugOption : NSObject 42 | @property (nullable, nonatomic, strong) UIColor *baselineColor; ///< baseline color 43 | @property (nullable, nonatomic, strong) UIColor *CTFrameBorderColor; ///< CTFrame path border color 44 | @property (nullable, nonatomic, strong) UIColor *CTFrameFillColor; ///< CTFrame path fill color 45 | @property (nullable, nonatomic, strong) UIColor *CTLineBorderColor; ///< CTLine bounds border color 46 | @property (nullable, nonatomic, strong) UIColor *CTLineFillColor; ///< CTLine bounds fill color 47 | @property (nullable, nonatomic, strong) UIColor *CTLineNumberColor; ///< CTLine line number color 48 | @property (nullable, nonatomic, strong) UIColor *CTRunBorderColor; ///< CTRun bounds border color 49 | @property (nullable, nonatomic, strong) UIColor *CTRunFillColor; ///< CTRun bounds fill color 50 | @property (nullable, nonatomic, strong) UIColor *CTRunNumberColor; ///< CTRun number color 51 | @property (nullable, nonatomic, strong) UIColor *CGGlyphBorderColor; ///< CGGlyph bounds border color 52 | @property (nullable, nonatomic, strong) UIColor *CGGlyphFillColor; ///< CGGlyph bounds fill color 53 | 54 | - (BOOL)needDrawDebug; ///< `YES`: at least one debug color is visible. `NO`: all debug color is invisible/nil. 55 | - (void)clear; ///< Set all debug color to nil. 56 | 57 | /** 58 | Add a debug target. 59 | 60 | @discussion When `setSharedDebugOption:` is called, all added debug target will 61 | receive `setDebugOption:` in main thread. It maintains an unsafe_unretained 62 | reference to this target. The target must to removed before dealloc. 63 | 64 | @param target A debug target. 65 | */ 66 | + (void)addDebugTarget:(id)target; 67 | 68 | /** 69 | Remove a debug target which is added by `addDebugTarget:`. 70 | 71 | @param target A debug target. 72 | */ 73 | + (void)removeDebugTarget:(id)target; 74 | 75 | /** 76 | Returns the shared debug option. 77 | 78 | @return The shared debug option, default is nil. 79 | */ 80 | + (nullable YYTextDebugOption *)sharedDebugOption; 81 | 82 | /** 83 | Set a debug option as shared debug option. 84 | This method must be called on main thread. 85 | 86 | @discussion When call this method, the new option will set to all debug target 87 | which is added by `addDebugTarget:`. 88 | 89 | @param option A new debug option (nil is valid). 90 | */ 91 | + (void)setSharedDebugOption:(nullable YYTextDebugOption *)option; 92 | 93 | @end 94 | 95 | NS_ASSUME_NONNULL_END 96 | -------------------------------------------------------------------------------- /ATDemo(OC)/Lib/YYText/Component/YYTextDebugOption.m: -------------------------------------------------------------------------------- 1 | // 2 | // YYTextDebugOption.m 3 | // YYText 4 | // 5 | // Created by ibireme on 15/4/8. 6 | // Copyright (c) 2015 ibireme. 7 | // 8 | // This source code is licensed under the MIT-style license found in the 9 | // LICENSE file in the root directory of this source tree. 10 | // 11 | 12 | #import "YYTextDebugOption.h" 13 | #import "YYTextWeakProxy.h" 14 | #import 15 | #import 16 | 17 | static pthread_mutex_t _sharedDebugLock; 18 | static CFMutableSetRef _sharedDebugTargets = nil; 19 | static YYTextDebugOption *_sharedDebugOption = nil; 20 | 21 | static const void* _sharedDebugSetRetain(CFAllocatorRef allocator, const void *value) { 22 | return value; 23 | } 24 | 25 | static void _sharedDebugSetRelease(CFAllocatorRef allocator, const void *value) { 26 | } 27 | 28 | void _sharedDebugSetFunction(const void *value, void *context) { 29 | id target = (__bridge id)(value); 30 | [target setDebugOption:_sharedDebugOption]; 31 | } 32 | 33 | static void _initSharedDebug() { 34 | static dispatch_once_t onceToken; 35 | dispatch_once(&onceToken, ^{ 36 | pthread_mutex_init(&_sharedDebugLock, NULL); 37 | CFSetCallBacks callbacks = kCFTypeSetCallBacks; 38 | callbacks.retain = _sharedDebugSetRetain; 39 | callbacks.release = _sharedDebugSetRelease; 40 | _sharedDebugTargets = CFSetCreateMutable(CFAllocatorGetDefault(), 0, &callbacks); 41 | }); 42 | } 43 | 44 | static void _setSharedDebugOption(YYTextDebugOption *option) { 45 | _initSharedDebug(); 46 | pthread_mutex_lock(&_sharedDebugLock); 47 | _sharedDebugOption = option.copy; 48 | CFSetApplyFunction(_sharedDebugTargets, _sharedDebugSetFunction, NULL); 49 | pthread_mutex_unlock(&_sharedDebugLock); 50 | } 51 | 52 | static YYTextDebugOption *_getSharedDebugOption() { 53 | _initSharedDebug(); 54 | pthread_mutex_lock(&_sharedDebugLock); 55 | YYTextDebugOption *op = _sharedDebugOption; 56 | pthread_mutex_unlock(&_sharedDebugLock); 57 | return op; 58 | } 59 | 60 | static void _addDebugTarget(id target) { 61 | _initSharedDebug(); 62 | pthread_mutex_lock(&_sharedDebugLock); 63 | CFSetAddValue(_sharedDebugTargets, (__bridge const void *)(target)); 64 | pthread_mutex_unlock(&_sharedDebugLock); 65 | } 66 | 67 | static void _removeDebugTarget(id target) { 68 | _initSharedDebug(); 69 | pthread_mutex_lock(&_sharedDebugLock); 70 | CFSetRemoveValue(_sharedDebugTargets, (__bridge const void *)(target)); 71 | pthread_mutex_unlock(&_sharedDebugLock); 72 | } 73 | 74 | 75 | @implementation YYTextDebugOption 76 | 77 | - (id)copyWithZone:(NSZone *)zone { 78 | YYTextDebugOption *op = [self.class new]; 79 | op.baselineColor = self.baselineColor; 80 | op.CTFrameBorderColor = self.CTFrameBorderColor; 81 | op.CTFrameFillColor = self.CTFrameFillColor; 82 | op.CTLineBorderColor = self.CTLineBorderColor; 83 | op.CTLineFillColor = self.CTLineFillColor; 84 | op.CTLineNumberColor = self.CTLineNumberColor; 85 | op.CTRunBorderColor = self.CTRunBorderColor; 86 | op.CTRunFillColor = self.CTRunFillColor; 87 | op.CTRunNumberColor = self.CTRunNumberColor; 88 | op.CGGlyphBorderColor = self.CGGlyphBorderColor; 89 | op.CGGlyphFillColor = self.CGGlyphFillColor; 90 | return op; 91 | } 92 | 93 | - (BOOL)needDrawDebug { 94 | if (self.baselineColor || 95 | self.CTFrameBorderColor || 96 | self.CTFrameFillColor || 97 | self.CTLineBorderColor || 98 | self.CTLineFillColor || 99 | self.CTLineNumberColor || 100 | self.CTRunBorderColor || 101 | self.CTRunFillColor || 102 | self.CTRunNumberColor || 103 | self.CGGlyphBorderColor || 104 | self.CGGlyphFillColor) return YES; 105 | return NO; 106 | } 107 | 108 | - (void)clear { 109 | self.baselineColor = nil; 110 | self.CTFrameBorderColor = nil; 111 | self.CTFrameFillColor = nil; 112 | self.CTLineBorderColor = nil; 113 | self.CTLineFillColor = nil; 114 | self.CTLineNumberColor = nil; 115 | self.CTRunBorderColor = nil; 116 | self.CTRunFillColor = nil; 117 | self.CTRunNumberColor = nil; 118 | self.CGGlyphBorderColor = nil; 119 | self.CGGlyphFillColor = nil; 120 | } 121 | 122 | + (void)addDebugTarget:(id)target { 123 | if (target) _addDebugTarget(target); 124 | } 125 | 126 | + (void)removeDebugTarget:(id)target { 127 | if (target) _removeDebugTarget(target); 128 | } 129 | 130 | + (YYTextDebugOption *)sharedDebugOption { 131 | return _getSharedDebugOption(); 132 | } 133 | 134 | + (void)setSharedDebugOption:(YYTextDebugOption *)option { 135 | NSAssert([NSThread isMainThread], @"This method must be called on the main thread"); 136 | _setSharedDebugOption(option); 137 | } 138 | 139 | @end 140 | 141 | -------------------------------------------------------------------------------- /ATDemo(OC)/Lib/YYText/Component/YYTextEffectWindow.h: -------------------------------------------------------------------------------- 1 | // 2 | // YYTextEffectWindow.h 3 | // YYText 4 | // 5 | // Created by ibireme on 15/2/25. 6 | // Copyright (c) 2015 ibireme. 7 | // 8 | // This source code is licensed under the MIT-style license found in the 9 | // LICENSE file in the root directory of this source tree. 10 | // 11 | 12 | #import 13 | 14 | #if __has_include() 15 | #import 16 | #import 17 | #else 18 | #import "YYTextMagnifier.h" 19 | #import "YYTextSelectionView.h" 20 | #endif 21 | 22 | NS_ASSUME_NONNULL_BEGIN 23 | 24 | /** 25 | A window to display magnifier and extra contents for text view. 26 | 27 | @discussion Use `sharedWindow` to get the instance, don't create your own instance. 28 | Typically, you should not use this class directly. 29 | */ 30 | @interface YYTextEffectWindow : UIWindow 31 | 32 | /// Returns the shared instance (returns nil in App Extension). 33 | + (nullable instancetype)sharedWindow; 34 | 35 | /// Show the magnifier in this window with a 'popup' animation. @param mag A magnifier. 36 | - (void)showMagnifier:(YYTextMagnifier *)mag; 37 | /// Update the magnifier content and position. @param mag A magnifier. 38 | - (void)moveMagnifier:(YYTextMagnifier *)mag; 39 | /// Remove the magnifier from this window with a 'shrink' animation. @param mag A magnifier. 40 | - (void)hideMagnifier:(YYTextMagnifier *)mag; 41 | 42 | 43 | /// Show the selection dot in this window if the dot is clipped by the selection view. 44 | /// @param selection A selection view. 45 | - (void)showSelectionDot:(YYTextSelectionView *)selection; 46 | /// Remove the selection dot from this window. 47 | /// @param selection A selection view. 48 | - (void)hideSelectionDot:(YYTextSelectionView *)selection; 49 | 50 | @end 51 | 52 | NS_ASSUME_NONNULL_END 53 | -------------------------------------------------------------------------------- /ATDemo(OC)/Lib/YYText/Component/YYTextInput.h: -------------------------------------------------------------------------------- 1 | // 2 | // YYTextInput.h 3 | // YYText 4 | // 5 | // Created by ibireme on 15/4/17. 6 | // Copyright (c) 2015 ibireme. 7 | // 8 | // This source code is licensed under the MIT-style license found in the 9 | // LICENSE file in the root directory of this source tree. 10 | // 11 | 12 | #import 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | /** 17 | Text position affinity. For example, the offset appears after the last 18 | character on a line is backward affinity, before the first character on 19 | the following line is forward affinity. 20 | */ 21 | typedef NS_ENUM(NSInteger, YYTextAffinity) { 22 | YYTextAffinityForward = 0, ///< offset appears before the character 23 | YYTextAffinityBackward = 1, ///< offset appears after the character 24 | }; 25 | 26 | 27 | /** 28 | A YYTextPosition object represents a position in a text container; in other words, 29 | it is an index into the backing string in a text-displaying view. 30 | 31 | YYTextPosition has the same API as Apple's implementation in UITextView/UITextField, 32 | so you can alse use it to interact with UITextView/UITextField. 33 | */ 34 | @interface YYTextPosition : UITextPosition 35 | 36 | @property (nonatomic, readonly) NSInteger offset; 37 | @property (nonatomic, readonly) YYTextAffinity affinity; 38 | 39 | + (instancetype)positionWithOffset:(NSInteger)offset; 40 | + (instancetype)positionWithOffset:(NSInteger)offset affinity:(YYTextAffinity) affinity; 41 | 42 | - (NSComparisonResult)compare:(id)otherPosition; 43 | 44 | @end 45 | 46 | 47 | /** 48 | A YYTextRange object represents a range of characters in a text container; in other words, 49 | it identifies a starting index and an ending index in string backing a text-displaying view. 50 | 51 | YYTextRange has the same API as Apple's implementation in UITextView/UITextField, 52 | so you can alse use it to interact with UITextView/UITextField. 53 | */ 54 | @interface YYTextRange : UITextRange 55 | 56 | @property (nonatomic, readonly) YYTextPosition *start; 57 | @property (nonatomic, readonly) YYTextPosition *end; 58 | @property (nonatomic, readonly, getter=isEmpty) BOOL empty; 59 | 60 | + (instancetype)rangeWithRange:(NSRange)range; 61 | + (instancetype)rangeWithRange:(NSRange)range affinity:(YYTextAffinity) affinity; 62 | + (instancetype)rangeWithStart:(YYTextPosition *)start end:(YYTextPosition *)end; 63 | + (instancetype)defaultRange; ///< <{0,0} Forward> 64 | 65 | - (NSRange)asRange; 66 | 67 | @end 68 | 69 | 70 | /** 71 | A YYTextSelectionRect object encapsulates information about a selected range of 72 | text in a text-displaying view. 73 | 74 | YYTextSelectionRect has the same API as Apple's implementation in UITextView/UITextField, 75 | so you can alse use it to interact with UITextView/UITextField. 76 | */ 77 | @interface YYTextSelectionRect : UITextSelectionRect 78 | 79 | @property (nonatomic, readwrite) CGRect rect; 80 | @property (nonatomic, readwrite) UITextWritingDirection writingDirection; 81 | @property (nonatomic, readwrite) BOOL containsStart; 82 | @property (nonatomic, readwrite) BOOL containsEnd; 83 | @property (nonatomic, readwrite) BOOL isVertical; 84 | 85 | @end 86 | 87 | NS_ASSUME_NONNULL_END 88 | -------------------------------------------------------------------------------- /ATDemo(OC)/Lib/YYText/Component/YYTextInput.m: -------------------------------------------------------------------------------- 1 | // 2 | // YYTextInput.m 3 | // YYText 4 | // 5 | // Created by ibireme on 15/4/17. 6 | // Copyright (c) 2015 ibireme. 7 | // 8 | // This source code is licensed under the MIT-style license found in the 9 | // LICENSE file in the root directory of this source tree. 10 | // 11 | 12 | #import "YYTextInput.h" 13 | #import "YYTextUtilities.h" 14 | 15 | 16 | @implementation YYTextPosition 17 | 18 | + (instancetype)positionWithOffset:(NSInteger)offset { 19 | return [self positionWithOffset:offset affinity:YYTextAffinityForward]; 20 | } 21 | 22 | + (instancetype)positionWithOffset:(NSInteger)offset affinity:(YYTextAffinity)affinity { 23 | YYTextPosition *p = [self new]; 24 | p->_offset = offset; 25 | p->_affinity = affinity; 26 | return p; 27 | } 28 | 29 | - (instancetype)copyWithZone:(NSZone *)zone { 30 | return [self.class positionWithOffset:_offset affinity:_affinity]; 31 | } 32 | 33 | - (NSString *)description { 34 | return [NSString stringWithFormat:@"<%@: %p> (%@%@)", self.class, self, @(_offset), _affinity == YYTextAffinityForward ? @"F":@"B"]; 35 | } 36 | 37 | - (NSUInteger)hash { 38 | return _offset * 2 + (_affinity == YYTextAffinityForward ? 1 : 0); 39 | } 40 | 41 | - (BOOL)isEqual:(YYTextPosition *)object { 42 | if (!object) return NO; 43 | return _offset == object.offset && _affinity == object.affinity; 44 | } 45 | 46 | - (NSComparisonResult)compare:(YYTextPosition *)otherPosition { 47 | if (!otherPosition) return NSOrderedAscending; 48 | if (_offset < otherPosition.offset) return NSOrderedAscending; 49 | if (_offset > otherPosition.offset) return NSOrderedDescending; 50 | if (_affinity == YYTextAffinityBackward && otherPosition.affinity == YYTextAffinityForward) return NSOrderedAscending; 51 | if (_affinity == YYTextAffinityForward && otherPosition.affinity == YYTextAffinityBackward) return NSOrderedDescending; 52 | return NSOrderedSame; 53 | } 54 | 55 | @end 56 | 57 | 58 | 59 | @implementation YYTextRange { 60 | YYTextPosition *_start; 61 | YYTextPosition *_end; 62 | } 63 | 64 | - (instancetype)init { 65 | self = [super init]; 66 | if (!self) return nil; 67 | _start = [YYTextPosition positionWithOffset:0]; 68 | _end = [YYTextPosition positionWithOffset:0]; 69 | return self; 70 | } 71 | 72 | - (YYTextPosition *)start { 73 | return _start; 74 | } 75 | 76 | - (YYTextPosition *)end { 77 | return _end; 78 | } 79 | 80 | - (BOOL)isEmpty { 81 | return _start.offset == _end.offset; 82 | } 83 | 84 | - (NSRange)asRange { 85 | return NSMakeRange(_start.offset, _end.offset - _start.offset); 86 | } 87 | 88 | + (instancetype)rangeWithRange:(NSRange)range { 89 | return [self rangeWithRange:range affinity:YYTextAffinityForward]; 90 | } 91 | 92 | + (instancetype)rangeWithRange:(NSRange)range affinity:(YYTextAffinity)affinity { 93 | YYTextPosition *start = [YYTextPosition positionWithOffset:range.location affinity:affinity]; 94 | YYTextPosition *end = [YYTextPosition positionWithOffset:range.location + range.length affinity:affinity]; 95 | return [self rangeWithStart:start end:end]; 96 | } 97 | 98 | + (instancetype)rangeWithStart:(YYTextPosition *)start end:(YYTextPosition *)end { 99 | if (!start || !end) return nil; 100 | if ([start compare:end] == NSOrderedDescending) { 101 | YYTEXT_SWAP(start, end); 102 | } 103 | YYTextRange *range = [YYTextRange new]; 104 | range->_start = start; 105 | range->_end = end; 106 | return range; 107 | } 108 | 109 | + (instancetype)defaultRange { 110 | return [self new]; 111 | } 112 | 113 | - (instancetype)copyWithZone:(NSZone *)zone { 114 | return [self.class rangeWithStart:_start end:_end]; 115 | } 116 | 117 | - (NSString *)description { 118 | return [NSString stringWithFormat:@"<%@: %p> (%@, %@)%@", self.class, self, @(_start.offset), @(_end.offset - _start.offset), _end.affinity == YYTextAffinityForward ? @"F":@"B"]; 119 | } 120 | 121 | - (NSUInteger)hash { 122 | return (sizeof(NSUInteger) == 8 ? OSSwapInt64(_start.hash) : OSSwapInt32(_start.hash)) + _end.hash; 123 | } 124 | 125 | - (BOOL)isEqual:(YYTextRange *)object { 126 | if (!object) return NO; 127 | return [_start isEqual:object.start] && [_end isEqual:object.end]; 128 | } 129 | 130 | @end 131 | 132 | 133 | 134 | @implementation YYTextSelectionRect 135 | 136 | @synthesize rect = _rect; 137 | @synthesize writingDirection = _writingDirection; 138 | @synthesize containsStart = _containsStart; 139 | @synthesize containsEnd = _containsEnd; 140 | @synthesize isVertical = _isVertical; 141 | 142 | - (id)copyWithZone:(NSZone *)zone { 143 | YYTextSelectionRect *one = [self.class new]; 144 | one.rect = _rect; 145 | one.writingDirection = _writingDirection; 146 | one.containsStart = _containsStart; 147 | one.containsEnd = _containsEnd; 148 | one.isVertical = _isVertical; 149 | return one; 150 | } 151 | 152 | @end 153 | -------------------------------------------------------------------------------- /ATDemo(OC)/Lib/YYText/Component/YYTextKeyboardManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // YYTextKeyboardManager.h 3 | // YYText 4 | // 5 | // Created by ibireme on 15/6/3. 6 | // Copyright (c) 2015 ibireme. 7 | // 8 | // This source code is licensed under the MIT-style license found in the 9 | // LICENSE file in the root directory of this source tree. 10 | // 11 | 12 | #import 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | /** 17 | System keyboard transition information. 18 | Use -[YYTextKeyboardManager convertRect:toView:] to convert frame to specified view. 19 | */ 20 | typedef struct { 21 | BOOL fromVisible; ///< Keyboard visible before transition. 22 | BOOL toVisible; ///< Keyboard visible after transition. 23 | CGRect fromFrame; ///< Keyboard frame before transition. 24 | CGRect toFrame; ///< Keyboard frame after transition. 25 | NSTimeInterval animationDuration; ///< Keyboard transition animation duration. 26 | UIViewAnimationCurve animationCurve; ///< Keyboard transition animation curve. 27 | UIViewAnimationOptions animationOption; ///< Keybaord transition animation option. 28 | } YYTextKeyboardTransition; 29 | 30 | 31 | /** 32 | The YYTextKeyboardObserver protocol defines the method you can use 33 | to receive system keyboard change information. 34 | */ 35 | @protocol YYTextKeyboardObserver 36 | @optional 37 | - (void)keyboardChangedWithTransition:(YYTextKeyboardTransition)transition; 38 | @end 39 | 40 | 41 | /** 42 | A YYTextKeyboardManager object lets you get the system keyboard information, 43 | and track the keyboard visible/frame/transition. 44 | 45 | @discussion You should access this class in main thread. 46 | Compatible: iPhone/iPad with iOS6/7/8/9. 47 | */ 48 | @interface YYTextKeyboardManager : NSObject 49 | 50 | - (instancetype)init UNAVAILABLE_ATTRIBUTE; 51 | + (instancetype)new UNAVAILABLE_ATTRIBUTE; 52 | 53 | /// Get the default manager (returns nil in App Extension). 54 | + (nullable instancetype)defaultManager; 55 | 56 | /// Get the keyboard window. nil if there's no keyboard window. 57 | @property (nullable, nonatomic, readonly) UIWindow *keyboardWindow; 58 | 59 | /// Get the keyboard view. nil if there's no keyboard view. 60 | @property (nullable, nonatomic, readonly) UIView *keyboardView; 61 | 62 | /// Whether the keyboard is visible. 63 | @property (nonatomic, readonly, getter=isKeyboardVisible) BOOL keyboardVisible; 64 | 65 | /// Get the keyboard frame. CGRectNull if there's no keyboard view. 66 | /// Use convertRect:toView: to convert frame to specified view. 67 | @property (nonatomic, readonly) CGRect keyboardFrame; 68 | 69 | 70 | /** 71 | Add an observer to manager to get keyboard change information. 72 | This method makes a weak reference to the observer. 73 | 74 | @param observer An observer. 75 | This method will do nothing if the observer is nil, or already added. 76 | */ 77 | - (void)addObserver:(id)observer; 78 | 79 | /** 80 | Remove an observer from manager. 81 | 82 | @param observer An observer. 83 | This method will do nothing if the observer is nil, or not in manager. 84 | */ 85 | - (void)removeObserver:(id)observer; 86 | 87 | /** 88 | Convert rect to specified view or window. 89 | 90 | @param rect The frame rect. 91 | @param view A specified view or window (pass nil to convert for main window). 92 | @return The converted rect in specifeid view. 93 | */ 94 | - (CGRect)convertRect:(CGRect)rect toView:(nullable UIView *)view; 95 | 96 | @end 97 | 98 | NS_ASSUME_NONNULL_END 99 | -------------------------------------------------------------------------------- /ATDemo(OC)/Lib/YYText/Component/YYTextLine.h: -------------------------------------------------------------------------------- 1 | // 2 | // YYTextLine.h 3 | // YYText 4 | // 5 | // Created by ibireme on 15/3/10. 6 | // Copyright (c) 2015 ibireme. 7 | // 8 | // This source code is licensed under the MIT-style license found in the 9 | // LICENSE file in the root directory of this source tree. 10 | // 11 | 12 | #import 13 | #import 14 | 15 | #if __has_include() 16 | #import 17 | #else 18 | #import "YYTextAttribute.h" 19 | #endif 20 | 21 | @class YYTextRunGlyphRange; 22 | 23 | NS_ASSUME_NONNULL_BEGIN 24 | 25 | /** 26 | A text line object wrapped `CTLineRef`, see `YYTextLayout` for more. 27 | */ 28 | @interface YYTextLine : NSObject 29 | 30 | + (instancetype)lineWithCTLine:(CTLineRef)CTLine position:(CGPoint)position vertical:(BOOL)isVertical; 31 | 32 | @property (nonatomic) NSUInteger index; ///< line index 33 | @property (nonatomic) NSUInteger row; ///< line row 34 | @property (nullable, nonatomic, strong) NSArray *> *verticalRotateRange; ///< Run rotate range 35 | 36 | @property (nonatomic, readonly) CTLineRef CTLine; ///< CoreText line 37 | @property (nonatomic, readonly) NSRange range; ///< string range 38 | @property (nonatomic, readonly) BOOL vertical; ///< vertical form 39 | 40 | @property (nonatomic, readonly) CGRect bounds; ///< bounds (ascent + descent) 41 | @property (nonatomic, readonly) CGSize size; ///< bounds.size 42 | @property (nonatomic, readonly) CGFloat width; ///< bounds.size.width 43 | @property (nonatomic, readonly) CGFloat height; ///< bounds.size.height 44 | @property (nonatomic, readonly) CGFloat top; ///< bounds.origin.y 45 | @property (nonatomic, readonly) CGFloat bottom; ///< bounds.origin.y + bounds.size.height 46 | @property (nonatomic, readonly) CGFloat left; ///< bounds.origin.x 47 | @property (nonatomic, readonly) CGFloat right; ///< bounds.origin.x + bounds.size.width 48 | 49 | @property (nonatomic) CGPoint position; ///< baseline position 50 | @property (nonatomic, readonly) CGFloat ascent; ///< line ascent 51 | @property (nonatomic, readonly) CGFloat descent; ///< line descent 52 | @property (nonatomic, readonly) CGFloat leading; ///< line leading 53 | @property (nonatomic, readonly) CGFloat lineWidth; ///< line width 54 | @property (nonatomic, readonly) CGFloat trailingWhitespaceWidth; 55 | 56 | @property (nullable, nonatomic, readonly) NSArray *attachments; ///< YYTextAttachment 57 | @property (nullable, nonatomic, readonly) NSArray *attachmentRanges; ///< NSRange(NSValue) 58 | @property (nullable, nonatomic, readonly) NSArray *attachmentRects; ///< CGRect(NSValue) 59 | 60 | @end 61 | 62 | 63 | typedef NS_ENUM(NSUInteger, YYTextRunGlyphDrawMode) { 64 | /// No rotate. 65 | YYTextRunGlyphDrawModeHorizontal = 0, 66 | 67 | /// Rotate vertical for single glyph. 68 | YYTextRunGlyphDrawModeVerticalRotate = 1, 69 | 70 | /// Rotate vertical for single glyph, and move the glyph to a better position, 71 | /// such as fullwidth punctuation. 72 | YYTextRunGlyphDrawModeVerticalRotateMove = 2, 73 | }; 74 | 75 | /** 76 | A range in CTRun, used for vertical form. 77 | */ 78 | @interface YYTextRunGlyphRange : NSObject 79 | @property (nonatomic) NSRange glyphRangeInRun; 80 | @property (nonatomic) YYTextRunGlyphDrawMode drawMode; 81 | + (instancetype)rangeWithRange:(NSRange)range drawMode:(YYTextRunGlyphDrawMode)mode; 82 | @end 83 | 84 | NS_ASSUME_NONNULL_END 85 | -------------------------------------------------------------------------------- /ATDemo(OC)/Lib/YYText/Component/YYTextLine.m: -------------------------------------------------------------------------------- 1 | // 2 | // YYYTextLine.m 3 | // YYText 4 | // 5 | // Created by ibireme on 15/3/3. 6 | // Copyright (c) 2015 ibireme. 7 | // 8 | // This source code is licensed under the MIT-style license found in the 9 | // LICENSE file in the root directory of this source tree. 10 | // 11 | 12 | #import "YYTextLine.h" 13 | #import "YYTextUtilities.h" 14 | 15 | 16 | @implementation YYTextLine { 17 | CGFloat _firstGlyphPos; // first glyph position for baseline, typically 0. 18 | } 19 | 20 | + (instancetype)lineWithCTLine:(CTLineRef)CTLine position:(CGPoint)position vertical:(BOOL)isVertical { 21 | if (!CTLine) return nil; 22 | YYTextLine *line = [self new]; 23 | line->_position = position; 24 | line->_vertical = isVertical; 25 | [line setCTLine:CTLine]; 26 | return line; 27 | } 28 | 29 | - (void)dealloc { 30 | if (_CTLine) CFRelease(_CTLine); 31 | } 32 | 33 | - (void)setCTLine:(_Nonnull CTLineRef)CTLine { 34 | if (_CTLine != CTLine) { 35 | if (CTLine) CFRetain(CTLine); 36 | if (_CTLine) CFRelease(_CTLine); 37 | _CTLine = CTLine; 38 | if (_CTLine) { 39 | _lineWidth = CTLineGetTypographicBounds(_CTLine, &_ascent, &_descent, &_leading); 40 | CFRange range = CTLineGetStringRange(_CTLine); 41 | _range = NSMakeRange(range.location, range.length); 42 | if (CTLineGetGlyphCount(_CTLine) > 0) { 43 | CFArrayRef runs = CTLineGetGlyphRuns(_CTLine); 44 | CTRunRef run = CFArrayGetValueAtIndex(runs, 0); 45 | CGPoint pos; 46 | CTRunGetPositions(run, CFRangeMake(0, 1), &pos); 47 | _firstGlyphPos = pos.x; 48 | } else { 49 | _firstGlyphPos = 0; 50 | } 51 | _trailingWhitespaceWidth = CTLineGetTrailingWhitespaceWidth(_CTLine); 52 | } else { 53 | _lineWidth = _ascent = _descent = _leading = _firstGlyphPos = _trailingWhitespaceWidth = 0; 54 | _range = NSMakeRange(0, 0); 55 | } 56 | [self reloadBounds]; 57 | } 58 | } 59 | 60 | - (void)setPosition:(CGPoint)position { 61 | _position = position; 62 | [self reloadBounds]; 63 | } 64 | 65 | - (void)reloadBounds { 66 | if (_vertical) { 67 | _bounds = CGRectMake(_position.x - _descent, _position.y, _ascent + _descent, _lineWidth); 68 | _bounds.origin.y += _firstGlyphPos; 69 | } else { 70 | _bounds = CGRectMake(_position.x, _position.y - _ascent, _lineWidth, _ascent + _descent); 71 | _bounds.origin.x += _firstGlyphPos; 72 | } 73 | 74 | _attachments = nil; 75 | _attachmentRanges = nil; 76 | _attachmentRects = nil; 77 | if (!_CTLine) return; 78 | CFArrayRef runs = CTLineGetGlyphRuns(_CTLine); 79 | NSUInteger runCount = CFArrayGetCount(runs); 80 | if (runCount == 0) return; 81 | 82 | NSMutableArray *attachments = [NSMutableArray new]; 83 | NSMutableArray *attachmentRanges = [NSMutableArray new]; 84 | NSMutableArray *attachmentRects = [NSMutableArray new]; 85 | for (NSUInteger r = 0; r < runCount; r++) { 86 | CTRunRef run = CFArrayGetValueAtIndex(runs, r); 87 | CFIndex glyphCount = CTRunGetGlyphCount(run); 88 | if (glyphCount == 0) continue; 89 | NSDictionary *attrs = (id)CTRunGetAttributes(run); 90 | YYTextAttachment *attachment = attrs[YYTextAttachmentAttributeName]; 91 | if (attachment) { 92 | CGPoint runPosition = CGPointZero; 93 | CTRunGetPositions(run, CFRangeMake(0, 1), &runPosition); 94 | 95 | CGFloat ascent, descent, leading, runWidth; 96 | CGRect runTypoBounds; 97 | runWidth = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, &leading); 98 | 99 | if (_vertical) { 100 | YYTEXT_SWAP(runPosition.x, runPosition.y); 101 | runPosition.y = _position.y + runPosition.y; 102 | runTypoBounds = CGRectMake(_position.x + runPosition.x - descent, runPosition.y , ascent + descent, runWidth); 103 | } else { 104 | runPosition.x += _position.x; 105 | runPosition.y = _position.y - runPosition.y; 106 | runTypoBounds = CGRectMake(runPosition.x, runPosition.y - ascent, runWidth, ascent + descent); 107 | } 108 | 109 | NSRange runRange = YYTextNSRangeFromCFRange(CTRunGetStringRange(run)); 110 | [attachments addObject:attachment]; 111 | [attachmentRanges addObject:[NSValue valueWithRange:runRange]]; 112 | [attachmentRects addObject:[NSValue valueWithCGRect:runTypoBounds]]; 113 | } 114 | } 115 | _attachments = attachments.count ? attachments : nil; 116 | _attachmentRanges = attachmentRanges.count ? attachmentRanges : nil; 117 | _attachmentRects = attachmentRects.count ? attachmentRects : nil; 118 | } 119 | 120 | - (CGSize)size { 121 | return _bounds.size; 122 | } 123 | 124 | - (CGFloat)width { 125 | return CGRectGetWidth(_bounds); 126 | } 127 | 128 | - (CGFloat)height { 129 | return CGRectGetHeight(_bounds); 130 | } 131 | 132 | - (CGFloat)top { 133 | return CGRectGetMinY(_bounds); 134 | } 135 | 136 | - (CGFloat)bottom { 137 | return CGRectGetMaxY(_bounds); 138 | } 139 | 140 | - (CGFloat)left { 141 | return CGRectGetMinX(_bounds); 142 | } 143 | 144 | - (CGFloat)right { 145 | return CGRectGetMaxX(_bounds); 146 | } 147 | 148 | - (NSString *)description { 149 | NSMutableString *desc = @"".mutableCopy; 150 | NSRange range = self.range; 151 | [desc appendFormat:@" row:%zd range:%tu,%tu",self, self.row, range.location, range.length]; 152 | [desc appendFormat:@" position:%@",NSStringFromCGPoint(self.position)]; 153 | [desc appendFormat:@" bounds:%@",NSStringFromCGRect(self.bounds)]; 154 | return desc; 155 | } 156 | 157 | @end 158 | 159 | 160 | @implementation YYTextRunGlyphRange 161 | + (instancetype)rangeWithRange:(NSRange)range drawMode:(YYTextRunGlyphDrawMode)mode { 162 | YYTextRunGlyphRange *one = [self new]; 163 | one.glyphRangeInRun = range; 164 | one.drawMode = mode; 165 | return one; 166 | } 167 | @end 168 | -------------------------------------------------------------------------------- /ATDemo(OC)/Lib/YYText/Component/YYTextMagnifier.h: -------------------------------------------------------------------------------- 1 | // 2 | // YYTextMagnifier.h 3 | // YYText 4 | // 5 | // Created by ibireme on 15/2/25. 6 | // Copyright (c) 2015 ibireme. 7 | // 8 | // This source code is licensed under the MIT-style license found in the 9 | // LICENSE file in the root directory of this source tree. 10 | // 11 | 12 | #import 13 | 14 | #if __has_include() 15 | #import 16 | #else 17 | #import "YYTextAttribute.h" 18 | #endif 19 | 20 | NS_ASSUME_NONNULL_BEGIN 21 | 22 | /// Magnifier type 23 | typedef NS_ENUM(NSInteger, YYTextMagnifierType) { 24 | YYTextMagnifierTypeCaret, ///< Circular magnifier 25 | YYTextMagnifierTypeRanged, ///< Round rectangle magnifier 26 | }; 27 | 28 | /** 29 | A magnifier view which can be displayed in `YYTextEffectWindow`. 30 | 31 | @discussion Use `magnifierWithType:` to create instance. 32 | Typically, you should not use this class directly. 33 | */ 34 | @interface YYTextMagnifier : UIView 35 | 36 | /// Create a mangifier with the specified type. @param type The magnifier type. 37 | + (id)magnifierWithType:(YYTextMagnifierType)type; 38 | 39 | @property (nonatomic, readonly) YYTextMagnifierType type; ///< Type of magnifier 40 | @property (nonatomic, readonly) CGSize fitSize; ///< The 'best' size for magnifier view. 41 | @property (nonatomic, readonly) CGSize snapshotSize; ///< The 'best' snapshot image size for magnifier. 42 | @property (nullable, nonatomic, strong) UIImage *snapshot; ///< The image in magnifier (readwrite). 43 | 44 | @property (nullable, nonatomic, weak) UIView *hostView; ///< The coordinate based view. 45 | @property (nonatomic) CGPoint hostCaptureCenter; ///< The snapshot capture center in `hostView`. 46 | @property (nonatomic) CGPoint hostPopoverCenter; ///< The popover center in `hostView`. 47 | @property (nonatomic) BOOL hostVerticalForm; ///< The host view is vertical form. 48 | @property (nonatomic) BOOL captureDisabled; ///< A hint for `YYTextEffectWindow` to disable capture. 49 | @property (nonatomic) BOOL captureFadeAnimation; ///< Show fade animation when the snapshot image changed. 50 | @end 51 | 52 | NS_ASSUME_NONNULL_END 53 | -------------------------------------------------------------------------------- /ATDemo(OC)/Lib/YYText/Component/YYTextSelectionView.h: -------------------------------------------------------------------------------- 1 | // 2 | // YYTextSelectionView.h 3 | // YYText 4 | // 5 | // Created by ibireme on 15/2/25. 6 | // Copyright (c) 2015 ibireme. 7 | // 8 | // This source code is licensed under the MIT-style license found in the 9 | // LICENSE file in the root directory of this source tree. 10 | // 11 | 12 | #import 13 | 14 | #if __has_include() 15 | #import 16 | #import 17 | #else 18 | #import "YYTextAttribute.h" 19 | #import "YYTextInput.h" 20 | #endif 21 | 22 | NS_ASSUME_NONNULL_BEGIN 23 | 24 | /** 25 | A single dot view. The frame should be foursquare. 26 | Change the background color for display. 27 | 28 | @discussion Typically, you should not use this class directly. 29 | */ 30 | @interface YYSelectionGrabberDot : UIView 31 | /// Dont't access this property. It was used by `YYTextEffectWindow`. 32 | @property (nonatomic, strong) UIView *mirror; 33 | @end 34 | 35 | 36 | /** 37 | A grabber (stick with a dot). 38 | 39 | @discussion Typically, you should not use this class directly. 40 | */ 41 | @interface YYSelectionGrabber : UIView 42 | 43 | @property (nonatomic, readonly) YYSelectionGrabberDot *dot; ///< the dot view 44 | @property (nonatomic) YYTextDirection dotDirection; ///< don't support composite direction 45 | @property (nullable, nonatomic, strong) UIColor *color; ///< tint color, default is nil 46 | 47 | @end 48 | 49 | 50 | /** 51 | The selection view for text edit and select. 52 | 53 | @discussion Typically, you should not use this class directly. 54 | */ 55 | @interface YYTextSelectionView : UIView 56 | 57 | @property (nullable, nonatomic, weak) UIView *hostView; ///< the holder view 58 | @property (nullable, nonatomic, strong) UIColor *color; ///< the tint color 59 | @property (nonatomic, getter = isCaretBlinks) BOOL caretBlinks; ///< whether the caret is blinks 60 | @property (nonatomic, getter = isCaretVisible) BOOL caretVisible; ///< whether the caret is visible 61 | @property (nonatomic, getter = isVerticalForm) BOOL verticalForm; ///< weather the text view is vertical form 62 | 63 | @property (nonatomic) CGRect caretRect; ///< caret rect (width==0 or height==0) 64 | @property (nullable, nonatomic, copy) NSArray *selectionRects; ///< default is nil 65 | 66 | @property (nonatomic, readonly) UIView *caretView; 67 | @property (nonatomic, readonly) YYSelectionGrabber *startGrabber; 68 | @property (nonatomic, readonly) YYSelectionGrabber *endGrabber; 69 | 70 | - (BOOL)isGrabberContainsPoint:(CGPoint)point; 71 | - (BOOL)isStartGrabberContainsPoint:(CGPoint)point; 72 | - (BOOL)isEndGrabberContainsPoint:(CGPoint)point; 73 | - (BOOL)isCaretContainsPoint:(CGPoint)point; 74 | - (BOOL)isSelectionRectsContainsPoint:(CGPoint)point; 75 | 76 | @end 77 | 78 | NS_ASSUME_NONNULL_END 79 | -------------------------------------------------------------------------------- /ATDemo(OC)/Lib/YYText/String/YYTextArchiver.h: -------------------------------------------------------------------------------- 1 | // 2 | // YYTextArchiver.h 3 | // YYText 4 | // 5 | // Created by ibireme on 15/3/16. 6 | // Copyright (c) 2015 ibireme. 7 | // 8 | // This source code is licensed under the MIT-style license found in the 9 | // LICENSE file in the root directory of this source tree. 10 | // 11 | 12 | #import 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | /** 17 | A subclass of `NSKeyedArchiver` which implement `NSKeyedArchiverDelegate` protocol. 18 | 19 | The archiver can encode the object which contains 20 | CGColor/CGImage/CTRunDelegateRef/.. (such as NSAttributedString). 21 | */ 22 | @interface YYTextArchiver : NSKeyedArchiver 23 | @end 24 | 25 | /** 26 | A subclass of `NSKeyedUnarchiver` which implement `NSKeyedUnarchiverDelegate` 27 | protocol. The unarchiver can decode the data which is encoded by 28 | `YYTextArchiver` or `NSKeyedArchiver`. 29 | */ 30 | @interface YYTextUnarchiver : NSKeyedUnarchiver 31 | @end 32 | 33 | NS_ASSUME_NONNULL_END 34 | -------------------------------------------------------------------------------- /ATDemo(OC)/Lib/YYText/String/YYTextArchiver.m: -------------------------------------------------------------------------------- 1 | // 2 | // YYTextArchiver.m 3 | // YYText 4 | // 5 | // Created by ibireme on 15/3/16. 6 | // Copyright (c) 2015 ibireme. 7 | // 8 | // This source code is licensed under the MIT-style license found in the 9 | // LICENSE file in the root directory of this source tree. 10 | // 11 | 12 | #import "YYTextArchiver.h" 13 | #import "YYTextRunDelegate.h" 14 | #import "YYTextRubyAnnotation.h" 15 | 16 | /** 17 | When call CTRunDelegateGetTypeID() on some devices (runs iOS6), I got the error: 18 | "dyld: lazy symbol binding failed: Symbol not found: _CTRunDelegateGetTypeID" 19 | 20 | Here's a workaround for this issue. 21 | */ 22 | static CFTypeID CTRunDelegateTypeID() { 23 | static CFTypeID typeID; 24 | static dispatch_once_t onceToken; 25 | dispatch_once(&onceToken, ^{ 26 | /* 27 | if ((long)CTRunDelegateGetTypeID + 1 > 1) { //avoid compiler optimization 28 | typeID = CTRunDelegateGetTypeID(); 29 | } 30 | */ 31 | YYTextRunDelegate *delegate = [YYTextRunDelegate new]; 32 | CTRunDelegateRef ref = delegate.CTRunDelegate; 33 | typeID = CFGetTypeID(ref); 34 | CFRelease(ref); 35 | }); 36 | return typeID; 37 | } 38 | 39 | static CFTypeID CTRubyAnnotationTypeID() { 40 | static CFTypeID typeID; 41 | static dispatch_once_t onceToken; 42 | dispatch_once(&onceToken, ^{ 43 | if ((long)CTRubyAnnotationGetTypeID + 1 > 1) { //avoid compiler optimization 44 | typeID = CTRunDelegateGetTypeID(); 45 | } else { 46 | typeID = kCFNotFound; 47 | } 48 | }); 49 | return typeID; 50 | } 51 | 52 | /** 53 | A wrapper for CGColorRef. Used for Archive/Unarchive/Copy. 54 | */ 55 | @interface _YYCGColor : NSObject 56 | @property (nonatomic, assign) CGColorRef CGColor; 57 | + (instancetype)colorWithCGColor:(CGColorRef)CGColor; 58 | @end 59 | 60 | @implementation _YYCGColor 61 | 62 | + (instancetype)colorWithCGColor:(CGColorRef)CGColor { 63 | _YYCGColor *color = [self new]; 64 | color.CGColor = CGColor; 65 | return color; 66 | } 67 | 68 | - (void)setCGColor:(CGColorRef)CGColor { 69 | if (_CGColor != CGColor) { 70 | if (CGColor) CGColor = (CGColorRef)CFRetain(CGColor); 71 | if (_CGColor) CFRelease(_CGColor); 72 | _CGColor = CGColor; 73 | } 74 | } 75 | 76 | - (void)dealloc { 77 | if (_CGColor) CFRelease(_CGColor); 78 | _CGColor = NULL; 79 | } 80 | 81 | - (id)copyWithZone:(NSZone *)zone { 82 | _YYCGColor *color = [self.class new]; 83 | color.CGColor = self.CGColor; 84 | return color; 85 | } 86 | 87 | - (void)encodeWithCoder:(NSCoder *)aCoder { 88 | UIColor *color = [UIColor colorWithCGColor:_CGColor]; 89 | [aCoder encodeObject:color forKey:@"color"]; 90 | } 91 | 92 | - (id)initWithCoder:(NSCoder *)aDecoder { 93 | self = [self init]; 94 | UIColor *color = [aDecoder decodeObjectForKey:@"color"]; 95 | self.CGColor = color.CGColor; 96 | return self; 97 | } 98 | 99 | @end 100 | 101 | /** 102 | A wrapper for CGImageRef. Used for Archive/Unarchive/Copy. 103 | */ 104 | @interface _YYCGImage : NSObject 105 | @property (nonatomic, assign) CGImageRef CGImage; 106 | + (instancetype)imageWithCGImage:(CGImageRef)CGImage; 107 | @end 108 | 109 | @implementation _YYCGImage 110 | 111 | + (instancetype)imageWithCGImage:(CGImageRef)CGImage { 112 | _YYCGImage *image = [self new]; 113 | image.CGImage = CGImage; 114 | return image; 115 | } 116 | 117 | - (void)setCGImage:(CGImageRef)CGImage { 118 | if (_CGImage != CGImage) { 119 | if (CGImage) CGImage = (CGImageRef)CFRetain(CGImage); 120 | if (_CGImage) CFRelease(_CGImage); 121 | _CGImage = CGImage; 122 | } 123 | } 124 | 125 | - (void)dealloc { 126 | if (_CGImage) CFRelease(_CGImage); 127 | } 128 | 129 | - (id)copyWithZone:(NSZone *)zone { 130 | _YYCGImage *image = [self.class new]; 131 | image.CGImage = self.CGImage; 132 | return image; 133 | } 134 | 135 | - (void)encodeWithCoder:(NSCoder *)aCoder { 136 | UIImage *image = [UIImage imageWithCGImage:_CGImage]; 137 | [aCoder encodeObject:image forKey:@"image"]; 138 | } 139 | 140 | - (id)initWithCoder:(NSCoder *)aDecoder { 141 | self = [self init]; 142 | UIImage *image = [aDecoder decodeObjectForKey:@"image"]; 143 | self.CGImage = image.CGImage; 144 | return self; 145 | } 146 | 147 | @end 148 | 149 | 150 | @implementation YYTextArchiver 151 | 152 | + (NSData *)archivedDataWithRootObject:(id)rootObject { 153 | if (!rootObject) return nil; 154 | NSMutableData *data = [NSMutableData data]; 155 | YYTextArchiver *archiver = [[[self class] alloc] initForWritingWithMutableData:data]; 156 | [archiver encodeRootObject:rootObject]; 157 | [archiver finishEncoding]; 158 | return data; 159 | } 160 | 161 | + (BOOL)archiveRootObject:(id)rootObject toFile:(NSString *)path { 162 | NSData *data = [self archivedDataWithRootObject:rootObject]; 163 | if (!data) return NO; 164 | return [data writeToFile:path atomically:YES]; 165 | } 166 | 167 | - (instancetype)init { 168 | self = [super init]; 169 | self.delegate = self; 170 | return self; 171 | } 172 | 173 | - (instancetype)initForWritingWithMutableData:(NSMutableData *)data { 174 | self = [super initForWritingWithMutableData:data]; 175 | self.delegate = self; 176 | return self; 177 | } 178 | 179 | - (id)archiver:(NSKeyedArchiver *)archiver willEncodeObject:(id)object { 180 | CFTypeID typeID = CFGetTypeID((CFTypeRef)object); 181 | if (typeID == CTRunDelegateTypeID()) { 182 | CTRunDelegateRef runDelegate = (__bridge CFTypeRef)(object); 183 | id ref = CTRunDelegateGetRefCon(runDelegate); 184 | if (ref) return ref; 185 | } else if (typeID == CTRubyAnnotationTypeID()) { 186 | CTRubyAnnotationRef ctRuby = (__bridge CFTypeRef)(object); 187 | YYTextRubyAnnotation *ruby = [YYTextRubyAnnotation rubyWithCTRubyRef:ctRuby]; 188 | if (ruby) return ruby; 189 | } else if (typeID == CGColorGetTypeID()) { 190 | return [_YYCGColor colorWithCGColor:(CGColorRef)object]; 191 | } else if (typeID == CGImageGetTypeID()) { 192 | return [_YYCGImage imageWithCGImage:(CGImageRef)object]; 193 | } 194 | return object; 195 | } 196 | 197 | @end 198 | 199 | 200 | @implementation YYTextUnarchiver 201 | 202 | + (id)unarchiveObjectWithData:(NSData *)data { 203 | if (data.length == 0) return nil; 204 | YYTextUnarchiver *unarchiver = [[self alloc] initForReadingWithData:data]; 205 | return [unarchiver decodeObject]; 206 | } 207 | 208 | + (id)unarchiveObjectWithFile:(NSString *)path { 209 | NSData *data = [NSData dataWithContentsOfFile:path]; 210 | return [self unarchiveObjectWithData:data]; 211 | } 212 | 213 | - (instancetype)init { 214 | self = [super init]; 215 | self.delegate = self; 216 | return self; 217 | } 218 | 219 | - (instancetype)initForReadingWithData:(NSData *)data { 220 | self = [super initForReadingWithData:data]; 221 | self.delegate = self; 222 | return self; 223 | } 224 | 225 | - (id)unarchiver:(NSKeyedUnarchiver *)unarchiver didDecodeObject:(id) NS_RELEASES_ARGUMENT object NS_RETURNS_RETAINED { 226 | if ([object class] == [YYTextRunDelegate class]) { 227 | YYTextRunDelegate *runDelegate = object; 228 | CTRunDelegateRef ct = runDelegate.CTRunDelegate; 229 | id ctObj = (__bridge id)ct; 230 | if (ct) CFRelease(ct); 231 | return ctObj; 232 | } else if ([object class] == [YYTextRubyAnnotation class]) { 233 | YYTextRubyAnnotation *ruby = object; 234 | if ([UIDevice currentDevice].systemVersion.floatValue >= 8) { 235 | CTRubyAnnotationRef ct = ruby.CTRubyAnnotation; 236 | id ctObj = (__bridge id)(ct); 237 | if (ct) CFRelease(ct); 238 | return ctObj; 239 | } else { 240 | return object; 241 | } 242 | } else if ([object class] == [_YYCGColor class]) { 243 | _YYCGColor *color = object; 244 | return (id)color.CGColor; 245 | } else if ([object class] == [_YYCGImage class]) { 246 | _YYCGImage *image = object; 247 | return (id)image.CGImage; 248 | } 249 | return object; 250 | } 251 | 252 | @end 253 | -------------------------------------------------------------------------------- /ATDemo(OC)/Lib/YYText/String/YYTextParser.h: -------------------------------------------------------------------------------- 1 | // 2 | // YYTextParser.h 3 | // YYText 4 | // 5 | // Created by ibireme on 15/3/6. 6 | // Copyright (c) 2015 ibireme. 7 | // 8 | // This source code is licensed under the MIT-style license found in the 9 | // LICENSE file in the root directory of this source tree. 10 | // 11 | 12 | #import 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | /** 17 | The YYTextParser protocol declares the required method for YYTextView and YYLabel 18 | to modify the text during editing. 19 | 20 | You can implement this protocol to add code highlighting or emoticon replacement for 21 | YYTextView and YYLabel. See `YYTextSimpleMarkdownParser` and `YYTextSimpleEmoticonParser` for example. 22 | */ 23 | @protocol YYTextParser 24 | @required 25 | /** 26 | When text is changed in YYTextView or YYLabel, this method will be called. 27 | 28 | @param text The original attributed string. This method may parse the text and 29 | change the text attributes or content. 30 | 31 | @param selectedRange Current selected range in `text`. 32 | This method should correct the range if the text content is changed. If there's 33 | no selected range (such as YYLabel), this value is NULL. 34 | 35 | @return If the 'text' is modified in this method, returns `YES`, otherwise returns `NO`. 36 | */ 37 | - (BOOL)parseText:(nullable NSMutableAttributedString *)text selectedRange:(nullable NSRangePointer)selectedRange; 38 | @end 39 | 40 | 41 | 42 | /** 43 | A simple markdown parser. 44 | 45 | It'a very simple markdown parser, you can use this parser to highlight some 46 | small piece of markdown text. 47 | 48 | This markdown parser use regular expression to parse text, slow and weak. 49 | If you want to write a better parser, try these projests: 50 | https://github.com/NimbusKit/markdown 51 | https://github.com/dreamwieber/AttributedMarkdown 52 | https://github.com/indragiek/CocoaMarkdown 53 | 54 | Or you can use lex/yacc to generate your custom parser. 55 | */ 56 | @interface YYTextSimpleMarkdownParser : NSObject 57 | @property (nonatomic) CGFloat fontSize; ///< default is 14 58 | @property (nonatomic) CGFloat headerFontSize; ///< default is 20 59 | 60 | @property (nullable, nonatomic, strong) UIColor *textColor; 61 | @property (nullable, nonatomic, strong) UIColor *controlTextColor; 62 | @property (nullable, nonatomic, strong) UIColor *headerTextColor; 63 | @property (nullable, nonatomic, strong) UIColor *inlineTextColor; 64 | @property (nullable, nonatomic, strong) UIColor *codeTextColor; 65 | @property (nullable, nonatomic, strong) UIColor *linkTextColor; 66 | 67 | - (void)setColorWithBrightTheme; ///< reset the color properties to pre-defined value. 68 | - (void)setColorWithDarkTheme; ///< reset the color properties to pre-defined value. 69 | @end 70 | 71 | 72 | 73 | /** 74 | A simple emoticon parser. 75 | 76 | Use this parser to map some specified piece of string to image emoticon. 77 | Example: "Hello :smile:" -> "Hello 😀" 78 | 79 | It can also be used to extend the "unicode emoticon". 80 | */ 81 | @interface YYTextSimpleEmoticonParser : NSObject 82 | 83 | /** 84 | The custom emoticon mapper. 85 | The key is a specified plain string, such as @":smile:". 86 | The value is a UIImage which will replace the specified plain string in text. 87 | */ 88 | @property (nullable, copy) NSDictionary *emoticonMapper; 89 | @end 90 | 91 | NS_ASSUME_NONNULL_END 92 | -------------------------------------------------------------------------------- /ATDemo(OC)/Lib/YYText/String/YYTextRubyAnnotation.h: -------------------------------------------------------------------------------- 1 | // 2 | // YYTextRubyAnnotation.h 3 | // YYText 4 | // 5 | // Created by ibireme on 15/4/24. 6 | // Copyright (c) 2015 ibireme. 7 | // 8 | // This source code is licensed under the MIT-style license found in the 9 | // LICENSE file in the root directory of this source tree. 10 | // 11 | 12 | #import 13 | #import 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | /** 18 | Wrapper for CTRubyAnnotationRef. 19 | 20 | Example: 21 | 22 | YYTextRubyAnnotation *ruby = [YYTextRubyAnnotation new]; 23 | ruby.textBefore = @"zhù yīn"; 24 | CTRubyAnnotationRef ctRuby = ruby.CTRubyAnnotation; 25 | if (ctRuby) { 26 | /// add to attributed string 27 | CFRelease(ctRuby); 28 | } 29 | 30 | */ 31 | @interface YYTextRubyAnnotation : NSObject 32 | 33 | /// Specifies how the ruby text and the base text should be aligned relative to each other. 34 | @property (nonatomic) CTRubyAlignment alignment; 35 | 36 | /// Specifies how the ruby text can overhang adjacent characters. 37 | @property (nonatomic) CTRubyOverhang overhang; 38 | 39 | /// Specifies the size of the annotation text as a percent of the size of the base text. 40 | @property (nonatomic) CGFloat sizeFactor; 41 | 42 | 43 | /// The ruby text is positioned before the base text; 44 | /// i.e. above horizontal text and to the right of vertical text. 45 | @property (nullable, nonatomic, copy) NSString *textBefore; 46 | 47 | /// The ruby text is positioned after the base text; 48 | /// i.e. below horizontal text and to the left of vertical text. 49 | @property (nullable, nonatomic, copy) NSString *textAfter; 50 | 51 | /// The ruby text is positioned to the right of the base text whether it is horizontal or vertical. 52 | /// This is the way that Bopomofo annotations are attached to Chinese text in Taiwan. 53 | @property (nullable, nonatomic, copy) NSString *textInterCharacter; 54 | 55 | /// The ruby text follows the base text with no special styling. 56 | @property (nullable, nonatomic, copy) NSString *textInline; 57 | 58 | 59 | /** 60 | Create a ruby object from CTRuby object. 61 | 62 | @param ctRuby A CTRuby object. 63 | 64 | @return A ruby object, or nil when an error occurs. 65 | */ 66 | + (instancetype)rubyWithCTRubyRef:(CTRubyAnnotationRef)ctRuby NS_AVAILABLE_IOS(8_0); 67 | 68 | /** 69 | Create a CTRuby object from the instance. 70 | 71 | @return A new CTRuby object, or NULL when an error occurs. 72 | The returned value should be release after used. 73 | */ 74 | - (nullable CTRubyAnnotationRef)CTRubyAnnotation CF_RETURNS_RETAINED NS_AVAILABLE_IOS(8_0); 75 | 76 | @end 77 | 78 | NS_ASSUME_NONNULL_END 79 | -------------------------------------------------------------------------------- /ATDemo(OC)/Lib/YYText/String/YYTextRubyAnnotation.m: -------------------------------------------------------------------------------- 1 | // 2 | // YYTextRubyAnnotation.m 3 | // YYText 4 | // 5 | // Created by ibireme on 15/4/24. 6 | // Copyright (c) 2015 ibireme. 7 | // 8 | // This source code is licensed under the MIT-style license found in the 9 | // LICENSE file in the root directory of this source tree. 10 | // 11 | 12 | #import "YYTextRubyAnnotation.h" 13 | 14 | @implementation YYTextRubyAnnotation 15 | 16 | - (instancetype)init { 17 | self = super.init; 18 | self.alignment = kCTRubyAlignmentAuto; 19 | self.overhang = kCTRubyOverhangAuto; 20 | self.sizeFactor = 0.5; 21 | return self; 22 | } 23 | 24 | + (instancetype)rubyWithCTRubyRef:(CTRubyAnnotationRef)ctRuby { 25 | if (!ctRuby) return nil; 26 | YYTextRubyAnnotation *one = [self new]; 27 | one.alignment = CTRubyAnnotationGetAlignment(ctRuby); 28 | one.overhang = CTRubyAnnotationGetOverhang(ctRuby); 29 | one.sizeFactor = CTRubyAnnotationGetSizeFactor(ctRuby); 30 | one.textBefore = (__bridge NSString *)(CTRubyAnnotationGetTextForPosition(ctRuby, kCTRubyPositionBefore)); 31 | one.textAfter = (__bridge NSString *)(CTRubyAnnotationGetTextForPosition(ctRuby, kCTRubyPositionAfter)); 32 | one.textInterCharacter = (__bridge NSString *)(CTRubyAnnotationGetTextForPosition(ctRuby, kCTRubyPositionInterCharacter)); 33 | one.textInline = (__bridge NSString *)(CTRubyAnnotationGetTextForPosition(ctRuby, kCTRubyPositionInline)); 34 | return one; 35 | } 36 | 37 | - (CTRubyAnnotationRef)CTRubyAnnotation CF_RETURNS_RETAINED { 38 | if (((long)CTRubyAnnotationCreate + 1) == 1) return NULL; // system not support 39 | 40 | CFStringRef text[kCTRubyPositionCount]; 41 | text[kCTRubyPositionBefore] = (__bridge CFStringRef)(_textBefore); 42 | text[kCTRubyPositionAfter] = (__bridge CFStringRef)(_textAfter); 43 | text[kCTRubyPositionInterCharacter] = (__bridge CFStringRef)(_textInterCharacter); 44 | text[kCTRubyPositionInline] = (__bridge CFStringRef)(_textInline); 45 | CTRubyAnnotationRef ruby = CTRubyAnnotationCreate(_alignment, _overhang, _sizeFactor, text); 46 | return ruby; 47 | } 48 | 49 | - (id)copyWithZone:(NSZone *)zone { 50 | YYTextRubyAnnotation *one = [self.class new]; 51 | one.alignment = _alignment; 52 | one.overhang = _overhang; 53 | one.sizeFactor = _sizeFactor; 54 | one.textBefore = _textBefore; 55 | one.textAfter = _textAfter; 56 | one.textInterCharacter = _textInterCharacter; 57 | one.textInline = _textInline; 58 | return one; 59 | } 60 | 61 | - (void)encodeWithCoder:(NSCoder *)aCoder { 62 | [aCoder encodeObject:@(_alignment) forKey:@"alignment"]; 63 | [aCoder encodeObject:@(_overhang) forKey:@"overhang"]; 64 | [aCoder encodeObject:@(_sizeFactor) forKey:@"sizeFactor"]; 65 | [aCoder encodeObject:_textBefore forKey:@"textBefore"]; 66 | [aCoder encodeObject:_textAfter forKey:@"textAfter"]; 67 | [aCoder encodeObject:_textInterCharacter forKey:@"textInterCharacter"]; 68 | [aCoder encodeObject:_textInline forKey:@"textInline"]; 69 | } 70 | 71 | - (id)initWithCoder:(NSCoder *)aDecoder { 72 | self = [self init]; 73 | _alignment = ((NSNumber *)[aDecoder decodeObjectForKey:@"alignment"]).intValue; 74 | _overhang = ((NSNumber *)[aDecoder decodeObjectForKey:@"overhang"]).intValue; 75 | _sizeFactor = ((NSNumber *)[aDecoder decodeObjectForKey:@"sizeFactor"]).intValue; 76 | _textBefore = [aDecoder decodeObjectForKey:@"textBefore"]; 77 | _textAfter = [aDecoder decodeObjectForKey:@"textAfter"]; 78 | _textInterCharacter = [aDecoder decodeObjectForKey:@"textInterCharacter"]; 79 | _textInline = [aDecoder decodeObjectForKey:@"textInline"]; 80 | return self; 81 | } 82 | 83 | @end 84 | -------------------------------------------------------------------------------- /ATDemo(OC)/Lib/YYText/String/YYTextRunDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // YYTextRunDelegate.h 3 | // YYText 4 | // 5 | // Created by ibireme on 14/10/14. 6 | // Copyright (c) 2015 ibireme. 7 | // 8 | // This source code is licensed under the MIT-style license found in the 9 | // LICENSE file in the root directory of this source tree. 10 | // 11 | 12 | #import 13 | #import 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | /** 18 | Wrapper for CTRunDelegateRef. 19 | 20 | Example: 21 | 22 | YYTextRunDelegate *delegate = [YYTextRunDelegate new]; 23 | delegate.ascent = 20; 24 | delegate.descent = 4; 25 | delegate.width = 20; 26 | CTRunDelegateRef ctRunDelegate = delegate.CTRunDelegate; 27 | if (ctRunDelegate) { 28 | /// add to attributed string 29 | CFRelease(ctRunDelegate); 30 | } 31 | 32 | */ 33 | @interface YYTextRunDelegate : NSObject 34 | 35 | /** 36 | Creates and returns the CTRunDelegate. 37 | 38 | @discussion You need call CFRelease() after used. 39 | The CTRunDelegateRef has a strong reference to this YYTextRunDelegate object. 40 | In CoreText, use CTRunDelegateGetRefCon() to get this YYTextRunDelegate object. 41 | 42 | @return The CTRunDelegate object. 43 | */ 44 | - (nullable CTRunDelegateRef)CTRunDelegate CF_RETURNS_RETAINED; 45 | 46 | /** 47 | Additional information about the the run delegate. 48 | */ 49 | @property (nullable, nonatomic, strong) NSDictionary *userInfo; 50 | 51 | /** 52 | The typographic ascent of glyphs in the run. 53 | */ 54 | @property (nonatomic) CGFloat ascent; 55 | 56 | /** 57 | The typographic descent of glyphs in the run. 58 | */ 59 | @property (nonatomic) CGFloat descent; 60 | 61 | /** 62 | The typographic width of glyphs in the run. 63 | */ 64 | @property (nonatomic) CGFloat width; 65 | 66 | @end 67 | 68 | NS_ASSUME_NONNULL_END 69 | -------------------------------------------------------------------------------- /ATDemo(OC)/Lib/YYText/String/YYTextRunDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // YYTextRunDelegate.m 3 | // YYText 4 | // 5 | // Created by ibireme on 14/10/14. 6 | // Copyright (c) 2015 ibireme. 7 | // 8 | // This source code is licensed under the MIT-style license found in the 9 | // LICENSE file in the root directory of this source tree. 10 | // 11 | 12 | #import "YYTextRunDelegate.h" 13 | 14 | static void DeallocCallback(void *ref) { 15 | YYTextRunDelegate *self = (__bridge_transfer YYTextRunDelegate *)(ref); 16 | self = nil; // release 17 | } 18 | 19 | static CGFloat GetAscentCallback(void *ref) { 20 | YYTextRunDelegate *self = (__bridge YYTextRunDelegate *)(ref); 21 | return self.ascent; 22 | } 23 | 24 | static CGFloat GetDecentCallback(void *ref) { 25 | YYTextRunDelegate *self = (__bridge YYTextRunDelegate *)(ref); 26 | return self.descent; 27 | } 28 | 29 | static CGFloat GetWidthCallback(void *ref) { 30 | YYTextRunDelegate *self = (__bridge YYTextRunDelegate *)(ref); 31 | return self.width; 32 | } 33 | 34 | @implementation YYTextRunDelegate 35 | 36 | - (CTRunDelegateRef)CTRunDelegate CF_RETURNS_RETAINED { 37 | CTRunDelegateCallbacks callbacks; 38 | callbacks.version = kCTRunDelegateCurrentVersion; 39 | callbacks.dealloc = DeallocCallback; 40 | callbacks.getAscent = GetAscentCallback; 41 | callbacks.getDescent = GetDecentCallback; 42 | callbacks.getWidth = GetWidthCallback; 43 | return CTRunDelegateCreate(&callbacks, (__bridge_retained void *)(self.copy)); 44 | } 45 | 46 | - (void)encodeWithCoder:(NSCoder *)aCoder { 47 | [aCoder encodeObject:@(_ascent) forKey:@"ascent"]; 48 | [aCoder encodeObject:@(_descent) forKey:@"descent"]; 49 | [aCoder encodeObject:@(_width) forKey:@"width"]; 50 | [aCoder encodeObject:_userInfo forKey:@"userInfo"]; 51 | } 52 | 53 | - (id)initWithCoder:(NSCoder *)aDecoder { 54 | self = [super init]; 55 | _ascent = ((NSNumber *)[aDecoder decodeObjectForKey:@"ascent"]).floatValue; 56 | _descent = ((NSNumber *)[aDecoder decodeObjectForKey:@"descent"]).floatValue; 57 | _width = ((NSNumber *)[aDecoder decodeObjectForKey:@"width"]).floatValue; 58 | _userInfo = [aDecoder decodeObjectForKey:@"userInfo"]; 59 | return self; 60 | } 61 | 62 | - (id)copyWithZone:(NSZone *)zone { 63 | typeof(self) one = [self.class new]; 64 | one.ascent = self.ascent; 65 | one.descent = self.descent; 66 | one.width = self.width; 67 | one.userInfo = self.userInfo; 68 | return one; 69 | } 70 | 71 | @end 72 | -------------------------------------------------------------------------------- /ATDemo(OC)/Lib/YYText/Utility/NSParagraphStyle+YYText.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSParagraphStyle+YYText.h 3 | // YYText 4 | // 5 | // Created by ibireme on 14/10/7. 6 | // Copyright (c) 2015 ibireme. 7 | // 8 | // This source code is licensed under the MIT-style license found in the 9 | // LICENSE file in the root directory of this source tree. 10 | // 11 | 12 | #import 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | /** 17 | Provides extensions for `NSParagraphStyle` to work with CoreText. 18 | */ 19 | @interface NSParagraphStyle (YYText) 20 | 21 | /** 22 | Creates a new NSParagraphStyle object from the CoreText Style. 23 | 24 | @param CTStyle CoreText Paragraph Style. 25 | 26 | @return a new NSParagraphStyle 27 | */ 28 | + (nullable NSParagraphStyle *)yy_styleWithCTStyle:(CTParagraphStyleRef)CTStyle; 29 | 30 | /** 31 | Creates and returns a CoreText Paragraph Style. (need call CFRelease() after used) 32 | */ 33 | - (nullable CTParagraphStyleRef)yy_CTStyle CF_RETURNS_RETAINED; 34 | 35 | @end 36 | 37 | NS_ASSUME_NONNULL_END 38 | -------------------------------------------------------------------------------- /ATDemo(OC)/Lib/YYText/Utility/NSParagraphStyle+YYText.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSParagraphStyle+YYText.m 3 | // YYText 4 | // 5 | // Created by ibireme on 14/10/7. 6 | // Copyright (c) 2015 ibireme. 7 | // 8 | // This source code is licensed under the MIT-style license found in the 9 | // LICENSE file in the root directory of this source tree. 10 | // 11 | 12 | #import "NSParagraphStyle+YYText.h" 13 | #import "YYTextAttribute.h" 14 | #import 15 | 16 | // Dummy class for category 17 | @interface NSParagraphStyle_YYText : NSObject @end 18 | @implementation NSParagraphStyle_YYText @end 19 | 20 | 21 | @implementation NSParagraphStyle (YYText) 22 | 23 | + (NSParagraphStyle *)yy_styleWithCTStyle:(CTParagraphStyleRef)CTStyle { 24 | if (CTStyle == NULL) return nil; 25 | 26 | NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; 27 | 28 | #pragma clang diagnostic push 29 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 30 | CGFloat lineSpacing; 31 | if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierLineSpacing, sizeof(CGFloat), &lineSpacing)) { 32 | style.lineSpacing = lineSpacing; 33 | } 34 | #pragma clang diagnostic pop 35 | 36 | CGFloat paragraphSpacing; 37 | if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierParagraphSpacing, sizeof(CGFloat), ¶graphSpacing)) { 38 | style.paragraphSpacing = paragraphSpacing; 39 | } 40 | 41 | CTTextAlignment alignment; 42 | if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierAlignment, sizeof(CTTextAlignment), &alignment)) { 43 | style.alignment = NSTextAlignmentFromCTTextAlignment(alignment); 44 | } 45 | 46 | CGFloat firstLineHeadIndent; 47 | if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierFirstLineHeadIndent, sizeof(CGFloat), &firstLineHeadIndent)) { 48 | style.firstLineHeadIndent = firstLineHeadIndent; 49 | } 50 | 51 | CGFloat headIndent; 52 | if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierHeadIndent, sizeof(CGFloat), &headIndent)) { 53 | style.headIndent = headIndent; 54 | } 55 | 56 | CGFloat tailIndent; 57 | if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierTailIndent, sizeof(CGFloat), &tailIndent)) { 58 | style.tailIndent = tailIndent; 59 | } 60 | 61 | CTLineBreakMode lineBreakMode; 62 | if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierLineBreakMode, sizeof(CTLineBreakMode), &lineBreakMode)) { 63 | style.lineBreakMode = (NSLineBreakMode)lineBreakMode; 64 | } 65 | 66 | CGFloat minimumLineHeight; 67 | if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierMinimumLineHeight, sizeof(CGFloat), &minimumLineHeight)) { 68 | style.minimumLineHeight = minimumLineHeight; 69 | } 70 | 71 | CGFloat maximumLineHeight; 72 | if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierMaximumLineHeight, sizeof(CGFloat), &maximumLineHeight)) { 73 | style.maximumLineHeight = maximumLineHeight; 74 | } 75 | 76 | CTWritingDirection baseWritingDirection; 77 | if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierBaseWritingDirection, sizeof(CTWritingDirection), &baseWritingDirection)) { 78 | style.baseWritingDirection = (NSWritingDirection)baseWritingDirection; 79 | } 80 | 81 | CGFloat lineHeightMultiple; 82 | if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierLineHeightMultiple, sizeof(CGFloat), &lineHeightMultiple)) { 83 | style.lineHeightMultiple = lineHeightMultiple; 84 | } 85 | 86 | CGFloat paragraphSpacingBefore; 87 | if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierParagraphSpacingBefore, sizeof(CGFloat), ¶graphSpacingBefore)) { 88 | style.paragraphSpacingBefore = paragraphSpacingBefore; 89 | } 90 | 91 | if ([style respondsToSelector:@selector(tabStops)]) { 92 | CFArrayRef tabStops; 93 | if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierTabStops, sizeof(CFArrayRef), &tabStops)) { 94 | if ([style respondsToSelector:@selector(setTabStops:)]) { 95 | NSMutableArray *tabs = [NSMutableArray new]; 96 | [((__bridge NSArray *)(tabStops))enumerateObjectsUsingBlock : ^(id obj, NSUInteger idx, BOOL *stop) { 97 | CTTextTabRef ctTab = (__bridge CFTypeRef)obj; 98 | 99 | NSTextTab *tab = [[NSTextTab alloc] initWithTextAlignment:NSTextAlignmentFromCTTextAlignment(CTTextTabGetAlignment(ctTab)) location:CTTextTabGetLocation(ctTab) options:(__bridge id)CTTextTabGetOptions(ctTab)]; 100 | [tabs addObject:tab]; 101 | }]; 102 | if (tabs.count) { 103 | style.tabStops = tabs; 104 | } 105 | } 106 | } 107 | 108 | CGFloat defaultTabInterval; 109 | if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierDefaultTabInterval, sizeof(CGFloat), &defaultTabInterval)) { 110 | if ([style respondsToSelector:@selector(setDefaultTabInterval:)]) { 111 | style.defaultTabInterval = defaultTabInterval; 112 | } 113 | } 114 | } 115 | 116 | return style; 117 | } 118 | 119 | - (CTParagraphStyleRef)yy_CTStyle CF_RETURNS_RETAINED { 120 | CTParagraphStyleSetting set[kCTParagraphStyleSpecifierCount] = { 0 }; 121 | int count = 0; 122 | 123 | #pragma clang diagnostic push 124 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 125 | CGFloat lineSpacing = self.lineSpacing; 126 | set[count].spec = kCTParagraphStyleSpecifierLineSpacing; 127 | set[count].valueSize = sizeof(CGFloat); 128 | set[count].value = &lineSpacing; 129 | count++; 130 | #pragma clang diagnostic pop 131 | 132 | CGFloat paragraphSpacing = self.paragraphSpacing; 133 | set[count].spec = kCTParagraphStyleSpecifierParagraphSpacing; 134 | set[count].valueSize = sizeof(CGFloat); 135 | set[count].value = ¶graphSpacing; 136 | count++; 137 | 138 | CTTextAlignment alignment = NSTextAlignmentToCTTextAlignment(self.alignment); 139 | set[count].spec = kCTParagraphStyleSpecifierAlignment; 140 | set[count].valueSize = sizeof(CTTextAlignment); 141 | set[count].value = &alignment; 142 | count++; 143 | 144 | CGFloat firstLineHeadIndent = self.firstLineHeadIndent; 145 | set[count].spec = kCTParagraphStyleSpecifierFirstLineHeadIndent; 146 | set[count].valueSize = sizeof(CGFloat); 147 | set[count].value = &firstLineHeadIndent; 148 | count++; 149 | 150 | CGFloat headIndent = self.headIndent; 151 | set[count].spec = kCTParagraphStyleSpecifierHeadIndent; 152 | set[count].valueSize = sizeof(CGFloat); 153 | set[count].value = &headIndent; 154 | count++; 155 | 156 | CGFloat tailIndent = self.tailIndent; 157 | set[count].spec = kCTParagraphStyleSpecifierTailIndent; 158 | set[count].valueSize = sizeof(CGFloat); 159 | set[count].value = &tailIndent; 160 | count++; 161 | 162 | CTLineBreakMode paraLineBreak = (CTLineBreakMode)self.lineBreakMode; 163 | set[count].spec = kCTParagraphStyleSpecifierLineBreakMode; 164 | set[count].valueSize = sizeof(CTLineBreakMode); 165 | set[count].value = ¶LineBreak; 166 | count++; 167 | 168 | CGFloat minimumLineHeight = self.minimumLineHeight; 169 | set[count].spec = kCTParagraphStyleSpecifierMinimumLineHeight; 170 | set[count].valueSize = sizeof(CGFloat); 171 | set[count].value = &minimumLineHeight; 172 | count++; 173 | 174 | CGFloat maximumLineHeight = self.maximumLineHeight; 175 | set[count].spec = kCTParagraphStyleSpecifierMaximumLineHeight; 176 | set[count].valueSize = sizeof(CGFloat); 177 | set[count].value = &maximumLineHeight; 178 | count++; 179 | 180 | CTWritingDirection paraWritingDirection = (CTWritingDirection)self.baseWritingDirection; 181 | set[count].spec = kCTParagraphStyleSpecifierBaseWritingDirection; 182 | set[count].valueSize = sizeof(CTWritingDirection); 183 | set[count].value = ¶WritingDirection; 184 | count++; 185 | 186 | CGFloat lineHeightMultiple = self.lineHeightMultiple; 187 | set[count].spec = kCTParagraphStyleSpecifierLineHeightMultiple; 188 | set[count].valueSize = sizeof(CGFloat); 189 | set[count].value = &lineHeightMultiple; 190 | count++; 191 | 192 | CGFloat paragraphSpacingBefore = self.paragraphSpacingBefore; 193 | set[count].spec = kCTParagraphStyleSpecifierParagraphSpacingBefore; 194 | set[count].valueSize = sizeof(CGFloat); 195 | set[count].value = ¶graphSpacingBefore; 196 | count++; 197 | 198 | if([self respondsToSelector:@selector(tabStops)]) { 199 | NSMutableArray *tabs = [NSMutableArray array]; 200 | if ([self respondsToSelector:@selector(tabStops)]) { 201 | NSInteger numTabs = self.tabStops.count; 202 | if (numTabs) { 203 | [self.tabStops enumerateObjectsUsingBlock: ^(NSTextTab *tab, NSUInteger idx, BOOL *stop) { 204 | CTTextTabRef ctTab = CTTextTabCreate(NSTextAlignmentToCTTextAlignment(tab.alignment), tab.location, (__bridge CFTypeRef)tab.options); 205 | [tabs addObject:(__bridge id)ctTab]; 206 | CFRelease(ctTab); 207 | }]; 208 | 209 | CFArrayRef tabStops = (__bridge CFArrayRef)(tabs); 210 | set[count].spec = kCTParagraphStyleSpecifierTabStops; 211 | set[count].valueSize = sizeof(CFArrayRef); 212 | set[count].value = &tabStops; 213 | count++; 214 | } 215 | } 216 | 217 | if ([self respondsToSelector:@selector(defaultTabInterval)]) { 218 | CGFloat defaultTabInterval = self.defaultTabInterval; 219 | set[count].spec = kCTParagraphStyleSpecifierDefaultTabInterval; 220 | set[count].valueSize = sizeof(CGFloat); 221 | set[count].value = &defaultTabInterval; 222 | count++; 223 | } 224 | } 225 | 226 | CTParagraphStyleRef style = CTParagraphStyleCreate(set, count); 227 | return style; 228 | } 229 | 230 | @end 231 | -------------------------------------------------------------------------------- /ATDemo(OC)/Lib/YYText/Utility/UIPasteboard+YYText.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIPasteboard+YYText.h 3 | // YYText 4 | // 5 | // Created by ibireme on 15/4/2. 6 | // Copyright (c) 2015 ibireme. 7 | // 8 | // This source code is licensed under the MIT-style license found in the 9 | // LICENSE file in the root directory of this source tree. 10 | // 11 | 12 | #import 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | /** 17 | Extend UIPasteboard to support image and attributed string. 18 | */ 19 | @interface UIPasteboard (YYText) 20 | 21 | @property (nullable, nonatomic, copy) NSData *yy_PNGData; ///< PNG file data 22 | @property (nullable, nonatomic, copy) NSData *yy_JPEGData; ///< JPEG file data 23 | @property (nullable, nonatomic, copy) NSData *yy_GIFData; ///< GIF file data 24 | @property (nullable, nonatomic, copy) NSData *yy_WEBPData; ///< WebP file data 25 | @property (nullable, nonatomic, copy) NSData *yy_ImageData; ///< image file data 26 | 27 | /// Attributed string, 28 | /// Set this attributed will also set the string property which is copy from the attributed string. 29 | /// If the attributed string contains one or more image, it will also set the `images` property. 30 | @property (nullable, nonatomic, copy) NSAttributedString *yy_AttributedString; 31 | 32 | @end 33 | 34 | 35 | /// The name identifying the attributed string in pasteboard. 36 | UIKIT_EXTERN NSString *const YYTextPasteboardTypeAttributedString; 37 | 38 | /// The UTI Type identifying WebP data in pasteboard. 39 | UIKIT_EXTERN NSString *const YYTextUTTypeWEBP; 40 | 41 | NS_ASSUME_NONNULL_END 42 | -------------------------------------------------------------------------------- /ATDemo(OC)/Lib/YYText/Utility/UIPasteboard+YYText.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIPasteboard+YYText.m 3 | // YYText 4 | // 5 | // Created by ibireme on 15/4/2. 6 | // Copyright (c) 2015 ibireme. 7 | // 8 | // This source code is licensed under the MIT-style license found in the 9 | // LICENSE file in the root directory of this source tree. 10 | // 11 | 12 | #import "UIPasteboard+YYText.h" 13 | #import "NSAttributedString+YYText.h" 14 | #import 15 | 16 | 17 | #if __has_include("YYImage.h") 18 | #import "YYImage.h" 19 | #define YYTextAnimatedImageAvailable 1 20 | #elif __has_include() 21 | #import 22 | #define YYTextAnimatedImageAvailable 1 23 | #elif __has_include() 24 | #import 25 | #define YYTextAnimatedImageAvailable 1 26 | #else 27 | #define YYTextAnimatedImageAvailable 0 28 | #endif 29 | 30 | 31 | // Dummy class for category 32 | @interface UIPasteboard_YYText : NSObject @end 33 | @implementation UIPasteboard_YYText @end 34 | 35 | 36 | NSString *const YYTextPasteboardTypeAttributedString = @"com.ibireme.NSAttributedString"; 37 | NSString *const YYTextUTTypeWEBP = @"com.google.webp"; 38 | 39 | @implementation UIPasteboard (YYText) 40 | 41 | 42 | - (void)setYy_PNGData:(NSData *)PNGData { 43 | [self setData:PNGData forPasteboardType:(id)kUTTypePNG]; 44 | } 45 | 46 | - (NSData *)yy_PNGData { 47 | return [self dataForPasteboardType:(id)kUTTypePNG]; 48 | } 49 | 50 | - (void)setYy_JPEGData:(NSData *)JPEGData { 51 | [self setData:JPEGData forPasteboardType:(id)kUTTypeJPEG]; 52 | } 53 | 54 | - (NSData *)yy_JPEGData { 55 | return [self dataForPasteboardType:(id)kUTTypeJPEG]; 56 | } 57 | 58 | - (void)setYy_GIFData:(NSData *)GIFData { 59 | [self setData:GIFData forPasteboardType:(id)kUTTypeGIF]; 60 | } 61 | 62 | - (NSData *)yy_GIFData { 63 | return [self dataForPasteboardType:(id)kUTTypeGIF]; 64 | } 65 | 66 | - (void)setYy_WEBPData:(NSData *)WEBPData { 67 | [self setData:WEBPData forPasteboardType:YYTextUTTypeWEBP]; 68 | } 69 | 70 | - (NSData *)yy_WEBPData { 71 | return [self dataForPasteboardType:YYTextUTTypeWEBP]; 72 | } 73 | 74 | - (void)setYy_ImageData:(NSData *)imageData { 75 | [self setData:imageData forPasteboardType:(id)kUTTypeImage]; 76 | } 77 | 78 | - (NSData *)yy_ImageData { 79 | return [self dataForPasteboardType:(id)kUTTypeImage]; 80 | } 81 | 82 | - (void)setYy_AttributedString:(NSAttributedString *)attributedString { 83 | self.string = [attributedString yy_plainTextForRange:NSMakeRange(0, attributedString.length)]; 84 | NSData *data = [attributedString yy_archiveToData]; 85 | if (data) { 86 | NSDictionary *item = @{YYTextPasteboardTypeAttributedString : data}; 87 | [self addItems:@[item]]; 88 | } 89 | [attributedString enumerateAttribute:YYTextAttachmentAttributeName inRange:NSMakeRange(0, attributedString.length) options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:^(YYTextAttachment *attachment, NSRange range, BOOL *stop) { 90 | 91 | // save image 92 | UIImage *simpleImage = nil; 93 | if ([attachment.content isKindOfClass:[UIImage class]]) { 94 | simpleImage = attachment.content; 95 | } else if ([attachment.content isKindOfClass:[UIImageView class]]) { 96 | simpleImage = ((UIImageView *)attachment.content).image; 97 | } 98 | if (simpleImage) { 99 | NSDictionary *item = @{@"com.apple.uikit.image" : simpleImage}; 100 | [self addItems:@[item]]; 101 | } 102 | 103 | #if YYTextAnimatedImageAvailable 104 | // save animated image 105 | if ([attachment.content isKindOfClass:[UIImageView class]]) { 106 | UIImageView *imageView = attachment.content; 107 | Class aniImageClass = NSClassFromString(@"YYImage"); 108 | UIImage *image = imageView.image; 109 | if (aniImageClass && [image isKindOfClass:aniImageClass]) { 110 | NSData *data = [image valueForKey:@"animatedImageData"]; 111 | NSNumber *type = [image valueForKey:@"animatedImageType"]; 112 | if (data) { 113 | switch (type.unsignedIntegerValue) { 114 | case YYImageTypeGIF: { 115 | NSDictionary *item = @{(id)kUTTypeGIF : data}; 116 | [self addItems:@[item]]; 117 | } break; 118 | case YYImageTypePNG: { // APNG 119 | NSDictionary *item = @{(id)kUTTypePNG : data}; 120 | [self addItems:@[item]]; 121 | } break; 122 | case YYImageTypeWebP: { 123 | NSDictionary *item = @{(id)YYTextUTTypeWEBP : data}; 124 | [self addItems:@[item]]; 125 | } break; 126 | default: break; 127 | } 128 | } 129 | } 130 | } 131 | #endif 132 | 133 | }]; 134 | } 135 | 136 | - (NSAttributedString *)yy_AttributedString { 137 | for (NSDictionary *items in self.items) { 138 | NSData *data = items[YYTextPasteboardTypeAttributedString]; 139 | if (data) { 140 | return [NSAttributedString yy_unarchiveFromData:data]; 141 | } 142 | } 143 | return nil; 144 | } 145 | 146 | @end 147 | -------------------------------------------------------------------------------- /ATDemo(OC)/Lib/YYText/Utility/UIView+YYText.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+YYText.h 3 | // YYText 4 | // 5 | // Created by ibireme on 13/4/3. 6 | // Copyright (c) 2015 ibireme. 7 | // 8 | // This source code is licensed under the MIT-style license found in the 9 | // LICENSE file in the root directory of this source tree. 10 | // 11 | 12 | #import 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | /** 17 | Provides extensions for `UIView`. 18 | */ 19 | @interface UIView (YYText) 20 | 21 | /** 22 | Returns the view's view controller (may be nil). 23 | */ 24 | @property (nullable, nonatomic, readonly) UIViewController *yy_viewController; 25 | 26 | /** 27 | Returns the visible alpha on screen, taking into account superview and window. 28 | */ 29 | @property (nonatomic, readonly) CGFloat yy_visibleAlpha; 30 | 31 | /** 32 | Converts a point from the receiver's coordinate system to that of the specified view or window. 33 | 34 | @param point A point specified in the local coordinate system (bounds) of the receiver. 35 | @param view The view or window into whose coordinate system point is to be converted. 36 | If view is nil, this method instead converts to window base coordinates. 37 | @return The point converted to the coordinate system of view. 38 | */ 39 | - (CGPoint)yy_convertPoint:(CGPoint)point toViewOrWindow:(UIView *)view; 40 | 41 | /** 42 | Converts a point from the coordinate system of a given view or window to that of the receiver. 43 | 44 | @param point A point specified in the local coordinate system (bounds) of view. 45 | @param view The view or window with point in its coordinate system. 46 | If view is nil, this method instead converts from window base coordinates. 47 | @return The point converted to the local coordinate system (bounds) of the receiver. 48 | */ 49 | - (CGPoint)yy_convertPoint:(CGPoint)point fromViewOrWindow:(UIView *)view; 50 | 51 | /** 52 | Converts a rectangle from the receiver's coordinate system to that of another view or window. 53 | 54 | @param rect A rectangle specified in the local coordinate system (bounds) of the receiver. 55 | @param view The view or window that is the target of the conversion operation. If view is nil, this method instead converts to window base coordinates. 56 | @return The converted rectangle. 57 | */ 58 | - (CGRect)yy_convertRect:(CGRect)rect toViewOrWindow:(UIView *)view; 59 | 60 | /** 61 | Converts a rectangle from the coordinate system of another view or window to that of the receiver. 62 | 63 | @param rect A rectangle specified in the local coordinate system (bounds) of view. 64 | @param view The view or window with rect in its coordinate system. 65 | If view is nil, this method instead converts from window base coordinates. 66 | @return The converted rectangle. 67 | */ 68 | - (CGRect)yy_convertRect:(CGRect)rect fromViewOrWindow:(UIView *)view; 69 | 70 | @end 71 | 72 | NS_ASSUME_NONNULL_END 73 | -------------------------------------------------------------------------------- /ATDemo(OC)/Lib/YYText/Utility/UIView+YYText.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+YYText.m 3 | // YYText 4 | // 5 | // Created by ibireme on 13/4/3. 6 | // Copyright (c) 2015 ibireme. 7 | // 8 | // This source code is licensed under the MIT-style license found in the 9 | // LICENSE file in the root directory of this source tree. 10 | // 11 | 12 | #import "UIView+YYText.h" 13 | 14 | // Dummy class for category 15 | @interface UIView_YYText : NSObject @end 16 | @implementation UIView_YYText @end 17 | 18 | 19 | @implementation UIView (YYText) 20 | 21 | - (UIViewController *)yy_viewController { 22 | for (UIView *view = self; view; view = view.superview) { 23 | UIResponder *nextResponder = [view nextResponder]; 24 | if ([nextResponder isKindOfClass:[UIViewController class]]) { 25 | return (UIViewController *)nextResponder; 26 | } 27 | } 28 | return nil; 29 | } 30 | 31 | - (CGFloat)yy_visibleAlpha { 32 | if ([self isKindOfClass:[UIWindow class]]) { 33 | if (self.hidden) return 0; 34 | return self.alpha; 35 | } 36 | if (!self.window) return 0; 37 | CGFloat alpha = 1; 38 | UIView *v = self; 39 | while (v) { 40 | if (v.hidden) { 41 | alpha = 0; 42 | break; 43 | } 44 | alpha *= v.alpha; 45 | v = v.superview; 46 | } 47 | return alpha; 48 | } 49 | 50 | - (CGPoint)yy_convertPoint:(CGPoint)point toViewOrWindow:(UIView *)view { 51 | if (!view) { 52 | if ([self isKindOfClass:[UIWindow class]]) { 53 | return [((UIWindow *)self) convertPoint:point toWindow:nil]; 54 | } else { 55 | return [self convertPoint:point toView:nil]; 56 | } 57 | } 58 | 59 | UIWindow *from = [self isKindOfClass:[UIWindow class]] ? (id)self : self.window; 60 | UIWindow *to = [view isKindOfClass:[UIWindow class]] ? (id)view : view.window; 61 | if ((!from || !to) || (from == to)) return [self convertPoint:point toView:view]; 62 | point = [self convertPoint:point toView:from]; 63 | point = [to convertPoint:point fromWindow:from]; 64 | point = [view convertPoint:point fromView:to]; 65 | return point; 66 | } 67 | 68 | - (CGPoint)yy_convertPoint:(CGPoint)point fromViewOrWindow:(UIView *)view { 69 | if (!view) { 70 | if ([self isKindOfClass:[UIWindow class]]) { 71 | return [((UIWindow *)self) convertPoint:point fromWindow:nil]; 72 | } else { 73 | return [self convertPoint:point fromView:nil]; 74 | } 75 | } 76 | 77 | UIWindow *from = [view isKindOfClass:[UIWindow class]] ? (id)view : view.window; 78 | UIWindow *to = [self isKindOfClass:[UIWindow class]] ? (id)self : self.window; 79 | if ((!from || !to) || (from == to)) return [self convertPoint:point fromView:view]; 80 | point = [from convertPoint:point fromView:view]; 81 | point = [to convertPoint:point fromWindow:from]; 82 | point = [self convertPoint:point fromView:to]; 83 | return point; 84 | } 85 | 86 | - (CGRect)yy_convertRect:(CGRect)rect toViewOrWindow:(UIView *)view { 87 | if (!view) { 88 | if ([self isKindOfClass:[UIWindow class]]) { 89 | return [((UIWindow *)self) convertRect:rect toWindow:nil]; 90 | } else { 91 | return [self convertRect:rect toView:nil]; 92 | } 93 | } 94 | 95 | UIWindow *from = [self isKindOfClass:[UIWindow class]] ? (id)self : self.window; 96 | UIWindow *to = [view isKindOfClass:[UIWindow class]] ? (id)view : view.window; 97 | if (!from || !to) return [self convertRect:rect toView:view]; 98 | if (from == to) return [self convertRect:rect toView:view]; 99 | rect = [self convertRect:rect toView:from]; 100 | rect = [to convertRect:rect fromWindow:from]; 101 | rect = [view convertRect:rect fromView:to]; 102 | return rect; 103 | } 104 | 105 | - (CGRect)yy_convertRect:(CGRect)rect fromViewOrWindow:(UIView *)view { 106 | if (!view) { 107 | if ([self isKindOfClass:[UIWindow class]]) { 108 | return [((UIWindow *)self) convertRect:rect fromWindow:nil]; 109 | } else { 110 | return [self convertRect:rect fromView:nil]; 111 | } 112 | } 113 | 114 | UIWindow *from = [view isKindOfClass:[UIWindow class]] ? (id)view : view.window; 115 | UIWindow *to = [self isKindOfClass:[UIWindow class]] ? (id)self : self.window; 116 | if ((!from || !to) || (from == to)) return [self convertRect:rect fromView:view]; 117 | rect = [from convertRect:rect fromView:view]; 118 | rect = [to convertRect:rect fromWindow:from]; 119 | rect = [self convertRect:rect fromView:to]; 120 | return rect; 121 | } 122 | 123 | @end -------------------------------------------------------------------------------- /ATDemo(OC)/Lib/YYText/Utility/YYTextAsyncLayer.h: -------------------------------------------------------------------------------- 1 | // 2 | // YYTextAsyncLayer.h 3 | // YYText 4 | // 5 | // Created by ibireme on 15/4/11. 6 | // Copyright (c) 2015 ibireme. 7 | // 8 | // This source code is licensed under the MIT-style license found in the 9 | // LICENSE file in the root directory of this source tree. 10 | // 11 | 12 | #import 13 | #import 14 | 15 | @class YYTextAsyncLayerDisplayTask; 16 | 17 | NS_ASSUME_NONNULL_BEGIN 18 | 19 | /** 20 | The YYTextAsyncLayer class is a subclass of CALayer used for render contents asynchronously. 21 | 22 | @discussion When the layer need update it's contents, it will ask the delegate 23 | for a async display task to render the contents in a background queue. 24 | */ 25 | @interface YYTextAsyncLayer : CALayer 26 | /// Whether the render code is executed in background. Default is YES. 27 | @property BOOL displaysAsynchronously; 28 | @end 29 | 30 | 31 | /** 32 | The YYTextAsyncLayer's delegate protocol. The delegate of the YYTextAsyncLayer (typically a UIView) 33 | must implements the method in this protocol. 34 | */ 35 | @protocol YYTextAsyncLayerDelegate 36 | @required 37 | /// This method is called to return a new display task when the layer's contents need update. 38 | - (YYTextAsyncLayerDisplayTask *)newAsyncDisplayTask; 39 | @end 40 | 41 | 42 | /** 43 | A display task used by YYTextAsyncLayer to render the contents in background queue. 44 | */ 45 | @interface YYTextAsyncLayerDisplayTask : NSObject 46 | 47 | /** 48 | This block will be called before the asynchronous drawing begins. 49 | It will be called on the main thread. 50 | 51 | block param layer: The layer. 52 | */ 53 | @property (nullable, nonatomic, copy) void (^willDisplay)(CALayer *layer); 54 | 55 | /** 56 | This block is called to draw the layer's contents. 57 | 58 | @discussion This block may be called on main thread or background thread, 59 | so is should be thread-safe. 60 | 61 | block param context: A new bitmap content created by layer. 62 | block param size: The content size (typically same as layer's bound size). 63 | block param isCancelled: If this block returns `YES`, the method should cancel the 64 | drawing process and return as quickly as possible. 65 | */ 66 | @property (nullable, nonatomic, copy) void (^display)(CGContextRef context, CGSize size, BOOL(^isCancelled)(void)); 67 | 68 | /** 69 | This block will be called after the asynchronous drawing finished. 70 | It will be called on the main thread. 71 | 72 | block param layer: The layer. 73 | block param finished: If the draw process is cancelled, it's `NO`, otherwise it's `YES`; 74 | */ 75 | @property (nullable, nonatomic, copy) void (^didDisplay)(CALayer *layer, BOOL finished); 76 | 77 | @end 78 | 79 | NS_ASSUME_NONNULL_END 80 | -------------------------------------------------------------------------------- /ATDemo(OC)/Lib/YYText/Utility/YYTextAsyncLayer.m: -------------------------------------------------------------------------------- 1 | // 2 | // YYTextAsyncLayer.m 3 | // YYText 4 | // 5 | // Created by ibireme on 15/4/11. 6 | // Copyright (c) 2015 ibireme. 7 | // 8 | // This source code is licensed under the MIT-style license found in the 9 | // LICENSE file in the root directory of this source tree. 10 | // 11 | 12 | #import "YYTextAsyncLayer.h" 13 | #import 14 | 15 | 16 | /// Global display queue, used for content rendering. 17 | static dispatch_queue_t YYTextAsyncLayerGetDisplayQueue() { 18 | #define MAX_QUEUE_COUNT 16 19 | static int queueCount; 20 | static dispatch_queue_t queues[MAX_QUEUE_COUNT]; 21 | static dispatch_once_t onceToken; 22 | static int32_t counter = 0; 23 | dispatch_once(&onceToken, ^{ 24 | queueCount = (int)[NSProcessInfo processInfo].activeProcessorCount; 25 | queueCount = queueCount < 1 ? 1 : queueCount > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : queueCount; 26 | if ([UIDevice currentDevice].systemVersion.floatValue >= 8.0) { 27 | for (NSUInteger i = 0; i < queueCount; i++) { 28 | dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, 0); 29 | queues[i] = dispatch_queue_create("com.ibireme.text.render", attr); 30 | } 31 | } else { 32 | for (NSUInteger i = 0; i < queueCount; i++) { 33 | queues[i] = dispatch_queue_create("com.ibireme.text.render", DISPATCH_QUEUE_SERIAL); 34 | dispatch_set_target_queue(queues[i], dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)); 35 | } 36 | } 37 | }); 38 | uint32_t cur = (uint32_t)OSAtomicIncrement32(&counter); 39 | return queues[(cur) % queueCount]; 40 | #undef MAX_QUEUE_COUNT 41 | } 42 | 43 | static dispatch_queue_t YYTextAsyncLayerGetReleaseQueue() { 44 | #ifdef YYDispatchQueuePool_h 45 | return YYDispatchQueueGetForQOS(NSQualityOfServiceDefault); 46 | #else 47 | return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0); 48 | #endif 49 | } 50 | 51 | 52 | /// a thread safe incrementing counter. 53 | @interface _YYTextSentinel : NSObject 54 | /// Returns the current value of the counter. 55 | @property (atomic, readonly) int32_t value; 56 | /// Increase the value atomically. @return The new value. 57 | - (int32_t)increase; 58 | @end 59 | 60 | @implementation _YYTextSentinel { 61 | int32_t _value; 62 | } 63 | - (int32_t)value { 64 | return _value; 65 | } 66 | - (int32_t)increase { 67 | return OSAtomicIncrement32(&_value); 68 | } 69 | @end 70 | 71 | 72 | @implementation YYTextAsyncLayerDisplayTask 73 | @end 74 | 75 | 76 | @implementation YYTextAsyncLayer { 77 | _YYTextSentinel *_sentinel; 78 | } 79 | 80 | #pragma mark - Override 81 | 82 | + (id)defaultValueForKey:(NSString *)key { 83 | if ([key isEqualToString:@"displaysAsynchronously"]) { 84 | return @(YES); 85 | } else { 86 | return [super defaultValueForKey:key]; 87 | } 88 | } 89 | 90 | - (instancetype)init { 91 | self = [super init]; 92 | static CGFloat scale; //global 93 | static dispatch_once_t onceToken; 94 | dispatch_once(&onceToken, ^{ 95 | scale = [UIScreen mainScreen].scale; 96 | }); 97 | self.contentsScale = scale; 98 | _sentinel = [_YYTextSentinel new]; 99 | _displaysAsynchronously = YES; 100 | return self; 101 | } 102 | 103 | - (void)dealloc { 104 | [_sentinel increase]; 105 | } 106 | 107 | - (void)setNeedsDisplay { 108 | [self _cancelAsyncDisplay]; 109 | [super setNeedsDisplay]; 110 | } 111 | 112 | - (void)display { 113 | super.contents = super.contents; 114 | [self _displayAsync:_displaysAsynchronously]; 115 | } 116 | 117 | #pragma mark - Private 118 | 119 | - (void)_displayAsync:(BOOL)async { 120 | __strong id delegate = (id)self.delegate; 121 | YYTextAsyncLayerDisplayTask *task = [delegate newAsyncDisplayTask]; 122 | if (!task.display) { 123 | if (task.willDisplay) task.willDisplay(self); 124 | self.contents = nil; 125 | if (task.didDisplay) task.didDisplay(self, YES); 126 | return; 127 | } 128 | 129 | if (async) { 130 | if (task.willDisplay) task.willDisplay(self); 131 | _YYTextSentinel *sentinel = _sentinel; 132 | int32_t value = sentinel.value; 133 | BOOL (^isCancelled)() = ^BOOL() { 134 | return value != sentinel.value; 135 | }; 136 | CGSize size = self.bounds.size; 137 | BOOL opaque = self.opaque; 138 | CGFloat scale = self.contentsScale; 139 | CGColorRef backgroundColor = (opaque && self.backgroundColor) ? CGColorRetain(self.backgroundColor) : NULL; 140 | if (size.width < 1 || size.height < 1) { 141 | CGImageRef image = (__bridge_retained CGImageRef)(self.contents); 142 | self.contents = nil; 143 | if (image) { 144 | dispatch_async(YYTextAsyncLayerGetReleaseQueue(), ^{ 145 | CFRelease(image); 146 | }); 147 | } 148 | if (task.didDisplay) task.didDisplay(self, YES); 149 | CGColorRelease(backgroundColor); 150 | return; 151 | } 152 | 153 | dispatch_async(YYTextAsyncLayerGetDisplayQueue(), ^{ 154 | if (isCancelled()) { 155 | CGColorRelease(backgroundColor); 156 | return; 157 | } 158 | UIGraphicsBeginImageContextWithOptions(size, opaque, scale); 159 | CGContextRef context = UIGraphicsGetCurrentContext(); 160 | if (opaque && context) { 161 | CGContextSaveGState(context); { 162 | if (!backgroundColor || CGColorGetAlpha(backgroundColor) < 1) { 163 | CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor); 164 | CGContextAddRect(context, CGRectMake(0, 0, size.width * scale, size.height * scale)); 165 | CGContextFillPath(context); 166 | } 167 | if (backgroundColor) { 168 | CGContextSetFillColorWithColor(context, backgroundColor); 169 | CGContextAddRect(context, CGRectMake(0, 0, size.width * scale, size.height * scale)); 170 | CGContextFillPath(context); 171 | } 172 | } CGContextRestoreGState(context); 173 | CGColorRelease(backgroundColor); 174 | } 175 | task.display(context, size, isCancelled); 176 | if (isCancelled()) { 177 | UIGraphicsEndImageContext(); 178 | dispatch_async(dispatch_get_main_queue(), ^{ 179 | if (task.didDisplay) task.didDisplay(self, NO); 180 | }); 181 | return; 182 | } 183 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 184 | UIGraphicsEndImageContext(); 185 | if (isCancelled()) { 186 | dispatch_async(dispatch_get_main_queue(), ^{ 187 | if (task.didDisplay) task.didDisplay(self, NO); 188 | }); 189 | return; 190 | } 191 | dispatch_async(dispatch_get_main_queue(), ^{ 192 | if (isCancelled()) { 193 | if (task.didDisplay) task.didDisplay(self, NO); 194 | } else { 195 | self.contents = (__bridge id)(image.CGImage); 196 | if (task.didDisplay) task.didDisplay(self, YES); 197 | } 198 | }); 199 | }); 200 | } else { 201 | [_sentinel increase]; 202 | if (task.willDisplay) task.willDisplay(self); 203 | UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.opaque, self.contentsScale); 204 | CGContextRef context = UIGraphicsGetCurrentContext(); 205 | if (self.opaque && context) { 206 | CGSize size = self.bounds.size; 207 | size.width *= self.contentsScale; 208 | size.height *= self.contentsScale; 209 | CGContextSaveGState(context); { 210 | if (!self.backgroundColor || CGColorGetAlpha(self.backgroundColor) < 1) { 211 | CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor); 212 | CGContextAddRect(context, CGRectMake(0, 0, size.width, size.height)); 213 | CGContextFillPath(context); 214 | } 215 | if (self.backgroundColor) { 216 | CGContextSetFillColorWithColor(context, self.backgroundColor); 217 | CGContextAddRect(context, CGRectMake(0, 0, size.width, size.height)); 218 | CGContextFillPath(context); 219 | } 220 | } CGContextRestoreGState(context); 221 | } 222 | task.display(context, self.bounds.size, ^{return NO;}); 223 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 224 | UIGraphicsEndImageContext(); 225 | self.contents = (__bridge id)(image.CGImage); 226 | if (task.didDisplay) task.didDisplay(self, YES); 227 | } 228 | } 229 | 230 | - (void)_cancelAsyncDisplay { 231 | [_sentinel increase]; 232 | } 233 | 234 | @end 235 | -------------------------------------------------------------------------------- /ATDemo(OC)/Lib/YYText/Utility/YYTextTransaction.h: -------------------------------------------------------------------------------- 1 | // 2 | // YYTextTransaction.h 3 | // YYText 4 | // 5 | // Created by ibireme on 15/4/18. 6 | // Copyright (c) 2015 ibireme. 7 | // 8 | // This source code is licensed under the MIT-style license found in the 9 | // LICENSE file in the root directory of this source tree. 10 | // 11 | 12 | #import 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | /** 17 | YYTextTransaction let you perform a selector once before current runloop sleep. 18 | */ 19 | @interface YYTextTransaction : NSObject 20 | 21 | /** 22 | Creates and returns a transaction with a specified target and selector. 23 | 24 | @param target A specified target, the target is retained until runloop end. 25 | @param selector A selector for target. 26 | 27 | @return A new transaction, or nil if an error occurs. 28 | */ 29 | + (YYTextTransaction *)transactionWithTarget:(id)target selector:(SEL)selector; 30 | 31 | /** 32 | Commit the trancaction to main runloop. 33 | 34 | @discussion It will perform the selector on the target once before main runloop's 35 | current loop sleep. If the same transaction (same target and same selector) has 36 | already commit to runloop in this loop, this method do nothing. 37 | */ 38 | - (void)commit; 39 | 40 | @end 41 | 42 | NS_ASSUME_NONNULL_END 43 | -------------------------------------------------------------------------------- /ATDemo(OC)/Lib/YYText/Utility/YYTextTransaction.m: -------------------------------------------------------------------------------- 1 | // 2 | // YYTextTransaction.m 3 | // YYText 4 | // 5 | // Created by ibireme on 15/4/18. 6 | // Copyright (c) 2015 ibireme. 7 | // 8 | // This source code is licensed under the MIT-style license found in the 9 | // LICENSE file in the root directory of this source tree. 10 | // 11 | 12 | #import "YYTextTransaction.h" 13 | 14 | 15 | @interface YYTextTransaction() 16 | @property (nonatomic, strong) id target; 17 | @property (nonatomic, assign) SEL selector; 18 | @end 19 | 20 | static NSMutableSet *transactionSet = nil; 21 | 22 | static void YYRunLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) { 23 | if (transactionSet.count == 0) return; 24 | NSSet *currentSet = transactionSet; 25 | transactionSet = [NSMutableSet new]; 26 | [currentSet enumerateObjectsUsingBlock:^(YYTextTransaction *transaction, BOOL *stop) { 27 | #pragma clang diagnostic push 28 | #pragma clang diagnostic ignored "-Warc-performSelector-leaks" 29 | [transaction.target performSelector:transaction.selector]; 30 | #pragma clang diagnostic pop 31 | }]; 32 | } 33 | 34 | static void YYTextTransactionSetup() { 35 | static dispatch_once_t onceToken; 36 | dispatch_once(&onceToken, ^{ 37 | transactionSet = [NSMutableSet new]; 38 | CFRunLoopRef runloop = CFRunLoopGetMain(); 39 | CFRunLoopObserverRef observer; 40 | 41 | observer = CFRunLoopObserverCreate(CFAllocatorGetDefault(), 42 | kCFRunLoopBeforeWaiting | kCFRunLoopExit, 43 | true, // repeat 44 | 0xFFFFFF, // after CATransaction(2000000) 45 | YYRunLoopObserverCallBack, NULL); 46 | CFRunLoopAddObserver(runloop, observer, kCFRunLoopCommonModes); 47 | CFRelease(observer); 48 | }); 49 | } 50 | 51 | 52 | @implementation YYTextTransaction 53 | 54 | + (YYTextTransaction *)transactionWithTarget:(id)target selector:(SEL)selector{ 55 | if (!target || !selector) return nil; 56 | YYTextTransaction *t = [YYTextTransaction new]; 57 | t.target = target; 58 | t.selector = selector; 59 | return t; 60 | } 61 | 62 | - (void)commit { 63 | if (!_target || !_selector) return; 64 | YYTextTransactionSetup(); 65 | [transactionSet addObject:self]; 66 | } 67 | 68 | - (NSUInteger)hash { 69 | long v1 = (long)((void *)_selector); 70 | long v2 = (long)_target; 71 | return v1 ^ v2; 72 | } 73 | 74 | - (BOOL)isEqual:(id)object { 75 | if (self == object) return YES; 76 | if (![object isMemberOfClass:self.class]) return NO; 77 | YYTextTransaction *other = object; 78 | return other.selector == _selector && other.target == _target; 79 | } 80 | 81 | @end 82 | -------------------------------------------------------------------------------- /ATDemo(OC)/Lib/YYText/Utility/YYTextWeakProxy.h: -------------------------------------------------------------------------------- 1 | // 2 | // YYTextWeakProxy.h 3 | // YYText 4 | // 5 | // Created by ibireme on 14/10/18. 6 | // Copyright (c) 2015 ibireme. 7 | // 8 | // This source code is licensed under the MIT-style license found in the 9 | // LICENSE file in the root directory of this source tree. 10 | // 11 | 12 | #import 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | /** 17 | A proxy used to hold a weak object. 18 | It can be used to avoid retain cycles, such as the target in NSTimer or CADisplayLink. 19 | 20 | sample code: 21 | 22 | @implementation MyView { 23 | NSTimer *_timer; 24 | } 25 | 26 | - (void)initTimer { 27 | YYTextWeakProxy *proxy = [YYTextWeakProxy proxyWithTarget:self]; 28 | _timer = [NSTimer timerWithTimeInterval:0.1 target:proxy selector:@selector(tick:) userInfo:nil repeats:YES]; 29 | } 30 | 31 | - (void)tick:(NSTimer *)timer {...} 32 | @end 33 | */ 34 | @interface YYTextWeakProxy : NSProxy 35 | 36 | /** 37 | The proxy target. 38 | */ 39 | @property (nullable, nonatomic, weak, readonly) id target; 40 | 41 | /** 42 | Creates a new weak proxy for target. 43 | 44 | @param target Target object. 45 | 46 | @return A new proxy object. 47 | */ 48 | - (instancetype)initWithTarget:(id)target; 49 | 50 | /** 51 | Creates a new weak proxy for target. 52 | 53 | @param target Target object. 54 | 55 | @return A new proxy object. 56 | */ 57 | + (instancetype)proxyWithTarget:(id)target; 58 | 59 | @end 60 | 61 | NS_ASSUME_NONNULL_END 62 | -------------------------------------------------------------------------------- /ATDemo(OC)/Lib/YYText/Utility/YYTextWeakProxy.m: -------------------------------------------------------------------------------- 1 | // 2 | // YYTextWeakProxy.m 3 | // YYText 4 | // 5 | // Created by ibireme on 14/10/18. 6 | // Copyright (c) 2015 ibireme. 7 | // 8 | // This source code is licensed under the MIT-style license found in the 9 | // LICENSE file in the root directory of this source tree. 10 | // 11 | 12 | #import "YYTextWeakProxy.h" 13 | 14 | 15 | @implementation YYTextWeakProxy 16 | 17 | - (instancetype)initWithTarget:(id)target { 18 | _target = target; 19 | return self; 20 | } 21 | 22 | + (instancetype)proxyWithTarget:(id)target { 23 | return [[YYTextWeakProxy alloc] initWithTarget:target]; 24 | } 25 | 26 | - (id)forwardingTargetForSelector:(SEL)selector { 27 | return _target; 28 | } 29 | 30 | - (void)forwardInvocation:(NSInvocation *)invocation { 31 | void *null = NULL; 32 | [invocation setReturnValue:&null]; 33 | } 34 | 35 | - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector { 36 | return [NSObject instanceMethodSignatureForSelector:@selector(init)]; 37 | } 38 | 39 | - (BOOL)respondsToSelector:(SEL)aSelector { 40 | return [_target respondsToSelector:aSelector]; 41 | } 42 | 43 | - (BOOL)isEqual:(id)object { 44 | return [_target isEqual:object]; 45 | } 46 | 47 | - (NSUInteger)hash { 48 | return [_target hash]; 49 | } 50 | 51 | - (Class)superclass { 52 | return [_target superclass]; 53 | } 54 | 55 | - (Class)class { 56 | return [_target class]; 57 | } 58 | 59 | - (BOOL)isKindOfClass:(Class)aClass { 60 | return [_target isKindOfClass:aClass]; 61 | } 62 | 63 | - (BOOL)isMemberOfClass:(Class)aClass { 64 | return [_target isMemberOfClass:aClass]; 65 | } 66 | 67 | - (BOOL)conformsToProtocol:(Protocol *)aProtocol { 68 | return [_target conformsToProtocol:aProtocol]; 69 | } 70 | 71 | - (BOOL)isProxy { 72 | return YES; 73 | } 74 | 75 | - (NSString *)description { 76 | return [_target description]; 77 | } 78 | 79 | - (NSString *)debugDescription { 80 | return [_target debugDescription]; 81 | } 82 | 83 | @end 84 | -------------------------------------------------------------------------------- /ATDemo(OC)/Lib/YYText/YYText.h: -------------------------------------------------------------------------------- 1 | // 2 | // YYText.h 3 | // YYText 4 | // 5 | // Created by ibireme on 15/2/25. 6 | // Copyright (c) 2015 ibireme. 7 | // 8 | // This source code is licensed under the MIT-style license found in the 9 | // LICENSE file in the root directory of this source tree. 10 | // 11 | 12 | #import 13 | 14 | #if __has_include() 15 | FOUNDATION_EXPORT double YYTextVersionNumber; 16 | FOUNDATION_EXPORT const unsigned char YYTextVersionString[]; 17 | #import 18 | #import 19 | #import 20 | #import 21 | #import 22 | #import 23 | #import 24 | #import 25 | #import 26 | #import 27 | #import 28 | #import 29 | #import 30 | #import 31 | #import 32 | #import 33 | #else 34 | #import "YYLabel.h" 35 | #import "YYTextView.h" 36 | #import "YYTextAttribute.h" 37 | #import "YYTextArchiver.h" 38 | #import "YYTextParser.h" 39 | #import "YYTextRunDelegate.h" 40 | #import "YYTextRubyAnnotation.h" 41 | #import "YYTextLayout.h" 42 | #import "YYTextLine.h" 43 | #import "YYTextInput.h" 44 | #import "YYTextDebugOption.h" 45 | #import "YYTextKeyboardManager.h" 46 | #import "YYTextUtilities.h" 47 | #import "NSAttributedString+YYText.h" 48 | #import "NSParagraphStyle+YYText.h" 49 | #import "UIPasteboard+YYText.h" 50 | #endif 51 | -------------------------------------------------------------------------------- /ATDemo(Swift)/ATDemo(Swift).xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ATDemo(Swift)/ATDemo(Swift).xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ATDemo(Swift)/ATDemo(Swift).xcodeproj/project.xcworkspace/xcuserdata/hn.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkey6468/ATDemo/6cfeb4ff0adf57aec20946fd231db14721fa95dc/ATDemo(Swift)/ATDemo(Swift).xcodeproj/project.xcworkspace/xcuserdata/hn.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /ATDemo(Swift)/ATDemo(Swift).xcodeproj/project.xcworkspace/xcuserdata/xwh.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkey6468/ATDemo/6cfeb4ff0adf57aec20946fd231db14721fa95dc/ATDemo(Swift)/ATDemo(Swift).xcodeproj/project.xcworkspace/xcuserdata/xwh.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /ATDemo(Swift)/ATDemo(Swift).xcodeproj/xcshareddata/xcschemes/ATDemo(Swift).xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /ATDemo(Swift)/ATDemo(Swift).xcodeproj/xcuserdata/xwh.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 21 | 22 | 23 | 25 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /ATDemo(Swift)/ATDemo(Swift).xcodeproj/xcuserdata/xwh.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | ATDemo(Swift).xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | C9A11348264FFC6B00A0F05F 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /ATDemo(Swift)/ATDemo/ATTextView/ATTextViewBinding.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ATTextViewBinding.swift 3 | // ATDemo 4 | // 5 | // Created by XWH on 2021/5/15. 6 | // 7 | 8 | import UIKit 9 | import Foundation 10 | 11 | let ATTextBindingAttributeName = "TextViewBingDingFlagName" 12 | 13 | class ATTextViewBinding: NSObject { 14 | var name: String! = "" 15 | var userId = 0 16 | var range: NSRange! = NSRange(location: 0, length: 0) 17 | 18 | init( 19 | name: String?, 20 | userId: Int 21 | ) { 22 | super.init() 23 | self.name = name 24 | self.userId = userId 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ATDemo(Swift)/ATDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // ATDemo 4 | // 5 | // Created by XWH on 2021/5/15. 6 | // 7 | 8 | import UIKit 9 | 10 | @main 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | 13 | 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | return true 18 | } 19 | 20 | // MARK: UISceneSession Lifecycle 21 | 22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 23 | // Called when a new scene session is being created. 24 | // Use this method to select a configuration to create the new scene with. 25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 26 | } 27 | 28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 29 | // Called when the user discards a scene session. 30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 32 | } 33 | 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /ATDemo(Swift)/ATDemo/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ATDemo(Swift)/ATDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /ATDemo(Swift)/ATDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ATDemo(Swift)/ATDemo/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 | -------------------------------------------------------------------------------- /ATDemo(Swift)/ATDemo/DataModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataModel.swift 3 | // ATDemo(Swift) 4 | // 5 | // Created by XWH on 2021/5/16. 6 | // 7 | 8 | import Foundation 9 | 10 | class DataModel: NSObject { 11 | var text: String! = "" 12 | var userList: Array! = [] 13 | } 14 | -------------------------------------------------------------------------------- /ATDemo(Swift)/ATDemo/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 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | UISceneStoryboardFile 37 | Main 38 | 39 | 40 | 41 | 42 | UIApplicationSupportsIndirectInputEvents 43 | 44 | UILaunchStoryboardName 45 | LaunchScreen 46 | UIMainStoryboardFile 47 | Main 48 | UIRequiredDeviceCapabilities 49 | 50 | armv7 51 | 52 | UISupportedInterfaceOrientations 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationLandscapeLeft 56 | UIInterfaceOrientationLandscapeRight 57 | 58 | UISupportedInterfaceOrientations~ipad 59 | 60 | UIInterfaceOrientationPortrait 61 | UIInterfaceOrientationPortraitUpsideDown 62 | UIInterfaceOrientationLandscapeLeft 63 | UIInterfaceOrientationLandscapeRight 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /ATDemo(Swift)/ATDemo/Lib/Extension_String.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String_Extension.swift 3 | // ATDemo 4 | // 5 | // Created by XWH on 2021/5/16. 6 | // 7 | 8 | import Foundation 9 | 10 | // MARK: - 字符串截取 11 | // https://www.cnblogs.com/qq9070/p/10271009.html 12 | extension String { 13 | /// String使用下标截取字符串 14 | /// string[index] 例如:"abcdefg"[3] // c 15 | subscript (i:Int)->String{ 16 | let startIndex = self.index(self.startIndex, offsetBy: i) 17 | let endIndex = self.index(startIndex, offsetBy: 1) 18 | return String(self[startIndex..) -> String { 23 | get { 24 | let startIndex = self.index(self.startIndex, offsetBy: r.lowerBound) 25 | let endIndex = self.index(self.startIndex, offsetBy: r.upperBound) 26 | return String(self[startIndex.. String { 32 | get { 33 | let startIndex = self.index(self.startIndex, offsetBy: index) 34 | let endIndex = self.index(startIndex, offsetBy: length) 35 | return String(self[startIndex.. String{ 40 | return self[0.. String{ 44 | return self[from.. CGFloat { 62 | 63 | guard self.count > 0 && fixedWidth > 0 else { 64 | return 0 65 | } 66 | 67 | let size: CGSize = CGSize(width: fixedWidth, height: CGFloat(MAXFLOAT)) 68 | let text = self as NSString 69 | let rect = text.boundingRect(with: size, options: .usesLineFragmentOrigin, attributes: attributes, context: nil) 70 | return rect.size.height 71 | } 72 | 73 | /** 74 | Get the height with font. 75 | 76 | - parameter font: The font. 77 | - parameter fixedWidth: The fixed width. 78 | 79 | - returns: The height. 80 | */ 81 | func heightWithFont(font : UIFont = UIFont.systemFont(ofSize: 18), fixedWidth : CGFloat) -> CGFloat { 82 | 83 | guard self.count > 0 && fixedWidth > 0 else { 84 | return 0 85 | } 86 | 87 | let size: CGSize = CGSize(width: fixedWidth, height: CGFloat(MAXFLOAT)) 88 | let text = self as NSString 89 | let rect = text.boundingRect(with: size, options:.usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font : font], context:nil) 90 | 91 | return rect.size.height 92 | } 93 | 94 | /** 95 | Get the width with the string. 96 | 97 | - parameter attributes: The string attributes. 98 | 99 | - returns: The width. 100 | */ 101 | func widthWithStringAttributes(attributes : [NSAttributedString.Key : AnyObject]) -> CGFloat { 102 | 103 | guard self.count > 0 else { 104 | return 0 105 | } 106 | 107 | let size: CGSize = CGSize(width: CGFloat(MAXFLOAT), height: 0) 108 | 109 | let text = self as NSString 110 | let rect = text.boundingRect(with: size, options: .usesLineFragmentOrigin, attributes: attributes, context: nil) 111 | 112 | return rect.size.width 113 | } 114 | 115 | /** 116 | Get the width with the string. 117 | 118 | - parameter font: The font. 119 | 120 | - returns: The string's width. 121 | */ 122 | func widthWithFont(font : UIFont = UIFont.systemFont(ofSize: 18)) -> CGFloat { 123 | 124 | guard self.count > 0 else { 125 | return 0 126 | } 127 | 128 | let size: CGSize = CGSize(width: CGFloat(MAXFLOAT), height: 0) 129 | let text = self as NSString 130 | let rect = text.boundingRect(with: size, options:.usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font : font], context:nil) 131 | 132 | return rect.size.width 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /ATDemo(Swift)/ATDemo/ListViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ListViewController.swift 3 | // ATDemo(Swift) 4 | // 5 | // Created by XWH on 2021/5/16. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | 11 | typealias didSelectedBlock = (_ user: User, _ viewController: UIViewController) -> Void // 声明必包 12 | 13 | class ListViewController: UIViewController { 14 | 15 | var callBack: didSelectedBlock? 16 | 17 | @IBOutlet weak var tableView: UITableView! 18 | private var dataArray: [User] = [] 19 | 20 | override func viewDidLoad() { 21 | self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "取消", style: .plain, target: self, action: #selector(done)) 22 | 23 | tableView.tableFooterView = UIView.init() 24 | self.initData() 25 | } 26 | 27 | @objc func done() { 28 | self.dismiss(animated: true, completion: nil) 29 | } 30 | 31 | func initData() -> Void { 32 | for i in 0..<10 { 33 | let user: User = User(name: "测试_\(i+1)_A", userId: i+1) 34 | dataArray.append(user) 35 | } 36 | } 37 | } 38 | 39 | extension ListViewController: UITableViewDataSource, UITableViewDelegate { 40 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 41 | return dataArray.count 42 | } 43 | 44 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 45 | let cellid = "testCellID" 46 | var cell = tableView.dequeueReusableCell(withIdentifier: cellid) 47 | if cell == nil { 48 | cell = UITableViewCell(style: .subtitle, reuseIdentifier: cellid) 49 | } 50 | 51 | let user: User = dataArray[indexPath.row] 52 | cell?.textLabel?.text = user.name 53 | return cell! 54 | } 55 | 56 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 57 | let user: User = dataArray[indexPath.row] 58 | self.callBack!(user, self) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ATDemo(Swift)/ATDemo/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // ATDemo 4 | // 5 | // Created by XWH on 2021/5/15. 6 | // 7 | 8 | import UIKit 9 | 10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 11 | 12 | var window: UIWindow? 13 | 14 | 15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 16 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 17 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 18 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 19 | guard let _ = (scene as? UIWindowScene) else { return } 20 | } 21 | 22 | func sceneDidDisconnect(_ scene: UIScene) { 23 | // Called as the scene is being released by the system. 24 | // This occurs shortly after the scene enters the background, or when its session is discarded. 25 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 26 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). 27 | } 28 | 29 | func sceneDidBecomeActive(_ scene: UIScene) { 30 | // Called when the scene has moved from an inactive state to an active state. 31 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 32 | } 33 | 34 | func sceneWillResignActive(_ scene: UIScene) { 35 | // Called when the scene will move from an active state to an inactive state. 36 | // This may occur due to temporary interruptions (ex. an incoming phone call). 37 | } 38 | 39 | func sceneWillEnterForeground(_ scene: UIScene) { 40 | // Called as the scene transitions from the background to the foreground. 41 | // Use this method to undo the changes made on entering the background. 42 | } 43 | 44 | func sceneDidEnterBackground(_ scene: UIScene) { 45 | // Called as the scene transitions from the foreground to the background. 46 | // Use this method to save data, release shared resources, and store enough scene-specific state information 47 | // to restore the scene back to its current state. 48 | } 49 | 50 | 51 | } 52 | 53 | -------------------------------------------------------------------------------- /ATDemo(Swift)/ATDemo/SwiftOC-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftOC-Header.h 3 | // ATDemo(Swift) 4 | // 5 | // Created by XWH on 2021/5/16. 6 | // 7 | 8 | #ifndef SwiftOC_Header_h 9 | #define SwiftOC_Header_h 10 | 11 | #import "YYText.h" 12 | 13 | #endif /* SwiftOC_Header_h */ 14 | -------------------------------------------------------------------------------- /ATDemo(Swift)/ATDemo/TableViewSwiftCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableViewSwiftCell.swift 3 | // ATDemo(Swift) 4 | // 5 | // Created by XWH on 2021/5/16. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | 11 | class TableViewSwiftCell: UITableViewCell { 12 | 13 | @IBOutlet weak var yyLabel: YYLabel! 14 | 15 | var _model: DataModel! 16 | public var model: DataModel! { 17 | set { 18 | _model = newValue 19 | 20 | let muAttriSting: NSMutableAttributedString = NSMutableAttributedString(string: newValue.text) 21 | muAttriSting.yy_font = UIFont.systemFont(ofSize: 25) 22 | muAttriSting.yy_color = UIColor.purple 23 | 24 | let arr = model.userList! 25 | for bindingModel in arr { 26 | muAttriSting.yy_setTextHighlight(NSRange(location: bindingModel.range.location, length: bindingModel.range.length), color: .red, backgroundColor: .darkGray, userInfo: ["id": bindingModel.userId]) { containerView, text, range, rect in 27 | 28 | let yyLabel = containerView as! YYLabel 29 | let highlight = yyLabel.value(forKey: "_highlight") as! YYTextHighlight 30 | let dictInfo: NSDictionary? = highlight.userInfo as NSDictionary? 31 | if dictInfo == nil { 32 | return 33 | } 34 | 35 | let toUserId = dictInfo?["id"] as! Int 36 | print("点击了: \(toUserId)"); 37 | 38 | } longPressAction: { _,_,_,_ in } 39 | 40 | } 41 | yyLabel.attributedText = muAttriSting 42 | 43 | } get { 44 | return _model 45 | } 46 | } 47 | 48 | override func awakeFromNib() { 49 | super.awakeFromNib() 50 | 51 | yyLabel.numberOfLines = 0 52 | self.setupGesture() 53 | } 54 | 55 | func setupGesture() { 56 | let contentTap = UITapGestureRecognizer(target: self, action: #selector(onTapReply(_:))) 57 | contentTap.delegate = self 58 | yyLabel.addGestureRecognizer(contentTap) 59 | } 60 | 61 | public class func rowHeightWithModel(model: DataModel) -> CGFloat { 62 | let height = (model.text.heightWithFont(font: UIFont.systemFont(ofSize: 25), fixedWidth: UIScreen.main.bounds.size.width)) as CGFloat 63 | return height+5 64 | } 65 | 66 | @objc func onTapReply(_ sender: UITapGestureRecognizer) { 67 | print("点击了cell22") 68 | } 69 | 70 | override func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { 71 | if ((touch.view?.isKind(of: YYLabel.self)) != nil) { 72 | let label = touch.view as! YYLabel 73 | var highlightRange: NSRange = NSRange(location: 0, length: 0) 74 | let highlight: YYTextHighlight? 75 | = label._getHighlight(at: touch.location(in: label), range: &highlightRange) 76 | if highlight != nil { 77 | return false 78 | } 79 | return true 80 | } 81 | return true 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /ATDemo(Swift)/ATDemo/TableViewSwiftCell.xib: -------------------------------------------------------------------------------- 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 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /ATDemo(Swift)/ATDemo/User.swift: -------------------------------------------------------------------------------- 1 | // 2 | // User.swift 3 | // ATDemo(Swift) 4 | // 5 | // Created by XWH on 2021/5/16. 6 | // 7 | 8 | import Foundation 9 | 10 | class User: NSObject { 11 | var userId = 0 // 用户ID 12 | var name: String! = "" 13 | var range: NSRange! = NSRange(location: 0, length: 0) 14 | 15 | init( 16 | name: String = "", 17 | userId: Int 18 | ) { 19 | super.init() 20 | self.name = name 21 | self.userId = userId 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ATDemo(Swift)/ATDemo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // ATDemo 4 | // 5 | // Created by XWH on 2021/5/15. 6 | // 7 | 8 | import UIKit 9 | 10 | private let k_defaultColor = UIColor.blue // 默认特殊文本高亮颜色 11 | private let k_defaultFont = UIFont.systemFont(ofSize: 15) // 默认字体大小 12 | 13 | class ViewController: UIViewController { 14 | 15 | @IBOutlet weak var textView: ATTextView! 16 | @IBOutlet weak var tableView: UITableView! 17 | 18 | @IBOutlet weak var textViewConstraintH: NSLayoutConstraint! 19 | @IBOutlet weak var bottomViewConstraintB: NSLayoutConstraint! 20 | 21 | private var bNeedShowKeyboard: Bool = false 22 | private var dataArray: [DataModel] = [] 23 | 24 | override func viewWillAppear(_ animated: Bool) { 25 | super.viewWillAppear(animated) 26 | self.addNotify() 27 | if self.bNeedShowKeyboard { 28 | self.bNeedShowKeyboard = false 29 | textView.becomeFirstResponder() 30 | } 31 | } 32 | 33 | override func viewWillDisappear(_ animated: Bool) { 34 | super.viewWillDisappear(animated) 35 | self.removeNotify() 36 | } 37 | 38 | override func viewDidLoad() { 39 | super.viewDidLoad() 40 | 41 | navigationItem.title = "ATTextView_Swift"; 42 | 43 | textView.atDelegate = self; 44 | textView.placeholder = "我是测试placeholder" 45 | textView.font = k_defaultFont 46 | textView.attributedTextColor = k_defaultColor 47 | 48 | // textView.placeholderTextColor = UIColor(red: 0.9, green: 0.9, blue: 0.9, alpha: 0.75) 49 | // textView.bSupport = false 50 | // textView.hightTextColor = UIColor.yellow 51 | textView.becomeFirstResponder() 52 | 53 | self.initTableView() 54 | } 55 | 56 | //MARK: UI 57 | func initTableView() -> Void { 58 | tableView.tableFooterView = UIView.init() 59 | // 60 | tableView.register(UINib(nibName: String(describing: TableViewSwiftCell.self) 61 | , bundle: nil), 62 | forCellReuseIdentifier: String(describing: TableViewSwiftCell.self)) 63 | } 64 | 65 | public func updateUI() -> Void { 66 | let frame = textView.attributedText.boundingRect(with: CGSize(width: UIScreen.main.bounds.size.width-85, height: 0), options: .usesLineFragmentOrigin, context: nil) as CGRect 67 | var h = frame.size.height 68 | if h <= 80 { 69 | h = 80 70 | } 71 | textViewConstraintH.constant = h 72 | } 73 | 74 | //MARK: Notify 75 | func addNotify() -> Void { 76 | NotificationCenter.default.addObserver(self, selector: #selector(keyBoardWillShow(_ :)), 77 | name: UIResponder.keyboardWillShowNotification, object: nil) 78 | 79 | NotificationCenter.default.addObserver(self, selector: #selector(keyBoardWillHide(_ :)), 80 | name: UIResponder.keyboardWillHideNotification, object: nil) 81 | } 82 | 83 | func removeNotify(){ 84 | NotificationCenter.default.removeObserver(self) 85 | } 86 | 87 | @objc func keyBoardWillShow(_ notification:Notification) { 88 | let user_info = notification.userInfo 89 | let keyboardRect = (user_info?[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue 90 | if #available(iOS 11.0, *) { 91 | bottomViewConstraintB.constant = keyboardRect.size.height-self.view.safeAreaInsets.bottom 92 | } else { 93 | bottomViewConstraintB.constant = keyboardRect.size.height 94 | } 95 | 96 | let duration = user_info![UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber 97 | UIView.animate(withDuration: TimeInterval(truncating: duration)) { 98 | self.view.layoutIfNeeded() 99 | } 100 | } 101 | 102 | @objc func keyBoardWillHide(_ notification:Notification){ 103 | bottomViewConstraintB.constant = 0; 104 | let user_info = notification.userInfo 105 | let duration = user_info![UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber 106 | UIView.animate(withDuration: TimeInterval(truncating: duration)) { 107 | self.view.layoutIfNeeded() 108 | } 109 | } 110 | 111 | @IBAction func pushListVC(_ sender: UIButton) { 112 | self.textView.bAtChart = false 113 | self.pushAtVc() 114 | } 115 | 116 | func pushAtVc() -> Void { 117 | self.bNeedShowKeyboard = true; 118 | 119 | let storyboard: UIStoryboard! = UIStoryboard(name: "Main", bundle: nil) 120 | let vc: ListViewController! = storyboard?.instantiateViewController(withIdentifier: "ListViewController") as? ListViewController 121 | let nav : UINavigationController = UINavigationController(rootViewController: vc) as UINavigationController 122 | nav.modalPresentationStyle = .fullScreen 123 | self.present(nav, animated: true, completion: nil) 124 | 125 | vc.callBack = { (user: User, viewController: UIViewController) in 126 | viewController.dismiss(animated: true, completion: nil) 127 | let bindingModel = ATTextViewBinding(name: user.name, userId: user.userId) 128 | self.textView.insertModel(withBindingModel: bindingModel) 129 | } 130 | } 131 | 132 | @IBAction func onActionDone(_ sender: UIBarButtonItem) { 133 | self.view.endEditing(true) 134 | 135 | let results = textView.atUserList; 136 | 137 | print("输出打印:"); 138 | for i in 0.. Int { 172 | return dataArray.count 173 | } 174 | 175 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 176 | let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TableViewSwiftCell.self), 177 | for: indexPath) as! TableViewSwiftCell 178 | 179 | let user: DataModel = dataArray[indexPath.row] 180 | cell.model = user 181 | return cell 182 | } 183 | 184 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 185 | let user: DataModel = dataArray[indexPath.row] 186 | return TableViewSwiftCell.rowHeightWithModel(model: user) 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /ATDemo.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 9 | 10 | 11 | 14 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /ATDemo.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ATDemo.xcworkspace/xcuserdata/hn.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkey6468/ATDemo/6cfeb4ff0adf57aec20946fd231db14721fa95dc/ATDemo.xcworkspace/xcuserdata/hn.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /ATDemo.xcworkspace/xcuserdata/hn.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 21 | 22 | 23 | 25 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /ATDemo.xcworkspace/xcuserdata/xwh.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkey6468/ATDemo/6cfeb4ff0adf57aec20946fd231db14721fa95dc/ATDemo.xcworkspace/xcuserdata/xwh.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ATDemo 2 | 用于仿微博、微信、钉钉的艾特(@)功能【话题功能类型处理】【swift 已经支持】 3 | 4 | * 艾特所在的`特殊文本`不支持编辑,为一个整体存在 5 | 6 | ## 一、实现功能 7 | - 支持OC和Swift 8 | - 以UITextView为基础实现,可以输入时支持`特殊文本`变色 9 | - 支持 `特殊文本`列表输出(包含在文本中的定位信息、可以自定义其它内容),减少服务器的交互 10 | - 富文本用 `YYLabel`显示,支持可点击 11 | - 输入时,支持监测艾特会自动唤起其它界面 12 | - 最大字数限制控制功能,暂不支持 13 | 14 | ## 二、效果图 15 | ![](screenshots/2021-04-30.png) 16 | 17 | ## 三、如何使用 18 | 19 | 与UITextView使用一样,只是方法的拓展【Swift 同理调用】 20 | 21 | 1、导入头文件 22 | ``` 23 | #import "ATTextView.h" 24 | ``` 25 | 26 | 2、使用时,集成ATTextView即可 27 | 28 | 3、插入话题或人名等特殊文本时,需要更新光标位置 29 | ``` 30 | NSArray *results = self.textView.atUserList; 31 | 32 | NSLog(@"输出打印:"); 33 | for (TextViewBinding *model in results) { 34 | NSLog(@"user info - name:%@ - location:%ld",model.name, model.range.location); 35 | } 36 | ``` 37 | 38 | 4、获取已经拆入的话题或人名等特殊文本列表 39 | ``` 40 | [weakSelf.textView insertWithBindingModel:bindingModel]; 41 | 42 | ``` 43 | 44 | ## 四、说重点!!! 45 | #### 1、通过实现UITextViewDelegate中的三个方法完成主要的核心操作 46 | 47 | 用于处理光标移动的逻辑 48 | ``` 49 | - (void)textViewDidChangeSelection:(UITextView *)textView 50 | ``` 51 | 52 | 文本有改变时,重置attributedText属性 53 | ``` 54 | - (void)textViewDidChange:(UITextView *)textView 55 | ``` 56 | 57 | 文本进行增、删、改时的处理逻辑 58 | ``` 59 | - (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text 60 | ``` 61 | #### 2、通过正则表达时,查找定位@符号列表数据 62 | 63 | ``` 64 | #define kATRegular @"@[\\u4e00-\\u9fa5\\w\\-\\_]+ " 65 | ``` 66 | ``` 67 | - (NSArray *)getResultsListArrayWithTextView:(NSAttributedString *)attributedString { 68 | __block NSMutableArray *resultArray = [NSMutableArray array]; 69 | NSRegularExpression *iExpression = [NSRegularExpression regularExpressionWithPattern:kATRegular options:0 error:NULL]; 70 | [iExpression enumerateMatchesInString:attributedString.string 71 | options:0 72 | range:NSMakeRange(0, attributedString.string.length) 73 | usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { 74 | NSRange resultRange = result.range; 75 | NSString *atString = [attributedString.string substringWithRange:result.range]; 76 | ATTextViewBinding *bindingModel = [attributedString attribute:ATTextBindingAttributeName atIndex:resultRange.location longestEffectiveRange:&resultRange inRange:NSMakeRange(0, atString.length)]; 77 | if (bindingModel) { 78 | bindingModel.range = result.range; 79 | [resultArray addObject:bindingModel]; 80 | } 81 | }]; 82 | return resultArray; 83 | } 84 | ``` 85 | 86 | ## 五、文档参考 87 | 为了研究这个 `艾特` 功能花费了大量的时间和精力,也参考了网上许多的案例实现。 88 | 89 | 以下为主要参考文档链接,需要请查看: 90 | 91 | 1、[iOS中@功能的完整实现](https://blog.csdn.net/olsQ93038o99S/article/details/80730096) 92 | 93 | 2、[UITextView中,如何对特殊文本进行整体绑定](https://www.jianshu.com/p/891275b93d29) 94 | 95 | ### 更多问题请issue me!!! 96 | -------------------------------------------------------------------------------- /screenshots/2021-04-30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkey6468/ATDemo/6cfeb4ff0adf57aec20946fd231db14721fa95dc/screenshots/2021-04-30.png --------------------------------------------------------------------------------