YYTextView.m 152 KB

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