实现过程1 今天来谈谈类似于新浪微博话题功能的简单实现,当文字是”#话题#”这种格式时,该文字字体得变颜色。个人觉得,这种问题的处理方式可以是,监听用户输入的信息,如果遇到有”#”号输入或删除时,再处理看是否需要改变字体颜色。于是我就按照这种思路写了一段改变颜色的代码,它就是是遍历textview.text,然后在将两个”#”号之间有文字的字体设置颜色,不会玩正则,所以这个方法比较蠢。O(∩_∩)O~ - /**8 i$ H, B1 z& [ Q, X
- * 设置textview字体属性0 ~3 t* O$ @" w: U
- */0 x% Y% x$ i7 W8 C; |
- - (void)setTextViewAttributed {: V" D5 G5 L w
- NSMutableArray *indexArray = [NSMutableArray array];
; w( @2 G# H4 [, [, s5 Z# M# m - for (NSInteger i = 0; i < self.topicTextView.text.length; i++) {$ F& \' q& H3 C' i, v- _
- NSString *indexString = [self.topicTextView.text substringWithRange:NSMakeRange(i, 1)]; D6 l1 E5 G/ I3 t7 y
- if ([indexString isEqualToString:topicString]) {. I, C' x0 ]$ r5 J3 A
- [indexArray addObject:@(i)];$ b, N+ d2 q0 [& U: g1 Q
- }1 `5 c5 t! J* H* z5 {+ a. o$ q
- }, [9 P; R5 {; C1 k; U y
- // reset* @' j& F( W0 f; g
- NSMutableAttributedString *aText = [[NSMutableAttributedString alloc] initWithString:self.topicTextView.text];& K: q( r$ I: V$ }+ n0 e
- self.topicTextView.attributedText = aText;# X8 B I7 U) t1 [, r, ~
- self.topicTextView.font = [UIFont systemFontOfSize:16.0];% S* k- X. a2 A0 X$ @
- # l6 e e* B$ u& X; R: |
- // change* J. J( {: l& o0 o4 `7 T
- if (indexArray.count > 1) {# P( `) e8 p2 d1 }$ K2 k. x6 W' g
- NSMutableAttributedString *aText = [[NSMutableAttributedString alloc] initWithString:self.topicTextView.text];( K9 D# ?* N/ _6 }
- for (NSInteger i = 0; i < indexArray.count; i++) {
i, Q& R7 L }/ L! l' B - NSInteger index1 = [indexArray integerValue];
5 J4 _: w& c3 n9 |( H - NSInteger index2 = 0;
* Y3 V" S8 B/ I$ H - if ((i + 1) < indexArray.count) {/ `3 L- L: u8 O7 O/ k3 V/ f6 V: A
- index2 = [indexArray[i + 1] integerValue];
8 C; T" M: F1 x5 p - }
! n: A. ~/ {" o) ? - if (index2 - index1 > 1) {! O) E- S& ]) b$ |7 O5 c
- // 多余中间有值才显示
- L% q, R2 ?8 L4 D2 x- h - [aText setAttributes:@{ NSForegroundColorAttributeName: TopicColor } range:NSMakeRange(index1, index2 - index1 + 1)];/ N, w% s3 x/ G8 ?
- ++i;
- s. T& [# [- r3 L1 u - }
4 `4 @1 W. v7 N6 d3 V2 _1 I2 T" W - }
" t" Y7 b! T7 u7 C9 Q- a" S/ C# C - self.topicTextView.attributedText = aText;. q+ |( n) H3 s! ~1 y8 F5 _" ?1 l
- self.topicTextView.font = [UIFont systemFontOfSize:16.0];
7 c+ G/ Q' x J6 T4 x5 X - }
/ y h, Z* |4 Q0 [ L) t9 p - }
复制代码 实现过程2这个是我一厢情愿写的demo。因为现实并不是这样子的,客户端的话题只能从服务端获取,每个话题都是有标识的,因为不能让用户随心所欲的创建话题撒!所以呢,用户自己手动输入”#”号,然并卵。如果是这样子的话,那我只有三个问题要解决了: - 改变话题字符串颜色;
- 光标不能移动到话题字符串中间,当用户光标移动至话题后面时,用户第一次点击键盘删除按钮,其实是选中这个话题的,再一次点击键盘删除按钮时,才会删除这个话题字符串;
- 上传至服务器的时候,并不是上传#话题#,而是上传协议好的字符串,就是能让服务端能够识别这个话题;
! j1 q1 ?! _, A* Z
在实现过程中,我以AttributedString的颜色值为基准,用几个正则为查找工具,结合UITextView的三个代理方法。 - /// Prior to replacing text, this method is called to give your delegate a chance to accept or reject the edits. If you do not implement this method, the return value defaults to YES.
+ z: {% w. m* s! k - - (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text;
0 u, c: L/ x& c$ \
& W3 q! H& j; w# T3 u/ p* m9 B- /// The text view calls this method in response to user-initiated changes to the text. This method is not called in response to programmatically initiated changes. Implementation of this method is optional.
: a5 K! w, c1 e f' g ~4 H - - (void)textViewDidChange:(UITextView *)textView;! f8 `- O$ P9 U( N0 s0 v: v* Q, Q# s
+ l4 j7 Q$ l" D; k- /// Implementation of this method is optional. You can use the selectedRange property of the text view to get the new selection.: u! ?* |; {2 O' W' _* ]5 f
- - (void)textViewDidChangeSelection:(UITextView *)textView;
复制代码这三个方法都不陌生吧,那么我的思路就简单了 - shouldChangeTextInRange 代理方法中,第一,实现第一次选中,第二次删除功能;第二,实现插入话题后,需要改变其他字符串的初始颜色,得在这个方法里面做个标志。
- textViewDidChange 代理方法中,实现 根据 shouldChangeTextInRange 方法中所得到的标志,设置字符串的初始颜色;
- textViewDidChangeSelection 代理方法中,实现让光标不能移动到话题里面;
" d0 B) E; y6 Z; l9 A7 P9 u2 {
首先我定义了两个变量,插入了话题以后,继续在后面输入字符的话,字符颜色就跟话题颜色一样了。所以,我得用这两个变量来实现改变输入字符的初始颜色。 - /// 改变Range
" r; k3 k y+ b - @property (assign, nonatomic) NSRange changeRange;
0 ]* ?% M3 o5 x2 [, o n) G' ^ - /// 是否改变" c. E/ X6 L3 l9 M9 g/ V( ~
- @property (assign, nonatomic) BOOL isChanged;
复制代码哦,对了,我还得用一个变量来记录上次光标所在的位置,因为话题字符串是不让它输入的。 - /// 光标位置* R2 D0 a) ? Z/ ]" ^5 E
- @property (assign, nonatomic) NSInteger cursorLocation;
复制代码用户从其他界面选择好话题以后,它得插入到textview中啊: - NSString *insertText = [NSString stringWithFormat:@"#%@#", dict[KeyTopicName]];2 S: `5 k3 X1 Q+ h2 L4 B
- [self.textView insertText:insertText];
- d1 R0 l; S: _7 T - NSMutableAttributedString *tmpAString = [[NSMutableAttributedString alloc] initWithAttributedString:self.textView.attributedText];+ Z0 U. o2 R8 K( [
- [tmpAString setAttributes:@{ NSForegroundColorAttributeName: TopicColor, NSFontAttributeName: DefaultSizeFont } range:NSMakeRange(self.textView.selectedRange.location - insertText.length, insertText.length)];$ V- F# }+ D& t& e/ }" K
- self.textView.attributedText = tmpAString;
复制代码然后我还得找到将用户所选择插入的话题位置啊。 - /**) ~: g2 K9 f8 u# ^
- * 得到话题Range数组: g) f' Y+ B7 v4 ^
- * V$ S: }. n( q! u+ e
- * @return return value description
" |% T9 ?/ N6 d+ v9 k% g5 K* T2 _ - */2 [ ` C2 c+ z
- - (NSArray *)getTopicRangeArray:(NSAttributedString *)attributedString {7 o4 A- ^4 B6 V! d/ ~
- NSAttributedString *traveAStr = attributedString ?: _textView.attributedText;( G2 w, k+ q! O" o9 m6 [
- __block NSMutableArray *rangeArray = [NSMutableArray array];
2 Z# W( d1 b$ N, Q' Y1 Q: \ - static NSRegularExpression *iExpression;
* d) Z) Y$ U/ N# g, s* L3 j* } - iExpression = iExpression ?: [NSRegularExpression regularExpressionWithPattern:@"#(.*?)#" options:0 error:NULL];9 k* L F# A2 g1 d" z: z; X
- [iExpression enumerateMatchesInString:traveAStr.string9 h! w# K4 g6 E0 H" T+ U
- options:0
& A7 d2 ~6 R, g3 A% P% { - range:NSMakeRange(0, traveAStr.string.length)
& r6 \4 m j* s. y1 a( ~' h - usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { u1 x% {( k8 h d- ^( p
- NSRange resultRange = result.range;1 R8 j% m% H0 y1 n& t
- NSDictionary *attributedDict = [traveAStr attributesAtIndex:resultRange.location effectiveRange:&resultRange];! p# Q- d: o+ z, }4 `5 y
- if ([attributedDict[NSForegroundColorAttributeName] isEqual:TopicColor]) {
( g3 u, o5 f# [2 W - [rangeArray addObject:NSStringFromRange(result.range)];' A9 m3 _* F7 h1 `" M6 W5 ^ k
- }
% H4 t: K3 \, D/ o; p" e - }];
/ A! e, r& u/ K8 h8 }/ r1 ^! L - return rangeArray;; }% S# H; A9 ?9 |1 K
- }
复制代码那么,三个UITextView delegate方法里的代码就可以这么玩了: - #pragma mark - UITextView Delegate$ }, c) W1 {) ?2 j. m W, f0 \
- - (void)textViewDidChangeSelection:(UITextView *)textView {
2 V7 x: i& `( m+ m - NSArray *rangeArray = [self getTopicRangeArray:nil];( m7 {! s* x& u* }
- BOOL inRange = NO;
/ w" W' m& X* R - for (NSInteger i = 0; i < rangeArray.count; i++) {
' I2 ~0 _ J$ N" a; y! |2 z - NSRange range = NSRangeFromString(rangeArray);& S7 j9 ?+ [- f) Z7 P( J0 l
- if (textView.selectedRange.location > range.location && textView.selectedRange.location < range.location + range.length) {9 }0 l7 O$ y2 i' V2 w
- inRange = YES;1 O# i, ^3 j r8 b1 K% S
- break;) q _+ {; v) R1 h
- }
5 I( r8 q4 W8 r2 X4 O0 O7 t' P1 ~ - }1 t" x1 x$ G8 @8 a- }
- if (inRange) {" r& k u! _! O$ @6 ]
- textView.selectedRange = NSMakeRange(self.cursorLocation, textView.selectedRange.length);4 \( L& I- e' U+ W
- return;3 g. w$ u) ?- b0 q m* a' F
- }5 m( V- N! K! M8 y+ T- z
- self.cursorLocation = textView.selectedRange.location;
0 @7 `% X/ A# i& d. a - }
8 H- y, X$ [3 X/ c - N4 O* L0 u5 N# K, s- Y+ U
- - (void)textViewDidChange:(UITextView *)textView {
! V$ w5 P( { q7 V4 h7 H - if (_isChanged) {
% @# F# g" h! ], A2 M/ ~0 R$ h - NSMutableAttributedString *tmpAString = [[NSMutableAttributedString alloc] initWithAttributedString:self.textView.attributedText];
9 l, b O% x% {2 } - [tmpAString setAttributes:@{ NSForegroundColorAttributeName: [UIColor blackColor], NSFontAttributeName: DefaultSizeFont } range:_changeRange];$ a' F/ M$ L- r% A* I4 j
- _textView.attributedText = tmpAString;
; B A& a' y% E; n6 s - _isChanged = NO;
0 W7 X% f7 U& Y! w - }
: { T5 _" i, j- d3 z - }
* P: Q; \( f/ t f P! G6 a7 F7 Q - 5 e% }1 w h: c6 o
- - (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {& U" h; H4 C0 y2 d2 ]
- if ([text isEqualToString:@""]) { // 删除) `! q& O5 F) k% F% N
- NSArray *rangeArray = [self getTopicRangeArray:nil];& T6 h/ J% e L& |. ]
- for (NSInteger i = 0; i < rangeArray.count; i++) {, j* `; o. K/ d
- NSRange tmpRange = NSRangeFromString(rangeArray);
+ n$ { X! s& ?' w2 u - if ((range.location + range.length) == (tmpRange.location + tmpRange.length)) {6 O' E* c, x; [, F4 V' v2 \
- if ([NSStringFromRange(tmpRange) isEqualToString:NSStringFromRange(textView.selectedRange)]) {
" b6 f/ U( Z _( O v* `# ^ - // 第二次点击删除按钮 删除
3 r! u$ H. b1 a( @0 R0 k - return YES;
$ t8 v# R& O- c& ] j2 T1 f - } else {
+ L& v: N# f4 u& E% F - // 第一次点击删除按钮 选中
* t" P- \1 i$ ~% y - textView.selectedRange = tmpRange;
* Y. v& @5 a8 ^2 p - return NO;0 k8 S1 a8 y( f @! H% D
- }, r s! J$ P' M7 G: A5 `
- }
6 Z7 ?4 Q1 W/ h7 T - }" M, C' ]) V' F6 h9 {
- } else { // 增加7 z3 z& p/ N1 M& }5 [: S% N; _4 P
- NSArray *rangeArray = [self getTopicRangeArray:nil];! i, s& C: ?; L8 e% A2 g) r
- if ([rangeArray count]) {4 }7 i9 s: Q, u" {3 e, l0 J6 X
- for (NSInteger i = 0; i < rangeArray.count; i++) {. v! s5 J* `5 H O( O( `6 I
- NSRange tmpRange = NSRangeFromString(rangeArray);
! [5 g- o" E1 F# r - if ((range.location + range.length) == (tmpRange.location + tmpRange.length) || !range.location) {. F7 E% _* \) ^3 @5 Q+ G+ e. h0 W
- _changeRange = NSMakeRange(range.location, text.length);$ @7 c9 N, {; b2 x X
- _isChanged = YES;& }' {" k& D; ]- b# g' i
- return YES;
6 S# Z4 f" X) [! A" ^- ^& S) Y - }" U5 ^: V3 z+ Q
- }
+ }$ f' ^; l( I - } else {7 b/ k, ?! |* L/ E! h4 H3 v
- // 话题在第一个删除后 重置text color
/ S9 G1 M3 y2 Y* H - if (!range.location) {
' v! U6 | I8 C/ m- C0 F& _ - _changeRange = NSMakeRange(range.location, text.length);% L/ ^# p: ]2 x8 H2 u" D7 W" z. J
- _isChanged = YES;/ `' I$ G% U1 S
- return YES;
$ |- ]$ J0 m- \! G' i" J - }
1 w" d5 J- I3 k U1 x# Q3 ^/ z - }
& T5 i* \5 t0 Y5 |$ Y: A - }
. J, c* V* s2 ]5 M2 t& u1 C; F& l - return YES;
- q l: J( S4 N2 a" s - }
复制代码好吧,通过以上方法,基本输入、删除操作功能是实现了。但是,上传貌似是个问题,因为我得上传跟服务端协议好的字符串格式啊;还有假如有一个聪明的用户,发现客户端和服务端的协议格式,他输入了那种格式的字符串,本着对服务端大哥的崇敬之心,我得将它转成”#话题#”格式啊。 - /**
$ B* b9 j3 F$ a, Y) f; X( B |8 a - * 获取上传时 textview text 字符串4 t* x( P1 y- l1 x8 ~# l8 J4 z* }
- *$ c8 K) `1 b* T. D
- * @return <#return value description#>
; t/ H2 o) [8 q& {, F - */
0 e8 b) r; R" @/ r! F+ ? - - (NSString *)getUploadString {
+ G4 W- [) B0 e - NSMutableString *lastTopic = [NSMutableString string];0 J5 Z N+ A! @& g9 v8 U
- NSAttributedString *formatAString = [self formatAttributedString];
+ l: Z6 H4 o) y7 S$ V0 n - NSArray *rangeArray = [self getTopicRangeArray:formatAString];3 ?2 R1 ?1 {0 B" `$ [, I
- NSInteger lastLocation = 0;6 a/ B9 E5 T! d2 c0 \
- for (NSInteger i = 0; i < rangeArray.count; i++) {$ g2 m4 J# h) ~$ Y9 Q; T* o
- // 转成协议字符串代码2 H/ o# N' j Z) t2 r6 c* M" E
- }
, ^" w, n* R1 R' {/ _2 f - if (lastLocation < formatAString.string.length) {
M- d% |# L( e' b, S - [lastTopic appendString:[formatAString.string substringFromIndex:lastLocation]];+ x! y5 F" X3 t- N; \# t# u1 `
- }
! f7 |2 M% d- F1 d9 |' ] - return lastTopic;; E& m/ ]8 C4 k
- }# P, J P9 D; O8 E9 N
- 6 s3 j$ g# }6 x- \! ~7 F; Z9 p; a
- /**
/ z) H3 k3 K4 @7 Q" ^ - * 上传时候 将文本中不带topic color的 协议字符串 改成 "#话题#" 上传至服务器
9 ]8 } q* o) z8 |1 w' y$ e0 K- |9 i - *
+ q* S# c. o% s# J! K0 L - * @return <#return value description#>, F$ M7 L3 Z0 m
- */6 W# B' t& E D/ s9 O
- - (NSMutableAttributedString *)formatAttributedString {- E: n/ Z$ a! f9 z
- NSMutableAttributedString *tmpAttributedString = [[NSMutableAttributedString alloc] initWithAttributedString:_textView.attributedText];4 H) R( l {+ P" |
- static NSRegularExpression *iExpression;
# l6 m$ |7 ]2 y6 Q8 S4 @0 p& C# t - iExpression = iExpression ?: [NSRegularExpression regularExpressionWithPattern:@"获取协议字符串正则" options:0 error:NULL];2 v6 P# A. V) l3 {/ _( B) z7 K
- // 临时遍历的 topic数组
( C* N) k: G% e M6 ~- R6 k - NSMutableArray *topicArray = [NSMutableArray array];
# W( s# D! B0 P - [topicArray addObjectsFromArray:[iExpression matchesInString:tmpAttributedString.string options:0 range:NSMakeRange(0, tmpAttributedString.string.length)]];
; v+ h* c2 i- v; u - while (topicArray.count) {, l4 Y1 x/ Q8 ~# M. i l
- NSTextCheckingResult *result = [topicArray firstObject];, x) K- e$ k- B% c- V
- NSString *searchStr = [tmpAttributedString.string substringWithRange:result.range];
4 o' Z: w( f% x& Q* k0 ^3 V5 x* x
4 W* W2 A( ^. T3 n- /**
5 x U1 ]% \/ X9 B - * 替换代码
& Y7 u8 x' p5 k% r9 a - */
/ |( L+ K2 j- O& _( C$ x
, U5 X. T) s5 [8 @& P- [topicArray removeAllObjects];' m% d. s' A7 U# g
4 y. x' T+ e$ k2 g% f4 x# p- [topicArray addObjectsFromArray:[iExpression matchesInString:tmpAttributedString.string options:0 range:NSMakeRange(0, tmpAttributedString.string.length)]];
5 t* ] C0 `) s. z - }
: p: Z& b2 _( C& H - return tmpAttributedString;& F, ], m7 q+ |) q2 ~
- }
复制代码 实现总结文中,我只说了大致的实现方式以及核心功能代码,因为有的东西涉及到公司的。。。所以,都懂得哈!' x" p7 h1 d) l! |
在做这个功能过程中,我走了一些弯路。因为在上次博文中,我延时的发现AttributedString可以设置图片,所以我当时的想法是将”#话题#”这段文字转成image然后添加到textview的AttributedString中。 牛逼的代码都找好了: \5 i! v3 r* D5 F- i# L5 ]6 m" [' s, k
- /**
' u" n7 D2 ~; r2 d: o - * Text to Image: s4 T! \, |5 e
- *
7 t9 N+ {8 o' S - * @param text text description
& Y$ f' D$ P* D - *7 F7 v. ^& x+ Q: E) B
- * @return return value description: L) L) H% k: ?! d0 m n
- */
9 A3 H6 s) P- a - - (UIImage *)imageFromText:(NSString *)text {: n! D: K9 V/ \2 y2 {
- NSMutableParagraphStyle *paragraph = [NSMutableParagraphStyle new];
5 X, ~9 Z" {; F6 t9 L8 V - paragraph.lineBreakMode = NSLineBreakByCharWrapping;
; U3 b4 g" J* y% X1 L m5 d* ~5 S5 n - paragraph.alignment = NSTextAlignmentLeft;% Z& ] Z( \# c$ H; ]5 ]( O
- NSDictionary *attributeDict = @{NSFontAttributeName: [UIFont systemFontOfSize:14.0], NSForegroundColorAttributeName: [UIColor redColor], NSParagraphStyleAttributeName: paragraph};) o) R+ C$ a$ E4 H8 k% \9 V
- CGSize textSize = [text sizeWithAttributes:attributeDict];
( w" Q4 w, w5 R& i - NSAttributedString *mutableString = [[NSAttributedString alloc] initWithString:text attributes:attributeDict];
* K$ Q! x, J( I8 {& I: H9 x - UIGraphicsBeginImageContextWithOptions(textSize, NO, 0.0);
8 O2 U9 k; {: ? - [mutableString drawInRect:CGRectMake(0.0, 0.0, textSize.width, textSize.height)];" q6 F6 Y ^( ]% J: A8 y6 L
- UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
+ C6 ?8 j" s+ {& ~ x) m - UIGraphicsEndImageContext();7 `9 o- S: p Z
- return image;$ ^( G5 l6 X( T; F' k, C1 x( }
- }
复制代码可是。。。后面因为实现新浪微博,第一次删除选中,第二次删除删除的效果,这个思路被抛弃了。其实,这个思路当时还有两个问题,我还没有解决: - textview的AttributedString添加图片后,后面输入的文字和图片中的文字,centerY不相等,有偏差。
- 如果#话题#转为图片了,我得用什么方法去获取它在最终上传字符串中的位置,因为添加完成后,用户可以在任意位置删除、添加新的字符。: e; I! `& u* |7 @2 b
在解决这个问题后,我觉得潜意识挺重要的,因为当时这个功能要在三天之内完成嘛,到了第二天标准下班时间时我还没弄出来,晚上10点多下班后,还是没有进展。当时,就紧张了,因为我没有解决这个问题,那我就成为了问题了撒。穷则变嘛,后面机智的我果断决定用最熟悉的笨方法解决,下班后我就一直在思考这个用textview的代理方法该怎么搞,结果第三天早晨我醒的特别早,并且一醒来还在想那个问题,那睁开眼睛的情形宛如电视剧男主角失忆后第一次睁开眼,好帅的感觉,O(∩_∩)O哈哈~。洗完脸后,我深深的体会到了那句话:每天叫醒你的不只是闹钟,还有八阿哥!!!其实,就是把问题复杂化了!!!(⊙o⊙)… 8 y, W# R* E- g0 L6 g% f9 Z
原文链接:http://joakimliu.github.io/2015/08/09/Simple-implementation-for-topic/
; [9 o& h5 n" X& O: Q; ` |