YYTextView.m 151 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792
  1. //
  2. // YYTextView.m
  3. // YYKit <https://github.com/ibireme/YYKit>
  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. #import "YYTextView.h"
  12. #import "YYKitMacro.h"
  13. #import "YYTextInput.h"
  14. #import "YYTextContainerView.h"
  15. #import "YYTextSelectionView.h"
  16. #import "YYTextMagnifier.h"
  17. #import "YYTextEffectWindow.h"
  18. #import "YYTextKeyboardManager.h"
  19. #import "YYTextUtilities.h"
  20. #import "YYCGUtilities.h"
  21. #import "YYTransaction.h"
  22. #import "YYWeakProxy.h"
  23. #import "UIView+YYAdd.h"
  24. #import "NSAttributedString+YYText.h"
  25. #import "UIPasteboard+YYText.h"
  26. #import "UIDevice+YYAdd.h"
  27. #import "UIApplication+YYAdd.h"
  28. #import "YYImage.h"
  29. #define kDefaultUndoLevelMax 20 // Default maximum undo level
  30. #define kAutoScrollMinimumDuration 0.1 // Time in seconds to tick auto-scroll.
  31. #define kLongPressMinimumDuration 0.5 // Time in seconds the fingers must be held down for long press gesture.
  32. #define kLongPressAllowableMovement 10.0 // Maximum movement in points allowed before the long press fails.
  33. #define kMagnifierRangedTrackFix -6.0 // Magnifier ranged offset fix.
  34. #define kMagnifierRangedPopoverOffset 4.0 // Magnifier ranged popover offset.
  35. #define kMagnifierRangedCaptureOffset -6.0 // Magnifier ranged capture center offset.
  36. #define kHighlightFadeDuration 0.15 // Time in seconds for highlight fadeout animation.
  37. #define kDefaultInset UIEdgeInsetsMake(6, 4, 6, 4)
  38. #define kDefaultVerticalInset UIEdgeInsetsMake(4, 6, 4, 6)
  39. NSString *const YYTextViewTextDidBeginEditingNotification = @"YYTextViewTextDidBeginEditing";
  40. NSString *const YYTextViewTextDidChangeNotification = @"YYTextViewTextDidChange";
  41. NSString *const YYTextViewTextDidEndEditingNotification = @"YYTextViewTextDidEndEditing";
  42. typedef NS_ENUM (NSUInteger, YYTextGrabberDirection) {
  43. kStart = 1,
  44. kEnd = 2,
  45. };
  46. typedef NS_ENUM(NSUInteger, YYTextMoveDirection) {
  47. kLeft = 1,
  48. kTop = 2,
  49. kRight = 3,
  50. kBottom = 4,
  51. };
  52. /// An object that captures the state of the text view. Used for undo and redo.
  53. @interface _YYTextViewUndoObject : NSObject
  54. @property (nonatomic, strong) NSAttributedString *text;
  55. @property (nonatomic, assign) NSRange selectedRange;
  56. @end
  57. @implementation _YYTextViewUndoObject
  58. + (instancetype)objectWithText:(NSAttributedString *)text range:(NSRange)range {
  59. _YYTextViewUndoObject *obj = [self new];
  60. obj.text = text ? text : [NSAttributedString new];
  61. obj.selectedRange = range;
  62. return obj;
  63. }
  64. @end
  65. @interface YYTextView () <UIScrollViewDelegate, UIAlertViewDelegate, YYTextDebugTarget, YYTextKeyboardObserver> {
  66. YYTextRange *_selectedTextRange; /// nonnull
  67. YYTextRange *_markedTextRange;
  68. __weak id<YYTextViewDelegate> _outerDelegate;
  69. UIImageView *_placeHolderView;
  70. NSMutableAttributedString *_innerText; ///< nonnull, inner attributed text
  71. NSMutableAttributedString *_delectedText; ///< detected text for display
  72. YYTextContainer *_innerContainer; ///< nonnull, inner text container
  73. YYTextLayout *_innerLayout; ///< inner text layout, the text in this layout is longer than `_innerText` by appending '\n'
  74. YYTextContainerView *_containerView; ///< nonnull
  75. YYTextSelectionView *_selectionView; ///< nonnull
  76. YYTextMagnifier *_magnifierCaret; ///< nonnull
  77. YYTextMagnifier *_magnifierRanged; ///< nonnull
  78. NSMutableAttributedString *_typingAttributesHolder; ///< nonnull, typing attributes
  79. NSDataDetector *_dataDetector;
  80. CGFloat _magnifierRangedOffset;
  81. NSRange _highlightRange; ///< current highlight range
  82. YYTextHighlight *_highlight; ///< highlight attribute in `_highlightRange`
  83. YYTextLayout *_highlightLayout; ///< when _state.showingHighlight=YES, this layout should be displayed
  84. YYTextRange *_trackingRange; ///< the range in _innerLayout, may out of _innerText.
  85. BOOL _insetModifiedByKeyboard; ///< text is covered by keyboard, and the contentInset is modified
  86. UIEdgeInsets _originalContentInset; ///< the original contentInset before modified
  87. UIEdgeInsets _originalScrollIndicatorInsets; ///< the original scrollIndicatorInsets before modified
  88. NSTimer *_longPressTimer;
  89. NSTimer *_autoScrollTimer;
  90. CGFloat _autoScrollOffset; ///< current auto scroll offset which shoud add to scroll view
  91. NSInteger _autoScrollAcceleration; ///< an acceleration coefficient for auto scroll
  92. NSTimer *_selectionDotFixTimer; ///< fix the selection dot in window if the view is moved by parents
  93. CGPoint _previousOriginInWindow;
  94. CGPoint _touchBeganPoint;
  95. CGPoint _trackingPoint;
  96. NSTimeInterval _touchBeganTime;
  97. NSTimeInterval _trackingTime;
  98. NSMutableArray *_undoStack;
  99. NSMutableArray *_redoStack;
  100. NSRange _lastTypeRange;
  101. struct {
  102. unsigned int trackingGrabber : 2; ///< YYTextGrabberDirection, current tracking grabber
  103. unsigned int trackingCaret : 1; ///< track the caret
  104. unsigned int trackingPreSelect : 1; ///< track pre-select
  105. unsigned int trackingTouch : 1; ///< is in touch phase
  106. unsigned int swallowTouch : 1; ///< don't forward event to next responder
  107. unsigned int touchMoved : 3; ///< YYTextMoveDirection, move direction after touch began
  108. unsigned int selectedWithoutEdit : 1; ///< show selected range but not first responder
  109. unsigned int deleteConfirm : 1; ///< delete a binding text range
  110. unsigned int ignoreFirstResponder : 1; ///< ignore become first responder temporary
  111. unsigned int ignoreTouchBegan : 1; ///< ignore begin tracking touch temporary
  112. unsigned int showingMagnifierCaret : 1;
  113. unsigned int showingMagnifierRanged : 1;
  114. unsigned int showingMenu : 1;
  115. unsigned int showingHighlight : 1;
  116. unsigned int typingAttributesOnce : 1; ///< apply the typing attributes once
  117. unsigned int clearsOnInsertionOnce : 1; ///< select all once when become first responder
  118. unsigned int autoScrollTicked : 1; ///< auto scroll did tick scroll at this timer period
  119. unsigned int firstShowDot : 1; ///< the selection grabber dot has displayed at least once
  120. unsigned int needUpdate : 1; ///< the layout or selection view is 'dirty' and need update
  121. unsigned int placeholderNeedUpdate : 1; ///< the placeholder need update it's contents
  122. unsigned int insideUndoBlock : 1;
  123. unsigned int firstResponderBeforeUndoAlert : 1;
  124. } _state;
  125. }
  126. @end
  127. @implementation YYTextView
  128. #pragma mark - @protocol UITextInputTraits
  129. @synthesize autocapitalizationType = _autocapitalizationType;
  130. @synthesize autocorrectionType = _autocorrectionType;
  131. @synthesize spellCheckingType = _spellCheckingType;
  132. @synthesize keyboardType = _keyboardType;
  133. @synthesize keyboardAppearance = _keyboardAppearance;
  134. @synthesize returnKeyType = _returnKeyType;
  135. @synthesize enablesReturnKeyAutomatically = _enablesReturnKeyAutomatically;
  136. @synthesize secureTextEntry = _secureTextEntry;
  137. #pragma mark - @protocol UITextInput
  138. @synthesize selectedTextRange = _selectedTextRange; //copy nonnull (YYTextRange*)
  139. @synthesize markedTextRange = _markedTextRange; //readonly (YYTextRange*)
  140. @synthesize markedTextStyle = _markedTextStyle; //copy
  141. @synthesize inputDelegate = _inputDelegate; //assign
  142. @synthesize tokenizer = _tokenizer; //readonly
  143. #pragma mark - @protocol UITextInput optional
  144. @synthesize selectionAffinity = _selectionAffinity;
  145. #pragma mark - Private
  146. /// Update layout and selection before runloop sleep/end.
  147. - (void)_commitUpdate {
  148. #if !TARGET_INTERFACE_BUILDER
  149. _state.needUpdate = YES;
  150. [[YYTransaction transactionWithTarget:self selector:@selector(_updateIfNeeded)] commit];
  151. #else
  152. [self _update];
  153. #endif
  154. }
  155. /// Update layout and selection view if needed.
  156. - (void)_updateIfNeeded {
  157. if (_state.needUpdate) {
  158. [self _update];
  159. }
  160. }
  161. /// Update layout and selection view immediately.
  162. - (void)_update {
  163. _state.needUpdate = NO;
  164. [self _updateLayout];
  165. [self _updateSelectionView];
  166. }
  167. /// Update layout immediately.
  168. - (void)_updateLayout {
  169. NSMutableAttributedString *text = _innerText.mutableCopy;
  170. _placeHolderView.hidden = text.length > 0;
  171. if ([self _detectText:text]) {
  172. _delectedText = text;
  173. } else {
  174. _delectedText = nil;
  175. }
  176. [text replaceCharactersInRange:NSMakeRange(text.length, 0) withString:@"\r"]; // add for nextline caret
  177. [text removeDiscontinuousAttributesInRange:NSMakeRange(_innerText.length, 1)];
  178. [text removeAttribute:YYTextBorderAttributeName range:NSMakeRange(_innerText.length, 1)];
  179. [text removeAttribute:YYTextBackgroundBorderAttributeName range:NSMakeRange(_innerText.length, 1)];
  180. if (_innerText.length == 0) {
  181. [text setAttributes:_typingAttributesHolder.attributes]; // add for empty text caret
  182. }
  183. if (_selectedTextRange.end.offset == _innerText.length) {
  184. [_typingAttributesHolder.attributes enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *stop) {
  185. [text setAttribute:key value:value range:NSMakeRange(_innerText.length, 1)];
  186. }];
  187. }
  188. [self willChangeValueForKey:@"textLayout"];
  189. _innerLayout = [YYTextLayout layoutWithContainer:_innerContainer text:text];
  190. [self didChangeValueForKey:@"textLayout"];
  191. CGSize size = [_innerLayout textBoundingSize];
  192. CGSize visibleSize = [self _getVisibleSize];
  193. if (_innerContainer.isVerticalForm) {
  194. size.height = visibleSize.height;
  195. if (size.width < visibleSize.width) size.width = visibleSize.width;
  196. } else {
  197. size.width = visibleSize.width;
  198. }
  199. [_containerView setLayout:_innerLayout withFadeDuration:0];
  200. _containerView.frame = (CGRect){.size = size};
  201. _state.showingHighlight = NO;
  202. self.contentSize = size;
  203. }
  204. /// Update selection view immediately.
  205. /// This method should be called after "layout update" finished.
  206. - (void)_updateSelectionView {
  207. _selectionView.frame = _containerView.frame;
  208. _selectionView.caretBlinks = NO;
  209. _selectionView.caretVisible = NO;
  210. _selectionView.selectionRects = nil;
  211. [[YYTextEffectWindow sharedWindow] hideSelectionDot:_selectionView];
  212. if (!_innerLayout) return;
  213. NSMutableArray *allRects = [NSMutableArray new];
  214. BOOL containsDot = NO;
  215. YYTextRange *selectedRange = _selectedTextRange;
  216. if (_state.trackingTouch && _trackingRange) {
  217. selectedRange = _trackingRange;
  218. }
  219. if (_markedTextRange) {
  220. NSArray *rects = [_innerLayout selectionRectsWithoutStartAndEndForRange:_markedTextRange];
  221. if (rects) [allRects addObjectsFromArray:rects];
  222. if (selectedRange.asRange.length > 0) {
  223. rects = [_innerLayout selectionRectsWithOnlyStartAndEndForRange:selectedRange];
  224. if (rects) [allRects addObjectsFromArray:rects];
  225. containsDot = rects.count > 0;
  226. } else {
  227. CGRect rect = [_innerLayout caretRectForPosition:selectedRange.end];
  228. _selectionView.caretRect = [self _convertRectFromLayout:rect];
  229. _selectionView.caretVisible = YES;
  230. _selectionView.caretBlinks = YES;
  231. }
  232. } else {
  233. if (selectedRange.asRange.length == 0) { // only caret
  234. if (self.isFirstResponder || _state.trackingPreSelect) {
  235. CGRect rect = [_innerLayout caretRectForPosition:selectedRange.end];
  236. _selectionView.caretRect = [self _convertRectFromLayout:rect];
  237. _selectionView.caretVisible = YES;
  238. if (!_state.trackingCaret && !_state.trackingPreSelect) {
  239. _selectionView.caretBlinks = YES;
  240. }
  241. }
  242. } else { // range selected
  243. if ((self.isFirstResponder && !_state.deleteConfirm) ||
  244. (!self.isFirstResponder && _state.selectedWithoutEdit)) {
  245. NSArray *rects = [_innerLayout selectionRectsForRange:selectedRange];
  246. if (rects) [allRects addObjectsFromArray:rects];
  247. containsDot = rects.count > 0;
  248. } else if ((!self.isFirstResponder && _state.trackingPreSelect) ||
  249. (self.isFirstResponder && _state.deleteConfirm)){
  250. NSArray *rects = [_innerLayout selectionRectsWithoutStartAndEndForRange:selectedRange];
  251. if (rects) [allRects addObjectsFromArray:rects];
  252. }
  253. }
  254. }
  255. [allRects enumerateObjectsUsingBlock:^(YYTextSelectionRect *rect, NSUInteger idx, BOOL *stop) {
  256. rect.rect = [self _convertRectFromLayout:rect.rect];
  257. }];
  258. _selectionView.selectionRects = allRects;
  259. if (!_state.firstShowDot && containsDot) {
  260. _state.firstShowDot = YES;
  261. /*
  262. The dot position may be wrong at the first time displayed.
  263. I can't find the reason. Here's a workaround.
  264. */
  265. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.02 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  266. [[YYTextEffectWindow sharedWindow] showSelectionDot:_selectionView];
  267. });
  268. }
  269. [[YYTextEffectWindow sharedWindow] showSelectionDot:_selectionView];
  270. if (containsDot) {
  271. [self _startSelectionDotFixTimer];
  272. } else {
  273. [self _endSelectionDotFixTimer];
  274. }
  275. }
  276. /// Update inner contains's size.
  277. - (void)_updateInnerContainerSize {
  278. CGSize size = [self _getVisibleSize];
  279. if (_innerContainer.isVerticalForm) size.width = CGFLOAT_MAX;
  280. else size.height = CGFLOAT_MAX;
  281. _innerContainer.size = size;
  282. }
  283. /// Update placeholder before runloop sleep/end.
  284. - (void)_commitPlaceholderUpdate {
  285. #if !TARGET_INTERFACE_BUILDER
  286. _state.placeholderNeedUpdate = YES;
  287. [[YYTransaction transactionWithTarget:self selector:@selector(_updatePlaceholderIfNeeded)] commit];
  288. #else
  289. [self _updatePlaceholder];
  290. #endif
  291. }
  292. /// Update placeholder if needed.
  293. - (void)_updatePlaceholderIfNeeded {
  294. if (_state.placeholderNeedUpdate) {
  295. _state.placeholderNeedUpdate = NO;
  296. [self _updatePlaceholder];
  297. }
  298. }
  299. /// Update placeholder immediately.
  300. - (void)_updatePlaceholder {
  301. CGRect frame = CGRectZero;
  302. _placeHolderView.image = nil;
  303. _placeHolderView.frame = frame;
  304. if (_placeholderAttributedText.length > 0) {
  305. YYTextContainer *container = _innerContainer.copy;
  306. container.size = self.bounds.size;
  307. container.truncationType = YYTextTruncationTypeEnd;
  308. container.truncationToken = nil;
  309. YYTextLayout *layout = [YYTextLayout layoutWithContainer:container text:_placeholderAttributedText];
  310. CGSize size = [layout textBoundingSize];
  311. BOOL needDraw = size.width > 1 && size.height > 1;
  312. if (needDraw) {
  313. UIGraphicsBeginImageContextWithOptions(size, NO, 0);
  314. CGContextRef context = UIGraphicsGetCurrentContext();
  315. [layout drawInContext:context size:size debug:self.debugOption];
  316. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  317. UIGraphicsEndImageContext();
  318. _placeHolderView.image = image;
  319. frame.size = image.size;
  320. if (container.isVerticalForm) {
  321. frame.origin.x = self.bounds.size.width - image.size.width;
  322. } else {
  323. frame.origin = CGPointZero;
  324. }
  325. _placeHolderView.frame = frame;
  326. }
  327. }
  328. }
  329. /// Update the `_selectedTextRange` to a single position by `_trackingPoint`.
  330. - (void)_updateTextRangeByTrackingCaret {
  331. if (!_state.trackingTouch) return;
  332. CGPoint trackingPoint = [self _convertPointToLayout:_trackingPoint];
  333. YYTextPosition *newPos = [_innerLayout closestPositionToPoint:trackingPoint];
  334. if (newPos) {
  335. newPos = [self _correctedTextPosition:newPos];
  336. if (_markedTextRange) {
  337. if ([newPos compare:_markedTextRange.start] == NSOrderedAscending) {
  338. newPos = _markedTextRange.start;
  339. } else if ([newPos compare:_markedTextRange.end] == NSOrderedDescending) {
  340. newPos = _markedTextRange.end;
  341. }
  342. }
  343. YYTextRange *newRange = [YYTextRange rangeWithRange:NSMakeRange(newPos.offset, 0) affinity:newPos.affinity];
  344. _trackingRange = newRange;
  345. }
  346. }
  347. /// Update the `_selectedTextRange` to a new range by `_trackingPoint` and `_state.trackingGrabber`.
  348. - (void)_updateTextRangeByTrackingGrabber {
  349. if (!_state.trackingTouch || !_state.trackingGrabber) return;
  350. BOOL isStart = _state.trackingGrabber == kStart;
  351. CGPoint magPoint = _trackingPoint;
  352. magPoint.y += kMagnifierRangedTrackFix;
  353. magPoint = [self _convertPointToLayout:magPoint];
  354. YYTextPosition *position = [_innerLayout positionForPoint:magPoint
  355. oldPosition:(isStart ? _selectedTextRange.start : _selectedTextRange.end)
  356. otherPosition:(isStart ? _selectedTextRange.end : _selectedTextRange.start)];
  357. if (position) {
  358. position = [self _correctedTextPosition:position];
  359. if ((NSUInteger)position.offset > _innerText.length) {
  360. position = [YYTextPosition positionWithOffset:_innerText.length];
  361. }
  362. YYTextRange *newRange = [YYTextRange rangeWithStart:(isStart ? position : _selectedTextRange.start)
  363. end:(isStart ? _selectedTextRange.end : position)];
  364. _trackingRange = newRange;
  365. }
  366. }
  367. /// Update the `_selectedTextRange` to a new range/position by `_trackingPoint`.
  368. - (void)_updateTextRangeByTrackingPreSelect {
  369. if (!_state.trackingTouch) return;
  370. YYTextRange *newRange = [self _getClosestTokenRangeAtPoint:_trackingPoint];
  371. _trackingRange = newRange;
  372. }
  373. /// Show or update `_magnifierCaret` based on `_trackingPoint`, and hide `_magnifierRange`.
  374. - (void)_showMagnifierCaret {
  375. if ([UIApplication isAppExtension]) return;
  376. if (_state.showingMagnifierRanged) {
  377. _state.showingMagnifierRanged = NO;
  378. [[YYTextEffectWindow sharedWindow] hideMagnifier:_magnifierRanged];
  379. }
  380. _magnifierCaret.hostPopoverCenter = _trackingPoint;
  381. _magnifierCaret.hostCaptureCenter = _trackingPoint;
  382. if (!_state.showingMagnifierCaret) {
  383. _state.showingMagnifierCaret = YES;
  384. [[YYTextEffectWindow sharedWindow] showMagnifier:_magnifierCaret];
  385. } else {
  386. [[YYTextEffectWindow sharedWindow] moveMagnifier:_magnifierCaret];
  387. }
  388. }
  389. /// Show or update `_magnifierRanged` based on `_trackingPoint`, and hide `_magnifierCaret`.
  390. - (void)_showMagnifierRanged {
  391. if ([UIApplication isAppExtension]) return;
  392. if (_verticalForm) { // hack for vertical form...
  393. [self _showMagnifierCaret];
  394. return;
  395. }
  396. if (_state.showingMagnifierCaret) {
  397. _state.showingMagnifierCaret = NO;
  398. [[YYTextEffectWindow sharedWindow] hideMagnifier:_magnifierCaret];
  399. }
  400. CGPoint magPoint = _trackingPoint;
  401. if (_verticalForm) {
  402. magPoint.x += kMagnifierRangedTrackFix;
  403. } else {
  404. magPoint.y += kMagnifierRangedTrackFix;
  405. }
  406. YYTextRange *selectedRange = _selectedTextRange;
  407. if (_state.trackingTouch && _trackingRange) {
  408. selectedRange = _trackingRange;
  409. }
  410. YYTextPosition *position;
  411. if (_markedTextRange) {
  412. position = selectedRange.end;
  413. } else {
  414. position = [_innerLayout positionForPoint:[self _convertPointToLayout:magPoint]
  415. oldPosition:(_state.trackingGrabber == kStart ? selectedRange.start : selectedRange.end)
  416. otherPosition:(_state.trackingGrabber == kStart ? selectedRange.end : selectedRange.start)];
  417. }
  418. NSUInteger lineIndex = [_innerLayout lineIndexForPosition:position];
  419. if (lineIndex < _innerLayout.lines.count) {
  420. YYTextLine *line = _innerLayout.lines[lineIndex];
  421. CGRect lineRect = [self _convertRectFromLayout:line.bounds];
  422. if (_verticalForm) {
  423. magPoint.x = YY_CLAMP(magPoint.x, CGRectGetMinX(lineRect), CGRectGetMaxX(lineRect));
  424. } else {
  425. magPoint.y = YY_CLAMP(magPoint.y, CGRectGetMinY(lineRect), CGRectGetMaxY(lineRect));
  426. }
  427. CGPoint linePoint = [_innerLayout linePositionForPosition:position];
  428. linePoint = [self _convertPointFromLayout:linePoint];
  429. CGPoint popoverPoint = linePoint;
  430. if (_verticalForm) {
  431. popoverPoint.x = linePoint.x + _magnifierRangedOffset;
  432. } else {
  433. popoverPoint.y = linePoint.y + _magnifierRangedOffset;
  434. }
  435. CGPoint capturePoint;
  436. if (_verticalForm) {
  437. capturePoint.x = linePoint.x + kMagnifierRangedCaptureOffset;
  438. capturePoint.y = linePoint.y;
  439. } else {
  440. capturePoint.x = linePoint.x;
  441. capturePoint.y = linePoint.y + kMagnifierRangedCaptureOffset;
  442. }
  443. _magnifierRanged.hostPopoverCenter = popoverPoint;
  444. _magnifierRanged.hostCaptureCenter = capturePoint;
  445. if (!_state.showingMagnifierRanged) {
  446. _state.showingMagnifierRanged = YES;
  447. [[YYTextEffectWindow sharedWindow] showMagnifier:_magnifierRanged];
  448. } else {
  449. [[YYTextEffectWindow sharedWindow] moveMagnifier:_magnifierRanged];
  450. }
  451. }
  452. }
  453. /// Update the showing magnifier.
  454. - (void)_updateMagnifier {
  455. if ([UIApplication isAppExtension]) return;
  456. if (_state.showingMagnifierCaret) {
  457. [[YYTextEffectWindow sharedWindow] moveMagnifier:_magnifierCaret];
  458. }
  459. if (_state.showingMagnifierRanged) {
  460. [[YYTextEffectWindow sharedWindow] moveMagnifier:_magnifierRanged];
  461. }
  462. }
  463. /// Hide the `_magnifierCaret` and `_magnifierRanged`.
  464. - (void)_hideMagnifier {
  465. if ([UIApplication isAppExtension]) return;
  466. if (_state.showingMagnifierCaret || _state.showingMagnifierRanged) {
  467. // disable touch began temporary to ignore caret animation overlap
  468. _state.ignoreTouchBegan = YES;
  469. __weak typeof(self) _self = self;
  470. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.15 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  471. __strong typeof(_self) self = _self;
  472. if (self) self->_state.ignoreTouchBegan = NO;
  473. });
  474. }
  475. if (_state.showingMagnifierCaret) {
  476. _state.showingMagnifierCaret = NO;
  477. [[YYTextEffectWindow sharedWindow] hideMagnifier:_magnifierCaret];
  478. }
  479. if (_state.showingMagnifierRanged) {
  480. _state.showingMagnifierRanged = NO;
  481. [[YYTextEffectWindow sharedWindow] hideMagnifier:_magnifierRanged];
  482. }
  483. }
  484. /// Show and update the UIMenuController.
  485. - (void)_showMenu {
  486. CGRect rect;
  487. if (_selectionView.caretVisible) {
  488. rect = _selectionView.caretView.frame;
  489. } else if (_selectionView.selectionRects.count > 0) {
  490. YYTextSelectionRect *sRect = _selectionView.selectionRects.firstObject;
  491. rect = sRect.rect;
  492. for (NSUInteger i = 1; i < _selectionView.selectionRects.count; i++) {
  493. sRect = _selectionView.selectionRects[i];
  494. rect = CGRectUnion(rect, sRect.rect);
  495. }
  496. CGRect inter = CGRectIntersection(rect, self.bounds);
  497. if (!CGRectIsNull(inter) && inter.size.height > 1) {
  498. rect = inter; //clip to bounds
  499. } else {
  500. if (CGRectGetMinY(rect) < CGRectGetMinY(self.bounds)) {
  501. rect.size.height = 1;
  502. rect.origin.y = CGRectGetMinY(self.bounds);
  503. } else {
  504. rect.size.height = 1;
  505. rect.origin.y = CGRectGetMaxY(self.bounds);
  506. }
  507. }
  508. YYTextKeyboardManager *mgr = [YYTextKeyboardManager defaultManager];
  509. if (mgr.keyboardVisible) {
  510. CGRect kbRect = [mgr convertRect:mgr.keyboardFrame toView:self];
  511. CGRect kbInter = CGRectIntersection(rect, kbRect);
  512. if (!CGRectIsNull(kbInter) && kbInter.size.height > 1 && kbInter.size.width > 1) {
  513. // self is covered by keyboard
  514. if (CGRectGetMinY(kbInter) > CGRectGetMinY(rect)) { // keyboard at bottom
  515. rect.size.height -= kbInter.size.height;
  516. } else if (CGRectGetMaxY(kbInter) < CGRectGetMaxY(rect)) { // keyboard at top
  517. rect.origin.y += kbInter.size.height;
  518. rect.size.height -= kbInter.size.height;
  519. }
  520. }
  521. }
  522. } else {
  523. rect = _selectionView.bounds;
  524. }
  525. if (!self.isFirstResponder) {
  526. if (!_containerView.isFirstResponder) {
  527. [_containerView becomeFirstResponder];
  528. }
  529. }
  530. if (self.isFirstResponder || _containerView.isFirstResponder) {
  531. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  532. UIMenuController *menu = [UIMenuController sharedMenuController];
  533. [menu setTargetRect:CGRectStandardize(rect) inView:_selectionView];
  534. [menu update];
  535. if (!_state.showingMenu || !menu.menuVisible) {
  536. _state.showingMenu = YES;
  537. [menu setMenuVisible:YES animated:YES];
  538. }
  539. });
  540. }
  541. }
  542. /// Hide the UIMenuController.
  543. - (void)_hideMenu {
  544. if (_state.showingMenu) {
  545. _state.showingMenu = NO;
  546. UIMenuController *menu = [UIMenuController sharedMenuController];
  547. [menu setMenuVisible:NO animated:YES];
  548. }
  549. if (_containerView.isFirstResponder) {
  550. _state.ignoreFirstResponder = YES;
  551. [_containerView resignFirstResponder]; // it will call [self becomeFirstResponder], ignore it temporary.
  552. _state.ignoreFirstResponder = NO;
  553. }
  554. }
  555. /// Show highlight layout based on `_highlight` and `_highlightRange`
  556. /// If the `_highlightLayout` is nil, try to create.
  557. - (void)_showHighlightAnimated:(BOOL)animated {
  558. NSTimeInterval fadeDuration = animated ? kHighlightFadeDuration : 0;
  559. if (!_highlight) return;
  560. if (!_highlightLayout) {
  561. NSMutableAttributedString *hiText = (_delectedText ? _delectedText : _innerText).mutableCopy;
  562. NSDictionary *newAttrs = _highlight.attributes;
  563. [newAttrs enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *stop) {
  564. [hiText setAttribute:key value:value range:_highlightRange];
  565. }];
  566. _highlightLayout = [YYTextLayout layoutWithContainer:_innerContainer text:hiText];
  567. if (!_highlightLayout) _highlight = nil;
  568. }
  569. if (_highlightLayout && !_state.showingHighlight) {
  570. _state.showingHighlight = YES;
  571. [_containerView setLayout:_highlightLayout withFadeDuration:fadeDuration];
  572. }
  573. }
  574. /// Show `_innerLayout` instead of `_highlightLayout`.
  575. /// It does not destory the `_highlightLayout`.
  576. - (void)_hideHighlightAnimated:(BOOL)animated {
  577. NSTimeInterval fadeDuration = animated ? kHighlightFadeDuration : 0;
  578. if (_state.showingHighlight) {
  579. _state.showingHighlight = NO;
  580. [_containerView setLayout:_innerLayout withFadeDuration:fadeDuration];
  581. }
  582. }
  583. /// Show `_innerLayout` and destory the `_highlight` and `_highlightLayout`.
  584. - (void)_removeHighlightAnimated:(BOOL)animated {
  585. [self _hideHighlightAnimated:animated];
  586. _highlight = nil;
  587. _highlightLayout = nil;
  588. }
  589. /// Scroll current selected range to visible.
  590. - (void)_scrollSelectedRangeToVisible {
  591. [self _scrollRangeToVisible:_selectedTextRange];
  592. }
  593. /// Scroll range to visible, take account into keyboard and insets.
  594. - (void)_scrollRangeToVisible:(YYTextRange *)range {
  595. if (!range) return;
  596. CGRect rect = [_innerLayout rectForRange:range];
  597. if (CGRectIsNull(rect)) return;
  598. rect = [self _convertRectFromLayout:rect];
  599. rect = [_containerView convertRect:rect toView:self];
  600. if (rect.size.width < 1) rect.size.width = 1;
  601. if (rect.size.height < 1) rect.size.height = 1;
  602. CGFloat extend = 3;
  603. BOOL insetModified = NO;
  604. YYTextKeyboardManager *mgr = [YYTextKeyboardManager defaultManager];
  605. if (mgr.keyboardVisible && self.window && self.superview && self.isFirstResponder && !_verticalForm) {
  606. CGRect bounds = self.bounds;
  607. bounds.origin = CGPointZero;
  608. CGRect kbRect = [mgr convertRect:mgr.keyboardFrame toView:self];
  609. kbRect.origin.y -= _extraAccessoryViewHeight;
  610. kbRect.size.height += _extraAccessoryViewHeight;
  611. kbRect.origin.x -= self.contentOffset.x;
  612. kbRect.origin.y -= self.contentOffset.y;
  613. CGRect inter = CGRectIntersection(bounds, kbRect);
  614. if (!CGRectIsNull(inter) && inter.size.height > 1 && inter.size.width > extend) { // self is covered by keyboard
  615. if (CGRectGetMinY(inter) > CGRectGetMinY(bounds)) { // keyboard below self.top
  616. UIEdgeInsets originalContentInset = self.contentInset;
  617. UIEdgeInsets originalScrollIndicatorInsets = self.scrollIndicatorInsets;
  618. if (_insetModifiedByKeyboard) {
  619. originalContentInset = _originalContentInset;
  620. originalScrollIndicatorInsets = _originalScrollIndicatorInsets;
  621. }
  622. if (originalContentInset.bottom < inter.size.height + extend) {
  623. insetModified = YES;
  624. if (!_insetModifiedByKeyboard) {
  625. _insetModifiedByKeyboard = YES;
  626. _originalContentInset = self.contentInset;
  627. _originalScrollIndicatorInsets = self.scrollIndicatorInsets;
  628. }
  629. UIEdgeInsets newInset = originalContentInset;
  630. UIEdgeInsets newIndicatorInsets = originalScrollIndicatorInsets;
  631. newInset.bottom = inter.size.height + extend;
  632. newIndicatorInsets.bottom = newInset.bottom;
  633. UIViewAnimationOptions curve;
  634. if (kiOS7Later) {
  635. curve = 7 << 16;
  636. } else {
  637. curve = UIViewAnimationOptionCurveEaseInOut;
  638. }
  639. [UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionAllowUserInteraction | curve animations:^{
  640. [super setContentInset:newInset];
  641. [super setScrollIndicatorInsets:newIndicatorInsets];
  642. [self scrollRectToVisible:CGRectInset(rect, -extend, -extend) animated:NO];
  643. } completion:NULL];
  644. }
  645. }
  646. }
  647. }
  648. if (!insetModified) {
  649. [UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionAllowUserInteraction | UIViewAnimationCurveEaseOut animations:^{
  650. [self _restoreInsetsAnimated:NO];
  651. [self scrollRectToVisible:CGRectInset(rect, -extend, -extend) animated:NO];
  652. } completion:NULL];
  653. }
  654. }
  655. /// Restore contents insets if modified by keyboard.
  656. - (void)_restoreInsetsAnimated:(BOOL)animated {
  657. if (_insetModifiedByKeyboard) {
  658. _insetModifiedByKeyboard = NO;
  659. if (animated) {
  660. [UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionAllowUserInteraction | UIViewAnimationCurveEaseOut animations:^{
  661. [super setContentInset:_originalContentInset];
  662. [super setScrollIndicatorInsets:_originalScrollIndicatorInsets];
  663. } completion:NULL];
  664. } else {
  665. [super setContentInset:_originalContentInset];
  666. [super setScrollIndicatorInsets:_originalScrollIndicatorInsets];
  667. }
  668. }
  669. }
  670. /// Keyboard frame changed, scroll the caret to visible range, or modify the content insets.
  671. - (void)_keyboardChanged {
  672. if (!self.isFirstResponder) return;
  673. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  674. if ([YYTextKeyboardManager defaultManager].keyboardVisible) {
  675. [self _scrollRangeToVisible:_selectedTextRange];
  676. } else {
  677. [self _restoreInsetsAnimated:YES];
  678. }
  679. [self _updateMagnifier];
  680. if (_state.showingMenu) {
  681. [self _showMenu];
  682. }
  683. });
  684. }
  685. /// Start long press timer, used for 'highlight' range text action.
  686. - (void)_startLongPressTimer {
  687. [_longPressTimer invalidate];
  688. _longPressTimer = [NSTimer timerWithTimeInterval:kLongPressMinimumDuration
  689. target:[YYWeakProxy proxyWithTarget:self]
  690. selector:@selector(_trackDidLongPress)
  691. userInfo:nil
  692. repeats:NO];
  693. [[NSRunLoop currentRunLoop] addTimer:_longPressTimer forMode:NSRunLoopCommonModes];
  694. }
  695. /// Invalidate the long press timer.
  696. - (void)_endLongPressTimer {
  697. [_longPressTimer invalidate];
  698. _longPressTimer = nil;
  699. }
  700. /// Long press detected.
  701. - (void)_trackDidLongPress {
  702. [self _endLongPressTimer];
  703. BOOL dealLongPressAction = NO;
  704. if (_state.showingHighlight) {
  705. [self _hideMenu];
  706. if (_highlight.longPressAction) {
  707. dealLongPressAction = YES;
  708. CGRect rect = [_innerLayout rectForRange:[YYTextRange rangeWithRange:_highlightRange]];
  709. rect = [self _convertRectFromLayout:rect];
  710. _highlight.longPressAction(self, _innerText, _highlightRange, rect);
  711. [self _endTouchTracking];
  712. } else {
  713. BOOL shouldHighlight = YES;
  714. if ([self.delegate respondsToSelector:@selector(textView:shouldLongPressHighlight:inRange:)]) {
  715. shouldHighlight = [self.delegate textView:self shouldLongPressHighlight:_highlight inRange:_highlightRange];
  716. }
  717. if (shouldHighlight && [self.delegate respondsToSelector:@selector(textView:didLongPressHighlight:inRange:rect:)]) {
  718. dealLongPressAction = YES;
  719. CGRect rect = [_innerLayout rectForRange:[YYTextRange rangeWithRange:_highlightRange]];
  720. rect = [self _convertRectFromLayout:rect];
  721. [self.delegate textView:self didLongPressHighlight:_highlight inRange:_highlightRange rect:rect];
  722. [self _endTouchTracking];
  723. }
  724. }
  725. }
  726. if (!dealLongPressAction){
  727. [self _removeHighlightAnimated:NO];
  728. if (_state.trackingTouch) {
  729. if (_state.trackingGrabber) {
  730. self.panGestureRecognizer.enabled = NO;
  731. [self _hideMenu];
  732. [self _showMagnifierRanged];
  733. } else if (self.isFirstResponder){
  734. self.panGestureRecognizer.enabled = NO;
  735. _selectionView.caretBlinks = NO;
  736. _state.trackingCaret = YES;
  737. CGPoint trackingPoint = [self _convertPointToLayout:_trackingPoint];
  738. YYTextPosition *newPos = [_innerLayout closestPositionToPoint:trackingPoint];
  739. newPos = [self _correctedTextPosition:newPos];
  740. if (newPos) {
  741. if (_markedTextRange) {
  742. if ([newPos compare:_markedTextRange.start] != NSOrderedDescending) {
  743. newPos = _markedTextRange.start;
  744. } else if ([newPos compare:_markedTextRange.end] != NSOrderedAscending) {
  745. newPos = _markedTextRange.end;
  746. }
  747. }
  748. _trackingRange = [YYTextRange rangeWithRange:NSMakeRange(newPos.offset, 0) affinity:newPos.affinity];
  749. [self _updateSelectionView];
  750. }
  751. [self _hideMenu];
  752. if (_markedTextRange) {
  753. [self _showMagnifierRanged];
  754. } else {
  755. [self _showMagnifierCaret];
  756. }
  757. } else if (self.selectable) {
  758. self.panGestureRecognizer.enabled = NO;
  759. _state.trackingPreSelect = YES;
  760. _state.selectedWithoutEdit = NO;
  761. [self _updateTextRangeByTrackingPreSelect];
  762. [self _updateSelectionView];
  763. [self _showMagnifierCaret];
  764. }
  765. }
  766. }
  767. }
  768. /// Start auto scroll timer, used for auto scroll tick.
  769. - (void)_startAutoScrollTimer {
  770. if (!_autoScrollTimer) {
  771. [_autoScrollTimer invalidate];
  772. _autoScrollTimer = [NSTimer timerWithTimeInterval:kAutoScrollMinimumDuration
  773. target:[YYWeakProxy proxyWithTarget:self]
  774. selector:@selector(_trackDidTickAutoScroll)
  775. userInfo:nil
  776. repeats:YES];
  777. [[NSRunLoop currentRunLoop] addTimer:_autoScrollTimer forMode:NSRunLoopCommonModes];
  778. }
  779. }
  780. /// Invalidate the auto scroll, and restore the text view state.
  781. - (void)_endAutoScrollTimer {
  782. if (_state.autoScrollTicked) [self flashScrollIndicators];
  783. [_autoScrollTimer invalidate];
  784. _autoScrollTimer = nil;
  785. _autoScrollOffset = 0;
  786. _autoScrollAcceleration = 0;
  787. _state.autoScrollTicked = NO;
  788. if (_magnifierCaret.captureDisabled) {
  789. _magnifierCaret.captureDisabled = NO;
  790. if (_state.showingMagnifierCaret) {
  791. [self _showMagnifierCaret];
  792. }
  793. }
  794. if (_magnifierRanged.captureDisabled) {
  795. _magnifierRanged.captureDisabled = NO;
  796. if (_state.showingMagnifierRanged) {
  797. [self _showMagnifierRanged];
  798. }
  799. }
  800. }
  801. /// Auto scroll ticked by timer.
  802. - (void)_trackDidTickAutoScroll {
  803. if (_autoScrollOffset != 0) {
  804. _magnifierCaret.captureDisabled = YES;
  805. _magnifierRanged.captureDisabled = YES;
  806. CGPoint offset = self.contentOffset;
  807. if (_verticalForm) {
  808. offset.x += _autoScrollOffset;
  809. if (_autoScrollAcceleration > 0) {
  810. offset.x += ((_autoScrollOffset > 0 ? 1 : -1) * _autoScrollAcceleration * _autoScrollAcceleration * 0.5);
  811. }
  812. _autoScrollAcceleration++;
  813. offset.x = round(offset.x);
  814. if (_autoScrollOffset < 0) {
  815. if (offset.x < -self.contentInset.left) offset.x = -self.contentInset.left;
  816. } else {
  817. CGFloat maxOffsetX = self.contentSize.width - self.bounds.size.width + self.contentInset.right;
  818. if (offset.x > maxOffsetX) offset.x = maxOffsetX;
  819. }
  820. if (offset.x < -self.contentInset.left) offset.x = -self.contentInset.left;
  821. } else {
  822. offset.y += _autoScrollOffset;
  823. if (_autoScrollAcceleration > 0) {
  824. offset.y += ((_autoScrollOffset > 0 ? 1 : -1) * _autoScrollAcceleration * _autoScrollAcceleration * 0.5);
  825. }
  826. _autoScrollAcceleration++;
  827. offset.y = round(offset.y);
  828. if (_autoScrollOffset < 0) {
  829. if (offset.y < -self.contentInset.top) offset.y = -self.contentInset.top;
  830. } else {
  831. CGFloat maxOffsetY = self.contentSize.height - self.bounds.size.height + self.contentInset.bottom;
  832. if (offset.y > maxOffsetY) offset.y = maxOffsetY;
  833. }
  834. if (offset.y < -self.contentInset.top) offset.y = -self.contentInset.top;
  835. }
  836. BOOL shouldScroll;
  837. if (_verticalForm) {
  838. shouldScroll = fabs(offset.x -self.contentOffset.x) > 0.5;
  839. } else {
  840. shouldScroll = fabs(offset.y -self.contentOffset.y) > 0.5;
  841. }
  842. if (shouldScroll) {
  843. _state.autoScrollTicked = YES;
  844. _trackingPoint.x += offset.x - self.contentOffset.x;
  845. _trackingPoint.y += offset.y - self.contentOffset.y;
  846. [UIView animateWithDuration:kAutoScrollMinimumDuration delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionCurveLinear animations:^{
  847. [self setContentOffset:offset];
  848. } completion:^(BOOL finished) {
  849. if (_state.trackingTouch) {
  850. if (_state.trackingGrabber) {
  851. [self _showMagnifierRanged];
  852. [self _updateTextRangeByTrackingGrabber];
  853. } else if (_state.trackingPreSelect) {
  854. [self _showMagnifierCaret];
  855. [self _updateTextRangeByTrackingPreSelect];
  856. } else if (_state.trackingCaret) {
  857. if (_markedTextRange) {
  858. [self _showMagnifierRanged];
  859. } else {
  860. [self _showMagnifierCaret];
  861. }
  862. [self _updateTextRangeByTrackingCaret];
  863. }
  864. [self _updateSelectionView];
  865. }
  866. }];
  867. } else {
  868. [self _endAutoScrollTimer];
  869. }
  870. } else {
  871. [self _endAutoScrollTimer];
  872. }
  873. }
  874. /// End current touch tracking (if is tracking now), and update the state.
  875. - (void)_endTouchTracking {
  876. if (!_state.trackingTouch) return;
  877. _state.trackingTouch = NO;
  878. _state.trackingGrabber = NO;
  879. _state.trackingCaret = NO;
  880. _state.trackingPreSelect = NO;
  881. _state.touchMoved = NO;
  882. _state.deleteConfirm = NO;
  883. _state.clearsOnInsertionOnce = NO;
  884. _trackingRange = nil;
  885. _selectionView.caretBlinks = YES;
  886. [self _removeHighlightAnimated:YES];
  887. [self _hideMagnifier];
  888. [self _endLongPressTimer];
  889. [self _endAutoScrollTimer];
  890. [self _updateSelectionView];
  891. self.panGestureRecognizer.enabled = self.scrollEnabled;
  892. }
  893. /// Start a timer to fix the selection dot.
  894. - (void)_startSelectionDotFixTimer {
  895. [_selectionDotFixTimer invalidate];
  896. _longPressTimer = [NSTimer timerWithTimeInterval:1/15.0
  897. target:[YYWeakProxy proxyWithTarget:self]
  898. selector:@selector(_fixSelectionDot)
  899. userInfo:nil
  900. repeats:NO];
  901. [[NSRunLoop currentRunLoop] addTimer:_longPressTimer forMode:NSRunLoopCommonModes];
  902. }
  903. /// End the timer.
  904. - (void)_endSelectionDotFixTimer {
  905. [_selectionDotFixTimer invalidate];
  906. _selectionDotFixTimer = nil;
  907. }
  908. /// If it shows selection grabber and this view was moved by super view,
  909. /// update the selection dot in window.
  910. - (void)_fixSelectionDot {
  911. if ([UIApplication isAppExtension]) return;
  912. CGPoint origin = [self convertPoint:CGPointZero toViewOrWindow:[YYTextEffectWindow sharedWindow]];
  913. if (!CGPointEqualToPoint(origin, _previousOriginInWindow)) {
  914. _previousOriginInWindow = origin;
  915. [[YYTextEffectWindow sharedWindow] hideSelectionDot:_selectionView];
  916. [[YYTextEffectWindow sharedWindow] showSelectionDot:_selectionView];
  917. }
  918. }
  919. /// Try to get the character range/position with word granularity from the tokenizer.
  920. - (YYTextRange *)_getClosestTokenRangeAtPosition:(YYTextPosition *)position {
  921. position = [self _correctedTextPosition:position];
  922. if (!position) return nil;
  923. YYTextRange *range = nil;
  924. if (_tokenizer) {
  925. range = (id)[_tokenizer rangeEnclosingPosition:position withGranularity:UITextGranularityWord inDirection:UITextStorageDirectionForward];
  926. if (range.asRange.length == 0) {
  927. range = (id)[_tokenizer rangeEnclosingPosition:position withGranularity:UITextGranularityWord inDirection:UITextStorageDirectionBackward];
  928. }
  929. }
  930. if (!range || range.asRange.length == 0) {
  931. range = [_innerLayout textRangeByExtendingPosition:position inDirection:UITextLayoutDirectionRight offset:1];
  932. range = [self _correctedTextRange:range];
  933. if (range.asRange.length == 0) {
  934. range = [_innerLayout textRangeByExtendingPosition:position inDirection:UITextLayoutDirectionLeft offset:1];
  935. range = [self _correctedTextRange:range];
  936. }
  937. } else {
  938. YYTextRange *extStart = [_innerLayout textRangeByExtendingPosition:range.start];
  939. YYTextRange *extEnd = [_innerLayout textRangeByExtendingPosition:range.end];
  940. if (extStart && extEnd) {
  941. NSArray *arr = [@[extStart.start, extStart.end, extEnd.start, extEnd.end] sortedArrayUsingSelector:@selector(compare:)];
  942. range = [YYTextRange rangeWithStart:arr.firstObject end:arr.lastObject];
  943. }
  944. }
  945. range = [self _correctedTextRange:range];
  946. if (range.asRange.length == 0) {
  947. range = [YYTextRange rangeWithRange:NSMakeRange(0, _innerText.length)];
  948. }
  949. return [self _correctedTextRange:range];
  950. }
  951. /// Try to get the character range/position with word granularity from the tokenizer.
  952. - (YYTextRange *)_getClosestTokenRangeAtPoint:(CGPoint)point {
  953. point = [self _convertPointToLayout:point];
  954. YYTextRange *touchRange = [_innerLayout closestTextRangeAtPoint:point];
  955. touchRange = [self _correctedTextRange:touchRange];
  956. if (_tokenizer && touchRange) {
  957. YYTextRange *encEnd = (id)[_tokenizer rangeEnclosingPosition:touchRange.end withGranularity:UITextGranularityWord inDirection:UITextStorageDirectionBackward];
  958. YYTextRange *encStart = (id)[_tokenizer rangeEnclosingPosition:touchRange.start withGranularity:UITextGranularityWord inDirection:UITextStorageDirectionForward];
  959. if (encEnd && encStart) {
  960. NSArray *arr = [@[encEnd.start, encEnd.end, encStart.start, encStart.end] sortedArrayUsingSelector:@selector(compare:)];
  961. touchRange = [YYTextRange rangeWithStart:arr.firstObject end:arr.lastObject];
  962. }
  963. }
  964. if (touchRange) {
  965. YYTextRange *extStart = [_innerLayout textRangeByExtendingPosition:touchRange.start];
  966. YYTextRange *extEnd = [_innerLayout textRangeByExtendingPosition:touchRange.end];
  967. if (extStart && extEnd) {
  968. NSArray *arr = [@[extStart.start, extStart.end, extEnd.start, extEnd.end] sortedArrayUsingSelector:@selector(compare:)];
  969. touchRange = [YYTextRange rangeWithStart:arr.firstObject end:arr.lastObject];
  970. }
  971. }
  972. if (!touchRange) touchRange = [YYTextRange defaultRange];
  973. if (_innerText.length && touchRange.asRange.length == 0) {
  974. touchRange = [YYTextRange rangeWithRange:NSMakeRange(0, _innerText.length)];
  975. }
  976. return touchRange;
  977. }
  978. /// Try to get the highlight property. If exist, the range will be returnd by the range pointer.
  979. /// If the delegate ignore the highlight, returns nil.
  980. - (YYTextHighlight *)_getHighlightAtPoint:(CGPoint)point range:(NSRangePointer)range {
  981. if (!_highlightable || !_innerLayout.containsHighlight) return nil;
  982. point = [self _convertPointToLayout:point];
  983. YYTextRange *textRange = [_innerLayout textRangeAtPoint:point];
  984. textRange = [self _correctedTextRange:textRange];
  985. if (!textRange) return nil;
  986. NSUInteger startIndex = textRange.start.offset;
  987. if (startIndex == _innerText.length) {
  988. if (startIndex == 0) return nil;
  989. else startIndex--;
  990. }
  991. NSRange highlightRange = {0};
  992. NSAttributedString *text = _delectedText ? _delectedText : _innerText;
  993. YYTextHighlight *highlight = [text attribute:YYTextHighlightAttributeName
  994. atIndex:startIndex
  995. longestEffectiveRange:&highlightRange
  996. inRange:NSMakeRange(0, _innerText.length)];
  997. if (!highlight) return nil;
  998. BOOL shouldTap = YES, shouldLongPress = YES;
  999. if (!highlight.tapAction && !highlight.longPressAction) {
  1000. if ([self.delegate respondsToSelector:@selector(textView:shouldTapHighlight:inRange:)]) {
  1001. shouldTap = [self.delegate textView:self shouldTapHighlight:highlight inRange:highlightRange];
  1002. }
  1003. if ([self.delegate respondsToSelector:@selector(textView:shouldLongPressHighlight:inRange:)]) {
  1004. shouldLongPress = [self.delegate textView:self shouldLongPressHighlight:highlight inRange:highlightRange];
  1005. }
  1006. }
  1007. if (!shouldTap && !shouldLongPress) return nil;
  1008. if (range) *range = highlightRange;
  1009. return highlight;
  1010. }
  1011. /// Return the ranged magnifier popover offset from the baseline, base on `_trackingPoint`.
  1012. - (CGFloat)_getMagnifierRangedOffset {
  1013. CGPoint magPoint = _trackingPoint;
  1014. magPoint = [self _convertPointToLayout:magPoint];
  1015. if (_verticalForm) {
  1016. magPoint.x += kMagnifierRangedTrackFix;
  1017. } else {
  1018. magPoint.y += kMagnifierRangedTrackFix;
  1019. }
  1020. YYTextPosition *position = [_innerLayout closestPositionToPoint:magPoint];
  1021. NSUInteger lineIndex = [_innerLayout lineIndexForPosition:position];
  1022. if (lineIndex < _innerLayout.lines.count) {
  1023. YYTextLine *line = _innerLayout.lines[lineIndex];
  1024. if (_verticalForm) {
  1025. magPoint.x = YY_CLAMP(magPoint.x, line.left, line.right);
  1026. return magPoint.x - line.position.x + kMagnifierRangedPopoverOffset;
  1027. } else {
  1028. magPoint.y = YY_CLAMP(magPoint.y, line.top, line.bottom);
  1029. return magPoint.y - line.position.y + kMagnifierRangedPopoverOffset;
  1030. }
  1031. } else {
  1032. return 0;
  1033. }
  1034. }
  1035. /// Return a YYTextMoveDirection from `_touchBeganPoint` to `_trackingPoint`.
  1036. - (unsigned int)_getMoveDirection {
  1037. CGFloat moveH = _trackingPoint.x - _touchBeganPoint.x;
  1038. CGFloat moveV = _trackingPoint.y - _touchBeganPoint.y;
  1039. if (fabs(moveH) > fabs(moveV)) {
  1040. if (fabs(moveH) > kLongPressAllowableMovement) {
  1041. return moveH > 0 ? kRight : kLeft;
  1042. }
  1043. } else {
  1044. if (fabs(moveV) > kLongPressAllowableMovement) {
  1045. return moveV > 0 ? kBottom : kTop;
  1046. }
  1047. }
  1048. return 0;
  1049. }
  1050. /// Get the auto scroll offset in one tick time.
  1051. - (CGFloat)_getAutoscrollOffset {
  1052. if (!_state.trackingTouch) return 0;
  1053. CGRect bounds = self.bounds;
  1054. bounds.origin = CGPointZero;
  1055. YYTextKeyboardManager *mgr = [YYTextKeyboardManager defaultManager];
  1056. if (mgr.keyboardVisible && self.window && self.superview && self.isFirstResponder && !_verticalForm) {
  1057. CGRect kbRect = [mgr convertRect:mgr.keyboardFrame toView:self];
  1058. kbRect.origin.y -= _extraAccessoryViewHeight;
  1059. kbRect.size.height += _extraAccessoryViewHeight;
  1060. kbRect.origin.x -= self.contentOffset.x;
  1061. kbRect.origin.y -= self.contentOffset.y;
  1062. CGRect inter = CGRectIntersection(bounds, kbRect);
  1063. if (!CGRectIsNull(inter) && inter.size.height > 1 && inter.size.width > 1) {
  1064. if (CGRectGetMinY(inter) > CGRectGetMinY(bounds)) {
  1065. bounds.size.height -= inter.size.height;
  1066. }
  1067. }
  1068. }
  1069. CGPoint point = _trackingPoint;
  1070. point.x -= self.contentOffset.x;
  1071. point.y -= self.contentOffset.y;
  1072. CGFloat maxOfs = 32; // a good value ~
  1073. CGFloat ofs = 0;
  1074. if (_verticalForm) {
  1075. if (point.x < self.contentInset.left) {
  1076. ofs = (point.x - self.contentInset.left - 5) * 0.5;
  1077. if (ofs < -maxOfs) ofs = -maxOfs;
  1078. } else if (point.x > bounds.size.width) {
  1079. ofs = ((point.x - bounds.size.width) + 5) * 0.5;
  1080. if (ofs > maxOfs) ofs = maxOfs;
  1081. }
  1082. } else {
  1083. if (point.y < self.contentInset.top) {
  1084. ofs = (point.y - self.contentInset.top - 5) * 0.5;
  1085. if (ofs < -maxOfs) ofs = -maxOfs;
  1086. } else if (point.y > bounds.size.height) {
  1087. ofs = ((point.y - bounds.size.height) + 5) * 0.5;
  1088. if (ofs > maxOfs) ofs = maxOfs;
  1089. }
  1090. }
  1091. return ofs;
  1092. }
  1093. /// Visible size based on bounds and insets
  1094. - (CGSize)_getVisibleSize {
  1095. CGSize visibleSize = self.bounds.size;
  1096. visibleSize.width -= self.contentInset.left - self.contentInset.right;
  1097. visibleSize.height -= self.contentInset.top - self.contentInset.bottom;
  1098. if (visibleSize.width < 0) visibleSize.width = 0;
  1099. if (visibleSize.height < 0) visibleSize.height = 0;
  1100. return visibleSize;
  1101. }
  1102. /// Returns whether the text view can paste data from pastboard.
  1103. - (BOOL)_isPasteboardContainsValidValue {
  1104. UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
  1105. if (pasteboard.string.length > 0) {
  1106. return YES;
  1107. }
  1108. if (pasteboard.attributedString.length > 0) {
  1109. if (_allowsPasteAttributedString) {
  1110. return YES;
  1111. }
  1112. }
  1113. if (pasteboard.image || pasteboard.imageData.length > 0) {
  1114. if (_allowsPasteImage) {
  1115. return YES;
  1116. }
  1117. }
  1118. return NO;
  1119. }
  1120. /// Save current selected attributed text to pasteboard.
  1121. - (void)_copySelectedTextToPasteboard {
  1122. if (_allowsCopyAttributedString) {
  1123. NSAttributedString *text = [_innerText attributedSubstringFromRange:_selectedTextRange.asRange];
  1124. if (text.length) {
  1125. [UIPasteboard generalPasteboard].attributedString = text;
  1126. }
  1127. } else {
  1128. NSString *string = [_innerText plainTextForRange:_selectedTextRange.asRange];
  1129. if (string.length) {
  1130. [UIPasteboard generalPasteboard].string = string;
  1131. }
  1132. }
  1133. }
  1134. /// Update the text view state when pasteboard changed.
  1135. - (void)_pasteboardChanged {
  1136. if (_state.showingMenu) {
  1137. UIMenuController *menu = [UIMenuController sharedMenuController];
  1138. [menu update];
  1139. }
  1140. }
  1141. /// Whether the position is valid (not out of bounds).
  1142. - (BOOL)_isTextPositionValid:(YYTextPosition *)position {
  1143. if (!position) return NO;
  1144. if (position.offset < 0) return NO;
  1145. if (position.offset > _innerText.length) return NO;
  1146. if (position.offset == 0 && position.affinity == YYTextAffinityBackward) return NO;
  1147. if (position.offset == _innerText.length && position.affinity == YYTextAffinityBackward) return NO;
  1148. return YES;
  1149. }
  1150. /// Whether the range is valid (not out of bounds).
  1151. - (BOOL)_isTextRangeValid:(YYTextRange *)range {
  1152. if (![self _isTextPositionValid:range.start]) return NO;
  1153. if (![self _isTextPositionValid:range.end]) return NO;
  1154. return YES;
  1155. }
  1156. /// Correct the position if it out of bounds.
  1157. - (YYTextPosition *)_correctedTextPosition:(YYTextPosition *)position {
  1158. if (!position) return nil;
  1159. if ([self _isTextPositionValid:position]) return position;
  1160. if (position.offset < 0) {
  1161. return [YYTextPosition positionWithOffset:0];
  1162. }
  1163. if (position.offset > _innerText.length) {
  1164. return [YYTextPosition positionWithOffset:_innerText.length];
  1165. }
  1166. if (position.offset == 0 && position.affinity == YYTextAffinityBackward) {
  1167. return [YYTextPosition positionWithOffset:position.offset];
  1168. }
  1169. if (position.offset == _innerText.length && position.affinity == YYTextAffinityBackward) {
  1170. return [YYTextPosition positionWithOffset:position.offset];
  1171. }
  1172. return position;
  1173. }
  1174. /// Correct the range if it out of bounds.
  1175. - (YYTextRange *)_correctedTextRange:(YYTextRange *)range {
  1176. if (!range) return nil;
  1177. if ([self _isTextRangeValid:range]) return range;
  1178. YYTextPosition *start = [self _correctedTextPosition:range.start];
  1179. YYTextPosition *end = [self _correctedTextPosition:range.end];
  1180. return [YYTextRange rangeWithStart:start end:end];
  1181. }
  1182. /// Convert the point from this view to text layout.
  1183. - (CGPoint)_convertPointToLayout:(CGPoint)point {
  1184. CGSize boundingSize = _innerLayout.textBoundingSize;
  1185. if (_innerLayout.container.isVerticalForm) {
  1186. CGFloat w = _innerLayout.textBoundingSize.width;
  1187. if (w < self.bounds.size.width) w = self.bounds.size.width;
  1188. point.x += _innerLayout.container.size.width - w;
  1189. if (boundingSize.width < self.bounds.size.width) {
  1190. if (_textVerticalAlignment == YYTextVerticalAlignmentCenter) {
  1191. point.x += (self.bounds.size.width - boundingSize.width) * 0.5;
  1192. } else if (_textVerticalAlignment == YYTextVerticalAlignmentBottom) {
  1193. point.x += (self.bounds.size.width - boundingSize.width);
  1194. }
  1195. }
  1196. return point;
  1197. } else {
  1198. if (boundingSize.height < self.bounds.size.height) {
  1199. if (_textVerticalAlignment == YYTextVerticalAlignmentCenter) {
  1200. point.y -= (self.bounds.size.height - boundingSize.height) * 0.5;
  1201. } else if (_textVerticalAlignment == YYTextVerticalAlignmentBottom) {
  1202. point.y -= (self.bounds.size.height - boundingSize.height);
  1203. }
  1204. }
  1205. return point;
  1206. }
  1207. }
  1208. /// Convert the point from text layout to this view.
  1209. - (CGPoint)_convertPointFromLayout:(CGPoint)point {
  1210. CGSize boundingSize = _innerLayout.textBoundingSize;
  1211. if (_innerLayout.container.isVerticalForm) {
  1212. CGFloat w = _innerLayout.textBoundingSize.width;
  1213. if (w < self.bounds.size.width) w = self.bounds.size.width;
  1214. point.x -= _innerLayout.container.size.width - w;
  1215. if (boundingSize.width < self.bounds.size.width) {
  1216. if (_textVerticalAlignment == YYTextVerticalAlignmentCenter) {
  1217. point.x -= (self.bounds.size.width - boundingSize.width) * 0.5;
  1218. } else if (_textVerticalAlignment == YYTextVerticalAlignmentBottom) {
  1219. point.x -= (self.bounds.size.width - boundingSize.width);
  1220. }
  1221. }
  1222. return point;
  1223. } else {
  1224. if (boundingSize.height < self.bounds.size.height) {
  1225. if (_textVerticalAlignment == YYTextVerticalAlignmentCenter) {
  1226. point.y += (self.bounds.size.height - boundingSize.height) * 0.5;
  1227. } else if (_textVerticalAlignment == YYTextVerticalAlignmentBottom) {
  1228. point.y += (self.bounds.size.height - boundingSize.height);
  1229. }
  1230. }
  1231. return point;
  1232. }
  1233. }
  1234. /// Convert the rect from this view to text layout.
  1235. - (CGRect)_convertRectToLayout:(CGRect)rect {
  1236. rect.origin = [self _convertPointToLayout:rect.origin];
  1237. return rect;
  1238. }
  1239. /// Convert the rect from text layout to this view.
  1240. - (CGRect)_convertRectFromLayout:(CGRect)rect {
  1241. rect.origin = [self _convertPointFromLayout:rect.origin];
  1242. return rect;
  1243. }
  1244. /// Replace the range with the text, and change the `_selectTextRange`.
  1245. /// The caller should make sure the `range` and `text` are valid before call this method.
  1246. - (void)_replaceRange:(YYTextRange *)range withText:(NSString *)text notifyToDelegate:(BOOL)notify{
  1247. if (NSEqualRanges(range.asRange, _selectedTextRange.asRange)) {
  1248. if (notify) [_inputDelegate selectionWillChange:self];
  1249. NSRange newRange = NSMakeRange(0, 0);
  1250. newRange.location = _selectedTextRange.start.offset + text.length;
  1251. _selectedTextRange = [YYTextRange rangeWithRange:newRange];
  1252. if (notify) [_inputDelegate selectionDidChange:self];
  1253. } else {
  1254. if (range.asRange.length != text.length) {
  1255. if (notify) [_inputDelegate selectionWillChange:self];
  1256. NSRange unionRange = NSIntersectionRange(_selectedTextRange.asRange, range.asRange);
  1257. if (unionRange.length == 0) {
  1258. // no intersection
  1259. if (range.end.offset <= _selectedTextRange.start.offset) {
  1260. NSInteger ofs = (NSInteger)text.length - (NSInteger)range.asRange.length;
  1261. NSRange newRange = _selectedTextRange.asRange;
  1262. newRange.location += ofs;
  1263. _selectedTextRange = [YYTextRange rangeWithRange:newRange];
  1264. }
  1265. } else if (unionRange.length == _selectedTextRange.asRange.length) {
  1266. // target range contains selected range
  1267. _selectedTextRange = [YYTextRange rangeWithRange:NSMakeRange(range.start.offset + text.length, 0)];
  1268. } else if (range.start.offset >= _selectedTextRange.start.offset &&
  1269. range.end.offset <= _selectedTextRange.end.offset) {
  1270. // target range inside selected range
  1271. NSInteger ofs = (NSInteger)text.length - (NSInteger)range.asRange.length;
  1272. NSRange newRange = _selectedTextRange.asRange;
  1273. newRange.length += ofs;
  1274. _selectedTextRange = [YYTextRange rangeWithRange:newRange];
  1275. } else {
  1276. // interleaving
  1277. if (range.start.offset < _selectedTextRange.start.offset) {
  1278. NSRange newRange = _selectedTextRange.asRange;
  1279. newRange.location = range.start.offset + text.length;
  1280. newRange.length -= unionRange.length;
  1281. _selectedTextRange = [YYTextRange rangeWithRange:newRange];
  1282. } else {
  1283. NSRange newRange = _selectedTextRange.asRange;
  1284. newRange.length -= unionRange.length;
  1285. _selectedTextRange = [YYTextRange rangeWithRange:newRange];
  1286. }
  1287. }
  1288. _selectedTextRange = [self _correctedTextRange:_selectedTextRange];
  1289. if (notify) [_inputDelegate selectionDidChange:self];
  1290. }
  1291. }
  1292. if (notify) [_inputDelegate textWillChange:self];
  1293. NSRange newRange = NSMakeRange(range.asRange.location, text.length);
  1294. [_innerText replaceCharactersInRange:range.asRange withString:text];
  1295. [_innerText removeDiscontinuousAttributesInRange:newRange];
  1296. if (notify) [_inputDelegate textDidChange:self];
  1297. }
  1298. /// Save current typing attributes to the attributes holder.
  1299. - (void)_updateAttributesHolder {
  1300. if (_innerText.length > 0) {
  1301. NSUInteger index = _selectedTextRange.end.offset == 0 ? 0 : _selectedTextRange.end.offset - 1;
  1302. NSDictionary *attributes = [_innerText attributesAtIndex:index];
  1303. if (!attributes) attributes = @{};
  1304. _typingAttributesHolder.attributes = attributes;
  1305. [_typingAttributesHolder removeDiscontinuousAttributesInRange:NSMakeRange(0, _typingAttributesHolder.length)];
  1306. [_typingAttributesHolder removeAttribute:YYTextBorderAttributeName range:NSMakeRange(0, _typingAttributesHolder.length)];
  1307. [_typingAttributesHolder removeAttribute:YYTextBackgroundBorderAttributeName range:NSMakeRange(0, _typingAttributesHolder.length)];
  1308. }
  1309. }
  1310. /// Update outer properties from current inner data.
  1311. - (void)_updateOuterProperties {
  1312. [self _updateAttributesHolder];
  1313. NSParagraphStyle *style = _innerText.paragraphStyle;
  1314. if (!style) style = _typingAttributesHolder.paragraphStyle;
  1315. if (!style) style = [NSParagraphStyle defaultParagraphStyle];
  1316. UIFont *font = _innerText.font;
  1317. if (!font) font = _typingAttributesHolder.font;
  1318. if (!font) font = [self _defaultFont];
  1319. UIColor *color = _innerText.color;
  1320. if (!color) color = _typingAttributesHolder.color;
  1321. if (!color) color = [UIColor blackColor];
  1322. [self _setText:[_innerText plainTextForRange:NSMakeRange(0, _innerText.length)]];
  1323. [self _setFont:font];
  1324. [self _setTextColor:color];
  1325. [self _setTextAlignment:style.alignment];
  1326. [self _setSelectedRange:_selectedTextRange.asRange];
  1327. [self _setTypingAttributes:_typingAttributesHolder.attributes];
  1328. [self _setAttributedText:_innerText];
  1329. }
  1330. /// Parse text with `textParser` and update the _selectedTextRange.
  1331. /// @return Whether changed (text or selection)
  1332. - (BOOL)_parseText {
  1333. if (self.textParser) {
  1334. YYTextRange *oldTextRange = _selectedTextRange;
  1335. NSRange newRange = _selectedTextRange.asRange;
  1336. [_inputDelegate textWillChange:self];
  1337. BOOL textChanged = [self.textParser parseText:_innerText selectedRange:&newRange];
  1338. [_inputDelegate textDidChange:self];
  1339. YYTextRange *newTextRange = [YYTextRange rangeWithRange:newRange];
  1340. newTextRange = [self _correctedTextRange:newTextRange];
  1341. if (![oldTextRange isEqual:newTextRange]) {
  1342. [_inputDelegate selectionWillChange:self];
  1343. _selectedTextRange = newTextRange;
  1344. [_inputDelegate selectionDidChange:self];
  1345. }
  1346. return textChanged;
  1347. }
  1348. return NO;
  1349. }
  1350. /// Returns whether the text should be detected by the data detector.
  1351. - (BOOL)_shouldDetectText {
  1352. if (!_dataDetector) return NO;
  1353. if (!_highlightable) return NO;
  1354. if (_linkTextAttributes.count == 0 && _highlightTextAttributes.count == 0) return NO;
  1355. if (self.isFirstResponder || _containerView.isFirstResponder) return NO;
  1356. return YES;
  1357. }
  1358. /// Detect the data in text and add highlight to the data range.
  1359. /// @return Whether detected.
  1360. - (BOOL)_detectText:(NSMutableAttributedString *)text {
  1361. if (![self _shouldDetectText]) return NO;
  1362. if (text.length == 0) return NO;
  1363. __block BOOL detected = NO;
  1364. [_dataDetector enumerateMatchesInString:text.string options:kNilOptions range:NSMakeRange(0, text.length) usingBlock: ^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
  1365. switch (result.resultType) {
  1366. case NSTextCheckingTypeDate:
  1367. case NSTextCheckingTypeAddress:
  1368. case NSTextCheckingTypeLink:
  1369. case NSTextCheckingTypePhoneNumber: {
  1370. detected = YES;
  1371. if (_highlightTextAttributes.count) {
  1372. YYTextHighlight *highlight = [YYTextHighlight highlightWithAttributes:_highlightTextAttributes];
  1373. [text setTextHighlight:highlight range:result.range];
  1374. }
  1375. if (_linkTextAttributes.count) {
  1376. [_linkTextAttributes enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
  1377. [text setAttribute:key value:obj range:result.range];
  1378. }];
  1379. }
  1380. } break;
  1381. default:
  1382. break;
  1383. }
  1384. }];
  1385. return detected;
  1386. }
  1387. /// Returns the `root` view controller (returns nil if not found).
  1388. - (UIViewController *)_getRootViewController {
  1389. UIViewController *ctrl = nil;
  1390. UIApplication *app = [UIApplication sharedExtensionApplication];
  1391. if (!ctrl) ctrl = app.keyWindow.rootViewController;
  1392. if (!ctrl) ctrl = [app.windows.firstObject rootViewController];
  1393. if (!ctrl) ctrl = self.viewController;
  1394. if (!ctrl) return nil;
  1395. while (!ctrl.view.window && ctrl.presentedViewController) {
  1396. ctrl = ctrl.presentedViewController;
  1397. }
  1398. if (!ctrl.view.window) return nil;
  1399. return ctrl;
  1400. }
  1401. /// Clear the undo and redo stack, and capture current state to undo stack.
  1402. - (void)_resetUndoAndRedoStack {
  1403. [_undoStack removeAllObjects];
  1404. [_redoStack removeAllObjects];
  1405. _YYTextViewUndoObject *object = [_YYTextViewUndoObject objectWithText:_innerText.copy range:_selectedTextRange.asRange];
  1406. _lastTypeRange = _selectedTextRange.asRange;
  1407. [_undoStack addObject:object];
  1408. }
  1409. /// Clear the redo stack.
  1410. - (void)_resetRedoStack {
  1411. [_redoStack removeAllObjects];
  1412. }
  1413. /// Capture current state to undo stack.
  1414. - (void)_saveToUndoStack {
  1415. if (!_allowsUndoAndRedo) return;
  1416. _YYTextViewUndoObject *lastObject = _undoStack.lastObject;
  1417. if ([lastObject.text isEqualToAttributedString:self.attributedText]) return;
  1418. _YYTextViewUndoObject *object = [_YYTextViewUndoObject objectWithText:_innerText.copy range:_selectedTextRange.asRange];
  1419. _lastTypeRange = _selectedTextRange.asRange;
  1420. [_undoStack addObject:object];
  1421. while (_undoStack.count > _maximumUndoLevel) {
  1422. [_undoStack removeObjectAtIndex:0];
  1423. }
  1424. }
  1425. /// Capture current state to redo stack.
  1426. - (void)_saveToRedoStack {
  1427. if (!_allowsUndoAndRedo) return;
  1428. _YYTextViewUndoObject *lastObject = _redoStack.lastObject;
  1429. if ([lastObject.text isEqualToAttributedString:self.attributedText]) return;
  1430. _YYTextViewUndoObject *object = [_YYTextViewUndoObject objectWithText:_innerText.copy range:_selectedTextRange.asRange];
  1431. [_redoStack addObject:object];
  1432. while (_redoStack.count > _maximumUndoLevel) {
  1433. [_redoStack removeObjectAtIndex:0];
  1434. }
  1435. }
  1436. - (BOOL)_canUndo {
  1437. if (_undoStack.count == 0) return NO;
  1438. _YYTextViewUndoObject *object = _undoStack.lastObject;
  1439. if ([object.text isEqualToAttributedString:_innerText]) return NO;
  1440. return YES;
  1441. }
  1442. - (BOOL)_canRedo {
  1443. if (_redoStack.count == 0) return NO;
  1444. _YYTextViewUndoObject *object = _undoStack.lastObject;
  1445. if ([object.text isEqualToAttributedString:_innerText]) return NO;
  1446. return YES;
  1447. }
  1448. - (void)_undo {
  1449. if (![self _canUndo]) return;
  1450. [self _saveToRedoStack];
  1451. _YYTextViewUndoObject *object = _undoStack.lastObject;
  1452. [_undoStack removeLastObject];
  1453. _state.insideUndoBlock = YES;
  1454. self.attributedText = object.text;
  1455. self.selectedRange = object.selectedRange;
  1456. _state.insideUndoBlock = NO;
  1457. }
  1458. - (void)_redo {
  1459. if (![self _canRedo]) return;
  1460. [self _saveToUndoStack];
  1461. _YYTextViewUndoObject *object = _redoStack.lastObject;
  1462. [_redoStack removeLastObject];
  1463. _state.insideUndoBlock = YES;
  1464. self.attributedText = object.text;
  1465. self.selectedRange = object.selectedRange;
  1466. _state.insideUndoBlock = NO;
  1467. }
  1468. - (void)_restoreFirstResponderAfterUndoAlert {
  1469. if (_state.firstResponderBeforeUndoAlert) {
  1470. [self performSelector:@selector(becomeFirstResponder) withObject:nil afterDelay:0];
  1471. }
  1472. }
  1473. /// Show undo alert if it can undo or redo.
  1474. #ifdef __IPHONE_OS_VERSION_MIN_REQUIRED
  1475. - (void)_showUndoRedoAlert NS_EXTENSION_UNAVAILABLE_IOS(""){
  1476. _state.firstResponderBeforeUndoAlert = self.isFirstResponder;
  1477. __weak typeof(self) _self = self;
  1478. NSArray *strings = [self _localizedUndoStrings];
  1479. BOOL canUndo = [self _canUndo];
  1480. BOOL canRedo = [self _canRedo];
  1481. UIViewController *ctrl = [self _getRootViewController];
  1482. if (canUndo && canRedo) {
  1483. if (kiOS8Later) {
  1484. UIAlertController *alert = [UIAlertController alertControllerWithTitle:strings[4] message:@"" preferredStyle:UIAlertControllerStyleAlert];
  1485. [alert addAction:[UIAlertAction actionWithTitle:strings[3] style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
  1486. [_self _undo];
  1487. [_self _restoreFirstResponderAfterUndoAlert];
  1488. }]];
  1489. [alert addAction:[UIAlertAction actionWithTitle:strings[2] style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
  1490. [_self _redo];
  1491. [_self _restoreFirstResponderAfterUndoAlert];
  1492. }]];
  1493. [alert addAction:[UIAlertAction actionWithTitle:strings[0] style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
  1494. [_self _restoreFirstResponderAfterUndoAlert];
  1495. }]];
  1496. [ctrl presentViewController:alert animated:YES completion:nil];
  1497. } else {
  1498. #pragma clang diagnostic push
  1499. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  1500. UIAlertView *alert = [[UIAlertView alloc] initWithTitle:strings[4] message:@"" delegate:self cancelButtonTitle:strings[0] otherButtonTitles:strings[3], strings[2], nil];
  1501. [alert show];
  1502. #pragma clang diagnostic pop
  1503. }
  1504. } else if (canUndo) {
  1505. if (kiOS8Later) {
  1506. UIAlertController *alert = [UIAlertController alertControllerWithTitle:strings[4] message:@"" preferredStyle:UIAlertControllerStyleAlert];
  1507. [alert addAction:[UIAlertAction actionWithTitle:strings[3] style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
  1508. [_self _undo];
  1509. [_self _restoreFirstResponderAfterUndoAlert];
  1510. }]];
  1511. [alert addAction:[UIAlertAction actionWithTitle:strings[0] style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
  1512. [_self _restoreFirstResponderAfterUndoAlert];
  1513. }]];
  1514. [ctrl presentViewController:alert animated:YES completion:nil];
  1515. } else {
  1516. #pragma clang diagnostic push
  1517. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  1518. UIAlertView *alert = [[UIAlertView alloc] initWithTitle:strings[4] message:@"" delegate:self cancelButtonTitle:strings[0] otherButtonTitles:strings[3], nil];
  1519. [alert show];
  1520. #pragma clang diagnostic pop
  1521. }
  1522. } else if (canRedo) {
  1523. if (kiOS8Later) {
  1524. UIAlertController *alert = [UIAlertController alertControllerWithTitle:strings[2] message:@"" preferredStyle:UIAlertControllerStyleAlert];
  1525. [alert addAction:[UIAlertAction actionWithTitle:strings[1] style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
  1526. [_self _redo];
  1527. [_self _restoreFirstResponderAfterUndoAlert];
  1528. }]];
  1529. [alert addAction:[UIAlertAction actionWithTitle:strings[0] style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
  1530. [_self _restoreFirstResponderAfterUndoAlert];
  1531. }]];
  1532. [ctrl presentViewController:alert animated:YES completion:nil];
  1533. } else {
  1534. #pragma clang diagnostic push
  1535. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  1536. UIAlertView *alert = [[UIAlertView alloc] initWithTitle:strings[2] message:@"" delegate:self cancelButtonTitle:strings[0] otherButtonTitles:strings[1], nil];
  1537. [alert show];
  1538. #pragma clang diagnostic pop
  1539. }
  1540. }
  1541. }
  1542. #endif
  1543. /// Get the localized undo alert strings based on app's main bundle.
  1544. - (NSArray *)_localizedUndoStrings {
  1545. static NSArray *strings = nil;
  1546. static dispatch_once_t onceToken;
  1547. dispatch_once(&onceToken, ^{
  1548. NSDictionary *dic = @{
  1549. @"ar" : @[ @"إلغاء", @"إعادة", @"إعادة الكتابة", @"تراجع", @"تراجع عن الكتابة" ],
  1550. @"ca" : @[ @"Cancel·lar", @"Refer", @"Refer l’escriptura", @"Desfer", @"Desfer l’escriptura" ],
  1551. @"cs" : @[ @"Zrušit", @"Opakovat akci", @"Opakovat akci Psát", @"Odvolat akci", @"Odvolat akci Psát" ],
  1552. @"da" : @[ @"Annuller", @"Gentag", @"Gentag Indtastning", @"Fortryd", @"Fortryd Indtastning" ],
  1553. @"de" : @[ @"Abbrechen", @"Wiederholen", @"Eingabe wiederholen", @"Widerrufen", @"Eingabe widerrufen" ],
  1554. @"el" : @[ @"Ακύρωση", @"Επανάληψη", @"Επανάληψη πληκτρολόγησης", @"Αναίρεση", @"Αναίρεση πληκτρολόγησης" ],
  1555. @"en" : @[ @"Cancel", @"Redo", @"Redo Typing", @"Undo", @"Undo Typing" ],
  1556. @"es" : @[ @"Cancelar", @"Rehacer", @"Rehacer escritura", @"Deshacer", @"Deshacer escritura" ],
  1557. @"es_MX" : @[ @"Cancelar", @"Rehacer", @"Rehacer escritura", @"Deshacer", @"Deshacer escritura" ],
  1558. @"fi" : @[ @"Kumoa", @"Tee sittenkin", @"Kirjoita sittenkin", @"Peru", @"Peru kirjoitus" ],
  1559. @"fr" : @[ @"Annuler", @"Rétablir", @"Rétablir la saisie", @"Annuler", @"Annuler la saisie" ],
  1560. @"he" : @[ @"ביטול", @"חזור על הפעולה האחרונה", @"חזור על הקלדה", @"בטל", @"בטל הקלדה" ],
  1561. @"hr" : @[ @"Odustani", @"Ponovi", @"Ponovno upiši", @"Poništi", @"Poništi upisivanje" ],
  1562. @"hu" : @[ @"Mégsem", @"Ismétlés", @"Gépelés ismétlése", @"Visszavonás", @"Gépelés visszavonása" ],
  1563. @"id" : @[ @"Batalkan", @"Ulang", @"Ulang Pengetikan", @"Kembalikan", @"Batalkan Pengetikan" ],
  1564. @"it" : @[ @"Annulla", @"Ripristina originale", @"Ripristina Inserimento", @"Annulla", @"Annulla Inserimento" ],
  1565. @"ja" : @[ @"キャンセル", @"やり直す", @"やり直す - 入力", @"取り消す", @"取り消す - 入力" ],
  1566. @"ko" : @[ @"취소", @"실행 복귀", @"입력 복귀", @"실행 취소", @"입력 실행 취소" ],
  1567. @"ms" : @[ @"Batal", @"Buat semula", @"Ulang Penaipan", @"Buat asal", @"Buat asal Penaipan" ],
  1568. @"nb" : @[ @"Avbryt", @"Utfør likevel", @"Utfør skriving likevel", @"Angre", @"Angre skriving" ],
  1569. @"nl" : @[ @"Annuleer", @"Opnieuw", @"Opnieuw typen", @"Herstel", @"Herstel typen" ],
  1570. @"pl" : @[ @"Anuluj", @"Przywróć", @"Przywróć Wpisz", @"Cofnij", @"Cofnij Wpisz" ],
  1571. @"pt" : @[ @"Cancelar", @"Refazer", @"Refazer Digitação", @"Desfazer", @"Desfazer Digitação" ],
  1572. @"pt_PT" : @[ @"Cancelar", @"Refazer", @"Refazer digitar", @"Desfazer", @"Desfazer digitar" ],
  1573. @"ro" : @[ @"Renunță", @"Refă", @"Refă tastare", @"Anulează", @"Anulează tastare" ],
  1574. @"ru" : @[ @"Отменить", @"Повторить", @"Повторить набор на клавиатуре", @"Отменить", @"Отменить набор на клавиатуре" ],
  1575. @"sk" : @[ @"Zrušiť", @"Obnoviť", @"Obnoviť písanie", @"Odvolať", @"Odvolať písanie" ],
  1576. @"sv" : @[ @"Avbryt", @"Gör om", @"Gör om skriven text", @"Ångra", @"Ångra skriven text" ],
  1577. @"th" : @[ @"ยกเลิก", @"ทำกลับมาใหม่", @"ป้อนกลับมาใหม่", @"เลิกทำ", @"เลิกป้อน" ],
  1578. @"tr" : @[ @"Vazgeç", @"Yinele", @"Yazmayı Yinele", @"Geri Al", @"Yazmayı Geri Al" ],
  1579. @"uk" : @[ @"Скасувати", @"Повторити", @"Повторити введення", @"Відмінити", @"Відмінити введення" ],
  1580. @"vi" : @[ @"Hủy", @"Làm lại", @"Làm lại thao tác Nhập", @"Hoàn tác", @"Hoàn tác thao tác Nhập" ],
  1581. @"zh" : @[ @"取消", @"重做", @"重做键入", @"撤销", @"撤销键入" ],
  1582. @"zh_CN" : @[ @"取消", @"重做", @"重做键入", @"撤销", @"撤销键入" ],
  1583. @"zh_HK" : @[ @"取消", @"重做", @"重做輸入", @"還原", @"還原輸入" ],
  1584. @"zh_TW" : @[ @"取消", @"重做", @"重做輸入", @"還原", @"還原輸入" ]
  1585. };
  1586. NSString *preferred = [[NSBundle mainBundle] preferredLocalizations].firstObject;
  1587. if (preferred.length == 0) preferred = @"English";
  1588. NSString *canonical = [NSLocale canonicalLocaleIdentifierFromString:preferred];
  1589. if (canonical.length == 0) canonical = @"en";
  1590. strings = dic[canonical];
  1591. if (!strings && [canonical containsString:@"_"]) {
  1592. NSString *prefix = [canonical componentsSeparatedByString:@"_"].firstObject;
  1593. if (prefix.length) strings = dic[prefix];
  1594. }
  1595. if (!strings) strings = dic[@"en"];
  1596. });
  1597. return strings;
  1598. }
  1599. /// Returns the default font for text view (same as CoreText).
  1600. - (UIFont *)_defaultFont {
  1601. return [UIFont systemFontOfSize:12];
  1602. }
  1603. /// Returns the default tint color for text view (used for caret and select range background).
  1604. - (UIColor *)_defaultTintColor {
  1605. return [UIColor colorWithRed:69/255.0 green:111/255.0 blue:238/255.0 alpha:1];
  1606. }
  1607. /// Returns the default placeholder color for text view (same as UITextField).
  1608. - (UIColor *)_defaultPlaceholderColor {
  1609. return [UIColor colorWithRed:0 green:0 blue:25/255.0 alpha:44/255.0];
  1610. }
  1611. #pragma mark - Private Setter
  1612. - (void)_setText:(NSString *)text {
  1613. if (_text == text || [_text isEqualToString:text]) return;
  1614. [self willChangeValueForKey:@"text"];
  1615. _text = text.copy;
  1616. if (!_text) _text = @"";
  1617. [self didChangeValueForKey:@"text"];
  1618. self.accessibilityLabel = _text;
  1619. }
  1620. - (void)_setFont:(UIFont *)font {
  1621. if (_font == font || [_font isEqual:font]) return;
  1622. [self willChangeValueForKey:@"font"];
  1623. _font = font;
  1624. [self didChangeValueForKey:@"font"];
  1625. }
  1626. - (void)_setTextColor:(UIColor *)textColor {
  1627. if (_textColor == textColor) return;
  1628. if (_textColor && textColor) {
  1629. if (CFGetTypeID(_textColor.CGColor) == CFGetTypeID(textColor.CGColor) &&
  1630. CFGetTypeID(_textColor.CGColor) == CGColorGetTypeID()) {
  1631. if ([_textColor isEqual:textColor]) {
  1632. return;
  1633. }
  1634. }
  1635. }
  1636. [self willChangeValueForKey:@"textColor"];
  1637. _textColor = textColor;
  1638. [self didChangeValueForKey:@"textColor"];
  1639. }
  1640. - (void)_setTextAlignment:(NSTextAlignment)textAlignment {
  1641. if (_textAlignment == textAlignment) return;
  1642. [self willChangeValueForKey:@"textAlignment"];
  1643. _textAlignment = textAlignment;
  1644. [self didChangeValueForKey:@"textAlignment"];
  1645. }
  1646. - (void)_setDataDetectorTypes:(UIDataDetectorTypes)dataDetectorTypes {
  1647. if (_dataDetectorTypes == dataDetectorTypes) return;
  1648. [self willChangeValueForKey:@"dataDetectorTypes"];
  1649. _dataDetectorTypes = dataDetectorTypes;
  1650. [self didChangeValueForKey:@"dataDetectorTypes"];
  1651. }
  1652. - (void)_setLinkTextAttributes:(NSDictionary *)linkTextAttributes {
  1653. if (_linkTextAttributes == linkTextAttributes || [_linkTextAttributes isEqual:linkTextAttributes]) return;
  1654. [self willChangeValueForKey:@"linkTextAttributes"];
  1655. _linkTextAttributes = linkTextAttributes.copy;
  1656. [self didChangeValueForKey:@"linkTextAttributes"];
  1657. }
  1658. - (void)_setHighlightTextAttributes:(NSDictionary *)highlightTextAttributes {
  1659. if (_highlightTextAttributes == highlightTextAttributes || [_highlightTextAttributes isEqual:highlightTextAttributes]) return;
  1660. [self willChangeValueForKey:@"highlightTextAttributes"];
  1661. _highlightTextAttributes = highlightTextAttributes.copy;
  1662. [self didChangeValueForKey:@"highlightTextAttributes"];
  1663. }
  1664. - (void)_setTextParser:(id<YYTextParser>)textParser {
  1665. if (_textParser == textParser || [_textParser isEqual:textParser]) return;
  1666. [self willChangeValueForKey:@"textParser"];
  1667. _textParser = textParser;
  1668. [self didChangeValueForKey:@"textParser"];
  1669. }
  1670. - (void)_setAttributedText:(NSAttributedString *)attributedText {
  1671. if (_attributedText == attributedText || [_attributedText isEqual:attributedText]) return;
  1672. [self willChangeValueForKey:@"attributedText"];
  1673. _attributedText = attributedText.copy;
  1674. if (!_attributedText) _attributedText = [NSAttributedString new];
  1675. [self didChangeValueForKey:@"attributedText"];
  1676. }
  1677. - (void)_setTextContainerInset:(UIEdgeInsets)textContainerInset {
  1678. if (UIEdgeInsetsEqualToEdgeInsets(_textContainerInset, textContainerInset)) return;
  1679. [self willChangeValueForKey:@"textContainerInset"];
  1680. _textContainerInset = textContainerInset;
  1681. [self didChangeValueForKey:@"textContainerInset"];
  1682. }
  1683. - (void)_setExclusionPaths:(NSArray *)exclusionPaths {
  1684. if (_exclusionPaths == exclusionPaths || [_exclusionPaths isEqual:exclusionPaths]) return;
  1685. [self willChangeValueForKey:@"exclusionPaths"];
  1686. _exclusionPaths = exclusionPaths.copy;
  1687. [self didChangeValueForKey:@"exclusionPaths"];
  1688. }
  1689. - (void)_setVerticalForm:(BOOL)verticalForm {
  1690. if (_verticalForm == verticalForm) return;
  1691. [self willChangeValueForKey:@"verticalForm"];
  1692. _verticalForm = verticalForm;
  1693. [self didChangeValueForKey:@"verticalForm"];
  1694. }
  1695. - (void)_setLinePositionModifier:(id<YYTextLinePositionModifier>)linePositionModifier {
  1696. if (_linePositionModifier == linePositionModifier) return;
  1697. [self willChangeValueForKey:@"linePositionModifier"];
  1698. _linePositionModifier = [(NSObject *)linePositionModifier copy];
  1699. [self didChangeValueForKey:@"linePositionModifier"];
  1700. }
  1701. - (void)_setSelectedRange:(NSRange)selectedRange {
  1702. if (NSEqualRanges(_selectedRange, selectedRange)) return;
  1703. [self willChangeValueForKey:@"selectedRange"];
  1704. _selectedRange = selectedRange;
  1705. [self didChangeValueForKey:@"selectedRange"];
  1706. if ([self.delegate respondsToSelector:@selector(textViewDidChangeSelection:)]) {
  1707. [self.delegate textViewDidChangeSelection:self];
  1708. }
  1709. }
  1710. - (void)_setTypingAttributes:(NSDictionary *)typingAttributes {
  1711. if (_typingAttributes == typingAttributes || [_typingAttributes isEqual:typingAttributes]) return;
  1712. [self willChangeValueForKey:@"typingAttributes"];
  1713. _typingAttributes = typingAttributes.copy;
  1714. [self didChangeValueForKey:@"typingAttributes"];
  1715. }
  1716. #pragma mark - Private Init
  1717. - (void)_initTextView {
  1718. self.delaysContentTouches = NO;
  1719. self.canCancelContentTouches = YES;
  1720. self.multipleTouchEnabled = NO;
  1721. self.clipsToBounds = YES;
  1722. [super setDelegate:self];
  1723. _text = @"";
  1724. _attributedText = [NSAttributedString new];
  1725. // UITextInputTraits
  1726. _autocapitalizationType = UITextAutocapitalizationTypeSentences;
  1727. _autocorrectionType = UITextAutocorrectionTypeDefault;
  1728. _spellCheckingType = UITextSpellCheckingTypeDefault;
  1729. _keyboardType = UIKeyboardTypeDefault;
  1730. _keyboardAppearance = UIKeyboardAppearanceDefault;
  1731. _returnKeyType = UIReturnKeyDefault;
  1732. _enablesReturnKeyAutomatically = NO;
  1733. _secureTextEntry = NO;
  1734. // UITextInput
  1735. _selectedTextRange = [YYTextRange defaultRange];
  1736. _markedTextRange = nil;
  1737. _markedTextStyle = nil;
  1738. _tokenizer = [[UITextInputStringTokenizer alloc] initWithTextInput:self];
  1739. _editable = YES;
  1740. _selectable = YES;
  1741. _highlightable = YES;
  1742. _allowsCopyAttributedString = YES;
  1743. _textAlignment = NSTextAlignmentNatural;
  1744. _innerText = [NSMutableAttributedString new];
  1745. _innerContainer = [YYTextContainer new];
  1746. _innerContainer.insets = kDefaultInset;
  1747. _textContainerInset = kDefaultInset;
  1748. _typingAttributesHolder = [[NSMutableAttributedString alloc] initWithString:@" "];
  1749. _linkTextAttributes = @{NSForegroundColorAttributeName : [self _defaultTintColor],
  1750. (id)kCTForegroundColorAttributeName : (id)[self _defaultTintColor].CGColor};
  1751. YYTextHighlight *highlight = [YYTextHighlight new];
  1752. YYTextBorder * border = [YYTextBorder new];
  1753. border.insets = UIEdgeInsetsMake(-2, -2, -2, -2);
  1754. border.fillColor = [UIColor colorWithWhite:0.1 alpha:0.2];
  1755. border.cornerRadius = 3;
  1756. [highlight setBorder:border];
  1757. _highlightTextAttributes = highlight.attributes.copy;
  1758. _placeHolderView = [UIImageView new];
  1759. _placeHolderView.userInteractionEnabled = NO;
  1760. _placeHolderView.hidden = YES;
  1761. _containerView = [YYTextContainerView new];
  1762. _containerView.hostView = self;
  1763. _selectionView = [YYTextSelectionView new];
  1764. _selectionView.userInteractionEnabled = NO;
  1765. _selectionView.hostView = self;
  1766. _selectionView.color = [self _defaultTintColor];
  1767. _magnifierCaret = [YYTextMagnifier magnifierWithType:YYTextMagnifierTypeCaret];
  1768. _magnifierCaret.hostView = _containerView;
  1769. _magnifierRanged = [YYTextMagnifier magnifierWithType:YYTextMagnifierTypeRanged];
  1770. _magnifierRanged.hostView = _containerView;
  1771. [self addSubview:_placeHolderView];
  1772. [self addSubview:_containerView];
  1773. [self addSubview:_selectionView];
  1774. _undoStack = [NSMutableArray new];
  1775. _redoStack = [NSMutableArray new];
  1776. _allowsUndoAndRedo = YES;
  1777. _maximumUndoLevel = kDefaultUndoLevelMax;
  1778. self.debugOption = [YYTextDebugOption sharedDebugOption];
  1779. [YYTextDebugOption addDebugTarget:self];
  1780. [self _updateInnerContainerSize];
  1781. [self _update];
  1782. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_pasteboardChanged) name:UIPasteboardChangedNotification object:nil];
  1783. [[YYTextKeyboardManager defaultManager] addObserver:self];
  1784. self.isAccessibilityElement = YES;
  1785. }
  1786. #pragma mark - Public
  1787. - (instancetype)initWithFrame:(CGRect)frame {
  1788. self = [super initWithFrame:frame];
  1789. if (!self) return nil;
  1790. [self _initTextView];
  1791. return self;
  1792. }
  1793. - (void)dealloc {
  1794. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIPasteboardChangedNotification object:nil];
  1795. [[YYTextKeyboardManager defaultManager] removeObserver:self];
  1796. [[YYTextEffectWindow sharedWindow] hideSelectionDot:_selectionView];
  1797. [[YYTextEffectWindow sharedWindow] hideMagnifier:_magnifierCaret];
  1798. [[YYTextEffectWindow sharedWindow] hideMagnifier:_magnifierRanged];
  1799. [YYTextDebugOption removeDebugTarget:self];
  1800. [_longPressTimer invalidate];
  1801. [_autoScrollTimer invalidate];
  1802. [_selectionDotFixTimer invalidate];
  1803. }
  1804. - (void)scrollRangeToVisible:(NSRange)range {
  1805. YYTextRange *textRange = [YYTextRange rangeWithRange:range];
  1806. textRange = [self _correctedTextRange:textRange];
  1807. [self _scrollRangeToVisible:textRange];
  1808. }
  1809. #pragma mark - Property
  1810. - (void)setText:(NSString *)text {
  1811. if (_text == text || [_text isEqualToString:text]) return;
  1812. [self _setText:text];
  1813. _state.selectedWithoutEdit = NO;
  1814. _state.deleteConfirm = NO;
  1815. [self _endTouchTracking];
  1816. [self _hideMenu];
  1817. [self _resetUndoAndRedoStack];
  1818. [self replaceRange:[YYTextRange rangeWithRange:NSMakeRange(0, _innerText.length)] withText:text];
  1819. }
  1820. - (void)setFont:(UIFont *)font {
  1821. if (_font == font || [_font isEqual:font]) return;
  1822. [self _setFont:font];
  1823. _state.typingAttributesOnce = NO;
  1824. _typingAttributesHolder.font = font;
  1825. _innerText.font = font;
  1826. [self _resetUndoAndRedoStack];
  1827. [self _commitUpdate];
  1828. }
  1829. - (void)setTextColor:(UIColor *)textColor {
  1830. if (_textColor == textColor || [_textColor isEqual:textColor]) return;
  1831. [self _setTextColor:textColor];
  1832. _state.typingAttributesOnce = NO;
  1833. _typingAttributesHolder.color = textColor;
  1834. _innerText.color = textColor;
  1835. [self _resetUndoAndRedoStack];
  1836. [self _commitUpdate];
  1837. }
  1838. - (void)setTextAlignment:(NSTextAlignment)textAlignment {
  1839. if (_textAlignment == textAlignment) return;
  1840. [self _setTextAlignment:textAlignment];
  1841. _typingAttributesHolder.alignment = textAlignment;
  1842. _innerText.alignment = textAlignment;
  1843. [self _resetUndoAndRedoStack];
  1844. [self _commitUpdate];
  1845. }
  1846. - (void)setDataDetectorTypes:(UIDataDetectorTypes)dataDetectorTypes {
  1847. if (_dataDetectorTypes == dataDetectorTypes) return;
  1848. [self _setDataDetectorTypes:dataDetectorTypes];
  1849. NSTextCheckingType type = NSTextCheckingTypeFromUIDataDetectorType(dataDetectorTypes);
  1850. _dataDetector = type ? [NSDataDetector dataDetectorWithTypes:type error:NULL] : nil;
  1851. [self _resetUndoAndRedoStack];
  1852. [self _commitUpdate];
  1853. }
  1854. - (void)setLinkTextAttributes:(NSDictionary *)linkTextAttributes {
  1855. if (_linkTextAttributes == linkTextAttributes || [_linkTextAttributes isEqual:linkTextAttributes]) return;
  1856. [self _setLinkTextAttributes:linkTextAttributes];
  1857. if (_dataDetector) {
  1858. [self _commitUpdate];
  1859. }
  1860. }
  1861. - (void)setHighlightTextAttributes:(NSDictionary *)highlightTextAttributes {
  1862. if (_highlightTextAttributes == highlightTextAttributes || [_highlightTextAttributes isEqual:highlightTextAttributes]) return;
  1863. [self _setHighlightTextAttributes:highlightTextAttributes];
  1864. if (_dataDetector) {
  1865. [self _commitUpdate];
  1866. }
  1867. }
  1868. - (void)setTextParser:(id<YYTextParser>)textParser {
  1869. if (_textParser == textParser || [_textParser isEqual:textParser]) return;
  1870. [self _setTextParser:textParser];
  1871. if (textParser && _text.length) {
  1872. [self replaceRange:[YYTextRange rangeWithRange:NSMakeRange(0, _text.length)] withText:_text];
  1873. }
  1874. [self _resetUndoAndRedoStack];
  1875. [self _commitUpdate];
  1876. }
  1877. - (void)setTypingAttributes:(NSDictionary *)typingAttributes {
  1878. [self _setTypingAttributes:typingAttributes];
  1879. _state.typingAttributesOnce = YES;
  1880. [typingAttributes enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
  1881. [_typingAttributesHolder setAttribute:key value:obj];
  1882. }];
  1883. [self _commitUpdate];
  1884. }
  1885. - (void)setAttributedText:(NSAttributedString *)attributedText {
  1886. if (_attributedText == attributedText) return;
  1887. [self _setAttributedText:attributedText];
  1888. _state.typingAttributesOnce = NO;
  1889. NSMutableAttributedString *text = attributedText.mutableCopy;
  1890. if (text.length == 0) {
  1891. [self replaceRange:[YYTextRange rangeWithRange:NSMakeRange(0, _innerText.length)] withText:@""];
  1892. return;
  1893. }
  1894. if ([self.delegate respondsToSelector:@selector(textView:shouldChangeTextInRange:replacementText:)]) {
  1895. BOOL should = [self.delegate textView:self shouldChangeTextInRange:NSMakeRange(0, _innerText.length) replacementText:text.string];
  1896. if (!should) return;
  1897. }
  1898. _state.selectedWithoutEdit = NO;
  1899. _state.deleteConfirm = NO;
  1900. [self _endTouchTracking];
  1901. [self _hideMenu];
  1902. [_inputDelegate selectionWillChange:self];
  1903. [_inputDelegate textWillChange:self];
  1904. _innerText = text;
  1905. [self _parseText];
  1906. _selectedTextRange = [YYTextRange rangeWithRange:NSMakeRange(0, _innerText.length)];
  1907. [_inputDelegate textDidChange:self];
  1908. [_inputDelegate selectionDidChange:self];
  1909. [self _setAttributedText:text];
  1910. if (_innerText.length > 0) {
  1911. _typingAttributesHolder.attributes = [_innerText attributesAtIndex:_innerText.length - 1];
  1912. }
  1913. [self _updateOuterProperties];
  1914. [self _updateLayout];
  1915. [self _updateSelectionView];
  1916. if (self.isFirstResponder) {
  1917. [self _scrollRangeToVisible:_selectedTextRange];
  1918. }
  1919. if ([self.delegate respondsToSelector:@selector(textViewDidChange:)]) {
  1920. [self.delegate textViewDidChange:self];
  1921. }
  1922. [[NSNotificationCenter defaultCenter] postNotificationName:YYTextViewTextDidChangeNotification object:self];
  1923. if (!_state.insideUndoBlock) {
  1924. [self _resetUndoAndRedoStack];
  1925. }
  1926. }
  1927. - (void)setTextVerticalAlignment:(YYTextVerticalAlignment)textVerticalAlignment {
  1928. if (_textVerticalAlignment == textVerticalAlignment) return;
  1929. [self willChangeValueForKey:@"textVerticalAlignment"];
  1930. _textVerticalAlignment = textVerticalAlignment;
  1931. [self didChangeValueForKey:@"textVerticalAlignment"];
  1932. _containerView.textVerticalAlignment = textVerticalAlignment;
  1933. [self _commitUpdate];
  1934. }
  1935. - (void)setTextContainerInset:(UIEdgeInsets)textContainerInset {
  1936. if (UIEdgeInsetsEqualToEdgeInsets(_textContainerInset, textContainerInset)) return;
  1937. [self _setTextContainerInset:textContainerInset];
  1938. _innerContainer.insets = textContainerInset;
  1939. [self _commitUpdate];
  1940. }
  1941. - (void)setExclusionPaths:(NSArray *)exclusionPaths {
  1942. if (_exclusionPaths == exclusionPaths || [_exclusionPaths isEqual:exclusionPaths]) return;
  1943. [self _setExclusionPaths:exclusionPaths];
  1944. _innerContainer.exclusionPaths = exclusionPaths;
  1945. if (_innerContainer.isVerticalForm) {
  1946. CGAffineTransform trans = CGAffineTransformMakeTranslation(_innerContainer.size.width - self.bounds.size.width, 0);
  1947. [_innerContainer.exclusionPaths enumerateObjectsUsingBlock:^(UIBezierPath *path, NSUInteger idx, BOOL *stop) {
  1948. [path applyTransform:trans];
  1949. }];
  1950. }
  1951. [self _commitUpdate];
  1952. }
  1953. - (void)setVerticalForm:(BOOL)verticalForm {
  1954. if (_verticalForm == verticalForm) return;
  1955. [self _setVerticalForm:verticalForm];
  1956. _innerContainer.verticalForm = verticalForm;
  1957. _selectionView.verticalForm = verticalForm;
  1958. [self _updateInnerContainerSize];
  1959. if (verticalForm) {
  1960. if (UIEdgeInsetsEqualToEdgeInsets(_innerContainer.insets, kDefaultInset)) {
  1961. _innerContainer.insets = kDefaultVerticalInset;
  1962. [self _setTextContainerInset:kDefaultVerticalInset];
  1963. }
  1964. } else {
  1965. if (UIEdgeInsetsEqualToEdgeInsets(_innerContainer.insets, kDefaultVerticalInset)) {
  1966. _innerContainer.insets = kDefaultInset;
  1967. [self _setTextContainerInset:kDefaultInset];
  1968. }
  1969. }
  1970. _innerContainer.exclusionPaths = _exclusionPaths;
  1971. if (verticalForm) {
  1972. CGAffineTransform trans = CGAffineTransformMakeTranslation(_innerContainer.size.width - self.bounds.size.width, 0);
  1973. [_innerContainer.exclusionPaths enumerateObjectsUsingBlock:^(UIBezierPath *path, NSUInteger idx, BOOL *stop) {
  1974. [path applyTransform:trans];
  1975. }];
  1976. }
  1977. [self _keyboardChanged];
  1978. [self _commitUpdate];
  1979. }
  1980. - (void)setLinePositionModifier:(id<YYTextLinePositionModifier>)linePositionModifier {
  1981. if (_linePositionModifier == linePositionModifier) return;
  1982. [self _setLinePositionModifier:linePositionModifier];
  1983. _innerContainer.linePositionModifier = linePositionModifier;
  1984. [self _commitUpdate];
  1985. }
  1986. - (void)setSelectedRange:(NSRange)selectedRange {
  1987. if (NSEqualRanges(_selectedRange, selectedRange)) return;
  1988. if (_markedTextRange) return;
  1989. _state.typingAttributesOnce = NO;
  1990. YYTextRange *range = [YYTextRange rangeWithRange:selectedRange];
  1991. range = [self _correctedTextRange:range];
  1992. [self _endTouchTracking];
  1993. _selectedTextRange = range;
  1994. [self _updateSelectionView];
  1995. [self _setSelectedRange:range.asRange];
  1996. if (!_state.insideUndoBlock) {
  1997. [self _resetUndoAndRedoStack];
  1998. }
  1999. }
  2000. - (void)setHighlightable:(BOOL)highlightable {
  2001. if (_highlightable == highlightable) return;
  2002. [self willChangeValueForKey:@"highlightable"];
  2003. _highlightable = highlightable;
  2004. [self didChangeValueForKey:@"highlightable"];
  2005. [self _commitUpdate];
  2006. }
  2007. - (void)setEditable:(BOOL)editable {
  2008. if (_editable == editable) return;
  2009. [self willChangeValueForKey:@"editable"];
  2010. _editable = editable;
  2011. [self didChangeValueForKey:@"editable"];
  2012. if (!editable) {
  2013. [self resignFirstResponder];
  2014. }
  2015. }
  2016. - (void)setSelectable:(BOOL)selectable {
  2017. if (_selectable == selectable) return;
  2018. [self willChangeValueForKey:@"selectable"];
  2019. _selectable = selectable;
  2020. [self didChangeValueForKey:@"selectable"];
  2021. if (!selectable) {
  2022. if (self.isFirstResponder) {
  2023. [self resignFirstResponder];
  2024. } else {
  2025. _state.selectedWithoutEdit = NO;
  2026. [self _endTouchTracking];
  2027. [self _hideMenu];
  2028. [self _updateSelectionView];
  2029. }
  2030. }
  2031. }
  2032. - (void)setClearsOnInsertion:(BOOL)clearsOnInsertion {
  2033. if (_clearsOnInsertion == clearsOnInsertion) return;
  2034. _clearsOnInsertion = clearsOnInsertion;
  2035. if (clearsOnInsertion) {
  2036. if (self.isFirstResponder) {
  2037. self.selectedRange = NSMakeRange(0, _attributedText.length);
  2038. } else {
  2039. _state.clearsOnInsertionOnce = YES;
  2040. }
  2041. }
  2042. }
  2043. - (void)setDebugOption:(YYTextDebugOption *)debugOption {
  2044. _containerView.debugOption = debugOption;
  2045. }
  2046. - (YYTextDebugOption *)debugOption {
  2047. return _containerView.debugOption;
  2048. }
  2049. - (YYTextLayout *)textLayout {
  2050. [self _updateIfNeeded];
  2051. return _innerLayout;
  2052. }
  2053. - (void)setPlaceholderText:(NSString *)placeholderText {
  2054. if (_placeholderAttributedText.length > 0) {
  2055. if (placeholderText.length > 0) {
  2056. [((NSMutableAttributedString *)_placeholderAttributedText) replaceCharactersInRange:NSMakeRange(0, _placeholderAttributedText.length) withString:placeholderText];
  2057. } else {
  2058. [((NSMutableAttributedString *)_placeholderAttributedText) replaceCharactersInRange:NSMakeRange(0, _placeholderAttributedText.length) withString:@""];
  2059. }
  2060. ((NSMutableAttributedString *)_placeholderAttributedText).font = _placeholderFont;
  2061. ((NSMutableAttributedString *)_placeholderAttributedText).color = _placeholderTextColor;
  2062. } else {
  2063. if (placeholderText.length > 0) {
  2064. NSMutableAttributedString *atr = [[NSMutableAttributedString alloc] initWithString:placeholderText];
  2065. if (!_placeholderFont) _placeholderFont = _font;
  2066. if (!_placeholderFont) _placeholderFont = [self _defaultFont];
  2067. if (!_placeholderTextColor) _placeholderTextColor = [self _defaultPlaceholderColor];
  2068. atr.font = _placeholderFont;
  2069. atr.color = _placeholderTextColor;
  2070. _placeholderAttributedText = atr;
  2071. }
  2072. }
  2073. _placeholderText = [_placeholderAttributedText plainTextForRange:NSMakeRange(0, _placeholderAttributedText.length)];
  2074. [self _commitPlaceholderUpdate];
  2075. }
  2076. - (void)setPlaceholderFont:(UIFont *)placeholderFont {
  2077. _placeholderFont = placeholderFont;
  2078. ((NSMutableAttributedString *)_placeholderAttributedText).font = _placeholderFont;
  2079. [self _commitPlaceholderUpdate];
  2080. }
  2081. - (void)setPlaceholderTextColor:(UIColor *)placeholderTextColor {
  2082. _placeholderTextColor = placeholderTextColor;
  2083. ((NSMutableAttributedString *)_placeholderAttributedText).color = _placeholderTextColor;
  2084. [self _commitPlaceholderUpdate];
  2085. }
  2086. - (void)setPlaceholderAttributedText:(NSAttributedString *)placeholderAttributedText {
  2087. _placeholderAttributedText = placeholderAttributedText.mutableCopy;
  2088. _placeholderText = [_placeholderAttributedText plainTextForRange:NSMakeRange(0, _placeholderAttributedText.length)];
  2089. _placeholderFont = _placeholderAttributedText.font;
  2090. _placeholderTextColor = _placeholderAttributedText.color;
  2091. [self _commitPlaceholderUpdate];
  2092. }
  2093. #pragma mark - Override For Protect
  2094. - (void)setMultipleTouchEnabled:(BOOL)multipleTouchEnabled {
  2095. [super setMultipleTouchEnabled:NO]; // must not enabled
  2096. }
  2097. - (void)setContentInset:(UIEdgeInsets)contentInset {
  2098. UIEdgeInsets oldInsets = self.contentInset;
  2099. if (_insetModifiedByKeyboard) {
  2100. _originalContentInset = contentInset;
  2101. } else {
  2102. [super setContentInset:contentInset];
  2103. BOOL changed = !UIEdgeInsetsEqualToEdgeInsets(oldInsets, contentInset);
  2104. if (changed) {
  2105. [self _updateInnerContainerSize];
  2106. [self _commitUpdate];
  2107. [self _commitPlaceholderUpdate];
  2108. }
  2109. }
  2110. }
  2111. - (void)setScrollIndicatorInsets:(UIEdgeInsets)scrollIndicatorInsets {
  2112. if (_insetModifiedByKeyboard) {
  2113. _originalScrollIndicatorInsets = scrollIndicatorInsets;
  2114. } else {
  2115. [super setScrollIndicatorInsets:scrollIndicatorInsets];
  2116. }
  2117. }
  2118. - (void)setFrame:(CGRect)frame {
  2119. CGSize oldSize = self.bounds.size;
  2120. [super setFrame:frame];
  2121. CGSize newSize = self.bounds.size;
  2122. BOOL changed = _innerContainer.isVerticalForm ? (oldSize.height != newSize.height) : (oldSize.width != newSize.width);
  2123. if (changed) {
  2124. [self _updateInnerContainerSize];
  2125. [self _commitUpdate];
  2126. }
  2127. if (!CGSizeEqualToSize(oldSize, newSize)) {
  2128. [self _commitPlaceholderUpdate];
  2129. }
  2130. }
  2131. - (void)setBounds:(CGRect)bounds {
  2132. CGSize oldSize = self.bounds.size;
  2133. [super setBounds:bounds];
  2134. CGSize newSize = self.bounds.size;
  2135. BOOL changed = _innerContainer.isVerticalForm ? (oldSize.height != newSize.height) : (oldSize.width != newSize.width);
  2136. if (changed) {
  2137. [self _updateInnerContainerSize];
  2138. [self _commitUpdate];
  2139. }
  2140. if (!CGSizeEqualToSize(oldSize, newSize)) {
  2141. [self _commitPlaceholderUpdate];
  2142. }
  2143. }
  2144. - (void)tintColorDidChange {
  2145. if ([self respondsToSelector:@selector(tintColor)]) {
  2146. UIColor *color = self.tintColor;
  2147. NSMutableDictionary *attrs = _highlightTextAttributes.mutableCopy;
  2148. NSMutableDictionary *linkAttrs = _linkTextAttributes.mutableCopy;
  2149. if (!linkAttrs) linkAttrs = @{}.mutableCopy;
  2150. if (!color) {
  2151. [attrs removeObjectForKey:NSForegroundColorAttributeName];
  2152. [attrs removeObjectForKey:(id)kCTForegroundColorAttributeName];
  2153. [linkAttrs setObject:[self _defaultTintColor] forKey:NSForegroundColorAttributeName];
  2154. [linkAttrs setObject:(id)[self _defaultTintColor].CGColor forKey:(id)kCTForegroundColorAttributeName];
  2155. } else {
  2156. [attrs setObject:color forKey:NSForegroundColorAttributeName];
  2157. [attrs setObject:(id)color.CGColor forKey:(id)kCTForegroundColorAttributeName];
  2158. [linkAttrs setObject:color forKey:NSForegroundColorAttributeName];
  2159. [linkAttrs setObject:(id)color.CGColor forKey:(id)kCTForegroundColorAttributeName];
  2160. }
  2161. self.highlightTextAttributes = attrs;
  2162. _selectionView.color = color ? color : [self _defaultTintColor];
  2163. _linkTextAttributes = linkAttrs;
  2164. [self _commitUpdate];
  2165. }
  2166. }
  2167. - (CGSize)sizeThatFits:(CGSize)size {
  2168. if (!_verticalForm && size.width <= 0) size.width = YYTextContainerMaxSize.width;
  2169. if (_verticalForm && size.height <= 0) size.height = YYTextContainerMaxSize.height;
  2170. if ((!_verticalForm && size.width == self.bounds.size.width) ||
  2171. (_verticalForm && size.height == self.bounds.size.height)) {
  2172. [self _updateIfNeeded];
  2173. if (!_verticalForm) {
  2174. if (_containerView.bounds.size.height <= size.height) {
  2175. return _containerView.bounds.size;
  2176. }
  2177. } else {
  2178. if (_containerView.bounds.size.width <= size.width) {
  2179. return _containerView.bounds.size;
  2180. }
  2181. }
  2182. }
  2183. if (!_verticalForm) {
  2184. size.height = YYTextContainerMaxSize.height;
  2185. } else {
  2186. size.width = YYTextContainerMaxSize.width;
  2187. }
  2188. YYTextContainer *container = [_innerContainer copy];
  2189. container.size = size;
  2190. YYTextLayout *layout = [YYTextLayout layoutWithContainer:container text:_innerText];
  2191. return layout.textBoundingSize;
  2192. }
  2193. #pragma mark - Override UIResponder
  2194. - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
  2195. [self _updateIfNeeded];
  2196. UITouch *touch = touches.anyObject;
  2197. CGPoint point = [touch locationInView:_containerView];
  2198. _touchBeganTime = _trackingTime = touch.timestamp;
  2199. _touchBeganPoint = _trackingPoint = point;
  2200. _trackingRange = _selectedTextRange;
  2201. _state.trackingGrabber = NO;
  2202. _state.trackingCaret = NO;
  2203. _state.trackingPreSelect = NO;
  2204. _state.trackingTouch = YES;
  2205. _state.swallowTouch = YES;
  2206. _state.touchMoved = NO;
  2207. if (!self.isFirstResponder && !_state.selectedWithoutEdit && self.highlightable) {
  2208. _highlight = [self _getHighlightAtPoint:point range:&_highlightRange];
  2209. _highlightLayout = nil;
  2210. }
  2211. if ((!self.selectable && !_highlight) || _state.ignoreTouchBegan) {
  2212. _state.swallowTouch = NO;
  2213. _state.trackingTouch = NO;
  2214. }
  2215. if (_state.trackingTouch) {
  2216. [self _startLongPressTimer];
  2217. if (_highlight) {
  2218. [self _showHighlightAnimated:NO];
  2219. } else {
  2220. if ([_selectionView isGrabberContainsPoint:point]) { // track grabber
  2221. self.panGestureRecognizer.enabled = NO; // disable scroll view
  2222. [self _hideMenu];
  2223. _state.trackingGrabber = [_selectionView isStartGrabberContainsPoint:point] ? kStart : kEnd;
  2224. _magnifierRangedOffset = [self _getMagnifierRangedOffset];
  2225. } else {
  2226. if (_selectedTextRange.asRange.length == 0 && self.isFirstResponder) {
  2227. if ([_selectionView isCaretContainsPoint:point]) { // track caret
  2228. _state.trackingCaret = YES;
  2229. self.panGestureRecognizer.enabled = NO; // disable scroll view
  2230. }
  2231. }
  2232. }
  2233. }
  2234. [self _updateSelectionView];
  2235. }
  2236. if (!_state.swallowTouch) [super touchesBegan:touches withEvent:event];
  2237. }
  2238. - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
  2239. [self _updateIfNeeded];
  2240. UITouch *touch = touches.anyObject;
  2241. CGPoint point = [touch locationInView:_containerView];
  2242. _trackingTime = touch.timestamp;
  2243. _trackingPoint = point;
  2244. if (!_state.touchMoved) {
  2245. _state.touchMoved = [self _getMoveDirection];
  2246. if (_state.touchMoved) [self _endLongPressTimer];
  2247. }
  2248. _state.clearsOnInsertionOnce = NO;
  2249. if (_state.trackingTouch) {
  2250. BOOL showMagnifierCaret = NO;
  2251. BOOL showMagnifierRanged = NO;
  2252. if (_highlight) {
  2253. YYTextHighlight *highlight = [self _getHighlightAtPoint:_trackingPoint range:NULL];
  2254. if (highlight == _highlight) {
  2255. [self _showHighlightAnimated:YES];
  2256. } else {
  2257. [self _hideHighlightAnimated:YES];
  2258. }
  2259. } else {
  2260. _trackingRange = _selectedTextRange;
  2261. if (_state.trackingGrabber) {
  2262. self.panGestureRecognizer.enabled = NO;
  2263. [self _hideMenu];
  2264. [self _updateTextRangeByTrackingGrabber];
  2265. showMagnifierRanged = YES;
  2266. } else if (_state.trackingPreSelect) {
  2267. [self _updateTextRangeByTrackingPreSelect];
  2268. showMagnifierCaret = YES;
  2269. } else if (_state.trackingCaret || _markedTextRange || self.isFirstResponder) {
  2270. if (_state.trackingCaret || _state.touchMoved) {
  2271. _state.trackingCaret = YES;
  2272. [self _hideMenu];
  2273. if (_verticalForm) {
  2274. if (_state.touchMoved == kTop || _state.touchMoved == kBottom) {
  2275. self.panGestureRecognizer.enabled = NO;
  2276. }
  2277. } else {
  2278. if (_state.touchMoved == kLeft || _state.touchMoved == kRight) {
  2279. self.panGestureRecognizer.enabled = NO;
  2280. }
  2281. }
  2282. [self _updateTextRangeByTrackingCaret];
  2283. if (_markedTextRange) {
  2284. showMagnifierRanged = YES;
  2285. } else {
  2286. showMagnifierCaret = YES;
  2287. }
  2288. }
  2289. }
  2290. }
  2291. [self _updateSelectionView];
  2292. if (showMagnifierCaret) [self _showMagnifierCaret];
  2293. if (showMagnifierRanged) [self _showMagnifierRanged];
  2294. }
  2295. CGFloat autoScrollOffset = [self _getAutoscrollOffset];
  2296. if (_autoScrollOffset != autoScrollOffset) {
  2297. if (fabs(autoScrollOffset) < fabs(_autoScrollOffset)) {
  2298. _autoScrollAcceleration *= 0.5;
  2299. }
  2300. _autoScrollOffset = autoScrollOffset;
  2301. if (_autoScrollOffset != 0 && _state.touchMoved) {
  2302. [self _startAutoScrollTimer];
  2303. }
  2304. }
  2305. if (!_state.swallowTouch) [super touchesMoved:touches withEvent:event];
  2306. }
  2307. - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
  2308. [self _updateIfNeeded];
  2309. UITouch *touch = touches.anyObject;
  2310. CGPoint point = [touch locationInView:_containerView];
  2311. _trackingTime = touch.timestamp;
  2312. _trackingPoint = point;
  2313. if (!_state.touchMoved) {
  2314. _state.touchMoved = [self _getMoveDirection];
  2315. }
  2316. if (_state.trackingTouch) {
  2317. [self _hideMagnifier];
  2318. if (_highlight) {
  2319. if (_state.showingHighlight) {
  2320. if (_highlight.tapAction) {
  2321. CGRect rect = [_innerLayout rectForRange:[YYTextRange rangeWithRange:_highlightRange]];
  2322. rect = [self _convertRectFromLayout:rect];
  2323. _highlight.tapAction(self, _innerText, _highlightRange, rect);
  2324. } else {
  2325. BOOL shouldTap = YES;
  2326. if ([self.delegate respondsToSelector:@selector(textView:shouldTapHighlight:inRange:)]) {
  2327. shouldTap = [self.delegate textView:self shouldTapHighlight:_highlight inRange:_highlightRange];
  2328. }
  2329. if (shouldTap && [self.delegate respondsToSelector:@selector(textView:didTapHighlight:inRange:rect:)]) {
  2330. CGRect rect = [_innerLayout rectForRange:[YYTextRange rangeWithRange:_highlightRange]];
  2331. rect = [self _convertRectFromLayout:rect];
  2332. [self.delegate textView:self didTapHighlight:_highlight inRange:_highlightRange rect:rect];
  2333. }
  2334. }
  2335. [self _removeHighlightAnimated:YES];
  2336. }
  2337. } else {
  2338. if (_state.trackingCaret) {
  2339. if (_state.touchMoved) {
  2340. [self _updateTextRangeByTrackingCaret];
  2341. [self _showMenu];
  2342. } else {
  2343. if (_state.showingMenu) [self _hideMenu];
  2344. else [self _showMenu];
  2345. }
  2346. } else if (_state.trackingGrabber) {
  2347. [self _updateTextRangeByTrackingGrabber];
  2348. [self _showMenu];
  2349. } else if (_state.trackingPreSelect) {
  2350. [self _updateTextRangeByTrackingPreSelect];
  2351. if (_trackingRange.asRange.length > 0) {
  2352. _state.selectedWithoutEdit = YES;
  2353. [self _showMenu];
  2354. } else {
  2355. [self performSelector:@selector(becomeFirstResponder) withObject:nil afterDelay:0];
  2356. }
  2357. } else if (_state.deleteConfirm || _markedTextRange) {
  2358. [self _updateTextRangeByTrackingCaret];
  2359. [self _hideMenu];
  2360. } else {
  2361. if (!_state.touchMoved) {
  2362. if (_state.selectedWithoutEdit) {
  2363. _state.selectedWithoutEdit = NO;
  2364. [self _hideMenu];
  2365. } else {
  2366. if (self.isFirstResponder) {
  2367. YYTextRange *_oldRange = _trackingRange;
  2368. [self _updateTextRangeByTrackingCaret];
  2369. if ([_oldRange isEqual:_trackingRange]) {
  2370. if (_state.showingMenu) [self _hideMenu];
  2371. else [self _showMenu];
  2372. } else {
  2373. [self _hideMenu];
  2374. }
  2375. } else {
  2376. [self _hideMenu];
  2377. if (_state.clearsOnInsertionOnce) {
  2378. _state.clearsOnInsertionOnce = NO;
  2379. _selectedTextRange = [YYTextRange rangeWithRange:NSMakeRange(0, _innerText.length)];
  2380. [self _setSelectedRange:_selectedTextRange.asRange];
  2381. } else {
  2382. [self _updateTextRangeByTrackingCaret];
  2383. }
  2384. [self performSelector:@selector(becomeFirstResponder) withObject:nil afterDelay:0];
  2385. }
  2386. }
  2387. }
  2388. }
  2389. }
  2390. if (_trackingRange && (![_trackingRange isEqual:_selectedTextRange] || _state.trackingPreSelect)) {
  2391. if (![_trackingRange isEqual:_selectedTextRange]) {
  2392. [_inputDelegate selectionWillChange:self];
  2393. _selectedTextRange = _trackingRange;
  2394. [_inputDelegate selectionDidChange:self];
  2395. [self _updateAttributesHolder];
  2396. [self _updateOuterProperties];
  2397. }
  2398. if (!_state.trackingGrabber && !_state.trackingPreSelect) {
  2399. [self _scrollRangeToVisible:_selectedTextRange];
  2400. }
  2401. }
  2402. [self _endTouchTracking];
  2403. }
  2404. if (!_state.swallowTouch) [super touchesEnded:touches withEvent:event];
  2405. }
  2406. - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
  2407. [self _endTouchTracking];
  2408. [self _hideMenu];
  2409. if (!_state.swallowTouch) [super touchesCancelled:touches withEvent:event];
  2410. }
  2411. - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
  2412. if (motion == UIEventSubtypeMotionShake && _allowsUndoAndRedo) {
  2413. if (![UIApplication isAppExtension]) {
  2414. #pragma clang diagnostic push
  2415. #pragma clang diagnostic ignored "-Wundeclared-selector"
  2416. [self performSelector:@selector(_showUndoRedoAlert)];
  2417. #pragma clang diagnostic pop
  2418. }
  2419. } else {
  2420. [super motionEnded:motion withEvent:event];
  2421. }
  2422. }
  2423. - (BOOL)canBecomeFirstResponder {
  2424. if (!self.isSelectable) return NO;
  2425. if (!self.isEditable) return NO;
  2426. if (_state.ignoreFirstResponder) return NO;
  2427. if ([self.delegate respondsToSelector:@selector(textViewShouldBeginEditing:)]) {
  2428. if (![self.delegate textViewShouldBeginEditing:self]) return NO;
  2429. }
  2430. return YES;
  2431. }
  2432. - (BOOL)becomeFirstResponder {
  2433. BOOL isFirstResponder = self.isFirstResponder;
  2434. if (isFirstResponder) return YES;
  2435. BOOL shouldDetectData = [self _shouldDetectText];
  2436. BOOL become = [super becomeFirstResponder];
  2437. if (!isFirstResponder && become) {
  2438. [self _endTouchTracking];
  2439. [self _hideMenu];
  2440. _state.selectedWithoutEdit = NO;
  2441. if (shouldDetectData != [self _shouldDetectText]) {
  2442. [self _update];
  2443. }
  2444. [self _updateIfNeeded];
  2445. [self _updateSelectionView];
  2446. [self performSelector:@selector(_scrollSelectedRangeToVisible) withObject:nil afterDelay:0];
  2447. if ([self.delegate respondsToSelector:@selector(textViewDidBeginEditing:)]) {
  2448. [self.delegate textViewDidBeginEditing:self];
  2449. }
  2450. [[NSNotificationCenter defaultCenter] postNotificationName:YYTextViewTextDidBeginEditingNotification object:self];
  2451. }
  2452. return become;
  2453. }
  2454. - (BOOL)canResignFirstResponder {
  2455. if (!self.isFirstResponder) return YES;
  2456. if ([self.delegate respondsToSelector:@selector(textViewShouldEndEditing:)]) {
  2457. if (![self.delegate textViewShouldEndEditing:self]) return NO;
  2458. }
  2459. return YES;
  2460. }
  2461. - (BOOL)resignFirstResponder {
  2462. BOOL isFirstResponder = self.isFirstResponder;
  2463. if (!isFirstResponder) return YES;
  2464. BOOL resign = [super resignFirstResponder];
  2465. if (resign) {
  2466. if (_markedTextRange) {
  2467. _markedTextRange = nil;
  2468. [self _parseText];
  2469. [self _setText:[_innerText plainTextForRange:NSMakeRange(0, _innerText.length)]];
  2470. }
  2471. _state.selectedWithoutEdit = NO;
  2472. if ([self _shouldDetectText]) {
  2473. [self _update];
  2474. }
  2475. [self _endTouchTracking];
  2476. [self _hideMenu];
  2477. [self _updateIfNeeded];
  2478. [self _updateSelectionView];
  2479. [self _restoreInsetsAnimated:YES];
  2480. if ([self.delegate respondsToSelector:@selector(textViewDidEndEditing:)]) {
  2481. [self.delegate textViewDidEndEditing:self];
  2482. }
  2483. [[NSNotificationCenter defaultCenter] postNotificationName:YYTextViewTextDidEndEditingNotification object:self];
  2484. }
  2485. return resign;
  2486. }
  2487. - (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
  2488. /*
  2489. ------------------------------------------------------
  2490. Default menu actions list:
  2491. cut: Cut
  2492. copy: Copy
  2493. select: Select
  2494. selectAll: Select All
  2495. paste: Paste
  2496. delete: Delete
  2497. _promptForReplace: Replace...
  2498. _transliterateChinese: 简⇄繁
  2499. _showTextStyleOptions: 𝐁𝐼𝐔
  2500. _define: Define
  2501. _addShortcut: Add...
  2502. _accessibilitySpeak: Speak
  2503. _accessibilitySpeakLanguageSelection: Speak...
  2504. _accessibilityPauseSpeaking: Pause Speak
  2505. makeTextWritingDirectionRightToLeft: ⇋
  2506. makeTextWritingDirectionLeftToRight: ⇌
  2507. ------------------------------------------------------
  2508. Default attribute modifier list:
  2509. toggleBoldface:
  2510. toggleItalics:
  2511. toggleUnderline:
  2512. increaseSize:
  2513. decreaseSize:
  2514. */
  2515. if (_selectedTextRange.asRange.length == 0) {
  2516. if (action == @selector(select:) ||
  2517. action == @selector(selectAll:)) {
  2518. return _innerText.length > 0;
  2519. }
  2520. if (action == @selector(paste:)) {
  2521. return [self _isPasteboardContainsValidValue];
  2522. }
  2523. } else {
  2524. if (action == @selector(cut:)) {
  2525. return self.isFirstResponder && self.editable;
  2526. }
  2527. if (action == @selector(copy:)) {
  2528. return YES;
  2529. }
  2530. if (action == @selector(selectAll:)) {
  2531. return _selectedTextRange.asRange.length < _innerText.length;
  2532. }
  2533. if (action == @selector(paste:)) {
  2534. return self.isFirstResponder && self.editable && [self _isPasteboardContainsValidValue];
  2535. }
  2536. NSString *selString = NSStringFromSelector(action);
  2537. if ([selString hasSuffix:@"define:"] && [selString hasPrefix:@"_"]) {
  2538. return [self _getRootViewController] != nil;
  2539. }
  2540. }
  2541. return NO;
  2542. }
  2543. - (void)reloadInputViews {
  2544. [super reloadInputViews];
  2545. if (_markedTextRange) {
  2546. [self unmarkText];
  2547. }
  2548. }
  2549. #pragma mark - Override NSObject(UIResponderStandardEditActions)
  2550. - (void)cut:(id)sender {
  2551. [self _endTouchTracking];
  2552. if (_selectedTextRange.asRange.length == 0) return;
  2553. [self _copySelectedTextToPasteboard];
  2554. [self _saveToUndoStack];
  2555. [self _resetRedoStack];
  2556. [self replaceRange:_selectedTextRange withText:@""];
  2557. }
  2558. - (void)copy:(id)sender {
  2559. [self _endTouchTracking];
  2560. [self _copySelectedTextToPasteboard];
  2561. }
  2562. - (void)paste:(id)sender {
  2563. [self _endTouchTracking];
  2564. UIPasteboard *p = [UIPasteboard generalPasteboard];
  2565. NSAttributedString *atr = nil;
  2566. if (_allowsPasteAttributedString) {
  2567. atr = p.attributedString;
  2568. if (atr.length == 0) atr = nil;
  2569. }
  2570. if (!atr && _allowsPasteImage) {
  2571. UIImage *img = nil;
  2572. if (p.GIFData) {
  2573. img = [YYImage imageWithData:p.GIFData scale:kScreenScale];
  2574. }
  2575. if (!img && p.PNGData) {
  2576. img = [YYImage imageWithData:p.PNGData scale:kScreenScale];
  2577. }
  2578. if (!img && p.WEBPData) {
  2579. img = [YYImage imageWithData:p.WEBPData scale:kScreenScale];
  2580. }
  2581. if (!img) {
  2582. img = p.image;
  2583. }
  2584. if (!img && p.imageData) {
  2585. img = [UIImage imageWithData:p.imageData scale:kScreenScale];
  2586. }
  2587. if (img && img.size.width > 1 && img.size.height > 1) {
  2588. id content = img;
  2589. if ([img conformsToProtocol:@protocol(YYAnimatedImage)]) {
  2590. id<YYAnimatedImage> ani = (id)img;
  2591. if (ani.animatedImageFrameCount > 1) {
  2592. YYAnimatedImageView *aniView = [[YYAnimatedImageView alloc] initWithImage:img];
  2593. if (aniView) {
  2594. content = aniView;
  2595. }
  2596. }
  2597. }
  2598. if ([content isKindOfClass:[UIImage class]] && img.images.count > 1) {
  2599. UIImageView *imgView = [UIImageView new];
  2600. imgView.image = img;
  2601. imgView.frame = CGRectMake(0, 0, img.size.width, img.size.height);
  2602. if (imgView) {
  2603. content = imgView;
  2604. }
  2605. }
  2606. NSMutableAttributedString *attText = [NSAttributedString attachmentStringWithContent:content contentMode:UIViewContentModeScaleToFill width:img.size.width ascent:img.size.height descent:0];
  2607. NSDictionary *attrs = _typingAttributesHolder.attributes;
  2608. if (attrs) [attText addAttributes:attrs range:NSMakeRange(0, attText.length)];
  2609. atr = attText;
  2610. }
  2611. }
  2612. if (atr) {
  2613. NSUInteger endPosition = _selectedTextRange.start.offset + atr.length;
  2614. NSMutableAttributedString *text = _innerText.mutableCopy;
  2615. [text replaceCharactersInRange:_selectedTextRange.asRange withAttributedString:atr];
  2616. self.attributedText = text;
  2617. YYTextPosition *pos = [self _correctedTextPosition:[YYTextPosition positionWithOffset:endPosition]];
  2618. YYTextRange *range = [_innerLayout textRangeByExtendingPosition:pos];
  2619. range = [self _correctedTextRange:range];
  2620. if (range) {
  2621. self.selectedRange = NSMakeRange(range.end.offset, 0);
  2622. }
  2623. } else {
  2624. NSString *string = p.string;
  2625. if (string.length > 0) {
  2626. [self _saveToUndoStack];
  2627. [self _resetRedoStack];
  2628. [self replaceRange:_selectedTextRange withText:string];
  2629. }
  2630. }
  2631. }
  2632. - (void)select:(id)sender {
  2633. [self _endTouchTracking];
  2634. if (_selectedTextRange.asRange.length > 0 || _innerText.length == 0) return;
  2635. YYTextRange *newRange = [self _getClosestTokenRangeAtPosition:_selectedTextRange.start];
  2636. if (newRange.asRange.length > 0) {
  2637. [_inputDelegate selectionWillChange:self];
  2638. _selectedTextRange = newRange;
  2639. [_inputDelegate selectionDidChange:self];
  2640. }
  2641. [self _updateIfNeeded];
  2642. [self _updateOuterProperties];
  2643. [self _updateSelectionView];
  2644. [self _hideMenu];
  2645. [self _showMenu];
  2646. }
  2647. - (void)selectAll:(id)sender {
  2648. _trackingRange = nil;
  2649. [_inputDelegate selectionWillChange:self];
  2650. _selectedTextRange = [YYTextRange rangeWithRange:NSMakeRange(0, _innerText.length)];
  2651. [_inputDelegate selectionDidChange:self];
  2652. [self _updateIfNeeded];
  2653. [self _updateOuterProperties];
  2654. [self _updateSelectionView];
  2655. [self _hideMenu];
  2656. [self _showMenu];
  2657. }
  2658. - (void)_define:(id)sender {
  2659. [self _hideMenu];
  2660. NSString *string = [_innerText plainTextForRange:_selectedTextRange.asRange];
  2661. if (string.length == 0) return;
  2662. BOOL resign = [self resignFirstResponder];
  2663. if (!resign) return;
  2664. UIReferenceLibraryViewController* ref = [[UIReferenceLibraryViewController alloc] initWithTerm:string];
  2665. ref.view.backgroundColor = [UIColor whiteColor];
  2666. [[self _getRootViewController] presentViewController:ref animated:YES completion:^{}];
  2667. }
  2668. #pragma mark - Overrice NSObject(NSKeyValueObservingCustomization)
  2669. + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
  2670. static NSSet *keys = nil;
  2671. static dispatch_once_t onceToken;
  2672. dispatch_once(&onceToken, ^{
  2673. keys = [NSSet setWithArray:@[
  2674. @"text",
  2675. @"font",
  2676. @"textColor",
  2677. @"textAlignment",
  2678. @"dataDetectorTypes",
  2679. @"linkTextAttributes",
  2680. @"highlightTextAttributes",
  2681. @"textParser",
  2682. @"attributedText",
  2683. @"textVerticalAlignment",
  2684. @"textContainerInset",
  2685. @"exclusionPaths",
  2686. @"verticalForm",
  2687. @"linePositionModifier",
  2688. @"selectedRange",
  2689. @"typingAttributes"
  2690. ]];
  2691. });
  2692. if ([keys containsObject:key]) {
  2693. return NO;
  2694. }
  2695. return [super automaticallyNotifiesObserversForKey:key];
  2696. }
  2697. #pragma mark - @protocol NSCoding
  2698. - (instancetype)initWithCoder:(NSCoder *)aDecoder {
  2699. self = [super initWithCoder:aDecoder];
  2700. [self _initTextView];
  2701. self.attributedText = [aDecoder decodeObjectForKey:@"attributedText"];
  2702. self.selectedRange = ((NSValue *)[aDecoder decodeObjectForKey:@"selectedRange"]).rangeValue;
  2703. self.textVerticalAlignment = [aDecoder decodeIntegerForKey:@"textVerticalAlignment"];
  2704. self.dataDetectorTypes = [aDecoder decodeIntegerForKey:@"dataDetectorTypes"];
  2705. self.textContainerInset = ((NSValue *)[aDecoder decodeObjectForKey:@"textContainerInset"]).UIEdgeInsetsValue;
  2706. self.exclusionPaths = [aDecoder decodeObjectForKey:@"exclusionPaths"];
  2707. self.verticalForm = [aDecoder decodeBoolForKey:@"verticalForm"];
  2708. return self;
  2709. }
  2710. - (void)encodeWithCoder:(NSCoder *)aCoder {
  2711. [super encodeWithCoder:aCoder];
  2712. [aCoder encodeObject:self.attributedText forKey:@"attributedText"];
  2713. [aCoder encodeObject:[NSValue valueWithRange:self.selectedRange] forKey:@"selectedRange"];
  2714. [aCoder encodeInteger:self.textVerticalAlignment forKey:@"textVerticalAlignment"];
  2715. [aCoder encodeInteger:self.dataDetectorTypes forKey:@"dataDetectorTypes"];
  2716. [aCoder encodeUIEdgeInsets:self.textContainerInset forKey:@"textContainerInset"];
  2717. [aCoder encodeObject:self.exclusionPaths forKey:@"exclusionPaths"];
  2718. [aCoder encodeBool:self.verticalForm forKey:@"verticalForm"];
  2719. }
  2720. #pragma mark - @protocol UIScrollViewDelegate
  2721. - (id<YYTextViewDelegate>)delegate {
  2722. return _outerDelegate;
  2723. }
  2724. - (void)setDelegate:(id<YYTextViewDelegate>)delegate {
  2725. _outerDelegate = delegate;
  2726. }
  2727. - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
  2728. [[YYTextEffectWindow sharedWindow] hideSelectionDot:_selectionView];
  2729. if ([_outerDelegate respondsToSelector:_cmd]) {
  2730. [_outerDelegate scrollViewDidScroll:scrollView];
  2731. }
  2732. }
  2733. - (void)scrollViewDidZoom:(UIScrollView *)scrollView {
  2734. if ([_outerDelegate respondsToSelector:_cmd]) {
  2735. [_outerDelegate scrollViewDidZoom:scrollView];
  2736. }
  2737. }
  2738. - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
  2739. if ([_outerDelegate respondsToSelector:_cmd]) {
  2740. [_outerDelegate scrollViewWillBeginDragging:scrollView];
  2741. }
  2742. }
  2743. - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
  2744. if ([_outerDelegate respondsToSelector:_cmd]) {
  2745. [_outerDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset];
  2746. }
  2747. }
  2748. - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
  2749. if (!decelerate) {
  2750. [[YYTextEffectWindow sharedWindow] showSelectionDot:_selectionView];
  2751. }
  2752. if ([_outerDelegate respondsToSelector:_cmd]) {
  2753. [_outerDelegate scrollViewDidEndDragging:scrollView willDecelerate:decelerate];
  2754. }
  2755. }
  2756. - (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView {
  2757. if ([_outerDelegate respondsToSelector:_cmd]) {
  2758. [_outerDelegate scrollViewWillBeginDecelerating:scrollView];
  2759. }
  2760. }
  2761. - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
  2762. [[YYTextEffectWindow sharedWindow] showSelectionDot:_selectionView];
  2763. if ([_outerDelegate respondsToSelector:_cmd]) {
  2764. [_outerDelegate scrollViewDidEndDecelerating:scrollView];
  2765. }
  2766. }
  2767. - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
  2768. if ([_outerDelegate respondsToSelector:_cmd]) {
  2769. [_outerDelegate scrollViewDidEndScrollingAnimation:scrollView];
  2770. }
  2771. }
  2772. - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
  2773. if ([_outerDelegate respondsToSelector:_cmd]) {
  2774. return [_outerDelegate viewForZoomingInScrollView:scrollView];
  2775. } else {
  2776. return nil;
  2777. }
  2778. }
  2779. - (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view{
  2780. if ([_outerDelegate respondsToSelector:_cmd]) {
  2781. [_outerDelegate scrollViewWillBeginZooming:scrollView withView:view];
  2782. }
  2783. }
  2784. - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale {
  2785. if ([_outerDelegate respondsToSelector:_cmd]) {
  2786. [_outerDelegate scrollViewDidEndZooming:scrollView withView:view atScale:scale];
  2787. }
  2788. }
  2789. - (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView {
  2790. if ([_outerDelegate respondsToSelector:_cmd]) {
  2791. return [_outerDelegate scrollViewShouldScrollToTop:scrollView];
  2792. }
  2793. return YES;
  2794. }
  2795. - (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView {
  2796. if ([_outerDelegate respondsToSelector:_cmd]) {
  2797. [_outerDelegate scrollViewDidScrollToTop:scrollView];
  2798. }
  2799. }
  2800. #pragma mark - @protocol YYTextKeyboardObserver
  2801. - (void)keyboardChangedWithTransition:(YYTextKeyboardTransition)transition {
  2802. [self _keyboardChanged];
  2803. }
  2804. #pragma mark - @protocol UIALertViewDelegate
  2805. - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
  2806. NSString *title = [alertView buttonTitleAtIndex:buttonIndex];
  2807. if (title.length == 0) return;
  2808. NSArray *strings = [self _localizedUndoStrings];
  2809. if ([title isEqualToString:strings[1]] || [title isEqualToString:strings[2]]) {
  2810. [self _redo];
  2811. } else if ([title isEqualToString:strings[3]] || [title isEqualToString:strings[4]]) {
  2812. [self _undo];
  2813. }
  2814. [self _restoreFirstResponderAfterUndoAlert];
  2815. }
  2816. #pragma mark - @protocol UIKeyInput
  2817. - (BOOL)hasText {
  2818. return _innerText.length > 0;
  2819. }
  2820. - (void)insertText:(NSString *)text {
  2821. if (text.length == 0) return;
  2822. if (!NSEqualRanges(_lastTypeRange, _selectedTextRange.asRange)) {
  2823. [self _saveToUndoStack];
  2824. [self _resetRedoStack];
  2825. }
  2826. [self replaceRange:_selectedTextRange withText:text];
  2827. }
  2828. - (void)deleteBackward {
  2829. [self _updateIfNeeded];
  2830. NSRange range = _selectedTextRange.asRange;
  2831. if (range.location == 0 && range.length == 0) return;
  2832. _state.typingAttributesOnce = NO;
  2833. // test if there's 'TextBinding' before the caret
  2834. if (!_state.deleteConfirm && range.length == 0 && range.location > 0) {
  2835. NSRange effectiveRange;
  2836. YYTextBinding *binding = [_innerText attribute:YYTextBindingAttributeName atIndex:range.location - 1 longestEffectiveRange:&effectiveRange inRange:NSMakeRange(0, _innerText.length)];
  2837. if (binding && binding.deleteConfirm) {
  2838. _state.deleteConfirm = YES;
  2839. [_inputDelegate selectionWillChange:self];
  2840. _selectedTextRange = [YYTextRange rangeWithRange:effectiveRange];
  2841. _selectedTextRange = [self _correctedTextRange:_selectedTextRange];
  2842. [_inputDelegate selectionDidChange:self];
  2843. [self _updateOuterProperties];
  2844. [self _updateSelectionView];
  2845. return;
  2846. }
  2847. }
  2848. _state.deleteConfirm = NO;
  2849. if (range.length == 0) {
  2850. YYTextRange *extendRange = [_innerLayout textRangeByExtendingPosition:_selectedTextRange.end inDirection:UITextLayoutDirectionLeft offset:1];
  2851. if ([self _isTextRangeValid:extendRange]) {
  2852. range = extendRange.asRange;
  2853. }
  2854. }
  2855. if (!NSEqualRanges(_lastTypeRange, _selectedTextRange.asRange)) {
  2856. [self _saveToUndoStack];
  2857. [self _resetRedoStack];
  2858. }
  2859. [self replaceRange:[YYTextRange rangeWithRange:range] withText:@""];
  2860. }
  2861. #pragma mark - @protocol UITextInput
  2862. - (void)setInputDelegate:(id<UITextInputDelegate>)inputDelegate {
  2863. _inputDelegate = inputDelegate;
  2864. }
  2865. - (void)setSelectedTextRange:(YYTextRange *)selectedTextRange {
  2866. if (!selectedTextRange) return;
  2867. selectedTextRange = [self _correctedTextRange:selectedTextRange];
  2868. if ([selectedTextRange isEqual:_selectedTextRange]) return;
  2869. [self _updateIfNeeded];
  2870. [self _endTouchTracking];
  2871. [self _hideMenu];
  2872. _state.deleteConfirm = NO;
  2873. _state.typingAttributesOnce = NO;
  2874. [_inputDelegate selectionWillChange:self];
  2875. _selectedTextRange = selectedTextRange;
  2876. _lastTypeRange = _selectedTextRange.asRange;
  2877. [_inputDelegate selectionDidChange:self];
  2878. [self _updateOuterProperties];
  2879. [self _updateSelectionView];
  2880. if (self.isFirstResponder) {
  2881. [self _scrollRangeToVisible:_selectedTextRange];
  2882. }
  2883. }
  2884. - (void)setMarkedTextStyle:(NSDictionary *)markedTextStyle {
  2885. _markedTextStyle = markedTextStyle.copy;
  2886. }
  2887. /*
  2888. Replace current markedText with the new markedText
  2889. @param markedText New marked text.
  2890. @param selectedRange The range from the '_markedTextRange'
  2891. */
  2892. - (void)setMarkedText:(NSString *)markedText selectedRange:(NSRange)selectedRange {
  2893. [self _updateIfNeeded];
  2894. [self _endTouchTracking];
  2895. [self _hideMenu];
  2896. if ([self.delegate respondsToSelector:@selector(textView:shouldChangeTextInRange:replacementText:)]) {
  2897. NSRange range = _markedTextRange ? _markedTextRange.asRange : NSMakeRange(_selectedTextRange.end.offset, 0);
  2898. BOOL should = [self.delegate textView:self shouldChangeTextInRange:range replacementText:markedText];
  2899. if (!should) return;
  2900. }
  2901. if (!NSEqualRanges(_lastTypeRange, _selectedTextRange.asRange)) {
  2902. [self _saveToUndoStack];
  2903. [self _resetRedoStack];
  2904. }
  2905. BOOL needApplyHolderAttribute = NO;
  2906. if (_innerText.length > 0 && _markedTextRange) {
  2907. [self _updateAttributesHolder];
  2908. } else {
  2909. needApplyHolderAttribute = YES;
  2910. }
  2911. if (_selectedTextRange.asRange.length > 0) {
  2912. [self replaceRange:_selectedTextRange withText:@""];
  2913. }
  2914. [_inputDelegate textWillChange:self];
  2915. [_inputDelegate selectionWillChange:self];
  2916. if (!markedText) markedText = @"";
  2917. if (_markedTextRange == nil) {
  2918. _markedTextRange = [YYTextRange rangeWithRange:NSMakeRange(_selectedTextRange.end.offset, markedText.length)];
  2919. [_innerText replaceCharactersInRange:NSMakeRange(_selectedTextRange.end.offset, 0) withString:markedText];
  2920. _selectedTextRange = [YYTextRange rangeWithRange:NSMakeRange(_selectedTextRange.start.offset + selectedRange.location, selectedRange.length)];
  2921. } else {
  2922. _markedTextRange = [self _correctedTextRange:_markedTextRange];
  2923. [_innerText replaceCharactersInRange:_markedTextRange.asRange withString:markedText];
  2924. _markedTextRange = [YYTextRange rangeWithRange:NSMakeRange(_markedTextRange.start.offset, markedText.length)];
  2925. _selectedTextRange = [YYTextRange rangeWithRange:NSMakeRange(_markedTextRange.start.offset + selectedRange.location, selectedRange.length)];
  2926. }
  2927. _selectedTextRange = [self _correctedTextRange:_selectedTextRange];
  2928. _markedTextRange = [self _correctedTextRange:_markedTextRange];
  2929. if (_markedTextRange.asRange.length == 0) {
  2930. _markedTextRange = nil;
  2931. } else {
  2932. if (needApplyHolderAttribute) {
  2933. [_innerText setAttributes:_typingAttributesHolder.attributes range:_markedTextRange.asRange];
  2934. }
  2935. [_innerText removeDiscontinuousAttributesInRange:_markedTextRange.asRange];
  2936. }
  2937. [_inputDelegate selectionDidChange:self];
  2938. [_inputDelegate textDidChange:self];
  2939. [self _updateOuterProperties];
  2940. [self _updateLayout];
  2941. [self _updateSelectionView];
  2942. [self _scrollRangeToVisible:_selectedTextRange];
  2943. if ([self.delegate respondsToSelector:@selector(textViewDidChange:)]) {
  2944. [self.delegate textViewDidChange:self];
  2945. }
  2946. [[NSNotificationCenter defaultCenter] postNotificationName:YYTextViewTextDidChangeNotification object:self];
  2947. _lastTypeRange = _selectedTextRange.asRange;
  2948. }
  2949. - (void)unmarkText {
  2950. _markedTextRange = nil;
  2951. [self _endTouchTracking];
  2952. [self _hideMenu];
  2953. if ([self _parseText]) _state.needUpdate = YES;
  2954. [self _updateIfNeeded];
  2955. [self _updateOuterProperties];
  2956. [self _updateSelectionView];
  2957. [self _scrollRangeToVisible:_selectedTextRange];
  2958. }
  2959. - (void)replaceRange:(YYTextRange *)range withText:(NSString *)text {
  2960. if (!range) return;
  2961. if (!text) text = @"";
  2962. if (range.asRange.length == 0 && text.length == 0) return;
  2963. range = [self _correctedTextRange:range];
  2964. if ([self.delegate respondsToSelector:@selector(textView:shouldChangeTextInRange:replacementText:)]) {
  2965. BOOL should = [self.delegate textView:self shouldChangeTextInRange:range.asRange replacementText:text];
  2966. if (!should) return;
  2967. }
  2968. BOOL useInnerAttributes = NO;
  2969. if (_innerText.length > 0) {
  2970. if (range.start.offset == 0 && range.end.offset == _innerText.length) {
  2971. if (text.length == 0) {
  2972. NSMutableDictionary *attrs = [_innerText attributesAtIndex:0].mutableCopy;
  2973. [attrs removeObjectsForKeys:[NSMutableAttributedString allDiscontinuousAttributeKeys]];
  2974. _typingAttributesHolder.attributes = attrs;
  2975. }
  2976. }
  2977. } else { // no text
  2978. useInnerAttributes = YES;
  2979. }
  2980. BOOL applyTypingAttributes = NO;
  2981. if (_state.typingAttributesOnce) {
  2982. _state.typingAttributesOnce = NO;
  2983. if (!useInnerAttributes) {
  2984. if (range.asRange.length == 0 && text.length > 0) {
  2985. applyTypingAttributes = YES;
  2986. }
  2987. }
  2988. }
  2989. _state.selectedWithoutEdit = NO;
  2990. _state.deleteConfirm = NO;
  2991. [self _endTouchTracking];
  2992. [self _hideMenu];
  2993. [self _replaceRange:range withText:text notifyToDelegate:YES];
  2994. if (useInnerAttributes) {
  2995. [_innerText setAttributes:_typingAttributesHolder.attributes];
  2996. } else if (applyTypingAttributes) {
  2997. NSRange newRange = NSMakeRange(range.asRange.location, text.length);
  2998. [_typingAttributesHolder.attributes enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
  2999. [_innerText setAttribute:key value:obj range:newRange];
  3000. }];
  3001. }
  3002. [self _parseText];
  3003. [self _updateOuterProperties];
  3004. [self _update];
  3005. if (self.isFirstResponder) {
  3006. [self _scrollRangeToVisible:_selectedTextRange];
  3007. }
  3008. if ([self.delegate respondsToSelector:@selector(textViewDidChange:)]) {
  3009. [self.delegate textViewDidChange:self];
  3010. }
  3011. [[NSNotificationCenter defaultCenter] postNotificationName:YYTextViewTextDidChangeNotification object:self];
  3012. _lastTypeRange = _selectedTextRange.asRange;
  3013. }
  3014. - (void)setBaseWritingDirection:(UITextWritingDirection)writingDirection forRange:(YYTextRange *)range {
  3015. if (!range) return;
  3016. range = [self _correctedTextRange:range];
  3017. [_innerText setBaseWritingDirection:(NSWritingDirection)writingDirection range:range.asRange];
  3018. [self _commitUpdate];
  3019. }
  3020. - (NSString *)textInRange:(YYTextRange *)range {
  3021. range = [self _correctedTextRange:range];
  3022. if (!range) return @"";
  3023. return [_innerText.string substringWithRange:range.asRange];
  3024. }
  3025. - (UITextWritingDirection)baseWritingDirectionForPosition:(YYTextPosition *)position inDirection:(UITextStorageDirection)direction {
  3026. [self _updateIfNeeded];
  3027. position = [self _correctedTextPosition:position];
  3028. if (!position) return UITextWritingDirectionNatural;
  3029. if (_innerText.length == 0) return UITextWritingDirectionNatural;
  3030. NSUInteger idx = position.offset;
  3031. if (idx == _innerText.length) idx--;
  3032. NSDictionary *attrs = [_innerText attributesAtIndex:idx];
  3033. CTParagraphStyleRef paraStyle = (__bridge CFTypeRef)(attrs[NSParagraphStyleAttributeName]);
  3034. if (paraStyle) {
  3035. CTWritingDirection baseWritingDirection;
  3036. if (CTParagraphStyleGetValueForSpecifier(paraStyle, kCTParagraphStyleSpecifierBaseWritingDirection, sizeof(CTWritingDirection), &baseWritingDirection)) {
  3037. return (UITextWritingDirection)baseWritingDirection;
  3038. }
  3039. }
  3040. return UITextWritingDirectionNatural;
  3041. }
  3042. - (YYTextPosition *)beginningOfDocument {
  3043. return [YYTextPosition positionWithOffset:0];
  3044. }
  3045. - (YYTextPosition *)endOfDocument {
  3046. return [YYTextPosition positionWithOffset:_innerText.length];
  3047. }
  3048. - (YYTextPosition *)positionFromPosition:(YYTextPosition *)position offset:(NSInteger)offset {
  3049. if (offset == 0) return position;
  3050. NSUInteger location = position.offset;
  3051. NSInteger newLocation = (NSInteger)location + offset;
  3052. if (newLocation < 0 || newLocation > _innerText.length) return nil;
  3053. if (newLocation != 0 && newLocation != _innerText.length) {
  3054. // fix emoji
  3055. [self _updateIfNeeded];
  3056. YYTextRange *extendRange = [_innerLayout textRangeByExtendingPosition:[YYTextPosition positionWithOffset:newLocation]];
  3057. if (extendRange.asRange.length > 0) {
  3058. if (offset < 0) {
  3059. newLocation = extendRange.start.offset;
  3060. } else {
  3061. newLocation = extendRange.end.offset;
  3062. }
  3063. }
  3064. }
  3065. YYTextPosition *p = [YYTextPosition positionWithOffset:newLocation];
  3066. return [self _correctedTextPosition:p];
  3067. }
  3068. - (YYTextPosition *)positionFromPosition:(YYTextPosition *)position inDirection:(UITextLayoutDirection)direction offset:(NSInteger)offset {
  3069. [self _updateIfNeeded];
  3070. YYTextRange *range = [_innerLayout textRangeByExtendingPosition:position inDirection:direction offset:offset];
  3071. BOOL forward;
  3072. if (_innerContainer.isVerticalForm) {
  3073. forward = direction == UITextLayoutDirectionLeft || direction == UITextLayoutDirectionDown;
  3074. } else {
  3075. forward = direction == UITextLayoutDirectionDown || direction == UITextLayoutDirectionRight;
  3076. }
  3077. if (!forward && offset < 0) {
  3078. forward = -forward;
  3079. }
  3080. YYTextPosition *newPosition = forward ? range.end : range.start;
  3081. if (newPosition.offset > _innerText.length) {
  3082. newPosition = [YYTextPosition positionWithOffset:_innerText.length affinity:YYTextAffinityBackward];
  3083. }
  3084. return [self _correctedTextPosition:newPosition];
  3085. }
  3086. - (YYTextRange *)textRangeFromPosition:(YYTextPosition *)fromPosition toPosition:(YYTextPosition *)toPosition {
  3087. return [YYTextRange rangeWithStart:fromPosition end:toPosition];
  3088. }
  3089. - (NSComparisonResult)comparePosition:(YYTextPosition *)position toPosition:(YYTextPosition *)other {
  3090. return [position compare:other];
  3091. }
  3092. - (NSInteger)offsetFromPosition:(YYTextPosition *)from toPosition:(YYTextPosition *)toPosition {
  3093. return toPosition.offset - from.offset;
  3094. }
  3095. - (YYTextPosition *)positionWithinRange:(YYTextRange *)range farthestInDirection:(UITextLayoutDirection)direction {
  3096. NSRange nsRange = range.asRange;
  3097. if (direction == UITextLayoutDirectionLeft | direction == UITextLayoutDirectionUp) {
  3098. return [YYTextPosition positionWithOffset:nsRange.location];
  3099. } else {
  3100. return [YYTextPosition positionWithOffset:nsRange.location + nsRange.length affinity:YYTextAffinityBackward];
  3101. }
  3102. }
  3103. - (YYTextRange *)characterRangeByExtendingPosition:(YYTextPosition *)position inDirection:(UITextLayoutDirection)direction {
  3104. [self _updateIfNeeded];
  3105. YYTextRange *range = [_innerLayout textRangeByExtendingPosition:position inDirection:direction offset:1];
  3106. return [self _correctedTextRange:range];
  3107. }
  3108. - (YYTextPosition *)closestPositionToPoint:(CGPoint)point {
  3109. [self _updateIfNeeded];
  3110. point = [self _convertPointToLayout:point];
  3111. YYTextPosition *position = [_innerLayout closestPositionToPoint:point];
  3112. return [self _correctedTextPosition:position];
  3113. }
  3114. - (YYTextPosition *)closestPositionToPoint:(CGPoint)point withinRange:(YYTextRange *)range {
  3115. YYTextPosition *pos = (id)[self closestPositionToPoint:point];
  3116. if (!pos) return nil;
  3117. range = [self _correctedTextRange:range];
  3118. if ([pos compare:range.start] == NSOrderedAscending) {
  3119. pos = range.start;
  3120. } else if ([pos compare:range.end] == NSOrderedDescending) {
  3121. pos = range.end;
  3122. }
  3123. return pos;
  3124. }
  3125. - (YYTextRange *)characterRangeAtPoint:(CGPoint)point {
  3126. [self _updateIfNeeded];
  3127. point = [self _convertPointToLayout:point];
  3128. YYTextRange *r = [_innerLayout closestTextRangeAtPoint:point];
  3129. return [self _correctedTextRange:r];
  3130. }
  3131. - (CGRect)firstRectForRange:(YYTextRange *)range {
  3132. [self _updateIfNeeded];
  3133. CGRect rect = [_innerLayout firstRectForRange:range];
  3134. if (CGRectIsNull(rect)) rect = CGRectZero;
  3135. return [self _convertRectFromLayout:rect];
  3136. }
  3137. - (CGRect)caretRectForPosition:(YYTextPosition *)position {
  3138. [self _updateIfNeeded];
  3139. CGRect caretRect = [_innerLayout caretRectForPosition:position];
  3140. if (!CGRectIsNull(caretRect)) {
  3141. caretRect = [self _convertRectFromLayout:caretRect];
  3142. caretRect = CGRectStandardize(caretRect);
  3143. if (_verticalForm) {
  3144. if (caretRect.size.height == 0) {
  3145. caretRect.size.height = 2;
  3146. caretRect.origin.y -= 2 * 0.5;
  3147. }
  3148. if (caretRect.origin.y < 0) {
  3149. caretRect.origin.y = 0;
  3150. } else if (caretRect.origin.y + caretRect.size.height > self.bounds.size.height) {
  3151. caretRect.origin.y = self.bounds.size.height - caretRect.size.height;
  3152. }
  3153. } else {
  3154. if (caretRect.size.width == 0) {
  3155. caretRect.size.width = 2;
  3156. caretRect.origin.x -= 2 * 0.5;
  3157. }
  3158. if (caretRect.origin.x < 0) {
  3159. caretRect.origin.x = 0;
  3160. } else if (caretRect.origin.x + caretRect.size.width > self.bounds.size.width) {
  3161. caretRect.origin.x = self.bounds.size.width - caretRect.size.width;
  3162. }
  3163. }
  3164. return CGRectPixelRound(caretRect);
  3165. }
  3166. return CGRectZero;
  3167. }
  3168. - (NSArray *)selectionRectsForRange:(YYTextRange *)range {
  3169. [self _updateIfNeeded];
  3170. NSArray *rects = [_innerLayout selectionRectsForRange:range];
  3171. [rects enumerateObjectsUsingBlock:^(YYTextSelectionRect *rect, NSUInteger idx, BOOL *stop) {
  3172. rect.rect = [self _convertRectFromLayout:rect.rect];
  3173. }];
  3174. return rects;
  3175. }
  3176. #pragma mark - @protocol UITextInput optional
  3177. - (UITextStorageDirection)selectionAffinity {
  3178. if (_selectedTextRange.end.affinity == YYTextAffinityForward) {
  3179. return UITextStorageDirectionForward;
  3180. } else {
  3181. return UITextStorageDirectionBackward;
  3182. }
  3183. }
  3184. - (void)setSelectionAffinity:(UITextStorageDirection)selectionAffinity {
  3185. _selectedTextRange = [YYTextRange rangeWithRange:_selectedTextRange.asRange affinity:selectionAffinity == UITextStorageDirectionForward ? YYTextAffinityForward : YYTextAffinityBackward];
  3186. [self _updateSelectionView];
  3187. }
  3188. - (NSDictionary *)textStylingAtPosition:(YYTextPosition *)position inDirection:(UITextStorageDirection)direction {
  3189. if (!position) return nil;
  3190. if (_innerText.length == 0) return _typingAttributesHolder.attributes;
  3191. NSDictionary *attrs = nil;
  3192. if (0 <= position.offset && position.offset <= _innerText.length) {
  3193. NSUInteger ofs = position.offset;
  3194. if (position.offset == _innerText.length ||
  3195. direction == UITextStorageDirectionBackward) {
  3196. ofs--;
  3197. }
  3198. attrs = [_innerText attributesAtIndex:ofs effectiveRange:NULL];
  3199. }
  3200. return attrs;
  3201. }
  3202. - (YYTextPosition *)positionWithinRange:(YYTextRange *)range atCharacterOffset:(NSInteger)offset {
  3203. if (!range) return nil;
  3204. if (offset < range.start.offset || offset > range.end.offset) return nil;
  3205. if (offset == range.start.offset) return range.start;
  3206. else if (offset == range.end.offset) return range.end;
  3207. else return [YYTextPosition positionWithOffset:offset];
  3208. }
  3209. - (NSInteger)characterOffsetOfPosition:(YYTextPosition *)position withinRange:(YYTextRange *)range {
  3210. return position ? position.offset : NSNotFound;
  3211. }
  3212. @end
  3213. @interface YYTextView(IBInspectableProperties)
  3214. @end
  3215. @implementation YYTextView(IBInspectableProperties)
  3216. - (BOOL)fontIsBold_:(UIFont *)font {
  3217. if (![font respondsToSelector:@selector(fontDescriptor)]) return NO;
  3218. return (font.fontDescriptor.symbolicTraits & UIFontDescriptorTraitBold) > 0;
  3219. }
  3220. - (UIFont *)boldFont_:(UIFont *)font {
  3221. if (![font respondsToSelector:@selector(fontDescriptor)]) return font;
  3222. return [UIFont fontWithDescriptor:[font.fontDescriptor fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitBold] size:font.pointSize];
  3223. }
  3224. - (UIFont *)normalFont_:(UIFont *)font {
  3225. if (![font respondsToSelector:@selector(fontDescriptor)]) return font;
  3226. return [UIFont fontWithDescriptor:[font.fontDescriptor fontDescriptorWithSymbolicTraits:0] size:font.pointSize];
  3227. }
  3228. - (void)setFontName_:(NSString *)fontName {
  3229. if (!fontName) return;
  3230. UIFont *font = self.font;
  3231. if (!font) font = [self _defaultFont];
  3232. if ((fontName.length == 0 || [fontName.lowercaseString isEqualToString:@"system"]) && ![self fontIsBold_:font]) {
  3233. font = [UIFont systemFontOfSize:font.pointSize];
  3234. } else if ([fontName.lowercaseString isEqualToString:@"system bold"]) {
  3235. font = [UIFont boldSystemFontOfSize:font.pointSize];
  3236. } else {
  3237. if ([self fontIsBold_:font] && ![fontName.lowercaseString containsString:@"bold"]) {
  3238. font = [UIFont fontWithName:fontName size:font.pointSize];
  3239. font = [self boldFont_:font];
  3240. } else {
  3241. font = [UIFont fontWithName:fontName size:font.pointSize];
  3242. }
  3243. }
  3244. if (font) self.font = font;
  3245. }
  3246. - (void)setFontSize_:(CGFloat)fontSize {
  3247. if (fontSize <= 0) return;
  3248. UIFont *font = self.font;
  3249. if (!font) font = [self _defaultFont];
  3250. if (!font) font = [self _defaultFont];
  3251. font = [font fontWithSize:fontSize];
  3252. if (font) self.font = font;
  3253. }
  3254. - (void)setFontIsBold_:(BOOL)fontBold {
  3255. UIFont *font = self.font;
  3256. if (!font) font = [self _defaultFont];
  3257. if ([self fontIsBold_:font] == fontBold) return;
  3258. if (fontBold) {
  3259. font = [self boldFont_:font];
  3260. } else {
  3261. font = [self normalFont_:font];
  3262. }
  3263. if (font) self.font = font;
  3264. }
  3265. - (void)setPlaceholderFontName_:(NSString *)fontName {
  3266. if (!fontName) return;
  3267. UIFont *font = self.placeholderFont;
  3268. if (!font) font = [self _defaultFont];
  3269. if ((fontName.length == 0 || [fontName.lowercaseString isEqualToString:@"system"]) && ![self fontIsBold_:font]) {
  3270. font = [UIFont systemFontOfSize:font.pointSize];
  3271. } else if ([fontName.lowercaseString isEqualToString:@"system bold"]) {
  3272. font = [UIFont boldSystemFontOfSize:font.pointSize];
  3273. } else {
  3274. if ([self fontIsBold_:font] && ![fontName.lowercaseString containsString:@"bold"]) {
  3275. font = [UIFont fontWithName:fontName size:font.pointSize];
  3276. font = [self boldFont_:font];
  3277. } else {
  3278. font = [UIFont fontWithName:fontName size:font.pointSize];
  3279. }
  3280. }
  3281. if (font) self.placeholderFont = font;
  3282. }
  3283. - (void)setPlaceholderFontSize_:(CGFloat)fontSize {
  3284. if (fontSize <= 0) return;
  3285. UIFont *font = self.placeholderFont;
  3286. if (!font) font = [self _defaultFont];
  3287. font = [font fontWithSize:fontSize];
  3288. if (font) self.placeholderFont = font;
  3289. }
  3290. - (void)setPlaceholderFontIsBold_:(BOOL)fontBold {
  3291. UIFont *font = self.placeholderFont;
  3292. if (!font) font = [self _defaultFont];
  3293. if ([self fontIsBold_:font] == fontBold) return;
  3294. if (fontBold) {
  3295. font = [self boldFont_:font];
  3296. } else {
  3297. font = [self normalFont_:font];
  3298. }
  3299. if (font) self.placeholderFont = font;
  3300. }
  3301. - (void)setInsetTop_:(CGFloat)textInsetTop {
  3302. UIEdgeInsets insets = self.textContainerInset;
  3303. insets.top = textInsetTop;
  3304. self.textContainerInset = insets;
  3305. }
  3306. - (void)setInsetBottom_:(CGFloat)textInsetBottom {
  3307. UIEdgeInsets insets = self.textContainerInset;
  3308. insets.bottom = textInsetBottom;
  3309. self.textContainerInset = insets;
  3310. }
  3311. - (void)setInsetLeft_:(CGFloat)textInsetLeft {
  3312. UIEdgeInsets insets = self.textContainerInset;
  3313. insets.left = textInsetLeft;
  3314. self.textContainerInset = insets;
  3315. }
  3316. - (void)setInsetRight_:(CGFloat)textInsetRight {
  3317. UIEdgeInsets insets = self.textContainerInset;
  3318. insets.right = textInsetRight;
  3319. self.textContainerInset = insets;
  3320. }
  3321. - (void)setDebugEnabled_:(BOOL)enabled {
  3322. if (!enabled) {
  3323. self.debugOption = nil;
  3324. } else {
  3325. YYTextDebugOption *debugOption = [YYTextDebugOption new];
  3326. debugOption.baselineColor = [UIColor redColor];
  3327. debugOption.CTFrameBorderColor = [UIColor redColor];
  3328. debugOption.CTLineFillColor = [UIColor colorWithRed:0.000 green:0.463 blue:1.000 alpha:0.180];
  3329. debugOption.CGGlyphBorderColor = [UIColor colorWithRed:1.000 green:0.524 blue:0.000 alpha:0.200];
  3330. self.debugOption = debugOption;
  3331. }
  3332. }
  3333. @end