|
实现过程1 今天来谈谈类似于新浪微博话题功能的简单实现,当文字是”#话题#”这种格式时,该文字字体得变颜色。个人觉得,这种问题的处理方式可以是,监听用户输入的信息,如果遇到有”#”号输入或删除时,再处理看是否需要改变字体颜色。于是我就按照这种思路写了一段改变颜色的代码,它就是是遍历textview.text,然后在将两个”#”号之间有文字的字体设置颜色,不会玩正则,所以这个方法比较蠢。O(∩_∩)O~ - /**6 c, O- E6 x/ H, a i* Y
- * 设置textview字体属性! w, _0 c3 G3 Y9 R
- */: O) O/ G3 k1 l; Z6 l. t2 G3 t
- - (void)setTextViewAttributed {( D: ?3 Q$ L0 r3 O% \( i
- NSMutableArray *indexArray = [NSMutableArray array];& O0 B5 K( y7 b. L% ]( m$ f/ J# P
- for (NSInteger i = 0; i < self.topicTextView.text.length; i++) {" h' {' `2 k/ C7 J8 _
- NSString *indexString = [self.topicTextView.text substringWithRange:NSMakeRange(i, 1)];/ s" Y$ c' A) ]; l' s7 w8 h
- if ([indexString isEqualToString:topicString]) {. g2 n& M" a9 v Y, ]
- [indexArray addObject:@(i)];
: q O& L2 P* k7 `3 y$ t - }* D. ]3 t/ _0 ^. ~/ V2 f b# h( s2 u
- }1 F# V0 A6 x/ |0 ~" [6 O
- // reset# ^5 @+ ?7 s+ @7 K- ~* s4 H$ [( }
- NSMutableAttributedString *aText = [[NSMutableAttributedString alloc] initWithString:self.topicTextView.text];7 w4 C6 }' L5 D8 J- A
- self.topicTextView.attributedText = aText;" c. D; _: a6 e* c6 k& x
- self.topicTextView.font = [UIFont systemFontOfSize:16.0];/ f; o1 @+ G3 H6 G6 c
- # I. D; Y5 p0 p- B
- // change
. q! f0 o7 i( t7 {& X( Y - if (indexArray.count > 1) {) B9 `. Y1 U2 i; S& u, o$ J
- NSMutableAttributedString *aText = [[NSMutableAttributedString alloc] initWithString:self.topicTextView.text];
7 R' Y$ \$ \9 y4 R+ b - for (NSInteger i = 0; i < indexArray.count; i++) {
) g1 R6 X0 o% j2 T6 g: C* Q! R/ E w5 R - NSInteger index1 = [indexArray integerValue];
9 c8 S# [# b: ~% B$ X2 M - NSInteger index2 = 0;
- D0 m q- n8 x0 E* G - if ((i + 1) < indexArray.count) {
2 O! U9 |1 I1 c; u - index2 = [indexArray[i + 1] integerValue];0 {0 l. @" W9 b4 T- \3 P$ e
- }
5 C$ [* H( P; E8 Y) N4 S) z - if (index2 - index1 > 1) {
L' g% S$ z' T( b( p" P - // 多余中间有值才显示
) |& N6 ?8 c, F5 ]6 E5 Q6 Z! E9 F - [aText setAttributes:@{ NSForegroundColorAttributeName: TopicColor } range:NSMakeRange(index1, index2 - index1 + 1)];1 q- u7 V6 V" ^7 g( e; u6 ? U" o
- ++i;
f0 H) [; I& ^! A1 q5 H - }
6 E, q! H9 J& v1 q$ _ - }& h$ D/ @" O; G/ h$ ?+ ^2 y
- self.topicTextView.attributedText = aText;) }3 X5 S7 G8 _4 }1 M
- self.topicTextView.font = [UIFont systemFontOfSize:16.0];
: Q5 w- E% c$ g s0 G. s - }2 e4 q/ H8 N/ ^2 T6 A; C+ A
- }
复制代码 实现过程2这个是我一厢情愿写的demo。因为现实并不是这样子的,客户端的话题只能从服务端获取,每个话题都是有标识的,因为不能让用户随心所欲的创建话题撒!所以呢,用户自己手动输入”#”号,然并卵。如果是这样子的话,那我只有三个问题要解决了: - 改变话题字符串颜色;
- 光标不能移动到话题字符串中间,当用户光标移动至话题后面时,用户第一次点击键盘删除按钮,其实是选中这个话题的,再一次点击键盘删除按钮时,才会删除这个话题字符串;
- 上传至服务器的时候,并不是上传#话题#,而是上传协议好的字符串,就是能让服务端能够识别这个话题;' G* x; }. n. Y
在实现过程中,我以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.( O S" [+ |2 w& P2 w" L
- - (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text;
; ]: S" R: K: o; y1 Y/ _& i: q" t) B
. v; T g$ \+ T- /// 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.9 m5 y3 I) v; x" O
- - (void)textViewDidChange:(UITextView *)textView;
! A2 ^; ?: x9 F5 M% g4 _ - % s: t! K2 _ c6 R! R5 ~$ F
- /// Implementation of this method is optional. You can use the selectedRange property of the text view to get the new selection.
0 z" w/ k) E: C/ S9 _$ k# w0 h# { g - - (void)textViewDidChangeSelection:(UITextView *)textView;
复制代码这三个方法都不陌生吧,那么我的思路就简单了 - shouldChangeTextInRange 代理方法中,第一,实现第一次选中,第二次删除功能;第二,实现插入话题后,需要改变其他字符串的初始颜色,得在这个方法里面做个标志。
- textViewDidChange 代理方法中,实现 根据 shouldChangeTextInRange 方法中所得到的标志,设置字符串的初始颜色;
- textViewDidChangeSelection 代理方法中,实现让光标不能移动到话题里面;1 x ]4 u1 K8 S
首先我定义了两个变量,插入了话题以后,继续在后面输入字符的话,字符颜色就跟话题颜色一样了。所以,我得用这两个变量来实现改变输入字符的初始颜色。 - /// 改变Range& L* }: A/ f" \* w; h
- @property (assign, nonatomic) NSRange changeRange;; Y# f: P1 D4 Z% x) I
- /// 是否改变4 a: v* W4 I4 o0 e+ M
- @property (assign, nonatomic) BOOL isChanged;
复制代码哦,对了,我还得用一个变量来记录上次光标所在的位置,因为话题字符串是不让它输入的。 - /// 光标位置
$ r7 V( {9 [# V) B - @property (assign, nonatomic) NSInteger cursorLocation;
复制代码用户从其他界面选择好话题以后,它得插入到textview中啊: - NSString *insertText = [NSString stringWithFormat:@"#%@#", dict[KeyTopicName]];
- x0 b4 T. u2 r - [self.textView insertText:insertText];
( x' M; y& x: P& ~5 G - NSMutableAttributedString *tmpAString = [[NSMutableAttributedString alloc] initWithAttributedString:self.textView.attributedText];. W8 s/ C+ x7 z1 O$ N, }
- [tmpAString setAttributes:@{ NSForegroundColorAttributeName: TopicColor, NSFontAttributeName: DefaultSizeFont } range:NSMakeRange(self.textView.selectedRange.location - insertText.length, insertText.length)];
) H# ^2 r% {9 B7 v: ?! Q - self.textView.attributedText = tmpAString;
复制代码然后我还得找到将用户所选择插入的话题位置啊。 - /**
( _; S* Q1 g+ i. F, v - * 得到话题Range数组
5 p2 ]4 c7 C S! A. W+ a - *# K9 k3 _9 m+ ?# F* x0 P
- * @return return value description+ y1 q1 k: C: r$ e; f2 S, I
- */
( O3 O; J1 ]! k - - (NSArray *)getTopicRangeArray:(NSAttributedString *)attributedString {
* B, j9 @+ {1 B$ s/ S5 W6 I - NSAttributedString *traveAStr = attributedString ?: _textView.attributedText;6 y5 E; f: U- c* T8 q
- __block NSMutableArray *rangeArray = [NSMutableArray array];
, @) l8 _ h' i; P8 B - static NSRegularExpression *iExpression;0 w5 u2 S# l9 y( i
- iExpression = iExpression ?: [NSRegularExpression regularExpressionWithPattern:@"#(.*?)#" options:0 error:NULL];
' G+ C1 r/ w1 v: J! _( _ - [iExpression enumerateMatchesInString:traveAStr.string7 u& R7 K' T) ] h) K( m( J
- options:0' c! _+ o2 I3 W- c
- range:NSMakeRange(0, traveAStr.string.length)- Z- Z. Y: L! K0 j
- usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {- p6 D7 z, `- j4 Z) b% F# x
- NSRange resultRange = result.range;/ e" f$ e& r- F6 B/ M3 u1 |
- NSDictionary *attributedDict = [traveAStr attributesAtIndex:resultRange.location effectiveRange:&resultRange];
3 _8 R: X4 v" e+ z/ f% O: t, h- P - if ([attributedDict[NSForegroundColorAttributeName] isEqual:TopicColor]) {
) B3 F3 [0 S5 g9 P/ S- |7 D: Z - [rangeArray addObject:NSStringFromRange(result.range)];
" a! y9 Q) f: e3 `# H; x% F - }# y" j( s1 ?, t* k7 M
- }];
" d1 o8 l1 M$ V7 z - return rangeArray;
9 h- @ f6 y" Z" y1 {- C - }
复制代码那么,三个UITextView delegate方法里的代码就可以这么玩了: - #pragma mark - UITextView Delegate
: k, Y F4 C1 Q; Q$ ~ - - (void)textViewDidChangeSelection:(UITextView *)textView {$ f$ H3 t. q0 e) n3 \) m7 g2 T
- NSArray *rangeArray = [self getTopicRangeArray:nil];
5 P I5 u8 a3 w4 K; m2 J, s - BOOL inRange = NO;7 T0 d7 D0 Q# y+ @
- for (NSInteger i = 0; i < rangeArray.count; i++) {& {' o; \* Z; u2 W) b( W3 ~& [3 F
- NSRange range = NSRangeFromString(rangeArray);
0 M4 R6 w( f4 J+ v; f' `; e - if (textView.selectedRange.location > range.location && textView.selectedRange.location < range.location + range.length) {5 q; S; r* Z% B& `4 L2 d0 q0 q
- inRange = YES;, r( }( M( ^& @ z( r& }4 K* `
- break;0 d" w0 [3 r3 r% P6 [
- }
0 l" ?. Y3 `# A% e - }
1 h5 G/ M7 ^; X; z( n' k, x2 z - if (inRange) {9 x! N/ Z8 @' L! r
- textView.selectedRange = NSMakeRange(self.cursorLocation, textView.selectedRange.length);% M+ _; P8 x n" L9 W g( G+ q
- return;
# G3 Z5 u' q7 @* O - }# F' r' s! @! @2 q; g
- self.cursorLocation = textView.selectedRange.location; V8 E; x0 j2 d( f2 D9 d
- }
. B' r* N) Y* o+ C- n% j
. g) l2 ~/ E+ u3 }- - (void)textViewDidChange:(UITextView *)textView {# B+ R) ?. ?2 c; g
- if (_isChanged) {
9 S% y6 L0 Q4 X: h5 u - NSMutableAttributedString *tmpAString = [[NSMutableAttributedString alloc] initWithAttributedString:self.textView.attributedText];1 P: |9 U' Z" B8 w$ X0 T
- [tmpAString setAttributes:@{ NSForegroundColorAttributeName: [UIColor blackColor], NSFontAttributeName: DefaultSizeFont } range:_changeRange];) N, O; G9 y/ M
- _textView.attributedText = tmpAString;
/ Q/ G) x) V$ x% v! V R - _isChanged = NO;
8 e# K0 L* i5 y6 o* |" V - }
0 P& |( ^9 A& w" [" Q( F) W - }
9 ?* q5 {% X3 Y4 p: a$ u; b - 2 @( F: _# z ^6 ]
- - (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
' e* g1 v ^6 c; J! Q; z - if ([text isEqualToString:@""]) { // 删除, a1 X# M5 G' f2 Y+ q
- NSArray *rangeArray = [self getTopicRangeArray:nil];3 G! {) Q8 b5 y& z! L, w v
- for (NSInteger i = 0; i < rangeArray.count; i++) {
- l; o- ^9 @4 n9 w5 }6 A - NSRange tmpRange = NSRangeFromString(rangeArray);" C- S) y# T6 t
- if ((range.location + range.length) == (tmpRange.location + tmpRange.length)) {; ?* U" ]+ M5 q* q
- if ([NSStringFromRange(tmpRange) isEqualToString:NSStringFromRange(textView.selectedRange)]) {; Z! e1 x! V5 {7 G$ B; ?
- // 第二次点击删除按钮 删除/ V$ s' y9 f5 r) n3 r9 a- ^
- return YES;
2 L: o6 T9 r0 b; g1 j, i: w5 t - } else {; p3 h( |0 G s& V. ]/ ~# e0 G& c
- // 第一次点击删除按钮 选中
" ^: F4 a4 E. [/ m; W - textView.selectedRange = tmpRange;5 f1 j" ], S" x
- return NO;
" P5 v5 f& s! y; E% r! G - }
2 A+ G! V4 k- y3 y% s' Q( W, V0 [" d4 Y - }0 q) C, Q+ b3 r( \* H6 c0 G) {
- }
4 J* g. p( u& D6 a. a% }! R - } else { // 增加
" r% j1 s4 K/ x% U - NSArray *rangeArray = [self getTopicRangeArray:nil];
" a2 A! d# q1 O4 H" y" m - if ([rangeArray count]) {
0 N4 N7 W7 ^2 b! P6 e - for (NSInteger i = 0; i < rangeArray.count; i++) {% r& q! q) i7 K; l- F5 w" J
- NSRange tmpRange = NSRangeFromString(rangeArray);9 ]- m/ h( \/ V: S1 u
- if ((range.location + range.length) == (tmpRange.location + tmpRange.length) || !range.location) {
. ]! e, ?5 @ S6 w# J - _changeRange = NSMakeRange(range.location, text.length);; O* A) S& l% [
- _isChanged = YES;
0 I6 y4 f( U n$ l* t# L. |; U- M' k - return YES;
$ N4 P8 Y$ s g - }/ l/ O0 b2 U* k
- }
2 ?7 {) {- `+ Y( c0 C2 U/ \4 z. z3 w - } else {- V8 \- Z0 K8 ]' a
- // 话题在第一个删除后 重置text color
0 Z& ~( t4 X9 t9 M# G - if (!range.location) {
4 b5 G3 J5 `# d2 [ - _changeRange = NSMakeRange(range.location, text.length);
+ b' m3 @3 N p. S7 n9 s; P; j6 w - _isChanged = YES;1 F \ Q0 c' b" r' }
- return YES;
6 C& ] S% O4 I u) X# g9 V - }
m1 c |: L, h5 M+ O2 ~8 T% U* k* P - }5 r" e- M! p! a5 w7 a( G
- }
& x& q: ?% l' ~: Y) c - return YES;& ` {# Q" t. L$ S
- }
复制代码好吧,通过以上方法,基本输入、删除操作功能是实现了。但是,上传貌似是个问题,因为我得上传跟服务端协议好的字符串格式啊;还有假如有一个聪明的用户,发现客户端和服务端的协议格式,他输入了那种格式的字符串,本着对服务端大哥的崇敬之心,我得将它转成”#话题#”格式啊。 - /**
t2 F' S: o% N P6 m - * 获取上传时 textview text 字符串
& D6 O2 a4 u" V# t; I. H/ Z - */ V8 C9 j. e' B+ Q* M
- * @return <#return value description#>
/ w" C! t8 Z8 i" \& g - */* f# ^* ~5 }" }% _, x2 o. T+ L
- - (NSString *)getUploadString {( j. N4 K* D; J$ M5 Y! p
- NSMutableString *lastTopic = [NSMutableString string];
, T u5 J" D8 g6 _& S9 u, N7 q - NSAttributedString *formatAString = [self formatAttributedString];
% e7 s$ D4 l) Y, z) ]4 Y# z - NSArray *rangeArray = [self getTopicRangeArray:formatAString];& d* C4 c" I) }/ u m
- NSInteger lastLocation = 0;
7 `9 F5 ?) {8 s/ l: G7 b8 q& R6 c - for (NSInteger i = 0; i < rangeArray.count; i++) {! \- p( u% V- g5 A
- // 转成协议字符串代码8 a$ R6 Q. A: U/ U& S
- }) k: L3 J$ @6 m- _# O2 ?
- if (lastLocation < formatAString.string.length) {, k o- J# x. o: s, x
- [lastTopic appendString:[formatAString.string substringFromIndex:lastLocation]];
/ _& n) r/ k X E8 R! |) } - }# i. o& y- \( e( v
- return lastTopic;7 v0 z" j5 q& V S" N
- }9 V$ g( X( i( T+ h7 {6 N
$ E, p% r3 W1 p9 N; k* |- /**3 F: l; f. n+ d1 T$ [
- * 上传时候 将文本中不带topic color的 协议字符串 改成 "#话题#" 上传至服务器
( I, P) z) C% h/ s, r* @# D* L - *
' m6 e! @5 Y/ u2 Q - * @return <#return value description#> g6 N' b; \$ M: U* K8 E6 s) e
- */# Z" H4 G$ T2 X, T8 I7 f
- - (NSMutableAttributedString *)formatAttributedString {6 F V, R+ P, A% B5 u% k
- NSMutableAttributedString *tmpAttributedString = [[NSMutableAttributedString alloc] initWithAttributedString:_textView.attributedText];
6 t7 j( l. K5 _ - static NSRegularExpression *iExpression;
. {0 F2 ?$ h+ J" X - iExpression = iExpression ?: [NSRegularExpression regularExpressionWithPattern:@"获取协议字符串正则" options:0 error:NULL];
: c) |5 O# K5 F" Y B6 m- \, D - // 临时遍历的 topic数组
2 T1 U4 I( }3 v$ H. K5 u - NSMutableArray *topicArray = [NSMutableArray array];- Y1 Y( Y% A$ M+ x. |2 `: P3 p
- [topicArray addObjectsFromArray:[iExpression matchesInString:tmpAttributedString.string options:0 range:NSMakeRange(0, tmpAttributedString.string.length)]];
/ |2 b# u9 [ p: h. w5 j/ S - while (topicArray.count) {( [8 q v& a( h; o
- NSTextCheckingResult *result = [topicArray firstObject];
7 C- _* ?3 }! H( i4 U - NSString *searchStr = [tmpAttributedString.string substringWithRange:result.range];
+ r5 P3 {% Q) \, q - 2 ?6 F8 [9 L! n# q
- /**1 O3 D" z; f# d' j' |
- * 替换代码5 i5 g. y( p, ?8 F# U; z
- */
+ O0 m9 ?- c9 J. `/ x - 4 I- K5 d b6 y* S' @' \
- [topicArray removeAllObjects];+ p5 u% Q% J: c2 \, B) W
$ \6 ], O7 ^6 r9 \; W( a- [topicArray addObjectsFromArray:[iExpression matchesInString:tmpAttributedString.string options:0 range:NSMakeRange(0, tmpAttributedString.string.length)]];. U# h3 h/ {" Y& g3 u
- }0 j7 w5 @) ^+ }# I0 q
- return tmpAttributedString;6 q5 k+ S; U* v
- }
复制代码 实现总结文中,我只说了大致的实现方式以及核心功能代码,因为有的东西涉及到公司的。。。所以,都懂得哈!
" l. o5 U. D6 j9 y& u在做这个功能过程中,我走了一些弯路。因为在上次博文中,我延时的发现AttributedString可以设置图片,所以我当时的想法是将”#话题#”这段文字转成image然后添加到textview的AttributedString中。 牛逼的代码都找好了. U; F+ {( q; C: ~1 L$ h
- /**9 t7 l' M+ t, B4 x4 K& F8 _+ a2 |
- * Text to Image) e' @, l6 e p; c
- *
8 \ ]3 x8 X x" p6 k - * @param text text description9 w' ^+ A0 J7 a8 N
- *) B, U M) u3 i: `% ]3 h) y, k+ |
- * @return return value description
6 }" P2 g: @5 L( m - */
0 @- d% b4 d) ~# w - - (UIImage *)imageFromText:(NSString *)text {9 k+ n! v" S1 |. K
- NSMutableParagraphStyle *paragraph = [NSMutableParagraphStyle new];: y- }4 \% B* y6 J* }) r
- paragraph.lineBreakMode = NSLineBreakByCharWrapping;) a% C. ]& H; P$ i' j* X
- paragraph.alignment = NSTextAlignmentLeft;
, ]2 @7 P1 N# D1 o - NSDictionary *attributeDict = @{NSFontAttributeName: [UIFont systemFontOfSize:14.0], NSForegroundColorAttributeName: [UIColor redColor], NSParagraphStyleAttributeName: paragraph};7 h* {9 o1 m4 `( D% x* P2 v- q0 V! X
- CGSize textSize = [text sizeWithAttributes:attributeDict];
4 f, M G! M0 A, X, E( D y - NSAttributedString *mutableString = [[NSAttributedString alloc] initWithString:text attributes:attributeDict];
' x) X/ R6 `% x8 D - UIGraphicsBeginImageContextWithOptions(textSize, NO, 0.0);& A# x6 M1 y1 {% ]
- [mutableString drawInRect:CGRectMake(0.0, 0.0, textSize.width, textSize.height)]; N. l. u7 t. z2 ]
- UIImage *image = UIGraphicsGetImageFromCurrentImageContext();8 P; z, H7 ?* {9 K
- UIGraphicsEndImageContext();
+ M( U' m- ~: _' y - return image;- w) j6 H" B6 @5 [
- }
复制代码可是。。。后面因为实现新浪微博,第一次删除选中,第二次删除删除的效果,这个思路被抛弃了。其实,这个思路当时还有两个问题,我还没有解决: - textview的AttributedString添加图片后,后面输入的文字和图片中的文字,centerY不相等,有偏差。
- 如果#话题#转为图片了,我得用什么方法去获取它在最终上传字符串中的位置,因为添加完成后,用户可以在任意位置删除、添加新的字符。
5 m2 r s/ V/ R0 }8 q- b( K0 E
在解决这个问题后,我觉得潜意识挺重要的,因为当时这个功能要在三天之内完成嘛,到了第二天标准下班时间时我还没弄出来,晚上10点多下班后,还是没有进展。当时,就紧张了,因为我没有解决这个问题,那我就成为了问题了撒。穷则变嘛,后面机智的我果断决定用最熟悉的笨方法解决,下班后我就一直在思考这个用textview的代理方法该怎么搞,结果第三天早晨我醒的特别早,并且一醒来还在想那个问题,那睁开眼睛的情形宛如电视剧男主角失忆后第一次睁开眼,好帅的感觉,O(∩_∩)O哈哈~。洗完脸后,我深深的体会到了那句话:每天叫醒你的不只是闹钟,还有八阿哥!!!其实,就是把问题复杂化了!!!(⊙o⊙)… 8 S0 m& a4 P, n7 T# I& J
原文链接:http://joakimliu.github.io/2015/08/09/Simple-implementation-for-topic/ " t( |+ C- y( F2 m' ]7 S% w/ C
|