|
实现过程1 今天来谈谈类似于新浪微博话题功能的简单实现,当文字是”#话题#”这种格式时,该文字字体得变颜色。个人觉得,这种问题的处理方式可以是,监听用户输入的信息,如果遇到有”#”号输入或删除时,再处理看是否需要改变字体颜色。于是我就按照这种思路写了一段改变颜色的代码,它就是是遍历textview.text,然后在将两个”#”号之间有文字的字体设置颜色,不会玩正则,所以这个方法比较蠢。O(∩_∩)O~ - /**
; G* p4 C$ b4 v, e; [ - * 设置textview字体属性- Q( n9 P. `, e7 R2 P
- */6 ^2 u# K7 o7 k8 P0 E& u; {' S
- - (void)setTextViewAttributed {
' S2 \! P( E6 X; M - NSMutableArray *indexArray = [NSMutableArray array];
4 p' I5 R2 `& t# Y7 L - for (NSInteger i = 0; i < self.topicTextView.text.length; i++) {
5 J! I- ?$ x5 u - NSString *indexString = [self.topicTextView.text substringWithRange:NSMakeRange(i, 1)];
! l$ R$ d2 S9 u2 y) X# ^- D4 I0 y - if ([indexString isEqualToString:topicString]) {8 g0 H/ a2 o, p% z% g4 e- \
- [indexArray addObject:@(i)];
. U" z$ T% U2 |7 ]; I8 ] - }1 L$ A. ~3 c* `# ], h4 H! O
- }
3 p- d, v. r* v( {. J - // reset
. P4 a. ]% K! b2 Q+ Z9 I, O x, ~ - NSMutableAttributedString *aText = [[NSMutableAttributedString alloc] initWithString:self.topicTextView.text];
6 w2 n/ Q' j3 S8 x u8 ^ - self.topicTextView.attributedText = aText;
' c/ g: S) ?, U6 q- d6 O - self.topicTextView.font = [UIFont systemFontOfSize:16.0];
9 G8 g- [ ~( }8 l, c/ ~* k - 9 h5 H5 A+ a. R6 w
- // change
T: [$ I6 t3 t7 f1 i - if (indexArray.count > 1) {* r2 l/ o& g z0 x0 m6 _
- NSMutableAttributedString *aText = [[NSMutableAttributedString alloc] initWithString:self.topicTextView.text];/ X# e7 _* b6 F" w0 F
- for (NSInteger i = 0; i < indexArray.count; i++) {. k% V5 v- u) S' m+ O
- NSInteger index1 = [indexArray integerValue];( z: ?# l' [: U
- NSInteger index2 = 0;
% d) V! [: Y* c; A% r - if ((i + 1) < indexArray.count) {, n4 i( ]2 W5 H2 Q
- index2 = [indexArray[i + 1] integerValue];
4 r' N# @7 T5 p/ {& u1 S7 K6 g7 L - }8 p- a( C8 [. f! c* \
- if (index2 - index1 > 1) {8 V+ F/ |" r7 }* y
- // 多余中间有值才显示( R+ P/ @: p& u$ U* ^% r2 }# G7 d
- [aText setAttributes:@{ NSForegroundColorAttributeName: TopicColor } range:NSMakeRange(index1, index2 - index1 + 1)];
" L8 _/ F: P2 I' J; P# H* j - ++i;
* C. q/ a% C2 F - }/ ~! K% ^3 l5 T7 ?+ Q- F, D
- }
! {' {" O4 r5 U( \! M - self.topicTextView.attributedText = aText;3 g! R4 \' _% @& H" Y+ X5 k
- self.topicTextView.font = [UIFont systemFontOfSize:16.0];
2 S8 N! K0 v2 J) d' c! v" M - }
: A( s s2 H) Z0 y4 P1 o/ e - }
复制代码 实现过程2这个是我一厢情愿写的demo。因为现实并不是这样子的,客户端的话题只能从服务端获取,每个话题都是有标识的,因为不能让用户随心所欲的创建话题撒!所以呢,用户自己手动输入”#”号,然并卵。如果是这样子的话,那我只有三个问题要解决了: - 改变话题字符串颜色;
- 光标不能移动到话题字符串中间,当用户光标移动至话题后面时,用户第一次点击键盘删除按钮,其实是选中这个话题的,再一次点击键盘删除按钮时,才会删除这个话题字符串;
- 上传至服务器的时候,并不是上传#话题#,而是上传协议好的字符串,就是能让服务端能够识别这个话题;, \* x+ `' P: z+ B- v+ _; p
在实现过程中,我以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.$ f5 ^$ {( r; g8 o7 ?
- - (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text;
A2 \1 J, d+ \& F# e
! c, t1 o! J' |! _/ l" G! r7 ]7 @- /// 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.
; y& V0 x7 a, P, V - - (void)textViewDidChange:(UITextView *)textView;) X& {7 ^) m" V7 S* O, u+ o% D6 T4 g& l2 l
7 M- C/ q0 A( `- /// Implementation of this method is optional. You can use the selectedRange property of the text view to get the new selection.9 W/ [8 B) {) D' u
- - (void)textViewDidChangeSelection:(UITextView *)textView;
复制代码这三个方法都不陌生吧,那么我的思路就简单了 - shouldChangeTextInRange 代理方法中,第一,实现第一次选中,第二次删除功能;第二,实现插入话题后,需要改变其他字符串的初始颜色,得在这个方法里面做个标志。
- textViewDidChange 代理方法中,实现 根据 shouldChangeTextInRange 方法中所得到的标志,设置字符串的初始颜色;
- textViewDidChangeSelection 代理方法中,实现让光标不能移动到话题里面;( q( q' ]* Y' `/ u( `
首先我定义了两个变量,插入了话题以后,继续在后面输入字符的话,字符颜色就跟话题颜色一样了。所以,我得用这两个变量来实现改变输入字符的初始颜色。 - /// 改变Range/ w: _$ G1 y4 N# r% @8 \, r
- @property (assign, nonatomic) NSRange changeRange;
3 Y9 b4 C9 T4 g - /// 是否改变
2 N. x: j4 }; w# i- w6 B8 ~ - @property (assign, nonatomic) BOOL isChanged;
复制代码哦,对了,我还得用一个变量来记录上次光标所在的位置,因为话题字符串是不让它输入的。 - /// 光标位置) q, A7 f( x8 T z
- @property (assign, nonatomic) NSInteger cursorLocation;
复制代码用户从其他界面选择好话题以后,它得插入到textview中啊: - NSString *insertText = [NSString stringWithFormat:@"#%@#", dict[KeyTopicName]];$ B9 |: w: n' u0 u& f0 L4 x
- [self.textView insertText:insertText];
9 Q* F; Q( k4 f0 c - NSMutableAttributedString *tmpAString = [[NSMutableAttributedString alloc] initWithAttributedString:self.textView.attributedText];
; J4 w! }+ y- s9 i8 r! x! H - [tmpAString setAttributes:@{ NSForegroundColorAttributeName: TopicColor, NSFontAttributeName: DefaultSizeFont } range:NSMakeRange(self.textView.selectedRange.location - insertText.length, insertText.length)];
9 ?) B+ ~5 G! Y- P - self.textView.attributedText = tmpAString;
复制代码然后我还得找到将用户所选择插入的话题位置啊。 - /**1 j; x+ f! V* ?
- * 得到话题Range数组
" G2 r9 k2 }! r - *
5 ]6 n( l: O1 ~/ Q: l - * @return return value description+ N" s2 s) k% B4 r* [, {/ K( I; s( I
- */
; n7 V1 g( q' e3 h - - (NSArray *)getTopicRangeArray:(NSAttributedString *)attributedString {
' N* d! [6 a7 @3 r( T' O - NSAttributedString *traveAStr = attributedString ?: _textView.attributedText;
3 a+ p* N( y+ j! f - __block NSMutableArray *rangeArray = [NSMutableArray array];
- z2 G7 }% g. D" q* R/ t - static NSRegularExpression *iExpression;" r" Q7 S8 i8 O: a1 m$ C
- iExpression = iExpression ?: [NSRegularExpression regularExpressionWithPattern:@"#(.*?)#" options:0 error:NULL];( ~! G5 r2 `3 I/ m; @
- [iExpression enumerateMatchesInString:traveAStr.string
( D( S/ Y' F+ w - options:0" `6 [9 L( `4 R
- range:NSMakeRange(0, traveAStr.string.length)* V8 Q0 I0 W' m A
- usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {. P, d. l$ m" ~% C4 ~3 ~7 @
- NSRange resultRange = result.range;' ]* d4 h8 d% m4 o! [) D5 O+ m
- NSDictionary *attributedDict = [traveAStr attributesAtIndex:resultRange.location effectiveRange:&resultRange];9 ~ a3 Z# E; s" V
- if ([attributedDict[NSForegroundColorAttributeName] isEqual:TopicColor]) {* U* u9 Z9 U( o* Y
- [rangeArray addObject:NSStringFromRange(result.range)];( @, V; d# X8 ] W* p& K
- }1 q, |. j7 o# ~" V! ~( h
- }];6 s! x$ Z, A# E/ _( ?3 ]
- return rangeArray;
7 B% r; A: s! ^. f& \* u; Q - }
复制代码那么,三个UITextView delegate方法里的代码就可以这么玩了: - #pragma mark - UITextView Delegate
# J6 D' {4 q e* i - - (void)textViewDidChangeSelection:(UITextView *)textView {/ `* c0 a/ {: \2 n7 T2 d4 U( Q, m! K2 b
- NSArray *rangeArray = [self getTopicRangeArray:nil];# _* e. r% `+ {; ]+ t
- BOOL inRange = NO;1 H$ K) t# g( S+ ~5 l* b* {& D
- for (NSInteger i = 0; i < rangeArray.count; i++) {0 c% |0 `3 B9 c; h+ T( s
- NSRange range = NSRangeFromString(rangeArray);
v6 u6 t; X. `: d - if (textView.selectedRange.location > range.location && textView.selectedRange.location < range.location + range.length) {
1 w/ ?2 J+ V6 W7 y. ~4 t5 y4 N - inRange = YES;$ c- p6 _/ g. M1 l d3 |
- break;
( B' F( `, o( G) ?! M7 j" c - }
3 q+ X) }' d: v4 j; l w! Z - }) ?8 c+ C: T3 @3 d- h& t6 ^' K
- if (inRange) {" K( A9 B9 d; m7 _& {7 ^# _& I
- textView.selectedRange = NSMakeRange(self.cursorLocation, textView.selectedRange.length);$ l% R: _2 u! M% V" l9 G
- return;5 {, g9 i, M: q' ~
- }. f# `6 d9 S! `9 n' w
- self.cursorLocation = textView.selectedRange.location;2 @( x3 H+ M$ g) h
- }
! F3 i1 d" x" Z+ f+ m' U( c6 ]' g& z
6 h1 f2 P6 K3 I+ F- - (void)textViewDidChange:(UITextView *)textView {
. D" B+ k4 E8 F0 A1 n - if (_isChanged) {6 \) R9 N' r7 }+ F) i2 N
- NSMutableAttributedString *tmpAString = [[NSMutableAttributedString alloc] initWithAttributedString:self.textView.attributedText];* O% d' _2 C! ^2 }; K6 P2 z3 p
- [tmpAString setAttributes:@{ NSForegroundColorAttributeName: [UIColor blackColor], NSFontAttributeName: DefaultSizeFont } range:_changeRange];
. P0 N D& [0 Y M - _textView.attributedText = tmpAString;
! W; }( q5 M. \- X - _isChanged = NO;
w4 v# M6 I @7 ?( t# Y7 M" p, X& ? - }
% z& Q! {9 P3 I( g# {* [7 o - }
& s1 \& B, {+ @) l) W - & B% p4 d/ ~. ~8 ^; J" ?
- - (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
9 q2 e5 [# H' ?+ f. ~ - if ([text isEqualToString:@""]) { // 删除
6 F1 h6 B! U K" m/ K- E - NSArray *rangeArray = [self getTopicRangeArray:nil];
" _! z* i0 v) O2 o - for (NSInteger i = 0; i < rangeArray.count; i++) {/ U: U0 ], n( d% J
- NSRange tmpRange = NSRangeFromString(rangeArray);
4 `! c5 ^7 l$ j( z4 v$ A$ Y - if ((range.location + range.length) == (tmpRange.location + tmpRange.length)) {2 m% F0 H/ i/ S% a! z0 y9 [
- if ([NSStringFromRange(tmpRange) isEqualToString:NSStringFromRange(textView.selectedRange)]) {
; O2 r+ v& {. L: |, Y - // 第二次点击删除按钮 删除
; {4 a1 P$ V' U7 u5 ~- v/ |/ f% T - return YES;
! ?: W! {+ b. A7 p' R6 Z - } else {
3 K; g# H, V% d/ i! u. h' f3 t - // 第一次点击删除按钮 选中
: _0 o. b* ~5 D5 g- s - textView.selectedRange = tmpRange;0 |; Q/ W8 O4 f0 ?
- return NO;) W# e- m) e8 ?1 ?
- }, u$ b% d4 {. k1 z1 p
- }
% b5 ^4 Q$ Y% A; y5 m - }) Y" a! `5 o4 m7 z8 J3 o$ b. r
- } else { // 增加
% X# R5 F, L9 ]+ R9 P - NSArray *rangeArray = [self getTopicRangeArray:nil];; Q; q% k, U) `
- if ([rangeArray count]) {
3 V. }/ G* Z" d# C* J X5 l) S - for (NSInteger i = 0; i < rangeArray.count; i++) {
! j6 t% H4 W: j0 g1 ~ g/ p9 P - NSRange tmpRange = NSRangeFromString(rangeArray);+ @9 J1 ^$ r' Y% {0 \" e
- if ((range.location + range.length) == (tmpRange.location + tmpRange.length) || !range.location) {) L$ R0 v& f" Z' Q5 q- m9 M( S* b
- _changeRange = NSMakeRange(range.location, text.length);. h3 n3 S9 e& j$ I) @
- _isChanged = YES;6 H! A0 e- c4 v+ O. T: n$ v" T
- return YES;
' i8 \. V& |. d. }1 ]7 N- q - }
8 T' a) B0 n6 Q7 v1 x - }: n* a- W- @: K
- } else {
- h% h! @: W, N+ \/ k; a - // 话题在第一个删除后 重置text color
2 o1 U1 s* ~4 M, C& ~0 y* j; U, W - if (!range.location) {
/ Q' Q! H x, O$ M! c - _changeRange = NSMakeRange(range.location, text.length);
; {$ B6 L. F1 ^7 f! X3 u4 G3 A2 D. T& J - _isChanged = YES;+ k/ g& _* e$ q0 b8 \
- return YES;' |- }7 ~& h5 p4 h7 s
- }
/ T4 ]0 V3 s$ i! J - } W9 s$ ]8 k2 X5 ]& }
- }4 M& i7 z$ n3 K8 L
- return YES;0 G- d4 v7 o2 g8 H3 m. @+ U
- }
复制代码好吧,通过以上方法,基本输入、删除操作功能是实现了。但是,上传貌似是个问题,因为我得上传跟服务端协议好的字符串格式啊;还有假如有一个聪明的用户,发现客户端和服务端的协议格式,他输入了那种格式的字符串,本着对服务端大哥的崇敬之心,我得将它转成”#话题#”格式啊。 - /**
0 s% }& x; A: ]+ Z/ P: e2 ~/ X/ u - * 获取上传时 textview text 字符串
6 P9 i$ b4 D# w+ _+ n - *1 K1 [: t2 G- N) W0 ]0 N
- * @return <#return value description#>! U9 d1 P( t v$ I& p& k: X) `* ~
- */
2 o2 e' N) l4 t2 X - - (NSString *)getUploadString {
8 s! Y+ E' O5 t; d( M3 l - NSMutableString *lastTopic = [NSMutableString string];/ k2 N4 Y2 h' e( U
- NSAttributedString *formatAString = [self formatAttributedString];6 R' |- ~& F* L/ h3 G& M
- NSArray *rangeArray = [self getTopicRangeArray:formatAString];3 ~# G7 g) d8 n; V3 u% g
- NSInteger lastLocation = 0;/ ]: i# B2 y, B4 P' X% {/ C- `9 b/ f
- for (NSInteger i = 0; i < rangeArray.count; i++) {; B4 u8 `0 V* s1 D& D8 B5 p' |
- // 转成协议字符串代码
. E$ _+ e, L$ u - }9 |; @4 W! T$ a% y
- if (lastLocation < formatAString.string.length) {
3 E* Q7 s |+ g4 j& } - [lastTopic appendString:[formatAString.string substringFromIndex:lastLocation]];
/ N* l3 U; q9 Z0 {& U( A - }) Y% I B3 q7 M; a/ g8 K! y
- return lastTopic;
N* y- x% f) z5 V5 N- p - }, m ~0 E' n2 _# W+ o& ^0 k. R9 v
6 j( m/ P) e4 R. _/ a* y1 l' l' s# o; f- /**7 s6 t! V) o9 [' Y' ]/ V
- * 上传时候 将文本中不带topic color的 协议字符串 改成 "#话题#" 上传至服务器! W! T9 ~; g- K0 J
- *
5 L8 ~( q5 }, P' x& ?; H4 p" e - * @return <#return value description#>
" S2 v; g8 g2 P, d) N0 w2 L' e - */
4 f9 r9 [3 V- }, e8 _* V - - (NSMutableAttributedString *)formatAttributedString {* [# h# E2 Y+ n, ^0 i% S
- NSMutableAttributedString *tmpAttributedString = [[NSMutableAttributedString alloc] initWithAttributedString:_textView.attributedText];( w' L$ ^1 d- N% z6 m) ^) f
- static NSRegularExpression *iExpression;/ A) T3 n' I) o( f
- iExpression = iExpression ?: [NSRegularExpression regularExpressionWithPattern:@"获取协议字符串正则" options:0 error:NULL];, c4 \' E% b6 o& M4 a7 D' @
- // 临时遍历的 topic数组' F9 O6 R5 l3 ?6 F. E
- NSMutableArray *topicArray = [NSMutableArray array];1 O5 E0 L O4 O) q
- [topicArray addObjectsFromArray:[iExpression matchesInString:tmpAttributedString.string options:0 range:NSMakeRange(0, tmpAttributedString.string.length)]];' t( j" u, d3 k4 ` e/ H
- while (topicArray.count) {" v; t% C3 W) i1 D" X
- NSTextCheckingResult *result = [topicArray firstObject];+ Z4 o' q2 s6 p& Q. U
- NSString *searchStr = [tmpAttributedString.string substringWithRange:result.range];1 C. c. A# ]- b b& h( f
- 6 S1 y6 n, O7 u3 n0 j. K
- /**
' k- v7 d" ^( ]5 j7 a# ]3 H8 a2 p7 S+ s - * 替换代码; b1 |5 N6 C9 u8 m
- */, U' @' { q% Z& I4 v$ d
2 ^" [3 _/ v9 Q3 i J0 c- [topicArray removeAllObjects];- I/ L; }3 }7 ]3 r5 e3 d, C
' T+ ~5 R( b- m- [topicArray addObjectsFromArray:[iExpression matchesInString:tmpAttributedString.string options:0 range:NSMakeRange(0, tmpAttributedString.string.length)]];! A, \7 Z1 h# Z4 \
- }# ]+ j n4 a- C* e
- return tmpAttributedString;
, B# ~ `4 ?7 w' m, b - }
复制代码 实现总结文中,我只说了大致的实现方式以及核心功能代码,因为有的东西涉及到公司的。。。所以,都懂得哈!1 c4 m' k" l* ^5 _
在做这个功能过程中,我走了一些弯路。因为在上次博文中,我延时的发现AttributedString可以设置图片,所以我当时的想法是将”#话题#”这段文字转成image然后添加到textview的AttributedString中。 牛逼的代码都找好了
, g7 s0 I5 {: U$ S# {% @" p% K - /**
& W8 H; E( C( P - * Text to Image
' x% N: _+ k- N3 z - *
* E& p/ ]$ Y' [" f6 r4 S7 B' b - * @param text text description! _1 Z, c7 J' B! w, ~
- *
U' ~( R; ^" I; M; @ - * @return return value description
4 b( W0 r! I) K7 u' n3 O - */- q P, h' d! l0 A. [, Z
- - (UIImage *)imageFromText:(NSString *)text {3 v' Y! d' q' q5 j+ g. }
- NSMutableParagraphStyle *paragraph = [NSMutableParagraphStyle new];
/ h# x. f0 T p) v9 L - paragraph.lineBreakMode = NSLineBreakByCharWrapping; K& b/ e8 l+ n0 @) q
- paragraph.alignment = NSTextAlignmentLeft;4 z+ C- \& ?& K% j, P
- NSDictionary *attributeDict = @{NSFontAttributeName: [UIFont systemFontOfSize:14.0], NSForegroundColorAttributeName: [UIColor redColor], NSParagraphStyleAttributeName: paragraph};/ O, X! `, z5 F0 W
- CGSize textSize = [text sizeWithAttributes:attributeDict];" e4 ^9 w% o% ], G. r
- NSAttributedString *mutableString = [[NSAttributedString alloc] initWithString:text attributes:attributeDict]; S7 |; M3 U1 m5 r9 o* o
- UIGraphicsBeginImageContextWithOptions(textSize, NO, 0.0);1 x9 B1 U* i& r: b- `- l" S G- }
- [mutableString drawInRect:CGRectMake(0.0, 0.0, textSize.width, textSize.height)];, K2 \/ @; O# ^. e, q6 ?( J
- UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
. z" z* g8 Q( F6 e9 a# O1 a - UIGraphicsEndImageContext();
0 Y- o4 o+ g- t0 `% @1 m. c- \. J4 z - return image;8 f8 z6 k# a; h- c
- }
复制代码可是。。。后面因为实现新浪微博,第一次删除选中,第二次删除删除的效果,这个思路被抛弃了。其实,这个思路当时还有两个问题,我还没有解决: - textview的AttributedString添加图片后,后面输入的文字和图片中的文字,centerY不相等,有偏差。
- 如果#话题#转为图片了,我得用什么方法去获取它在最终上传字符串中的位置,因为添加完成后,用户可以在任意位置删除、添加新的字符。7 D& V, V5 s ~2 e
在解决这个问题后,我觉得潜意识挺重要的,因为当时这个功能要在三天之内完成嘛,到了第二天标准下班时间时我还没弄出来,晚上10点多下班后,还是没有进展。当时,就紧张了,因为我没有解决这个问题,那我就成为了问题了撒。穷则变嘛,后面机智的我果断决定用最熟悉的笨方法解决,下班后我就一直在思考这个用textview的代理方法该怎么搞,结果第三天早晨我醒的特别早,并且一醒来还在想那个问题,那睁开眼睛的情形宛如电视剧男主角失忆后第一次睁开眼,好帅的感觉,O(∩_∩)O哈哈~。洗完脸后,我深深的体会到了那句话:每天叫醒你的不只是闹钟,还有八阿哥!!!其实,就是把问题复杂化了!!!(⊙o⊙)… 1 E, Z- _( J8 B
原文链接:http://joakimliu.github.io/2015/08/09/Simple-implementation-for-topic/ c/ Q5 F9 {0 |+ X. p/ A- ^: S
|